Version 0.1.0-alpha.1 · canonical source: iRoute repository

iRoute Node.js and TypeScript SDK

@iroute/sdk is the typed Node.js client for the iRoute v1 HTTP and SSE API. It supports execution, polling, incremental event streaming, cancellation, approvals, artifacts, gateway health, and observability without owning routing or provider policy.

Status and requirements

  • SDK version: 0.1.0-alpha.1
  • Runtime minimum: Node.js 22.20.0
  • Verified baseline: Node.js 24.18.1 and TypeScript 7.0.2
  • Module format: ESM
  • Package name: @iroute/sdk

Once published, the public alpha is installed from npm as a prerelease package. The source installation below remains available.

Start iRoute

docker compose --file deploy/compose.sqlite.yaml up --build --wait

The default API URL is http://localhost:8080. See the shared SDK usage guide for source-run and authentication options.

Install from npm

npm install @iroute/sdk@0.1.0-alpha.1

Install from source

Build the SDK, then install its directory into an application:

npm ci --prefix ../iRoute/sdks/node
npm run build --prefix ../iRoute/sdks/node
npm install ../iRoute/sdks/node

The package exports dist/index.js and its TypeScript declarations.

Create a client

import { IRouteClient } from '@iroute/sdk';

const client = new IRouteClient(
  new URL(process.env.IROUTE_URL ?? 'http://localhost:8080'),
  {
    token: process.env.IROUTE_TOKEN,
    tenantId: process.env.IROUTE_TENANT ?? 'demo',
    actorId: process.env.IROUTE_ACTOR ?? 'node-app',
    permissionScopes: []
  }
);

In Development the identity options become headers. In JWT mode the server derives identity and scopes from token claims and will not allow headers to elevate them. A custom Fetch implementation can be supplied as fetch for testing or application-specific transport behavior.

Submit an execution

import { randomUUID } from 'node:crypto';
import type { TaskRequest } from '@iroute/sdk';

const request = {
  taskType: 'email.draft',
  input: { purpose: 'Confirm the Node SDK integration.' },
  idempotencyKey: `node-email-${randomUUID()}`,
  constraints: {
    maxOutputTokens: 500,
    deadlineMilliseconds: 30_000,
    minimumQuality: 0.8
  }
} satisfies TaskRequest;

let execution = await client.execute(request);

console.log(execution.executionId, execution.status);

Use a stable business idempotency key in real applications. Generating a UUID is appropriate only when the application is creating a new logical operation.

Poll until terminal

import type { ExecutionStatus } from '@iroute/sdk';

const terminal = new Set<ExecutionStatus>([
  'Succeeded', 'Failed', 'Cancelled', 'TimedOut'
]);
const sleep = (milliseconds: number) =>
  new Promise(resolve => setTimeout(resolve, milliseconds));

while (!terminal.has(execution.status)) {
  await sleep(500);
  const current = await client.get(execution.executionId);
  if (!current) throw new Error('Execution is not visible to this tenant.');
  execution = current;
}

if (execution.status === 'Succeeded') {
  console.log(execution.outcome?.output);
} else {
  console.error(execution.error?.code, execution.error?.detail);
}

WaitingForApproval is non-terminal.

Stream and reconnect to events

streamEvents consumes SSE incrementally and accepts an AbortSignal:

const controller = new AbortController();
let lastSequence = 0;

for await (const event of client.streamEvents(
  execution.executionId,
  lastSequence,
  controller.signal
)) {
  console.log(event.sequence, event.type);
  lastSequence = event.sequence;
}

Persist lastSequence after processing each event. On reconnect, call streamEvents(executionId, lastSequence) to receive only newer events. Ignore unknown event types and fields.

Time-bounded requests

Every operation accepts an optional AbortSignal:

const signal = AbortSignal.timeout(30_000);
const snapshot = await client.get(execution.executionId, signal);

Aborting the HTTP request does not automatically cancel the durable execution. Call cancel when the server-side execution should also be cancelled.

Cancel an execution

const accepted = await client.cancel(execution.executionId);
if (!accepted) console.log('Execution was not found.');

Cancellation is durable but cooperative. Poll or reconnect to events to observe the terminal result.

Approve or deny an action

Read actionId from an approval.required event:

const result = await client.submitApproval(execution.executionId, {
  actionId,
  approved: true,
  reason: 'Reviewed by an authorized operator'
});

execution = result.execution;

The deciding token needs approval:grant and the action's required scope.

Retrieve an artifact

const reference = execution.outcome?.artifacts[0];
if (reference) {
  const artifact = await client.getArtifact(reference.artifactId);
  console.log(artifact?.content);
}

Not found returns undefined, including for cross-tenant access.

Health and observability

const health = await client.getModelGatewayHealth();
console.log(health.gatewayId, health.status);

const summary = await client.getObservabilitySummary({
  from: new Date(Date.now() - 24 * 60 * 60 * 1000),
  to: new Date(),
  taskType: 'email.draft'
});
console.log(summary.totals.executions, summary.gatewayGroups);

const timeline = await client.getExecutionTimeline(execution.executionId);
console.log(timeline?.traceId, timeline?.events);

Gateway health returns its typed body for both HTTP 200 and 503. Observability is tenant scoped. Reported cost has no currency in v1.

Errors

import { IRouteApiError } from '@iroute/sdk';

try {
  execution = await client.execute(request, AbortSignal.timeout(30_000));
} catch (error) {
  if (error instanceof IRouteApiError) {
    console.error(error.status, error.code, error.detail);
    console.error(error.responseBody);
  } else {
    throw error;
  }
}

The SDK does not retry automatically. Retrying a submission requires the same idempotency key. Provider fallback remains server-side.

Method reference

MethodResult
execute(request, signal?)Typed ExecutionSnapshot
get(executionId, signal?)Snapshot or undefined
cancel(executionId, signal?)false when not found
submitApproval(executionId, decision, signal?)Typed ApprovalResult
streamEvents(executionId, afterSequence?, signal?)Incremental async generator
getArtifact(artifactId, signal?)Artifact or undefined
getModelGatewayHealth(signal?)Typed gateway health
getObservabilitySummary(query?, signal?)Typed summary
getExecutionTimeline(executionId, signal?)Timeline or undefined

Run the example and tests

From the repository root:

npm run build --prefix sdks/node
node examples/sdks/node/execute.mjs
npm test --prefix sdks/node

Public wire shapes are defined by the OpenAPI document and JSON Schemas.