@workerdeck/core 0.6.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.
@@ -0,0 +1,848 @@
1
+ import { Options, Query, SDKMessage, SDKUserMessage, SessionMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import { LanguageModel, ModelMessage, Tool, ToolSet } from "ai";
3
+ import { SandboxEngine, SandboxVfs } from "@workerdeck/sandbox";
4
+ import { ApiMessage, CreateSessionRequest, McpServerConfigWire, PermissionMode, PermissionRequest, ProfileEngine, ProfileInfo, SessionEvent, SessionEventBody, SessionInfo, SessionStatus, ToolCallRequestFrame, ToolExecutionBackend, ToolExecutionOutput } from "@workerdeck/protocol";
5
+
6
+ //#region src/tool-executor.d.ts
7
+ /**
8
+ * Result of one tool execution, whenever it arrives. `failed` is a normal
9
+ * outcome the agent loop adapts to — not an exception.
10
+ */
11
+ type ToolExecutionResult = {
12
+ status: 'ok';
13
+ output: unknown;
14
+ logs?: string[];
15
+ } | {
16
+ status: 'failed';
17
+ reason: string;
18
+ error: string;
19
+ logs?: string[];
20
+ };
21
+ type ToolExecutionCall = {
22
+ /** Stable, persisted correlation id. Results are matched and applied by it. */executionId: string;
23
+ sessionId: string; /** Tool name, e.g. 'eval_script'. */
24
+ tool: string; /** Validated tool input. */
25
+ input: unknown; /** Scoped scratch filesystem for this execution's thread. */
26
+ vfs?: SandboxVfs;
27
+ limits?: {
28
+ timeoutMs?: number;
29
+ memoryLimitBytes?: number;
30
+ };
31
+ signal?: AbortSignal;
32
+ };
33
+ /**
34
+ * Dispatch outcome. `settled` carries the result inline; `pending` means it
35
+ * arrives out-of-band later, keyed by executionId — the shape that lets a
36
+ * deferred or remote executor drop in without touching the runner or protocol.
37
+ */
38
+ type ToolExecutionDispatch = {
39
+ executionId: string;
40
+ status: 'settled';
41
+ result: ToolExecutionResult;
42
+ } | {
43
+ executionId: string;
44
+ status: 'pending';
45
+ };
46
+ /**
47
+ * The seam between the agent loop and wherever code actually runs — in-process
48
+ * QuickJS, a browser tab over the WS bridge, or a managed sandbox. Backends are
49
+ * interchangeable and selected by context.
50
+ */
51
+ /**
52
+ * How an executor will handle one specific call, asked before dispatch so the
53
+ * runner can announce it on `execution_dispatched` (which is emitted before the
54
+ * call goes out, so a bridged request never precedes its own record).
55
+ *
56
+ * Per **call**, not per executor: an executor that routes by tool name can send
57
+ * `eval_script` to the in-process sandbox and a long-running tool to a remote
58
+ * worker, and only the latter should park the session.
59
+ */
60
+ type ToolExecutionProfile = {
61
+ /** Reported on `execution_dispatched`; falls back to the runner's configured default. */backend?: ToolExecutionBackend;
62
+ /**
63
+ * True when this execution may outlive not just the turn but the live runner:
64
+ * the session parks (state persisted, runner torn down) and the result arrives
65
+ * out of band, keyed by `executionId`.
66
+ */
67
+ deferred?: boolean;
68
+ /** Advisory deadline, published as `expiresAt`. For a deferred execution the
69
+ * timer itself belongs to the host — the runner may be gone when it fires. */
70
+ timeoutMs?: number;
71
+ };
72
+ interface ToolExecutor {
73
+ /** Describe what this call will be: backend, deferredness, deadline. Omitted =
74
+ * an in-band execution on the runner's configured backend. */
75
+ describe?(call: ToolExecutionCall): ToolExecutionProfile;
76
+ dispatch(call: ToolExecutionCall): Promise<ToolExecutionDispatch>;
77
+ }
78
+ //#endregion
79
+ //#region src/runner-interface.d.ts
80
+ type SessionEventListener = (event: SessionEvent) => void;
81
+ /** One deferred execution a parked session is waiting on. */
82
+ type ParkedExecution = {
83
+ executionId: string;
84
+ toolName: string; /** Epoch ms the host's execution watchdog should fire at, when the backend set one. */
85
+ expiresAt?: number;
86
+ };
87
+ /**
88
+ * Everything needed to rebuild a torn-down session under the same id — the durable
89
+ * half of deferred execution. The engine-neutral fields are what the host persists,
90
+ * indexes, and replays; `state` is the engine's own continuation state (for the
91
+ * provider engine, its ModelMessage history) and is **opaque** outside it. Keeping
92
+ * it opaque is what lets `packages/server` persist a provider session without ever
93
+ * importing a model SDK.
94
+ *
95
+ * Must stay JSON-serializable end to end: a durable store round-trips it verbatim.
96
+ * "Serializable" here means round-trips *unchanged* — a Date, a Map, or a typed
97
+ * array inside `state` survives `JSON.stringify` as something else and rehydrates
98
+ * wrong. Only the in-memory store hides that, by never serializing at all.
99
+ */
100
+ type RunnerSnapshot = {
101
+ /** Engine that produced it. Rehydrating into a different one is refused. */engine: ProfileEngine; /** Session id the rebuilt runner must adopt. */
102
+ id: string;
103
+ createdAt: number;
104
+ /** Last emitted seq — the rebuilt runner continues numbering from here, so a
105
+ * client reattaching with `afterSeq` sees one unbroken stream. */
106
+ seq: number; /** The seq-numbered event log, replayed to clients on rehydration. */
107
+ events: SessionEvent[]; /** Scratch filesystem contents, for engines that have one. */
108
+ vfs?: Record<string, string>; /** The deferred executions this session parked on. */
109
+ parked: ParkedExecution[]; /** Engine-private continuation state. Never inspected by the host. */
110
+ state: unknown;
111
+ };
112
+ type PermissionDecision = {
113
+ behavior: 'allow';
114
+ updatedInput?: Record<string, unknown>;
115
+ } | {
116
+ behavior: 'deny';
117
+ message?: string;
118
+ interrupt?: boolean;
119
+ };
120
+ /**
121
+ * Engine-independent runner surface — exactly what the server and queue consume.
122
+ * `SessionRunner` (Claude / Agent SDK) implements it today; additional engines
123
+ * implement the same contract and are selected behind it. Engine-specific
124
+ * machinery (SDK options, approval callbacks, input-queue shapes) stays inside
125
+ * the implementations.
126
+ */
127
+ interface Runner {
128
+ readonly id: string;
129
+ readonly pendingApprovals: PermissionRequest[];
130
+ /** The session's scratch filesystem, when its engine has one. The server's
131
+ * file routes (GET /sessions/:id/files[...]) read it to serve deliverables;
132
+ * engines without a VFS (the Claude CLI engine) simply don't expose it. */
133
+ readonly vfs?: SandboxVfs;
134
+ /** Begin the session. Idempotent; returns the run promise (resolves when the run ends). */
135
+ start(): Promise<void>;
136
+ info(): SessionInfo;
137
+ /** Replay buffered events with seq > afterSeq, then deliver live events. Returns unsubscribe. */
138
+ subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
139
+ /** Queue a user message for the session (starts the next turn when idle). */
140
+ sendMessage(text: string): void;
141
+ /** Resolve a pending permission request. Returns false if the id is unknown (e.g. timed out). */
142
+ resolvePermission(requestId: string, decision: PermissionDecision): boolean;
143
+ interrupt(): Promise<void>;
144
+ setPermissionMode(mode: PermissionMode): Promise<void>;
145
+ /** Switch the model for subsequent responses; undefined = back to the default. */
146
+ setModel(model?: string): Promise<void>;
147
+ /** Deliver the terminal result of an out-of-band tool execution (e.g. a bridged
148
+ * call answered by a browser client). Optional: engines that never execute
149
+ * out-of-band simply don't expose it. Idempotent by executionId — unknown or
150
+ * already-settled ids return false. */
151
+ settleExecution?(executionId: string, result: ToolExecutionResult): boolean;
152
+ /**
153
+ * Park: capture durable state, release engine resources, and go inert — without
154
+ * ending the session (no `session_closed`; the status becomes `parked`). The host
155
+ * persists the snapshot, drops the runner, and rebuilds it under the same id when
156
+ * a deferred execution's result arrives.
157
+ *
158
+ * Returns undefined when parking is not possible right now — a turn is in flight,
159
+ * nothing is actually parked, or the engine doesn't support it (the Claude engine
160
+ * doesn't: the CLI owns its own process state).
161
+ */
162
+ park?(): RunnerSnapshot | undefined;
163
+ /** Emit a session_error and terminate. For host-enforced policy (e.g. requireApiKey). */
164
+ fail(message: string): void;
165
+ /** Terminate the session and any underlying engine process. */
166
+ close(reason?: 'client' | 'server' | 'error'): void;
167
+ }
168
+ //#endregion
169
+ //#region src/runner.d.ts
170
+ type QueryFn = (params: {
171
+ prompt: AsyncIterable<SDKUserMessage>;
172
+ options?: Options;
173
+ }) => Query;
174
+ type HistoryFn = (sdkSessionId: string, options: {
175
+ dir?: string;
176
+ }) => Promise<SessionMessage[]>;
177
+ type SessionRunnerConfig = CreateSessionRequest & {
178
+ /** Injectable query implementation (tests, instrumentation). Defaults to the SDK's query(). */queryFn?: QueryFn; /** Environment for the spawned Claude Code process. Defaults to process.env. */
179
+ env?: Record<string, string | undefined>;
180
+ pathToClaudeCodeExecutable?: string; /** Escape hatch merged last into the SDK Options. */
181
+ extraOptions?: Partial<Options>; /** Timeout for pending approvals when the request itself doesn't set one. Default 300000. */
182
+ defaultApprovalTimeoutMs?: number;
183
+ /** With `resume`: emit the resumed session's history as replay events before the query
184
+ * starts, so late-attaching clients get a full transcript. Default true. */
185
+ backfillHistory?: boolean; /** Injectable history reader (tests). Defaults to the SDK's getSessionMessages. */
186
+ historyFn?: HistoryFn;
187
+ };
188
+ /**
189
+ * One live Agent SDK session: owns the query() call, the streaming input queue, the
190
+ * pending-approval table, and a seq-numbered event log that subscribers can replay.
191
+ * No transport — the server (or any host) subscribes and bridges to the wire.
192
+ */
193
+ declare class SessionRunner implements Runner {
194
+ #private;
195
+ readonly id: string;
196
+ readonly createdAt: number;
197
+ constructor(config: SessionRunnerConfig, id?: string);
198
+ get status(): SessionStatus;
199
+ get sdkSessionId(): string | undefined;
200
+ get lastSeq(): number;
201
+ /** 'oauth' = claude.ai subscription credentials; other values are API-key provenance. */
202
+ get apiKeySource(): string | undefined;
203
+ get pendingApprovals(): PermissionRequest[];
204
+ info(): SessionInfo;
205
+ /** Begin the session. Idempotent; returns the run promise (resolves when the query ends). */
206
+ start(): Promise<void>;
207
+ /** Queue a user message for the session (starts the next turn when idle). */
208
+ sendMessage(text: string): void;
209
+ /** Resolve a pending permission request. Returns false if the id is unknown (e.g. timed out). */
210
+ resolvePermission(requestId: string, decision: PermissionDecision): boolean;
211
+ interrupt(): Promise<void>;
212
+ setPermissionMode(mode: PermissionMode): Promise<void>;
213
+ /** Switch the model for subsequent responses; undefined = back to the default. */
214
+ setModel(model?: string): Promise<void>;
215
+ /** Emit a session_error and terminate. For host-enforced policy (e.g. requireApiKey). */
216
+ fail(message: string): void;
217
+ /** Terminate the session and the underlying CLI subprocess. */
218
+ close(reason?: 'client' | 'server' | 'error'): void;
219
+ /**
220
+ * Replay buffered events with seq > afterSeq, then deliver live events.
221
+ * Returns an unsubscribe function.
222
+ */
223
+ subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
224
+ }
225
+ //#endregion
226
+ //#region src/ai-sdk-runner.d.ts
227
+ /** `cwd` is optional for this engine: the loop has no host-filesystem coupling
228
+ * (tools get scoped VFS handles instead). Defaults to process.cwd() for display. */
229
+ type AiSdkRunnerConfig = Omit<CreateSessionRequest, 'cwd'> & {
230
+ cwd?: string;
231
+ /** AI SDK language model instance (or gateway model id string). Provider
232
+ * resolution from profiles happens host-side; core takes the resolved model. */
233
+ languageModel: LanguageModel;
234
+ /** Tools available to the loop. Tools WITHOUT `execute` halt the loop when
235
+ * called; their calls surface via `pendingToolCalls` and are answered with
236
+ * `resolveToolCall()`, which re-enters the loop by message-state replay. */
237
+ tools?: ToolSet; /** System prompt (AI SDK v7 `instructions`). */
238
+ instructions?: string; /** Max loop steps per turn. Default 20. */
239
+ maxSteps?: number;
240
+ /**
241
+ * Executes tool calls the loop cannot run inline (tools declared without
242
+ * `execute`). With one set, the runner drives the whole cycle itself:
243
+ * dispatch on park, apply the result, re-enter. Without one, parked calls
244
+ * stay on {@link pendingToolCalls} for the host to answer via
245
+ * {@link resolveToolCall}.
246
+ */
247
+ executor?: ToolExecutor; /** Names the executor handles. Others stay pending for the host. */
248
+ executableTools?: string[]; /** Scratch filesystem handed to sandboxed executions. */
249
+ vfs?: SandboxVfs; /** Per-execution limits passed to the executor. */
250
+ executionLimits?: {
251
+ timeoutMs?: number;
252
+ memoryLimitBytes?: number;
253
+ }; /** Which backend the executor represents, for `execution_dispatched` events. */
254
+ executionBackend?: ToolExecutionBackend; /** Swap models mid-session (`set_model`). Unset = setModel() is rejected. */
255
+ resolveModel?: (modelId: string | undefined) => LanguageModel;
256
+ /** Called once when the session closes — release per-session resources the
257
+ * host attached (an MCP connection, a watcher). Errors are swallowed. Also
258
+ * runs when the session parks: parking releases the same resources. */
259
+ onClose?: () => void | Promise<void>;
260
+ /**
261
+ * Rebuild a parked session from {@link AiSdkRunner.park}'s snapshot instead of
262
+ * starting a fresh one: the id, event log, seq counter, message history, and
263
+ * the executions it parked on are all adopted. The rest of the config is the
264
+ * live wiring (model, tools, executor, VFS) and is taken as given — a
265
+ * rehydrated session may legitimately come up against a re-created tool set.
266
+ */
267
+ restore?: RunnerSnapshot;
268
+ };
269
+ /** An external (execute-less) tool call the loop is parked on. */
270
+ type PendingToolCall = {
271
+ toolCallId: string;
272
+ toolName: string;
273
+ input: unknown;
274
+ /** True when the executor declared the execution deferred — the session may
275
+ * park on it, and only a host-delivered result can settle it. */
276
+ deferred?: boolean; /** Epoch ms the host's execution watchdog should fire at. */
277
+ expiresAt?: number;
278
+ };
279
+ /** The provider engine's half of a {@link RunnerSnapshot} — its continuation
280
+ * state. Opaque to the host; only this class reads it. */
281
+ type AiSdkSessionState = {
282
+ messages: ModelMessage[];
283
+ pendingToolCalls: PendingToolCall[]; /** Calls already handed to an executor, so rehydration never re-dispatches them. */
284
+ dispatched: string[];
285
+ numTurns: number;
286
+ totalUsage: {
287
+ input: number;
288
+ output: number;
289
+ cacheWrite: number;
290
+ cacheRead: number;
291
+ };
292
+ /** The in-progress turn's accumulator: a parked turn's earlier legs still owe
293
+ * their tokens and elapsed time to the turn_result that eventually lands. */
294
+ turnAccum?: {
295
+ startedAt: number;
296
+ input: number;
297
+ output: number;
298
+ cacheWrite: number;
299
+ cacheRead: number;
300
+ };
301
+ permissionMode: PermissionMode;
302
+ /** Model alias last requested (config.model or a set_model), NOT the resolved
303
+ * provider model id — re-resolution goes back through `resolveModel`. */
304
+ model?: string;
305
+ lastActivityAt?: number;
306
+ /** When the snapshot was taken, so a rehydrated turn can discount the time it
307
+ * spent parked instead of billing it as elapsed turn duration. */
308
+ parkedAt?: number;
309
+ };
310
+ type ToolCallOutput = {
311
+ type: 'text';
312
+ value: string;
313
+ } | {
314
+ type: 'json';
315
+ value: unknown;
316
+ };
317
+ /**
318
+ * Model-agnostic runner over the AI SDK v7 ToolLoopAgent. The session's durable
319
+ * state is its ModelMessage history: every turn — including continuation after an
320
+ * externally-executed tool call — is a fresh streamed call over that history
321
+ * (message-state replay; the loop cannot be suspended). Output is emitted as it
322
+ * happens: `stream_delta` per token (unless includePartialMessages is false) and
323
+ * assistant/tool messages per step. Emits the same seq-numbered SessionEvent log
324
+ * as SessionRunner; engine-specific CLI telemetry (system_init, capabilities,
325
+ * rate_limit, ...) is simply never emitted.
326
+ */
327
+ declare class AiSdkRunner implements Runner {
328
+ #private;
329
+ readonly id: string;
330
+ readonly createdAt: number;
331
+ constructor(config: AiSdkRunnerConfig, id?: string);
332
+ get status(): SessionStatus;
333
+ get lastSeq(): number;
334
+ /** The session's durable state — persist to park, replay to rehydrate. */
335
+ get messages(): ModelMessage[];
336
+ /** External tool calls the loop is currently parked on. */
337
+ get pendingToolCalls(): PendingToolCall[];
338
+ get pendingApprovals(): PermissionRequest[];
339
+ /** The session's scratch filesystem (see Runner.vfs) — the server's file
340
+ * routes serve deliverables straight from it. */
341
+ get vfs(): SandboxVfs | undefined;
342
+ info(): SessionInfo;
343
+ start(): Promise<void>;
344
+ /**
345
+ * Snapshot durable state, release engine resources, and go inert — the session
346
+ * continues in the snapshot, not in this object. Returns undefined when parking
347
+ * would lose work or has nothing to wait for: a turn in flight, no parked call,
348
+ * or an already-closed/parked runner.
349
+ */
350
+ park(): RunnerSnapshot | undefined;
351
+ sendMessage(text: string): void;
352
+ /**
353
+ * Deliver the result of an external (execute-less) tool call. Appends the
354
+ * tool-result message and, once no calls remain pending, re-enters the loop.
355
+ * Idempotent per toolCallId: unknown/already-settled ids return false.
356
+ */
357
+ resolveToolCall(toolCallId: string, output: ToolCallOutput, options?: {
358
+ isError?: boolean;
359
+ }): boolean;
360
+ resolvePermission(_requestId: string, _decision: PermissionDecision): boolean;
361
+ /** Emit `file_delivered` — the deliver_file tool's hand-over event (wired by
362
+ * createEngineSession via ToolContextOptions.onFileDelivered). */
363
+ emitFileDelivered(file: {
364
+ path: string;
365
+ bytes: number;
366
+ description?: string;
367
+ }): void;
368
+ /**
369
+ * One plain generateText over the session's current model, billed into the
370
+ * running turn's usage accumulator — the web_fetch digest pass uses this so
371
+ * its tokens are never lost from the turn's accounting.
372
+ */
373
+ generateDigest(prompt: string): Promise<string>;
374
+ interrupt(): Promise<void>;
375
+ setPermissionMode(mode: PermissionMode): Promise<void>;
376
+ setModel(model?: string): Promise<void>;
377
+ fail(message: string): void;
378
+ close(reason?: 'client' | 'server' | 'error'): void;
379
+ subscribe(listener: SessionEventListener, afterSeq?: number): () => void;
380
+ /**
381
+ * Deliver the result of an execution this runner dispatched. Used by the host
382
+ * when a backend settled out-of-band (a browser bridge answering later, a
383
+ * deferred executor). Idempotent by executionId.
384
+ */
385
+ settleExecution(executionId: string, result: ToolExecutionResult): boolean;
386
+ }
387
+ //#endregion
388
+ //#region src/claude-auth.d.ts
389
+ /**
390
+ * Credential presence for one Claude Code environment, as the CLI itself reports
391
+ * it. 'unknown' means the check could not run at all (no binary, a CLI too old
392
+ * for `auth status`, unparseable output) — which is NOT evidence of a missing
393
+ * login and must never be surfaced as one.
394
+ */
395
+ type ClaudeAuthStatus = 'logged_in' | 'logged_out' | 'unknown';
396
+ /** Injectable form of {@link checkClaudeAuth} (tests, custom probes). */
397
+ type ClaudeAuthProbe = (env: Record<string, string | undefined>) => Promise<ClaudeAuthStatus>;
398
+ /**
399
+ * The native Claude Code binary the Agent SDK itself spawns, resolved the way
400
+ * the SDK resolves it: the platform-specific optional dependency installed next
401
+ * to the SDK package (`@anthropic-ai/claude-agent-sdk-<platform>-<arch>/claude`).
402
+ * Probing this binary rather than whatever `claude` is on PATH means an auth
403
+ * check answers for the executable sessions will actually run — the two can be
404
+ * different versions logged into different places. Returns undefined when it
405
+ * can't be found (optional dep skipped, unsupported platform); callers degrade
406
+ * to 'unknown', and the SDK surfaces its own error if a session is created.
407
+ */
408
+ declare function resolveBundledClaudeExecutable(): string | undefined;
409
+ /**
410
+ * Ask the CLI whether `env` holds usable credentials: `claude auth status`
411
+ * prints a JSON verdict covering every source the CLI itself consults for that
412
+ * environment — `<CLAUDE_CONFIG_DIR>/.credentials.json`, the macOS login
413
+ * Keychain, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, Bedrock/Vertex
414
+ * (verified against 2.1.217). Only the `loggedIn` boolean is ever read; the
415
+ * identity fields in the payload (email, org, subscription) never leave the
416
+ * parse. The exit code is deliberately ignored — 2.1.217 exits 1 on a
417
+ * logged-out verdict where other versions exit 0 — and anything that doesn't
418
+ * parse to a `loggedIn` boolean is 'unknown', because `auth status` is not a
419
+ * stable contract. Never rejects.
420
+ */
421
+ declare function checkClaudeAuth(env: Record<string, string | undefined>, options?: {
422
+ executable?: string;
423
+ timeoutMs?: number;
424
+ }): Promise<ClaudeAuthStatus>;
425
+ //#endregion
426
+ //#region src/quickjs-executor.d.ts
427
+ /** Resolve a URL to text for the guest. Runs host-side with host authority —
428
+ * this is where a credential may be attached, never inside the sandbox. */
429
+ type HostFetch = (url: string, signal: AbortSignal) => Promise<string>;
430
+ type QuickJsExecutorOptions = {
431
+ engine: SandboxEngine;
432
+ /**
433
+ * Hostnames the guest may reach, exact or `*.example.com`. Empty/unset =
434
+ * no network at all (the guest's fetchText throws). Matched host-side; the
435
+ * guest is never told the allowlist and never holds a credential.
436
+ */
437
+ allowedHosts?: string[]; /** Performs the actual request. Unset = global fetch, text body. */
438
+ hostFetch?: HostFetch;
439
+ /** Per-fetch cap. The guest deadline does NOT cover host-function time, so
440
+ * every capability needs its own bound. Default 10000. */
441
+ fetchTimeoutMs?: number; /** Default guest wall-clock limit when the call doesn't set one. Default 5000. */
442
+ defaultTimeoutMs?: number; /** Default guest allocator cap when the call doesn't set one. Default 64 MiB. */
443
+ defaultMemoryLimitBytes?: number;
444
+ };
445
+ /**
446
+ * In-process execution backend: runs a tool's untrusted script in the QuickJS
447
+ * WASM guest. Always settles inline — nothing downstream assumes that, which is
448
+ * what lets a deferred backend replace it behind the same seam.
449
+ */
450
+ declare class QuickJsExecutor implements ToolExecutor {
451
+ #private;
452
+ constructor(options: QuickJsExecutorOptions);
453
+ dispatch(call: ToolExecutionCall): Promise<ToolExecutionDispatch>;
454
+ }
455
+ /** Exact hostname match, or a single leading `*.` wildcard covering subdomains
456
+ * (not the bare parent). Only http(s) — no file:, data:, or other schemes. */
457
+ declare function isHostAllowed(url: string, allowedHosts: string[]): boolean;
458
+ //#endregion
459
+ //#region src/pending-registry.d.ts
460
+ /**
461
+ * One registry for every request that leaves the runner and must come back:
462
+ * permission approvals, browser-bridged tool calls, and deferred executions.
463
+ * They differ only in who answers and how long that takes — the correlation,
464
+ * timeout, idempotent settle, and provenance tagging are identical, so they
465
+ * live here once.
466
+ */
467
+ /** What kind of async request this is. Purely descriptive — the mechanics are shared. */
468
+ type PendingKind = 'approval' | 'tool_call' | 'execution';
469
+ /** Who settled a request. Mirrors the existing approval vocabulary. */
470
+ type SettledBy = 'client' | 'timeout' | 'policy' | 'server';
471
+ type PendingOutcome<T> = {
472
+ ok: true;
473
+ value: T;
474
+ settledBy: SettledBy;
475
+ } | {
476
+ ok: false;
477
+ reason: string;
478
+ error: string;
479
+ settledBy: SettledBy;
480
+ };
481
+ type PendingEntry = {
482
+ id: string;
483
+ kind: PendingKind;
484
+ createdAt: number; /** Epoch ms the timeout policy fires at, when one was set. */
485
+ expiresAt?: number; /** Caller-supplied descriptor for display/rehydration (tool name, request, ...). */
486
+ meta?: Record<string, unknown>;
487
+ };
488
+ type RegisterOptions<T> = {
489
+ id: string;
490
+ kind: PendingKind;
491
+ /** Fail the request automatically after this long. Omit for no deadline
492
+ * (deferred executions whose watchdog lives elsewhere). */
493
+ timeoutMs?: number;
494
+ meta?: Record<string, unknown>; /** Called when the entry settles, however it settled. For emitting events. */
495
+ onSettle?: (outcome: PendingOutcome<T>, entry: PendingEntry) => void;
496
+ };
497
+ declare class PendingRequestRegistry {
498
+ #private;
499
+ get size(): number;
500
+ /**
501
+ * Register a request and get a promise for its outcome. The promise **never
502
+ * rejects**: a timeout or cancellation resolves with `ok: false` so callers
503
+ * feed the failure back into the agent loop instead of unwinding it.
504
+ *
505
+ * Re-registering a live id throws — silently replacing it would strand the
506
+ * first waiter forever.
507
+ */
508
+ register<T>(options: RegisterOptions<T>): Promise<PendingOutcome<T>>;
509
+ /** Deliver a result. Returns false for unknown or already-settled ids —
510
+ * duplicate and late deliveries are no-ops, never a second application. */
511
+ settle<T>(id: string, value: T, settledBy?: SettledBy): boolean;
512
+ /** Fail a request. Same idempotence guarantee as {@link settle}. */
513
+ fail(id: string, reason: string, error: string, settledBy?: SettledBy): boolean;
514
+ has(id: string): boolean;
515
+ get(id: string): PendingEntry | undefined;
516
+ list(kind?: PendingKind): PendingEntry[];
517
+ /** Fail everything (optionally of one kind) — session close, turn interrupt. */
518
+ cancelAll(reason: string, error: string, kind?: PendingKind): number;
519
+ }
520
+ //#endregion
521
+ //#region src/browser-bridge-executor.d.ts
522
+ /** Answer a bridged call, as delivered by the client over the wire. */
523
+ type BridgeAnswer = {
524
+ output: ToolExecutionOutput;
525
+ logs?: string[];
526
+ } | {
527
+ reason: string;
528
+ error: string;
529
+ logs?: string[];
530
+ };
531
+ type BrowserBridgeExecutorOptions = {
532
+ /**
533
+ * Put a `tool_call_request` on the wire to the attached client. Returning
534
+ * false means nobody is attached — the execution fails immediately rather
535
+ * than hanging until its deadline.
536
+ */
537
+ send: (frame: ToolCallRequestFrame) => boolean; /** Tell the client to abandon a call the server gave up on. */
538
+ cancel?: (executionId: string, reason: string) => void; /** How long to wait for the client before failing the execution. Default 60000. */
539
+ timeoutMs?: number;
540
+ /**
541
+ * Called once per dispatched execution when it reaches a terminal result,
542
+ * however it got there (client answer, timeout, abort, no client). This is
543
+ * the wire back into the agent loop — the host feeds it to the runner's
544
+ * `resolveToolCall`. A timeout arrives here as a failed result, not silence.
545
+ */
546
+ onResult?: (executionId: string, result: ToolExecutionResult) => void;
547
+ /** Share the session's registry so approvals, bridged calls, and deferred
548
+ * executions live in one table. Omit to get a private one. */
549
+ registry?: PendingRequestRegistry;
550
+ };
551
+ /**
552
+ * Executes tool calls in the attached client's own sandbox. The first backend
553
+ * that genuinely returns `pending`: dispatch puts a request on the wire and
554
+ * returns, and the result arrives later through {@link resolve}.
555
+ *
556
+ * Data locality is the point — documents can stay in the browser and never
557
+ * reach the server. The tradeoff is trust: whatever comes back is untrusted
558
+ * input, fine for the user's own data but never a source for authoritative
559
+ * server state (that is why MCP and secret-bearing tools are never bridged).
560
+ */
561
+ declare class BrowserBridgeExecutor implements ToolExecutor {
562
+ #private;
563
+ readonly registry: PendingRequestRegistry;
564
+ constructor(options: BrowserBridgeExecutorOptions);
565
+ dispatch(call: ToolExecutionCall): Promise<ToolExecutionDispatch>;
566
+ /**
567
+ * Apply a client's answer. Returns false when the id is unknown or already
568
+ * settled — a late result after a timeout must not re-open a settled call.
569
+ */
570
+ resolve(executionId: string, answer: BridgeAnswer): boolean;
571
+ }
572
+ /** Map a registry outcome onto the executor's result contract. */
573
+ declare function toExecutionResult(outcome: PendingOutcome<BridgeAnswer>): ToolExecutionResult;
574
+ //#endregion
575
+ //#region src/deferred-executor.d.ts
576
+ /** A dispatched execution, as handed to the backend that will run it. */
577
+ type DeferredDispatch = {
578
+ /** Correlation id. The result is delivered under it — `POST
579
+ * {basePath}/executions/:executionId/result` — and applied idempotently. */
580
+ executionId: string;
581
+ sessionId: string;
582
+ tool: string;
583
+ input: unknown; /** The session's scratch filesystem at dispatch time, by value. */
584
+ vfsSeed?: Record<string, string>;
585
+ limits?: {
586
+ timeoutMs?: number;
587
+ memoryLimitBytes?: number;
588
+ }; /** Epoch ms the host's execution watchdog fires at, when a timeout was configured. */
589
+ expiresAt?: number;
590
+ };
591
+ type DeferredExecutorOptions = {
592
+ /**
593
+ * Hand the call to whatever actually runs it — enqueue it, POST it to a worker,
594
+ * page a human. Called synchronously during dispatch; throwing fails the
595
+ * execution (the failure reaches the agent as ordinary tool output).
596
+ */
597
+ onDispatch: (call: DeferredDispatch) => void | Promise<void>;
598
+ /** How long the result may take before the host's watchdog fails the execution.
599
+ * Unset = no deadline; the execution then relies on the job's parked cap. */
600
+ timeoutMs?: number; /** Reported on `execution_dispatched`. Default 'remote'. */
601
+ backend?: ToolExecutionBackend;
602
+ };
603
+ /**
604
+ * The executor for work that outlives the session's process residency: dispatch
605
+ * hands the call off and returns `pending` **without holding a promise**, because
606
+ * the runner it would resolve into is about to be torn down. The result can only
607
+ * come back through the host — the execution-result route → `settleExecution` on a
608
+ * rehydrated runner — which is exactly what makes a park durable rather than a
609
+ * long in-memory await.
610
+ *
611
+ * Contrast {@link BrowserBridgeExecutor}, which is also `pending` but keeps its
612
+ * answer in memory for the ~60s the tab has to reply.
613
+ */
614
+ declare class DeferredExecutor implements ToolExecutor {
615
+ #private;
616
+ readonly backend: ToolExecutionBackend;
617
+ readonly timeoutMs: number | undefined;
618
+ constructor(options: DeferredExecutorOptions);
619
+ /** Every call this executor takes is deferred — route only the tools that
620
+ * belong on the remote side to it. */
621
+ describe(): ToolExecutionProfile;
622
+ dispatch(call: ToolExecutionCall): Promise<ToolExecutionDispatch>;
623
+ }
624
+ //#endregion
625
+ //#region src/web-fetch.d.ts
626
+ /**
627
+ * `web_fetch` backend, close to Claude Code's original WebFetch: fetch a URL,
628
+ * convert HTML to markdown, and (optionally) digest it with a model against the
629
+ * caller's prompt. Server-side only — this runs with server egress, which is
630
+ * exactly why it is an authoritative capability the operator grants explicitly.
631
+ */
632
+ type WebFetchResult = {
633
+ /** The URL that was fetched (after same-host redirects). */url: string; /** Model digest of the page against the prompt (when a digest fn is wired). */
634
+ digest?: string; /** Page content as markdown (when no digest fn is wired, or digesting failed). */
635
+ markdown?: string; /** True when the markdown was cut at the size cap. */
636
+ truncated?: boolean;
637
+ /** Redirect-to-a-different-host notice: the redirect is surfaced, not followed
638
+ * (the agent can decide to fetch `redirectUrl` itself). */
639
+ notice?: string;
640
+ redirectUrl?: string;
641
+ error?: string;
642
+ };
643
+ type WebFetchFn = (url: string, prompt: string) => Promise<WebFetchResult>;
644
+ /** Runs the digest pass over the fetched markdown. Wire the session's own model
645
+ * here (see createEngineSession) so its tokens land in the turn's usage. */
646
+ type WebFetchDigest = (markdown: string, prompt: string) => Promise<string>;
647
+ type WebFetchOptions = {
648
+ fetchImpl?: typeof fetch; /** Raw-body cap, enforced while streaming (before any conversion). Default 1 MiB. */
649
+ maxContentBytes?: number; /** Markdown cap handed to the model. Default 50 KB. */
650
+ maxMarkdownBytes?: number;
651
+ /** Fetched-page cache TTL (keyed by URL; the digest is per-prompt and never
652
+ * cached). Default 15 minutes. */
653
+ cacheTtlMs?: number;
654
+ /** Optional hostname allowlist on top of the SSRF guard (exact or `*.example.com`).
655
+ * Unset = any public host. */
656
+ allowedHosts?: string[]; /** Per-request timeout. Default 30000. */
657
+ timeoutMs?: number;
658
+ digest?: WebFetchDigest;
659
+ };
660
+ declare function createWebFetch(options?: WebFetchOptions): WebFetchFn;
661
+ /** Private / loopback / link-local / unspecified, IPv4 and IPv6 (incl. v4-mapped). */
662
+ declare function isPrivateAddress(address: string): boolean;
663
+ /**
664
+ * Dependency-free HTML → markdown, tuned for "give the model readable text":
665
+ * drops non-content subtrees, keeps headings/lists/links/emphasis/code, strips
666
+ * everything else. Not a spec-grade converter on purpose — a small predictable
667
+ * transform beats dragging a DOM into core.
668
+ */
669
+ declare function htmlToMarkdown(html: string): string;
670
+ //#endregion
671
+ //#region src/tools.d.ts
672
+ /**
673
+ * How much authority a tool carries, which decides where it may run.
674
+ *
675
+ * - `sandboxed` — no ambient authority; safe to execute anywhere, including an
676
+ * untrusted browser tab. Its results are untrusted input.
677
+ * - `authoritative` — runs server-side with server credentials (MCP, secret-bearing
678
+ * APIs). **Never bridged to a client**: bridging it would hand a browser the
679
+ * ability to forge authoritative results.
680
+ */
681
+ type ToolTrust = 'sandboxed' | 'authoritative';
682
+ type ToolDefinition = {
683
+ name: string;
684
+ trust: ToolTrust;
685
+ /** The AI SDK tool. Sandboxed tools are declared WITHOUT `execute` so the loop
686
+ * hands them to the ToolExecutor seam rather than running them inline. */
687
+ tool: Tool;
688
+ };
689
+ type ToolContextOptions = {
690
+ /** Executor for sandboxed tools. Selected per call by the host (browser bridge
691
+ * when a client is attached, server QuickJS otherwise). */
692
+ executor: ToolExecutor;
693
+ sessionId: string; /** Scratch filesystem shared by this session's sandboxed tools. */
694
+ vfs?: SandboxVfs; /** Search backend. Omitted = `web_search` is not granted at all. */
695
+ search?: (query: string, limit: number) => Promise<Array<{
696
+ title: string;
697
+ url: string;
698
+ snippet?: string;
699
+ }>>; /** Document fetcher for `download`. Omitted = the tool is not granted. */
700
+ download?: (url: string) => Promise<{
701
+ contentType?: string;
702
+ text: string;
703
+ }>;
704
+ /** Page digester for `web_fetch` (see {@link createWebFetch}). Omitted = the
705
+ * tool is not granted. */
706
+ webFetch?: WebFetchFn;
707
+ /** Notified when the agent hands over a VFS file via `deliver_file`, so the
708
+ * host can emit the `file_delivered` session event. The tool is only granted
709
+ * when this is set — a delivery nobody hears is not a delivery. */
710
+ onFileDelivered?: (file: {
711
+ path: string;
712
+ bytes: number;
713
+ description?: string;
714
+ }) => void; /** Per-call sandbox limits. */
715
+ limits?: {
716
+ timeoutMs?: number;
717
+ memoryLimitBytes?: number;
718
+ };
719
+ /** Notified when a sandboxed execution is dispatched and when it settles, so
720
+ * the host can emit execution_* events. */
721
+ onDispatch?: (executionId: string, toolName: string) => void;
722
+ onSettle?: (executionId: string, result: ToolExecutionResult) => void;
723
+ };
724
+ /** Everything a session's tools need, plus the tool set to hand the runner. */
725
+ type ToolContext = {
726
+ vfs: SandboxVfs;
727
+ tools: ToolSet;
728
+ definitions: ToolDefinition[]; /** Names the loop must not execute inline (they go through the executor). */
729
+ sandboxedToolNames: string[];
730
+ };
731
+ /**
732
+ * Build the capability-scoped tool set for a session.
733
+ *
734
+ * The agent's authority is exactly what is granted here — there are no built-in
735
+ * filesystem or shell tools, and nothing reaches the host filesystem: `fs_*`
736
+ * operate on an in-memory scratch VFS. Tools whose backend is not supplied are
737
+ * simply absent rather than present-and-failing, so a model cannot be tempted
738
+ * by a capability the operator did not grant.
739
+ */
740
+ declare function createToolContext(options: ToolContextOptions): ToolContext;
741
+ /** Add host-side MCP tools to a context. They are ALWAYS authoritative: they run
742
+ * server-side with server credentials, and must never be handed to a browser. */
743
+ declare function withMcpTools(context: ToolContext, mcpTools: ToolSet): ToolContext;
744
+ //#endregion
745
+ //#region src/engine.d.ts
746
+ type EngineSessionOptions = {
747
+ /** Resolved session config (profile defaults already applied). */config: AiSdkRunnerConfig; /** The profile that selected this engine, when there was one. */
748
+ profile?: ProfileInfo;
749
+ /**
750
+ * Resolve the profile's provider config into a model instance. The host owns
751
+ * this so core never imports a provider SDK and never reads credentials —
752
+ * they come from the operator's environment, exactly like the Claude chain.
753
+ */
754
+ resolveModel: (profile: ProfileInfo | undefined, config: AiSdkRunnerConfig) => LanguageModel;
755
+ /**
756
+ * Executor for sandboxed tools. Return the browser bridge when a client is
757
+ * attached and the server sandbox otherwise; the seam makes them
758
+ * interchangeable, so this is the only place the choice is made.
759
+ */
760
+ selectExecutor: () => ToolExecutor; /** Which backend `selectExecutor` returned, for the execution_* events. */
761
+ backend?: 'server' | 'browser' | 'managed' | 'remote'; /** Backends for the granted capabilities. Omitted ones are simply not granted. */
762
+ capabilities?: {
763
+ search?: ToolContextOptions['search'];
764
+ download?: ToolContextOptions['download'];
765
+ /**
766
+ * Grants `web_fetch`. Pass options (or `{}`) to use the built-in
767
+ * {@link createWebFetch} backend — its digest pass then runs on the
768
+ * session's own model, billed into the turn's usage. Pass `digest: false`
769
+ * to skip the digest (the tool returns page markdown), a custom digest fn
770
+ * to bring your own model, or a complete {@link WebFetchFn} to replace the
771
+ * backend outright.
772
+ */
773
+ webFetch?: WebFetchFn | (Omit<WebFetchOptions, 'digest'> & {
774
+ digest?: WebFetchOptions['digest'] | false;
775
+ });
776
+ /** Grants `deliver_file`: the agent can hand VFS files over to the user
777
+ * (emitting `file_delivered`, downloadable via the server's file routes).
778
+ * Default true — set false to withhold it. */
779
+ deliverFiles?: boolean;
780
+ };
781
+ /** Authoritative tools that run server-side with server credentials (MCP).
782
+ * Never bridged to a client. Namespaced `<server>__<tool>` by
783
+ * {@link connectMcpTools}, which is how a profile grants servers by name. */
784
+ mcpTools?: ToolSet;
785
+ /** Extra instructions prepended to the session's system prompt. Overridden by
786
+ * the profile's `session.instructions` when it declares one. */
787
+ instructions?: string;
788
+ executionLimits?: {
789
+ timeoutMs?: number;
790
+ memoryLimitBytes?: number;
791
+ };
792
+ };
793
+ /**
794
+ * Assemble a model-agnostic session: provider model, capability-scoped tools,
795
+ * a scratch VFS, and the executor that runs the sandboxed ones.
796
+ *
797
+ * This is the piece an operator wires into the server's `createEngineRunner`.
798
+ *
799
+ * The host wires the *backends*; the profile and the session request decide which
800
+ * of them are actually granted (`profile.session`, `config.capabilities`). A
801
+ * backend that isn't granted is simply not built into the tool set, so withholding
802
+ * a capability costs the host no branching. No declaration anywhere = everything
803
+ * the host wired, which is what a host that ignores profiles gets.
804
+ */
805
+ declare function createEngineSession(options: EngineSessionOptions): AiSdkRunner;
806
+ type McpConnection = {
807
+ tools: ToolSet;
808
+ close: () => Promise<void>;
809
+ };
810
+ /**
811
+ * Connect to MCP servers and return their tools, ready for {@link withMcpTools}.
812
+ *
813
+ * Server-side only, with server credentials: these tools are authoritative and
814
+ * must never be bridged to a browser. `@ai-sdk/mcp` is imported lazily and is an
815
+ * optional dependency — an operator who wires no MCP servers never needs it.
816
+ */
817
+ declare function connectMcpTools(servers: Record<string, McpServerConfigWire>,
818
+ /** `onError` may fire more than once for a single server: transport-level
819
+ * failures surface through the client's own uncaught-error channel as well as
820
+ * the connect failure. Treat it as a report, not a count. */
821
+
822
+ options?: {
823
+ onError?: (name: string, error: unknown) => void;
824
+ }): Promise<McpConnection>;
825
+ //#endregion
826
+ //#region src/input-queue.d.ts
827
+ /**
828
+ * Push-based single-consumer AsyncIterable bridging imperative sendMessage() calls
829
+ * into the streaming `prompt` the Agent SDK consumes.
830
+ */
831
+ declare class InputQueue implements AsyncIterable<SDKUserMessage> {
832
+ #private;
833
+ push(message: SDKUserMessage): void;
834
+ end(): void;
835
+ [Symbol.asyncIterator](): AsyncIterator<SDKUserMessage>;
836
+ }
837
+ //#endregion
838
+ //#region src/normalize.d.ts
839
+ declare function toApiMessage(message: unknown): ApiMessage;
840
+ /**
841
+ * Map one SDKMessage to a wire-protocol event body, or null for messages the runner
842
+ * consumes itself (system_init and session-state changes carry runner state and are
843
+ * emitted by the runner with extra context).
844
+ */
845
+ declare function normalizeSdkMessage(msg: SDKMessage): SessionEventBody | null;
846
+ //#endregion
847
+ export { AiSdkRunner, type AiSdkRunnerConfig, type AiSdkSessionState, type BridgeAnswer, BrowserBridgeExecutor, type BrowserBridgeExecutorOptions, type ClaudeAuthProbe, type ClaudeAuthStatus, type DeferredDispatch, DeferredExecutor, type DeferredExecutorOptions, type EngineSessionOptions, type HistoryFn, type HostFetch, InputQueue, type McpConnection, type ParkedExecution, type PendingEntry, type PendingKind, type PendingOutcome, PendingRequestRegistry, type PendingToolCall, type PermissionDecision, type QueryFn, QuickJsExecutor, type QuickJsExecutorOptions, type RegisterOptions, type Runner, type RunnerSnapshot, 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, checkClaudeAuth, connectMcpTools, createEngineSession, createToolContext, createWebFetch, htmlToMarkdown, isHostAllowed, isPrivateAddress, normalizeSdkMessage, resolveBundledClaudeExecutable, toApiMessage, toExecutionResult, withMcpTools };
848
+ //# sourceMappingURL=index.d.mts.map