InlayDocs

Without React

The framework-agnostic core.

@inlayai/react is a thin layer over @inlayai/sdk, which has no framework dependency. Use it directly for a CLI, a worker, a test, or another UI framework.

// No React, no DOM. `createChat` is the framework-agnostic core the hooks are
// built on: the same wasm reducer, the same wire format, driven by callbacks
// instead of a subscription. The API origin defaults to production.
const chat = createChat({
  agentId: process.env.INLAY_AGENT_ID!,
  session,
  tools: {
    // Hand-wired handlers receive `unknown` args — the SDK will not pretend to
    // know an arbitrary agent's tool schema. Narrow them yourself, or generate
    // a typed client (see typed-clients.md) and get them for free.
    ask_question: (args) => `answering: ${(args as { question: string }).question}`,
  },
  onSnapshot: (snapshot) => {
    // `snapshot.rows` is the flat top-level feed — a subagent's inner work is
    // dropped here. Read `chat.fullSnapshot` for the path-tagged version.
    const latest = snapshot.rows.at(-1);
    process.stdout.write(rowText(latest));
  },
});

// send() rejects when the run fails — catch it, or the worker exits with an
// unhandled rejection instead of a readable error. See failure-handling.md.
try {
  await chat.send("hello");
} catch (e) {
  console.error("the run failed:", e);
}

// The same derived views the hooks expose are plain functions over the rows.
for (const turn of transcript(chat.snapshot.rows)) {
  console.log(`${turn.type}: ${turn.content}`);
}

What changes

  • Callbacks instead of subscriptions. onSnapshot fires on every update; onRun fires only when the topology moves.
  • chat.snapshot / chat.fullSnapshot replace the hooks. The first is the flat top-level feed, the second is path-tagged and includes inner-subgraph rows — the same split described in The timeline.
  • Derived views are plain functions. transcript(rows), rowsAtPath, outputsNamed, rowText — the hooks call exactly these.

What does not change

The reducer. Both packages drive the same WebAssembly core over the same wire format, so a conversation folds identically in Node, in a browser, and in the server's own tests. There is no second implementation to disagree with.

Streaming text

Set smooth: false (or read the headless surface, where pacing is off by default) to get raw deltas as they arrive. The paced reveal exists to make text readable in a UI; a script that is parsing output wants the raw stream.

Pacing covers every streamed text surface, not just chat: assistant content, reasoning, and streamed artifacts (a building output with a text format). It is display smoothing, not data smoothing — rows slice on render, so useBinding and headless readers always see the true value.

The connection

Every entry point — createChat, the createBilling / createRetrieval / createOps facades, listConversations — takes the same bag: { session, baseUrl?, fetch?, retries? }. baseUrl defaults to production (INLAY_PRODUCTION_URL); set it for a self-hosted or staging deployment.

If you give it a session provider rather than a fixed session, it refreshes and retries once on a 401, so long-running processes do not need their own token loop. It retries transient network failures with backoff (default 2), and never retries HTTP error responses — a 400 is an answer, not a hiccup.

Advanced: custom transports

The bag's transport field accepts a full custom Transport implementation — the seam the test suite uses to drive the wasm core without a network, and the escape hatch for a non-browser host whose fetch needs replacing wholesale. HttpTransport is the built-in one; construct it directly if you need to share one transport's session cache across facades by hand.

On this page