Node SDK in 5 minutes
@tansr/sdk is the headless language binding of the tansr kernel: prompts in, events out, zero UI dependencies. The query loop, tool scheduling, permission engine and context compaction all come from the kernel and are compiled inline; what you get is the same runtime as the tansr CLI, embedded in your application as a library.
Where to configure application prompts
Section titled “Where to configure application prompts”Platform systemPrompt holds the shared role; SDK system holds code-managed business instructions. With the default fallback policy, explicit system (including []) replaces the platform segment. With prepend, the platform segment comes first and stays present even for []. Put environment notes in systemAppend to avoid overriding the platform default.
Windows Electron runs the SDK in its main process; renderer input remains a user message. These prompt features await compatible releases. Supporting builds expose read-only session.applicationPrompt with source / policy, without prompt text. Platform prompts and policies refresh before a subsequent turn; an executing turn keeps its configuration, and the same session and history are preserved. See Application system prompts for the truth table, platform PUT / GET, and complete examples.
Prerequisites
Section titled “Prerequisites”To add requirements while a task is running, use same-turn input with getInputTarget, submitInput and receipt lookup. It does not interrupt the execution or create a session. This first release supports memory text delivery and requires compatible package releases.
For application shutdown, see session and resource cleanup. The additive closeAsync / drain APIs distinguish terminal events from actual cleanup; compatible releases are required.
| Requirement | Value |
|---|---|
| Node.js | ≥ 22.19 |
| Electron (if applicable) | ≥ 39; the SDK runs in the main process |
| Module format | ESM only ("type": "module"; CJS require() is not supported) |
| TypeScript | target ≥ ES2022; Electron projects should set skipLibCheck: true |
The published artifact is a single-file ESM bundle plus a single .d.ts. The only third-party runtime dependencies are undici, zod and zod-to-json-schema; the optional dependency @vscode/ripgrep degrades automatically if it fails to install, nothing to do on your side.
Install
Section titled “Install”npm install @tansr/sdkCredentials: three model sources, pick one
Section titled “Credentials: three model sources, pick one”Where the SDK gets its model decides which credential you need:
| Source | What you pass | Fits |
|---|---|---|
| Token source | { token, baseUrl } |
Client distribution (Electron / desktop); the model catalog and capability flags are pushed by the platform per app config, zero local config |
| Managed source (BYOK) | model: '<alias>' + a local .tansr/settings.json |
Your own servers / scripts, bringing your own model API key |
| Injected source | { client, model } as a pair |
Tests (scripted models) or custom integrations |
The three are mutually exclusive; mixing them is rejected at assembly time with invalid_options. This article uses the token source, because that is the right form when handing an agent to end users.
The token source’s credential flow is a three-party loop:
- In the console, create a desktop app (or a server app) and obtain
appidandappkey. The appkey stays on your backend only. - The client app calls your backend with its own login state; your backend calls
POST /v1/app-tokenswith the appkey to mint a short-livedapp_usertoken for that user (ttlSeconds60–86400), and ships only{ token, expiresAt }to the client. - The client app hands this token to the SDK.
The official sample examples/token-server shows a complete mint endpoint (including request signing). The appkey never leaves your backend — not shipped to clients, not logged, not in error responses, not committed.
If you are just writing a script on your own machine, you can start with the managed source: declare a provider in .tansr/settings.json (write only the environment variable name for the API key; the value comes from the environment), then createSession({ model: 'main' }).
First session
Section titled “First session”import { createSession } from '@tansr/sdk';
// token 由你的服务端换发;baseUrl 是平台网关地址const session = await createSession({ token, baseUrl: 'https://api.tansr.com' });
session.send('帮我总结这份合同');
for await (const event of session.events) { if (event.type === 'msg.text.delta') process.stdout.write(event.text); if (event.type === 'turn.completed') break;}
session.close();Key points:
createSessionis async; alwaysawaitit.send()starts a new turn when idle; callingsend()again while a turn is running goes through the kernel’s injection barrier and joins the current turn — input is never lost.eventsis anAsyncIterable<KernelEvent>that is continuous across turns with a monotonicseq; several consumers canfor awaitat the same time and each gets the full stream.close()is idempotent; withoutclose()thefor awaitnever exits on its own — the most common cause of “the event stream is stuck”.
See the events
Section titled “See the events”Events are a flat discriminated union; branch directly on event.type. On your first run, print every type to get a feel for what happens in a turn:
for await (const event of session.events) { console.log(event.seq, event.type);}You will see roughly this sequence: session.created → turn.started → some msg.block.start / msg.text.delta / msg.block.end → (if tools are called) tool.proposed / tool.permission.decided / tool.started / tool.completed → cost.usage.updated → turn.completed.
If you are building a UI, do not stitch events by hand: use createSessionView to reduce the event stream into immutable view snapshots, then createNarrator to produce human-readable log lines. Both ship with the package.
Common first hurdles
Section titled “Common first hurdles”| Symptom | Cause and fix |
|---|---|
Throws invalid_options |
Fields from different sources were mixed, or the token source was given config / capabilities (with the token source, capability flags are always governed by the platform). Read the message and fix the code |
Throws assembly_failed |
The managed-source config is unusable, alias resolution failed, or the token-source bundle fetch failed; cause keeps the underlying error |
| Every tool is denied | permission.askUser is not wired and the tool is not read-only — the default is fail-closed. See Sessions and events |
| The event stream never exits | You forgot session.close() |
Next steps
Section titled “Next steps”- Sessions and events: multi-turn semantics, event families, the SessionView projection.
- Checkpoints and resume: persistence, recovery across restarts, manual compaction and snapshots.
- Platform assembly and the bundle: capability flags, the three tool rings, what the token source receives.
- Errors and retries: SDK error codes, platform error codes, what to retry and what not to.
Was this page helpful?
Thanks for your feedback.