EgoXDocs

Client SDK

@egox/client for Node.js — ask, streaming, threading, header forwarding, webhooks.

@egox/client is the official EgoX SDK for Node.js. One method — ask — turns your project's configuration (model, prompts, tools, knowledge) into an answer. Streaming, conversation threading, trusted-header forwarding, and webhook verification are built in. Wire types come from @egox/contracts — the same package the backend uses — so your decoded responses can't silently drift from the wire.

npm install @egox/client

Quick start

import { EgoX } from '@egox/client';

const egox = new EgoX({
  apiKey: process.env.EGOX_API_KEY,     // egox_live_xxx or egox_test_xxx
  tenantId: process.env.EGOX_TENANT_ID, // your project's tenant id
});

const response = await egox.ask({ message: 'What is the refund policy?' });

console.log(response.answer);
console.log(`Tokens used: ${response.usage.totalTokens}`);

Configuration

const egox = new EgoX({
  apiKey: 'egox_live_xxx',    // required
  tenantId: 'your-tenant-id', // required
  timeout: 30000,             // optional — request timeout (ms)
  retries: 2,                 // optional — retry attempts (exponential backoff)
});

Threading

Two ways to keep a conversation stateful:

// 1. Use EgoX's thread id — round-trip it back.
const first  = await egox.ask({ message: 'What is your return policy?', externalUserId: 'user-123' });
const second = await egox.ask({ message: 'What about electronics?', threadId: first.threadId, externalUserId: 'user-123' });

// 2. Bring your own conversation id — never track EgoX's UUID.
const r1 = await egox.ask({ message: 'Hello',           externalThreadId: 'conv:abc123', externalUserId: 'user:42' });
const r2 = await egox.ask({ message: 'Continue please', externalThreadId: 'conv:abc123' });
// r1.threadId === r2.threadId

externalThreadId is any string ≤ 255 chars, opaque to EgoX, unique per-tenant. Failure modes are explicit:

You sendBehaviour
externalThreadId (first time)New thread created with this id attached
externalThreadId (subsequent)Same thread resolved every time
threadId = unknown EgoX UUID404 NotFound — no silent new thread
threadId = not a UUID400 InvalidInput — use externalThreadId for your own ids
BoththreadId wins (back-compat); debug warning logged

Forwarding context to your tools

Three channels, each for a different job:

FieldChannelUse for
authTokenforwarded as Authorization: Bearer … to your toolthe caller's user token
headersinbound HTTP headers, passed through per the tool's forwardHeaders allowlisttrusted, server-set context — locale, app version, user/tenant id behind a gateway
toolHeadersrequest body → merged into the tool callper-end-user secrets whose name collides with EgoX's own auth (Authorization, X-API-Key)
// Trusted context (server-side, caller already authenticated):
await egox.ask({
  message: 'Show my recent orders',
  externalUserId: ctx.user.id,
  headers: { 'x-user-id': ctx.user.id, 'x-locale': ctx.user.locale },
});

// Per-end-user secret destined for the tool, not for EgoX:
await egox.ask({
  message: 'Show my recent invoices',
  toolHeaders: { Authorization: `Bearer ${ctx.user.tenantApiToken}` },
});

Reserved header names

For headers to reach a tool, add the header name to that tool's forwardHeaders allowlist in the Console. SDK-reserved names are stripped (Authorization, Content-Type, Accept, X-API-Key, X-Tenant-Id, X-Request-Id, plus HTTP framing headers) — the full list is exported as SDK_RESERVED_HEADERS.

Streaming

EgoX streams events as it thinks, retrieves knowledge, calls tools, and generates text. Pick the consumption style that fits the call site.

await egox.askStream(
  { message: 'Walk me through resetting my password.' },
  {
    onEvent: (event) => {
      if (event.type === 'delta')        process.stdout.write(event.content);
      if (event.type === 'tool.calling') console.log('\n→', event.name);
      if (event.type === 'done')         console.log('\n[done]', event.usage);
    },
  },
);

Natural for GraphQL Subscription resolvers and anywhere for await beats a callback. Breaking the loop aborts the SSE connection.

for await (const ev of egox.askStreamIter({ message: 'Reset my password.' })) {
  switch (ev.type) {
    case 'delta':        yield { token: ev.content };  break;
    case 'tool.calling': yield { toolStart: ev.name };  break;
    case 'done':         yield { final: ev };           return;
    case 'error':        throw new Error(ev.message);
  }
}

Pass an AbortSignal for external cancellation (WebSocket close, aborted request):

const ctrl = new AbortController();
req.on('close', () => ctrl.abort());
for await (const ev of egox.askStreamIter(req.body, { signal: ctrl.signal })) { /* … */ }

Response object

interface AskResponse {
  threadId: string;                                     // conversation thread id
  answer: string;                                       // the AI's response text
  intent: 'vanilla' | 'rag' | 'tools' | 'rag_tools';    // how the request was classified
  toolUsed: string | null;                              // tool name if one was called
  ragChunksUsed: number;                                // # knowledge chunks used
  usage: { promptTokens: number; completionTokens: number; totalTokens: number };
  model: string;                                        // e.g. 'gpt-4.1'
}

Webhook signature verification

When EgoX calls your tool endpoints it signs the request. Verify the signature so only EgoX can invoke your tools.

import { createEgoXMiddleware } from '@egox/client/express';

const verifyEgoX = createEgoXMiddleware({
  secret: process.env.EGOX_WEBHOOK_SECRET,
  tolerance: 300, // max age in seconds (default 5 min)
});

app.post('/api/tools/check-order', verifyEgoX, (req, res) => {
  req.egox?.requestId; req.egox?.tenantId;   // verified metadata
  res.json({ status: 'Order shipped' });
});
import { verifySignature, validateSignature } from '@egox/client';

// throws on invalid:
verifySignature({
  signature: req.headers['x-egox-signature'],
  timestamp: req.headers['x-egox-timestamp'],
  body: JSON.stringify(req.body),
  secret: process.env.EGOX_WEBHOOK_SECRET,
});

// or boolean:
const ok = validateSignature({ /* same params */ });

Headers EgoX sends to your endpoints:

HeaderMeaning
X-EgoX-SignatureHMAC-SHA256 (sha256=…)
X-EgoX-TimestampUnix timestamp
X-EgoX-Request-IdRequest id for debugging
X-EgoX-Tenant-IdYour tenant/project id

Error handling

import {
  EgoXError, AuthenticationError, ValidationError,
  NetworkError, TimeoutError, RateLimitError,
} from '@egox/client';

try {
  await egox.ask({ message: 'Hello' });
} catch (error) {
  if (error instanceof AuthenticationError) { /* invalid API key */ }
  else if (error instanceof ValidationError) { /* bad request */ }
  else if (error instanceof RateLimitError)  { /* retry after error.retryAfter s */ }
  else if (error instanceof TimeoutError)    { /* timed out */ }
  else if (error instanceof NetworkError)    { /* network */ }
  else if (error instanceof EgoXError)       { /* error.code / error.message */ }
}

Health check & types

const healthy = await egox.ping();
import type {
  EgoXConfig, AskRequest, AskResponse,
  AskStreamRequest, AskStreamEvent,
  SignatureParams, ExpressMiddlewareOptions,
} from '@egox/client';

Calling /ask without the SDK

The SDK is a thin wrapper over one HTTP call. Any language can hit it directly:

POST /egox/ask
Content-Type: application/json
X-API-Key: {your-api-key}

{ "tenantId": "your-tenant-id", "message": "Generate a product description…",
  "responseFormat": "json_object", "jsonSchema": { /* … */ },
  "metadata": { "requestType": "product-description" } }

Responses use one envelope — see Core concepts.

The SDK is licensed Apache-2.0.

On this page