@openelan/elan-node-sdk
v0.2.38
Published
Elan Integration SDK — Node.js client for the Elan Integration Service sessions API
Downloads
1,049
Readme
@openelan/elan-node-sdk
Node.js SDK for the Elan Integration Service and IAM auth APIs.
Installation
npm install @openelan/elan-node-sdkQuick Start
import { ElanIntegrationClient } from "@openelan/elan-node-sdk";
const client = new ElanIntegrationClient({
integration: {
baseUrl: "https://api.elan.ai",
clientId: "your-client-id",
clientSecret: "your-client-secret",
timeoutMs: 10000, // optional, defaults to 10 000 ms
},
auth: {
iamBaseUrl: "https://iam.elan.ai", // optional, falls back to integration.baseUrl
timeoutMs: 5000, // optional, falls back to integration.timeoutMs
},
});
const userId = "end-user-id";
// --- Swarm (session) operations ---
// Create a session
const { sessionId } = await client.swarm.createSession(userId);
// Send a plain-text message (async, returns accepted acknowledgement)
const response = await client.swarm.sendMessage(userId, sessionId, "Hello, agent!");
console.log(response.status); // "accepted"
// Send a message and wait for the final response in one call
const reply = await client.swarm.sendSync(userId, "What is 6 × 7?");
console.log(reply.finalText); // "42"
// --- Auth operations ---
// Introspect an access token server-side
const claims = await client.auth.introspectSession(accessToken);
console.log(claims.sub); // userId
console.log(claims.orgId); // organization ID
// Request email verification
await client.auth.requestEmailVerification("[email protected]");API Reference
new ElanIntegrationClient(config)
| Field | Type | Required | Description |
|----------------------------|----------|----------|--------------------------------------------------------------------------|
| integration.baseUrl | string | yes | Base URL of the Elan Integration Service |
| integration.clientId | string | yes | IAM swarm client ID |
| integration.clientSecret | string | yes | IAM swarm client secret |
| integration.timeoutMs | number | no | Per-request timeout in ms (default: 10000) |
| auth.iamBaseUrl | string | no | Base URL of the Elan IAM service (defaults to integration.baseUrl) |
| auth.timeoutMs | number | no | Per-request timeout for auth calls (defaults to integration.timeoutMs) |
The constructor exposes two namespaced sub-clients:
client.swarm— Integration Service sessions, file upload, and sync APIsclient.auth— IAM auth and admin endpoints
client.swarm — Integration / Session Methods
| Method | Returns |
|--------------------------------------------------------------|----------------------------|
| createSession(userId, opts?) | ElanCreateSessionResponse |
| listSessions(userId) | ElanSession[] |
| getSession(userId, sessionId, opts?) | ElanSession |
| deleteSession(userId, sessionId) | void |
| sendMessage(userId, sessionId, message, opts?) | ElanSendAcceptedResponse |
| sendMessage(userId, sessionId, parts, opts?) | ElanSendAcceptedResponse |
| initUpload(userId, sessionId, body) | ElanInitUploadData |
| downloadFile(userId, fileUid) | Response |
| sendSync(userId, message, opts?) | ElanSendSyncResponse |
| sendSync(userId, parts, opts?) | ElanSendSyncResponse |
createSession accepts an optional extraContext object that is forwarded as the
x-extra-context request header.
getSession accepts an optional numRecentEvents number (1–200).
sendMessage accepts either a plain string (sends { message }) or an
ElanSendPart[] array (sends { parts }). Both forms accept an optional extra
key-value map forwarded to the runtime.
Structured parts (ElanSendPart)
| Variant | Required fields | Optional fields |
|----------------------------|-----------------------------------------------|-----------------|
| ElanSendTextPart | type: "text", text | — |
| ElanSendFilePart | type: "file", fileName, mimeType, url | urls |
| ElanSendToolCallPart | type: "toolCall", name, args | callId |
| ElanSendToolResponsePart | type: "toolResponse", name, response | callId |
import type { ElanSendPart } from "@openelan/elan-node-sdk";
// Plain text
await client.swarm.sendMessage(userId, sessionId, "Hello, agent!");
// Structured text part
const textPart: ElanSendPart = { type: "text", text: "Hello, agent!" };
await client.swarm.sendMessage(userId, sessionId, [textPart]);
// File attachment
const filePart: ElanSendPart = {
type: "file",
fileName: "report.pdf",
mimeType: "application/pdf",
url: "https://example.com/report.pdf",
};
await client.swarm.sendMessage(userId, sessionId, [filePart]);
// Tool call
const toolCallPart: ElanSendPart = {
type: "toolCall",
name: "searchWeb",
args: { query: "elan AI" },
callId: "call-1",
};
await client.swarm.sendMessage(userId, sessionId, [toolCallPart]);
// Tool response
const toolResponsePart: ElanSendPart = {
type: "toolResponse",
name: "searchWeb",
response: { results: ["..."] },
callId: "call-1",
};
await client.swarm.sendMessage(userId, sessionId, [toolResponsePart]);
// Mixed parts with extra metadata
await client.swarm.sendMessage(userId, sessionId, [
{ type: "text", text: "See attached" },
{ type: "file", fileName: "img.png", mimeType: "image/png", url: "https://example.com/img.png" },
], { extra: { correlationId: "abc123" } });sendSync — synchronous response
sendSync posts the message and waits for the agent to finish its turn,
returning finalText, content, and eventMetadata in a single call. No
session ID is needed — the runtime manages ephemeral sessions automatically.
import type { ElanSendSyncResponse } from "@openelan/elan-node-sdk";
// Plain-text mode
const resp: ElanSendSyncResponse = await client.swarm.sendSync(userId, "What is 6 × 7?");
console.log(resp.finalText); // "42"
console.log(resp.content); // { parts: [{ text: "42" }], role: "model" } | null
console.log(resp.eventMetadata); // { invocationId: "…" } | null
// Structured-parts mode
import type { ElanSendPart } from "@openelan/elan-node-sdk";
const parts: ElanSendPart[] = [{ type: "text", text: "Summarise this document." }];
const resp2 = await client.swarm.sendSync(userId, parts);
console.log(resp2.finalText);
// With extra metadata
const resp3 = await client.swarm.sendSync(userId, "Hello", { extra: { correlationId: "req-1" } });client.auth — IAM / Auth / Admin Methods
All auth methods throw ElanIntegrationError on non-2xx responses or request timeout.
Session lifecycle
| Method | Signature | Returns |
|--------------------------|-------------------------------------------|--------------------------|
| exchangeCodeForSession | (params: ElanExchangeCodeParams) | ElanSessionResponse |
| refreshSession | (params?: ElanRefreshSessionParams) | ElanSessionResponse |
| logoutSession | (params?: ElanLogoutSessionParams) | void |
| introspectSession | (accessToken: string) | ElanIntrospectResponse |
Email verification
| Method | Signature | Returns |
|----------------------------|-----------------------|----------------------------------------|
| requestEmailVerification | (email: string) | ElanRequestEmailVerificationResponse |
| verifyEmail | (token: string) | ElanVerifyEmailResponse |
Password management
| Method | Signature | Returns |
|------------------|-----------------------------------------------------------------|------------------------------|
| changePassword | (currentPassword, newPassword, accessToken) | ElanChangePasswordResponse |
| forgetPassword | (email, action: { redirectUri: string }) | ElanForgetPasswordResponse |
| resetPassword | (code: string, newPassword: string) | ElanResetPasswordResponse |
User metadata
| Method | Signature | Returns |
|---------------|-----------------------------------------------------|---------------------------|
| setMetadata | (metadata: Record<string, string>, accessToken) | ElanSetMetadataResponse |
Organization selection
| Method | Signature | Returns |
|----------------------|------------------------------------------------------------------|-----------------------|
| selectOrganization | (loginChallengeToken, organizationSlug, requestedScopes?) | ElanSessionResponse |
| switchOrganization | (organizationSlug, requestedScopes, accessToken) | ElanSessionResponse |
Admin
| Method | Signature | Returns |
|-------------------|---------------------------------------------------------------|-------------------------------|
| adminCreateUser | (params: ElanAdminCreateUserParams, accessToken) | ElanAdminCreatedUser |
| setCustomClaims | (userId, customClaims: Record<string, string>, accessToken) | ElanSetCustomClaimsResponse |
Auth method examples
// Exchange a PKCE code after OAuth redirect
const session = await client.auth.exchangeCodeForSession({
code: "auth-code",
codeVerifier: "verifier",
redirectUri: "https://app.example.com/callback",
clientId: "my-client-id",
});
// Refresh an existing session
const refreshed = await client.auth.refreshSession({
refreshToken: session.tokens.refreshToken,
});
// Introspect a token server-side
const claims = await client.auth.introspectSession(session.tokens.accessToken);
console.log(claims.sub, claims.orgId, claims.scopes);
// Logout a session (invalidates the refresh token)
await client.auth.logoutSession({ refreshToken: session.tokens.refreshToken });
// Request email verification (safe to call even when already verified)
await client.auth.requestEmailVerification("[email protected]");
// Confirm email with the one-time token from the verification email
await client.auth.verifyEmail("one-time-token-from-email");
// Change password (requires an active session)
await client.auth.changePassword("old-pass", "new-pass", accessToken);
// Initiate forgot-password flow (always returns { requested: true } to prevent enumeration)
await client.auth.forgetPassword("[email protected]", {
redirectUri: "https://app.example.com/reset-password",
});
// Complete password reset with the one-time code from the reset email
await client.auth.resetPassword("reset-code", "new-password");
// Set arbitrary key-value metadata on the current user
await client.auth.setMetadata({ theme: "dark", locale: "en" }, accessToken);
// Select an org after a login challenge (multi-org users only)
// A login via your own flow returns { status: "ORG_SELECTION_REQUIRED", loginChallengeToken, organizations }
const authenticated = await client.auth.selectOrganization(
loginChallengeToken,
"my-org-slug",
["session:all"],
);
// Switch to a different org mid-session
const switched = await client.auth.switchOrganization(
"other-org-slug",
undefined,
accessToken,
);
// Admin: create a new user in your organization
const user = await client.auth.adminCreateUser({
email: "[email protected]",
password: "Secure123",
displayName: "New User",
role: "DEVELOPER",
}, adminAccessToken);
// Admin: set custom claims on a user (fully replaces existing claims)
await client.auth.setCustomClaims(user.uid, { tier: "pro" }, adminAccessToken);Webhook Verification
The Elan realtime service signs outbound callback payloads using HMAC-SHA256. Use the helper to verify inbound webhook requests in your callback handler:
import { verifyWebhookSignature } from "@openelan/elan-node-sdk";
import type { ElanWebhookPayload } from "@openelan/elan-node-sdk";
import type { Request, Response } from "express";
app.post("/webhook", (req: Request, res: Response) => {
const valid = verifyWebhookSignature({
headers: req.headers as Record<string, string>,
body: req.body as ElanWebhookPayload,
clientSecret: process.env.ELAN_CLIENT_SECRET!,
});
if (!valid) {
res.status(401).send("Invalid signature");
return;
}
// Process req.body as ElanWebhookPayload
res.sendStatus(200);
});Signature algorithm
signingInput = `${clientId}_${swarmId}_${nonce}`
signature = base64(HMAC-SHA256(clientSecret, signingInput))Headers delivered with each callback:
| Header | Description |
|--------------------|------------------------------|
| x-elan-signature | Base64 HMAC-SHA256 signature |
| x-elan-nonce | Random 32-char hex nonce |
You can also compute a signature independently:
import { computeWebhookSignature } from "@openelan/elan-node-sdk";
const sig = computeWebhookSignature(clientSecret, clientId, swarmId, nonce);Typed payload processing
After verifying the signature, cast the body to ElanWebhookPayload and work
with the strongly-typed event tree:
import type {
ElanWebhookPayload,
ElanWebhookPayloadEvent,
ElanEventContent,
ElanEventContentPart,
} from "@openelan/elan-node-sdk";Accessing content.parts
payload.payload.content is optional — it is only present on model-response
events. Always guard before iterating:
app.post("/webhook", (req: Request, res: Response) => {
// ... signature verification omitted for brevity
const body = req.body as ElanWebhookPayload;
const event: ElanWebhookPayloadEvent = body.payload;
const parts: ElanEventContentPart[] = event.content?.parts ?? [];
for (const part of parts) {
if (part.text !== undefined) {
console.log("text:", part.text);
} else if (part.functionCall !== undefined) {
console.log("functionCall:", part.functionCall.name, part.functionCall.args);
} else if (part.functionResponse !== undefined) {
console.log("functionResponse:", part.functionResponse.name, part.functionResponse.response);
}
}
res.sendStatus(200);
});Migration from v1 (flat API)
Prior versions of the SDK used a flat constructor and flat method calls directly on the client. The SDK now uses a namespace pattern. Update your code as follows:
Constructor
// Before (v1 — flat)
const client = new ElanIntegrationClient({
baseUrl: "https://api.elan.ai",
clientId: "my-client-id",
clientSecret: "my-client-secret",
timeoutMs: 10000,
});
// After (current — nested namespaces)
const client = new ElanIntegrationClient({
integration: {
baseUrl: "https://api.elan.ai",
clientId: "my-client-id",
clientSecret: "my-client-secret",
timeoutMs: 10000, // optional
},
auth: {
iamBaseUrl: "https://iam.elan.ai", // optional
},
});Method calls
| v1 (flat) | Current (namespaced) |
|-----------------------------------------------|-----------------------------------------------------|
| client.createSession(userId) | client.swarm.createSession(userId) |
| client.listSessions(userId) | client.swarm.listSessions(userId) |
| client.getSession(userId, sessionId, opts?) | client.swarm.getSession(userId, sessionId, opts?) |
| client.deleteSession(userId, sessionId) | client.swarm.deleteSession(userId, sessionId) |
| client.sendMessage(userId, sessionId, msg) | client.swarm.sendMessage(userId, sessionId, msg) |
| client.sendSync(userId, msg) | client.swarm.sendSync(userId, msg) |
| client.initUpload(userId, sessionId, body) | client.swarm.initUpload(userId, sessionId, body) |
| client.downloadFile(userId, fileUid) | client.swarm.downloadFile(userId, fileUid) |
| client.introspectSession(token) | client.auth.introspectSession(token) |
| client.logoutSession(params?) | client.auth.logoutSession(params?) |
