@workerdeck/core 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,26 +88,64 @@ supports — no CLI process, no config directory. `createEngineSession()` assemb
88
88
  the capability-scoped tool set, and the executor that runs tool calls.
89
89
 
90
90
  ```ts
91
+ import variant from '@jitl/quickjs-ng-wasmfile-release-asyncify'
92
+ import { loadEngine } from '@workerdeck/sandbox'
91
93
  import { createEngineSession, QuickJsExecutor } from '@workerdeck/core'
92
94
 
95
+ // Server-side, the WASM guest is loaded once for the process and shared by every
96
+ // session. The variant package is a peer dependency you install yourself — core
97
+ // does not pick one for you, because the browser build and the server build are
98
+ // different artifacts and only you know which side this is.
99
+ const executor = new QuickJsExecutor({ engine: await loadEngine(variant), defaultTimeoutMs: 15_000 })
100
+
93
101
  const runner = createEngineSession({
94
102
  config: { ...createSessionRequest, languageModel: anthropic('claude-sonnet-5') },
95
- selectExecutor: () => new QuickJsExecutor({ timeoutMs: 15_000 }),
103
+ selectExecutor: () => executor,
96
104
  capabilities: { webFetch: {} }, // backends, not grants
105
+ seedVfs: { '/README.md': 'scratch space' },
97
106
  })
98
107
  ```
99
108
 
100
- Two seams matter here:
109
+ Three seams matter here:
101
110
 
102
111
  - **Capabilities are grants, wired separately from backends.** `createToolContext` builds the tool
103
112
  set from what a profile grants (`fs_*`, `eval_script`, `web_search`, `download`, `web_fetch`,
104
113
  `deliver_file`) over what the host actually wired. There is no shell and no host filesystem: the
105
114
  files a session sees are an in-memory scratch VFS. Every tool is typed `sandboxed` or
106
115
  `authoritative`, and only sandboxed calls may leave the server.
107
- - **`ToolExecutor` decides where code runs.** `QuickJsExecutor` runs it in-process in the
108
- [QuickJS guest](https://www.npmjs.com/package/@workerdeck/sandbox); `BrowserBridgeExecutor`
109
- ships it to the user's own tab, so client-held documents never reach the server; and
110
- `DeferredExecutor` hands the call off to something that will answer later.
116
+ - **Your own tools go in at a stated trust level.** `tools: { name: { tool, trust } }` is the seam
117
+ for anything that is neither a built-in capability nor MCP. `authoritative` means it runs here
118
+ with this process's authority and must declare `execute`; `sandboxed` means it rides the executor
119
+ seam and must *not*. Both contradictions are refused at assembly rather than at runtime, because
120
+ a sandboxed tool that quietly ran in-process would defeat the only thing sandboxing it was for.
121
+ - **`ToolExecutor` decides where code runs**, and that is a real architectural choice — see below.
122
+
123
+ ### Which executor?
124
+
125
+ | | `QuickJsExecutor` (in-process) | `BrowserBridgeExecutor` (the tab) | `DeferredExecutor` |
126
+ |---|---|---|---|
127
+ | Runs where | this Node process, WASM guest | the attached client | wherever you send it |
128
+ | Needs a client attached | no | **yes** | no |
129
+ | Data locality | data must reach the server | client-held data never leaves the tab | n/a |
130
+ | Trust | you own both sides | results are **untrusted input** — the sandboxed party answers | depends |
131
+ | Latency | in-process | a WS round trip | unbounded (the session parks) |
132
+
133
+ The question to ask is **where the data the loop reasons over already lives**:
134
+
135
+ - In your database or on your disk → in-process. Pushing execution into the tab buys nothing and
136
+ hands an executor to the party you are sandboxing against.
137
+ - In the user's browser — a document they are editing, a file they dropped, something you would
138
+ rather not receive at all → the bridge. This is the case it exists for.
139
+ - Somewhere that answers in minutes or hours (a queue, a human, a build) → deferred, and let the
140
+ session park.
141
+
142
+ Two constraints that decide it for you regardless: an **unattended job** has no attached client, so
143
+ the bridge is not available to it; and a bridged result is by definition produced by the sandboxed
144
+ party, so nothing authoritative may ever be routed there.
145
+
146
+ An executor is chosen per *call*, not per session (`selectExecutor` runs at assembly, but a routing
147
+ executor may keep `eval_script` in-process and defer a long-running tool), which is what lets one
148
+ session mix all three.
111
149
 
112
150
  ## Work that outlives the runner
113
151
 
@@ -127,6 +165,36 @@ selectExecutor: () => new DeferredExecutor({
127
165
  for you — a `SessionStore` plus `POST /executions/:id/result` — but the mechanism is here, and works
128
166
  with no server at all.
129
167
 
168
+ ## Rules you cannot infer from the types
169
+
170
+ Things the compiler will not tell you, each of which has cost someone real time:
171
+
172
+ - **A declared MCP server that never connected is refused, not degraded.** If a profile's
173
+ `session.mcpServers` names a server and it isn't there, `createEngineSession` throws. The old
174
+ behaviour — start anyway, minus those tools — produced a session that reported perfectly healthy
175
+ while the agent apologised its way through every request that needed it. Pass
176
+ `connectMcpTools(servers, { required: true })` to fail at connect time instead, and hand the
177
+ resulting connection over as `mcp` (not just `mcp.tools`) so the check is exact.
178
+ - **A stateless MCP server must answer `GET` with 405.** The client opens the SSE stream with a
179
+ `GET` before it sends anything. Mounted under a framework's default 404, the whole connect fails
180
+ with an error that names neither the method nor the route.
181
+ - **Never seed the VFS by hand on a restore.** Use `seedVfs`, which is ignored when
182
+ `config.restore` is set. Building `config.vfs` yourself still works and still wins — and then
183
+ overwriting the files the parked turn wrote is yours to avoid.
184
+ - **Forward the host's `id`.** `createEngineSession({ id })` is how a session comes back as
185
+ *itself* across a gateway restart. Dropping it strands every client's route and unread mark, and
186
+ the rebuild is refused.
187
+ - **`onClose` runs on park as well as close.** Parking releases the same resources; a disposer that
188
+ assumes the session is over will close an MCP connection the woken session still needs to rebuild.
189
+ - **Authoritative tools are never bridged.** `withMcpTools` marks everything authoritative by
190
+ construction. If you want a host tool the tab may run, declare it `sandboxed` in `tools` — and
191
+ then treat its results as untrusted input, because the tab produced them.
192
+ - **Never make a tool's operation depend on a field being absent.** "Create when `id` is missing,
193
+ overwrite when it is present" is the shape that breaks: models send `""` — and, observed live,
194
+ `" "` — rather than omitting, and some providers mark every property required so the model
195
+ *cannot* omit. `z.string().min(1).optional()` does not save it (a space has length 1). Split it
196
+ into two tools with required arguments, and trim-and-blank-check optional strings inside `run`.
197
+
130
198
  ## Also exported
131
199
 
132
200
  `InputQueue` (the push-based `AsyncIterable` bridging `sendMessage()` into the SDK's streaming
package/build/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
- import { McpServerStatus, Options, Query, SDKMessage, SDKUserMessage, SessionMessage } from "@anthropic-ai/claude-agent-sdk";
1
+ import { McpServerStatus, Options, Query, SDKMessage, SDKSessionInfo, SDKUserMessage, SessionMessage } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { ApiMessage, CreateSessionRequest, EngineCapabilities, McpServerConfigWire, McpServerStatusInfo, MessageAttachment, ModelOption, PermissionMode, PermissionRequest, ProfileEngine, ProfileInfo, SdkSessionSummary, SessionEvent, SessionEventBody, SessionInfo, SessionStatus, ToolCallRequestFrame, ToolExecutionBackend, ToolExecutionOutput } from "@workerdeck/protocol";
3
- import { LanguageModel, ModelMessage, Tool, ToolSet } from "ai";
3
+ import { LanguageModel, LanguageModel as LanguageModel$1, ModelMessage, Tool, Tool as Tool$1, ToolSet, ToolSet as ToolSet$1 } from "ai";
4
4
  import { SandboxEngine, SandboxVfs } from "@workerdeck/sandbox";
5
5
  import { Readable, Writable } from "node:stream";
6
6
 
7
- //#region src/attachments.d.ts
7
+ //#region src/lib/attachments.d.ts
8
8
  /**
9
9
  * An attachment plus its bytes — what the host hands a runner at send time.
10
10
  *
@@ -44,7 +44,7 @@ declare function attachmentContentBlocks(attachments: readonly AttachmentInput[]
44
44
  /** Strip the bytes: the log-safe half of an attachment. */
45
45
  declare function attachmentRef(attachment: AttachmentInput): MessageAttachment;
46
46
  //#endregion
47
- //#region src/tool-executor.d.ts
47
+ //#region src/executors/tool-executor.d.ts
48
48
  /**
49
49
  * Result of one tool execution, whenever it arrives. `failed` is a normal
50
50
  * outcome the agent loop adapts to — not an exception.
@@ -175,8 +175,19 @@ interface Runner {
175
175
  /** Begin the session. Idempotent; returns the run promise (resolves when the run ends). */
176
176
  start(): Promise<void>;
177
177
  info(): SessionInfo;
178
- /** Replay buffered events with seq > afterSeq, then deliver live events. Returns unsubscribe. */
179
- subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
178
+ /** Replay buffered events with seq > afterSeq, then deliver live events. Returns unsubscribe.
179
+ *
180
+ * `coalesceReplay` drops state readings superseded later in the same replay —
181
+ * the fifty stale context/rate-limit polls a long session accumulates, which
182
+ * a client otherwise applies one by one and *renders*, counting its usage
183
+ * meters up through the session's history on every attach. Opt-in, and the
184
+ * default must stay off: it is only sound for a consumer whose handling of
185
+ * those events is last-write-wins, and `parking.ts` — which subscribes from
186
+ * seq 0 — branches on `status_changed` instead. Live events are never
187
+ * affected; this touches the buffered replay alone. */
188
+ subscribe(listener: SessionEventListener, afterSeq?: number, options?: {
189
+ coalesceReplay?: boolean;
190
+ }): () => void;
180
191
  /** Queue a user message for the session (starts the next turn when idle).
181
192
  * `attachments` carry their bytes to the engine and their reference to the
182
193
  * event log (see {@link AttachmentInput}). */
@@ -221,7 +232,7 @@ interface Runner {
221
232
  close(reason?: 'client' | 'server' | 'error'): void;
222
233
  }
223
234
  //#endregion
224
- //#region src/runner.d.ts
235
+ //#region src/engines/claude/runner.d.ts
225
236
  type QueryFn = (params: {
226
237
  prompt: AsyncIterable<SDKUserMessage>;
227
238
  options?: Options;
@@ -229,6 +240,9 @@ type QueryFn = (params: {
229
240
  type HistoryFn = (sdkSessionId: string, options: {
230
241
  dir?: string;
231
242
  }) => Promise<SessionMessage[]>;
243
+ type SessionInfoFn = (sdkSessionId: string, options: {
244
+ dir?: string;
245
+ }) => Promise<SDKSessionInfo | undefined>;
232
246
  type SessionRunnerConfig = CreateSessionRequest & {
233
247
  /** Injectable query implementation (tests, instrumentation). Defaults to the SDK's query(). */queryFn?: QueryFn; /** Environment for the spawned Claude Code process. Defaults to process.env. */
234
248
  env?: Record<string, string | undefined>;
@@ -239,6 +253,10 @@ type SessionRunnerConfig = CreateSessionRequest & {
239
253
  * starts, so late-attaching clients get a full transcript. Default true. */
240
254
  backfillHistory?: boolean; /** Injectable history reader (tests). Defaults to the SDK's getSessionMessages. */
241
255
  historyFn?: HistoryFn;
256
+ /** Injectable session-metadata reader (tests). Defaults to the SDK's
257
+ * getSessionInfo — the only place the CLI's own session title is readable
258
+ * from, since no message on the stream carries it. */
259
+ sessionInfoFn?: SessionInfoFn;
242
260
  };
243
261
  /**
244
262
  * One live Agent SDK session: owns the query() call, the streaming input queue, the
@@ -287,22 +305,33 @@ declare class SessionRunner implements Runner {
287
305
  /**
288
306
  * Replay buffered events with seq > afterSeq, then deliver live events.
289
307
  * Returns an unsubscribe function.
308
+ *
309
+ * Replay honours the reset watermark: transcript content below the latest
310
+ * `conversation_reset` is skipped (the reducer would clear it again anyway,
311
+ * and a pre-reset client that never learned the reducer's case would render
312
+ * a conversation the engine has discarded), while state-bearing events —
313
+ * which are emitted once and never again — always replay. The reset event
314
+ * itself replays (the skip is strictly-below), which is what clears a
315
+ * reconnecting client still holding pre-reset rows; superseded resets are
316
+ * content below the newer one and are skipped with what they cleared.
290
317
  */
291
- subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
318
+ subscribe(listener: SessionEventListener, afterSeq?: number, options?: {
319
+ coalesceReplay?: boolean;
320
+ }): () => void;
292
321
  }
293
322
  //#endregion
294
- //#region src/ai-sdk-runner.d.ts
323
+ //#region src/engines/provider/runner.d.ts
295
324
  /** `cwd` is optional for this engine: the loop has no host-filesystem coupling
296
325
  * (tools get scoped VFS handles instead). Defaults to process.cwd() for display. */
297
326
  type AiSdkRunnerConfig = Omit<CreateSessionRequest, 'cwd'> & {
298
327
  cwd?: string;
299
328
  /** AI SDK language model instance (or gateway model id string). Provider
300
329
  * resolution from profiles happens host-side; core takes the resolved model. */
301
- languageModel: LanguageModel;
330
+ languageModel: LanguageModel$1;
302
331
  /** Tools available to the loop. Tools WITHOUT `execute` halt the loop when
303
332
  * called; their calls surface via `pendingToolCalls` and are answered with
304
333
  * `resolveToolCall()`, which re-enters the loop by message-state replay. */
305
- tools?: ToolSet; /** System prompt (AI SDK v7 `instructions`). */
334
+ tools?: ToolSet$1; /** System prompt (AI SDK v7 `instructions`). */
306
335
  instructions?: string; /** Max loop steps per turn. Default 20. */
307
336
  maxSteps?: number;
308
337
  /**
@@ -320,7 +349,19 @@ type AiSdkRunnerConfig = Omit<CreateSessionRequest, 'cwd'> & {
320
349
  memoryLimitBytes?: number;
321
350
  }; /** Which backend the executor represents, for `execution_dispatched` events. */
322
351
  executionBackend?: ToolExecutionBackend; /** Swap models mid-session (`set_model`). Unset = setModel() is rejected. */
323
- resolveModel?: (modelId: string | undefined) => LanguageModel;
352
+ resolveModel?: (modelId: string | undefined) => LanguageModel$1;
353
+ /**
354
+ * Live MCP status for this session, when the host wired MCP at all. Unlike
355
+ * the CLI engines — which ask their binary — this engine's MCP is entirely
356
+ * host-assembled, so the host is the only party that can answer. Unset means
357
+ * "no MCP here", which reads as an empty list rather than an error: a session
358
+ * with no servers is a fact, not a missing feature.
359
+ *
360
+ * Named apart from the inherited `mcpServers` request field on purpose —
361
+ * that one is the *wire configuration* a client asked for, this one is what
362
+ * the host actually connected.
363
+ */
364
+ reportMcpServers?: () => Promise<McpServerStatusInfo[] | undefined>;
324
365
  /** Called once when the session closes — release per-session resources the
325
366
  * host attached (an MCP connection, a watcher). Errors are swallowed. Also
326
367
  * runs when the session parks: parking releases the same resources. */
@@ -444,19 +485,30 @@ declare class AiSdkRunner implements Runner {
444
485
  setModel(model?: string): Promise<void>;
445
486
  fail(message: string): void;
446
487
  close(reason?: 'client' | 'server' | 'error'): void;
447
- subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
488
+ subscribe(listener: SessionEventListener, afterSeq?: number, options?: {
489
+ coalesceReplay?: boolean;
490
+ }): () => void;
448
491
  /**
449
492
  * Deliver the result of an execution this runner dispatched. Used by the host
450
493
  * when a backend settled out-of-band (a browser bridge answering later, a
451
494
  * deferred executor). Idempotent by executionId.
452
495
  */
453
496
  settleExecution(executionId: string, result: ToolExecutionResult): boolean;
497
+ /**
498
+ * This session's MCP servers, as the host assembled them.
499
+ *
500
+ * Always answers — an empty list when no MCP was wired — because the
501
+ * alternative (undefined, which the server turns into a 501) says "this
502
+ * engine cannot tell you", and this engine can: the host that built the
503
+ * session is the only party who knows, and it has been asked.
504
+ */
505
+ mcpServers(): Promise<McpServerStatusInfo[] | undefined>;
454
506
  /** Host-facing rename: writes `meta.title`, which `#title()` prefers. Clearing
455
507
  * it (undefined) restores the derived title. The engine is never told. */
456
508
  setTitle(title: string | undefined): void;
457
509
  }
458
510
  //#endregion
459
- //#region src/claude-auth.d.ts
511
+ //#region src/engines/claude/auth.d.ts
460
512
  /**
461
513
  * Credential presence for one Claude Code environment, as the CLI itself reports
462
514
  * it. 'unknown' means the check could not run at all (no binary, a CLI too old
@@ -494,7 +546,7 @@ declare function checkClaudeAuth(env: Record<string, string | undefined>, option
494
546
  timeoutMs?: number;
495
547
  }): Promise<ClaudeAuthStatus>;
496
548
  //#endregion
497
- //#region src/quickjs-executor.d.ts
549
+ //#region src/executors/quickjs-executor.d.ts
498
550
  /** Resolve a URL to text for the guest. Runs host-side with host authority —
499
551
  * this is where a credential may be attached, never inside the sandbox. */
500
552
  type HostFetch = (url: string, signal: AbortSignal) => Promise<string>;
@@ -527,7 +579,7 @@ declare class QuickJsExecutor implements ToolExecutor {
527
579
  * (not the bare parent). Only http(s) — no file:, data:, or other schemes. */
528
580
  declare function isHostAllowed(url: string, allowedHosts: string[]): boolean;
529
581
  //#endregion
530
- //#region src/pending-registry.d.ts
582
+ //#region src/lib/pending-registry.d.ts
531
583
  /**
532
584
  * One registry for every request that leaves the runner and must come back:
533
585
  * permission approvals, browser-bridged tool calls, and deferred executions.
@@ -589,7 +641,7 @@ declare class PendingRequestRegistry {
589
641
  cancelAll(reason: string, error: string, kind?: PendingKind): number;
590
642
  }
591
643
  //#endregion
592
- //#region src/browser-bridge-executor.d.ts
644
+ //#region src/executors/browser-bridge-executor.d.ts
593
645
  /** Answer a bridged call, as delivered by the client over the wire. */
594
646
  type BridgeAnswer = {
595
647
  output: ToolExecutionOutput;
@@ -643,7 +695,7 @@ declare class BrowserBridgeExecutor implements ToolExecutor {
643
695
  /** Map a registry outcome onto the executor's result contract. */
644
696
  declare function toExecutionResult(outcome: PendingOutcome<BridgeAnswer>): ToolExecutionResult;
645
697
  //#endregion
646
- //#region src/deferred-executor.d.ts
698
+ //#region src/executors/deferred-executor.d.ts
647
699
  /** A dispatched execution, as handed to the backend that will run it. */
648
700
  type DeferredDispatch = {
649
701
  /** Correlation id. The result is delivered under it — `POST
@@ -693,7 +745,7 @@ declare class DeferredExecutor implements ToolExecutor {
693
745
  dispatch(call: ToolExecutionCall): Promise<ToolExecutionDispatch>;
694
746
  }
695
747
  //#endregion
696
- //#region src/web-fetch.d.ts
748
+ //#region src/engines/provider/web-fetch.d.ts
697
749
  /**
698
750
  * `web_fetch` backend, close to Claude Code's original WebFetch: fetch a URL,
699
751
  * convert HTML to markdown, and (optionally) digest it with a model against the
@@ -739,7 +791,7 @@ declare function isPrivateAddress(address: string): boolean;
739
791
  */
740
792
  declare function htmlToMarkdown(html: string): string;
741
793
  //#endregion
742
- //#region src/tools.d.ts
794
+ //#region src/engines/provider/tools.d.ts
743
795
  /**
744
796
  * How much authority a tool carries, which decides where it may run.
745
797
  *
@@ -755,7 +807,7 @@ type ToolDefinition = {
755
807
  trust: ToolTrust;
756
808
  /** The AI SDK tool. Sandboxed tools are declared WITHOUT `execute` so the loop
757
809
  * hands them to the ToolExecutor seam rather than running them inline. */
758
- tool: Tool;
810
+ tool: Tool$1;
759
811
  };
760
812
  type ToolContextOptions = {
761
813
  /** Executor for sandboxed tools. Selected per call by the host (browser bridge
@@ -795,7 +847,7 @@ type ToolContextOptions = {
795
847
  /** Everything a session's tools need, plus the tool set to hand the runner. */
796
848
  type ToolContext = {
797
849
  vfs: SandboxVfs;
798
- tools: ToolSet;
850
+ tools: ToolSet$1;
799
851
  definitions: ToolDefinition[]; /** Names the loop must not execute inline (they go through the executor). */
800
852
  sandboxedToolNames: string[];
801
853
  };
@@ -811,9 +863,38 @@ type ToolContext = {
811
863
  declare function createToolContext(options: ToolContextOptions): ToolContext;
812
864
  /** Add host-side MCP tools to a context. They are ALWAYS authoritative: they run
813
865
  * server-side with server credentials, and must never be handed to a browser. */
814
- declare function withMcpTools(context: ToolContext, mcpTools: ToolSet): ToolContext;
866
+ declare function withMcpTools(context: ToolContext, mcpTools: ToolSet$1): ToolContext;
867
+ /** A tool the host supplies, with the trust level it is to run at. */
868
+ type HostToolDefinition = {
869
+ tool: Tool$1;
870
+ /**
871
+ * Where this tool may run. `authoritative` tools execute inline in the
872
+ * gateway and MUST declare `execute`; `sandboxed` ones must NOT, because the
873
+ * loop hands them to the {@link ToolExecutor} seam instead — which is what
874
+ * makes them bridgeable to an untrusted tab.
875
+ */
876
+ trust: ToolTrust;
877
+ };
878
+ /**
879
+ * Add host-supplied tools to a context at an explicit trust level.
880
+ *
881
+ * The trust level is the whole point of the seam: {@link withMcpTools} can only
882
+ * produce authoritative tools, so a host tool that *should* be sandboxed — and
883
+ * therefore executable in the browser tab that asked for it — had no way to be
884
+ * expressed at all. Here the host says which it is, and the contradictions are
885
+ * refused rather than silently resolved:
886
+ *
887
+ * - a `sandboxed` tool carrying `execute` would run inline in this process with
888
+ * the gateway's ambient authority, which is exactly what sandboxing it was
889
+ * meant to prevent;
890
+ * - an `authoritative` tool *without* `execute` would park the turn on a call no
891
+ * executor claims, and the session would simply stop.
892
+ */
893
+ declare function withHostTools(context: ToolContext, hostTools: Record<string, HostToolDefinition>, /** What to call these in error messages ('MCP tool', 'host tool'). */
894
+
895
+ kind?: string): ToolContext;
815
896
  //#endregion
816
- //#region src/engine.d.ts
897
+ //#region src/engines/provider/session.d.ts
817
898
  type EngineSessionOptions = {
818
899
  /** Resolved session config (profile defaults already applied). */config: AiSdkRunnerConfig; /** The profile that selected this engine, when there was one. */
819
900
  profile?: ProfileInfo;
@@ -822,7 +903,7 @@ type EngineSessionOptions = {
822
903
  * this so core never imports a provider SDK and never reads credentials —
823
904
  * they come from the operator's environment, exactly like the Claude chain.
824
905
  */
825
- resolveModel: (profile: ProfileInfo | undefined, config: AiSdkRunnerConfig) => LanguageModel;
906
+ resolveModel: (profile: ProfileInfo | undefined, config: AiSdkRunnerConfig) => LanguageModel$1;
826
907
  /**
827
908
  * Executor for sandboxed tools. Return the browser bridge when a client is
828
909
  * attached and the server sandbox otherwise; the seam makes them
@@ -849,10 +930,38 @@ type EngineSessionOptions = {
849
930
  * Default true — set false to withhold it. */
850
931
  deliverFiles?: boolean;
851
932
  };
933
+ /**
934
+ * A live MCP connection from {@link connectMcpTools} — the preferred way to
935
+ * hand MCP to a session, and the only one that can fail loudly.
936
+ *
937
+ * With this set, the session knows *which servers connected*, so two things
938
+ * that were previously silent become impossible: a profile naming a server
939
+ * that never connected refuses to build (see {@link mcpTools} for what that
940
+ * used to look like), and `runner.mcpServers()` answers `GET
941
+ * /sessions/:id/mcp` with the real per-server status instead of 501.
942
+ */
943
+ mcp?: McpConnection;
852
944
  /** Authoritative tools that run server-side with server credentials (MCP).
853
945
  * Never bridged to a client. Namespaced `<server>__<tool>` by
854
- * {@link connectMcpTools}, which is how a profile grants servers by name. */
855
- mcpTools?: ToolSet;
946
+ * {@link connectMcpTools}, which is how a profile grants servers by name.
947
+ *
948
+ * The bare tool set, for a host assembling one itself. Prefer {@link mcp}:
949
+ * a tool set alone cannot distinguish "this server connected and exposes no
950
+ * tools" from "this server never connected", so the check here has to be the
951
+ * cruder one — a declared server contributing no tools is refused. */
952
+ mcpTools?: ToolSet$1;
953
+ /**
954
+ * Extra host tools, each at an explicit trust level (see
955
+ * {@link withHostTools}). This is the seam for a tool that is neither one of
956
+ * the built-in capabilities nor MCP — including a **sandboxed** one, which
957
+ * `mcpTools` cannot express because everything in it is authoritative by
958
+ * construction.
959
+ *
960
+ * A sandboxed tool here rides the same {@link ToolExecutor} seam
961
+ * `eval_script` does, so it executes wherever `selectExecutor` points — an
962
+ * in-process QuickJS guest, or the browser tab that asked the question.
963
+ */
964
+ tools?: Record<string, HostToolDefinition>;
856
965
  /** Extra instructions prepended to the session's system prompt. Overridden by
857
966
  * the profile's `session.instructions` when it declares one. */
858
967
  instructions?: string;
@@ -860,6 +969,26 @@ type EngineSessionOptions = {
860
969
  timeoutMs?: number;
861
970
  memoryLimitBytes?: number;
862
971
  };
972
+ /**
973
+ * Initial scratch-filesystem contents for a **new** session, and the safe way
974
+ * to seed one: it is ignored outright when `config.restore` is set, because a
975
+ * rehydrated session brings back the files its parked turn already wrote and
976
+ * seeding over them destroys exactly the work that was preserved.
977
+ *
978
+ * (Hand-building `config.vfs` still works and still wins — but then the
979
+ * `restore ? undefined : createVfs(...)` dance is yours to get right.)
980
+ */
981
+ seedVfs?: Record<string, string>;
982
+ /**
983
+ * Build the session under this id rather than minting one.
984
+ *
985
+ * Forward the server's `EngineRunnerContext.id` here, always: it is set when
986
+ * the gateway is rehydrating a session across a restart, and a runner that
987
+ * ignores it comes back as a *different* session — the rebuild is refused,
988
+ * and every client's route and unread watermark is stranded. Ignored when
989
+ * `config.restore` is present, which carries its own id.
990
+ */
991
+ id?: string;
863
992
  };
864
993
  /**
865
994
  * Assemble a model-agnostic session: provider model, capability-scoped tools,
@@ -875,7 +1004,14 @@ type EngineSessionOptions = {
875
1004
  */
876
1005
  declare function createEngineSession(options: EngineSessionOptions): AiSdkRunner;
877
1006
  type McpConnection = {
878
- tools: ToolSet;
1007
+ tools: ToolSet$1;
1008
+ /**
1009
+ * One entry per configured server, connected or not — the truth a session was
1010
+ * assembled against. Handed to {@link createEngineSession} as `mcp`, it is
1011
+ * what `GET /sessions/:id/mcp` answers with and what makes a half-connected
1012
+ * session refuse to build rather than run degraded.
1013
+ */
1014
+ servers: McpServerStatusInfo[];
879
1015
  close: () => Promise<void>;
880
1016
  };
881
1017
  /**
@@ -884,17 +1020,32 @@ type McpConnection = {
884
1020
  * Server-side only, with server credentials: these tools are authoritative and
885
1021
  * must never be bridged to a browser. `@ai-sdk/mcp` is imported lazily and is an
886
1022
  * optional dependency — an operator who wires no MCP servers never needs it.
1023
+ *
1024
+ * **A stateless MCP server must answer `GET` with 405.** The client opens the
1025
+ * SSE stream with a `GET` before it sends anything, and a POST-only server
1026
+ * mounted under a framework's default 404 makes the whole connect fail with an
1027
+ * error that names neither the method nor the route. This is the single most
1028
+ * common way an otherwise-correct MCP mount fails.
887
1029
  */
888
- declare function connectMcpTools(servers: Record<string, McpServerConfigWire>,
889
- /** `onError` may fire more than once for a single server: transport-level
890
- * failures surface through the client's own uncaught-error channel as well as
891
- * the connect failure. Treat it as a report, not a count. */
892
-
893
- options?: {
1030
+ declare function connectMcpTools(servers: Record<string, McpServerConfigWire>, options?: {
1031
+ /** `onError` may fire more than once for a single server: transport-level
1032
+ * failures surface through the client's own uncaught-error channel as well as
1033
+ * the connect failure. Treat it as a report, not a count. */
894
1034
  onError?: (name: string, error: unknown) => void;
1035
+ /**
1036
+ * Reject if any server fails to connect, after closing the ones that did.
1037
+ *
1038
+ * Off by default, which is right for an operator's fleet — one unreachable
1039
+ * server should not take a whole gateway's sessions down. Turn it **on**
1040
+ * when the servers are the app's own: an embedder who mounts one wiki server
1041
+ * and gets a session without it has a session that cannot do its job, and
1042
+ * finding that out at connect time beats finding it out from a transcript
1043
+ * where the agent apologises.
1044
+ */
1045
+ required?: boolean;
895
1046
  }): Promise<McpConnection>;
896
1047
  //#endregion
897
- //#region src/input-queue.d.ts
1048
+ //#region src/lib/input-queue.d.ts
898
1049
  /**
899
1050
  * Push-based single-consumer AsyncIterable bridging imperative sendMessage() calls
900
1051
  * into the streaming `prompt` the Agent SDK consumes.
@@ -906,7 +1057,7 @@ declare class InputQueue implements AsyncIterable<SDKUserMessage> {
906
1057
  [Symbol.asyncIterator](): AsyncIterator<SDKUserMessage>;
907
1058
  }
908
1059
  //#endregion
909
- //#region src/normalize.d.ts
1060
+ //#region src/lib/normalize.d.ts
910
1061
  declare function toApiMessage(message: unknown): ApiMessage;
911
1062
  /**
912
1063
  * The CLI's MCP status, as `McpServerStatusInfo`.
@@ -969,6 +1120,15 @@ type EngineRunnerRequest = {
969
1120
  /** Rebuild a parked session instead of starting fresh. Engines that cannot
970
1121
  * rehydrate throw. */
971
1122
  restore?: RunnerSnapshot;
1123
+ /**
1124
+ * Adopt this session id instead of minting one. For rehydrating a session
1125
+ * across a gateway restart: the transcript comes back from the *engine's* own
1126
+ * store via `config.resume`, but every client keys its watermarks, unread
1127
+ * counts and routes on the WorkerDeck id, so that id has to survive too
1128
+ * (`SessionInfo.id` is documented as stable across resumes). A `restore`
1129
+ * carries its own id in the snapshot and does not need this.
1130
+ */
1131
+ id?: string;
972
1132
  };
973
1133
  /**
974
1134
  * One engine, as the server consumes it: its capability record, its shipped
@@ -1368,7 +1528,9 @@ declare class CodexRunner implements Runner {
1368
1528
  setModel(model?: string): Promise<void>;
1369
1529
  fail(message: string): void;
1370
1530
  close(reason?: 'client' | 'server' | 'error'): void;
1371
- subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
1531
+ subscribe(listener: SessionEventListener, afterSeq?: number, options?: {
1532
+ coalesceReplay?: boolean;
1533
+ }): () => void;
1372
1534
  /**
1373
1535
  * The session's MCP servers, live from the binary.
1374
1536
  *
@@ -1463,5 +1625,5 @@ declare class JsonRpcStdioConnection {
1463
1625
  */
1464
1626
  declare const providerAdapter: EngineAdapter;
1465
1627
  //#endregion
1466
- export { AiSdkRunner, type AiSdkRunnerConfig, type AiSdkSessionState, type AppServerConnectFn, type AppServerConnectOptions, type AppServerConnection, type AppServerHistoryTurn, type AppServerItem, type AppServerThreadListResponse, type AppServerThreadSummary, type AppServerTokenUsage, type AppServerTurn, type AppServerUserInput, type AttachmentInput, type AttachmentKind, type BridgeAnswer, BrowserBridgeExecutor, type BrowserBridgeExecutorOptions, CLAUDE_CATALOG, CODEX_CATALOG, type ClaudeAuthProbe, type ClaudeAuthStatus, CodexRunner, type CodexRunnerConfig, type DeferredDispatch, DeferredExecutor, type DeferredExecutorOptions, type EngineAdapter, type EngineAvailability, type EngineRunnerRequest, type EngineSessionOptions, type HistoryFn, type HostFetch, InputQueue, JsonRpcError, JsonRpcStdioConnection, type McpConnection, type ModelCatalog, type ParkedExecution, type PendingEntry, type PendingKind, type PendingOutcome, PendingRequestRegistry, type PendingToolCall, type PermissionDecision, type QueryFn, QuickJsExecutor, type QuickJsExecutorOptions, type RegisterOptions, type Runner, type RunnerSnapshot, SUPPORTED_ATTACHMENT_TYPES, type SessionEventListener, SessionRunner, type SessionRunnerConfig, type SettledBy, type ToolCallOutput, type ToolContext, type ToolContextOptions, type ToolDefinition, type ToolExecutionCall, type ToolExecutionDispatch, type ToolExecutionResult, type ToolExecutor, type ToolTrust, type WebFetchDigest, type WebFetchFn, type WebFetchOptions, type WebFetchResult, attachmentContentBlocks, attachmentKind, attachmentRef, checkClaudeAuth, claudeAdapter, codexAdapter, connectAppServer, connectMcpTools, createEngineSession, createToolContext, createWebFetch, getEngineAdapter, htmlToMarkdown, isHostAllowed, isPrivateAddress, listCodexSessions, mcpStatusInfo, modelOptionsFromSdk, normalizeMediaType, normalizeSdkMessage, providerAdapter, resolveBundledClaudeExecutable, resolveBundledCodexExecutable, toApiMessage, toExecutionResult, withMcpTools };
1628
+ export { AiSdkRunner, type AiSdkRunnerConfig, type AiSdkSessionState, type AppServerConnectFn, type AppServerConnectOptions, type AppServerConnection, type AppServerHistoryTurn, type AppServerItem, type AppServerThreadListResponse, type AppServerThreadSummary, type AppServerTokenUsage, type AppServerTurn, type AppServerUserInput, type AttachmentInput, type AttachmentKind, type BridgeAnswer, BrowserBridgeExecutor, type BrowserBridgeExecutorOptions, CLAUDE_CATALOG, CODEX_CATALOG, type ClaudeAuthProbe, type ClaudeAuthStatus, CodexRunner, type CodexRunnerConfig, type DeferredDispatch, DeferredExecutor, type DeferredExecutorOptions, type EngineAdapter, type EngineAvailability, type EngineRunnerRequest, type EngineSessionOptions, type HistoryFn, type HostFetch, type HostToolDefinition, InputQueue, JsonRpcError, JsonRpcStdioConnection, type LanguageModel, type McpConnection, type ModelCatalog, type ParkedExecution, type PendingEntry, type PendingKind, type PendingOutcome, PendingRequestRegistry, type PendingToolCall, type PermissionDecision, type QueryFn, QuickJsExecutor, type QuickJsExecutorOptions, type RegisterOptions, type Runner, type RunnerSnapshot, SUPPORTED_ATTACHMENT_TYPES, type SessionEventListener, SessionRunner, type SessionRunnerConfig, type SettledBy, type Tool, type ToolCallOutput, type ToolContext, type ToolContextOptions, type ToolDefinition, type ToolExecutionCall, type ToolExecutionDispatch, type ToolExecutionResult, type ToolExecutor, type ToolSet, type ToolTrust, type WebFetchDigest, type WebFetchFn, type WebFetchOptions, type WebFetchResult, attachmentContentBlocks, attachmentKind, attachmentRef, checkClaudeAuth, claudeAdapter, codexAdapter, connectAppServer, connectMcpTools, createEngineSession, createToolContext, createWebFetch, getEngineAdapter, htmlToMarkdown, isHostAllowed, isPrivateAddress, listCodexSessions, mcpStatusInfo, modelOptionsFromSdk, normalizeMediaType, normalizeSdkMessage, providerAdapter, resolveBundledClaudeExecutable, resolveBundledCodexExecutable, toApiMessage, toExecutionResult, withHostTools, withMcpTools };
1467
1629
  //# sourceMappingURL=index.d.mts.map