@workerdeck/core 0.16.0 → 0.17.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 +22 -0
- package/build/index.d.mts +162 -11
- package/build/index.mjs +615 -53
- package/build/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -165,10 +165,32 @@ selectExecutor: () => new DeferredExecutor({
|
|
|
165
165
|
for you — a `SessionStore` plus `POST /executions/:id/result` — but the mechanism is here, and works
|
|
166
166
|
with no server at all.
|
|
167
167
|
|
|
168
|
+
`snapshot()` is the same value **without** the teardown: the runner stays live, attached and warm.
|
|
169
|
+
That separation is what makes a provider session survive a process restart, since it has no
|
|
170
|
+
engine-side store to resume from the way claude and codex do — the host writes the snapshot through
|
|
171
|
+
after each turn and rebuilds from the last one. The gate differs from `park()`'s in one direction
|
|
172
|
+
only: it refuses a turn in flight and pending *in-process* executions (whose results die with the
|
|
173
|
+
process), and allows the idle case `park()` exists to refuse.
|
|
174
|
+
|
|
168
175
|
## Rules you cannot infer from the types
|
|
169
176
|
|
|
170
177
|
Things the compiler will not tell you, each of which has cost someone real time:
|
|
171
178
|
|
|
179
|
+
- **Truncation happens on the replay path, never at emit.** `subscribe(..., { truncateResults })`
|
|
180
|
+
hands out a copy; `#events` keeps the whole result, because the live path, the parking snapshot
|
|
181
|
+
and `Runner.eventAt` (which serves `GET /sessions/:id/events/:seq/result`) all read it. Refuse
|
|
182
|
+
the temptation to truncate into a snapshot: it would break the fetch for exactly the sessions
|
|
183
|
+
most likely to be read late.
|
|
184
|
+
|
|
185
|
+
- **Image refs happen there too — and on the live path as well.** `subscribe(..., { imageRefs })`
|
|
186
|
+
replaces a `tool_result`'s base64 `image` parts with `image_ref` addresses, and unlike truncation
|
|
187
|
+
it applies to live events as well as the replay, because a client's one render path is
|
|
188
|
+
ref-then-fetch. The same "never at emit" rule holds for the same reason: `#events` keeps every
|
|
189
|
+
byte, which is what the fetch route serves back. `SubscriberSet` (`src/lib/subscribers.ts`) is
|
|
190
|
+
where that per-subscriber decision lives — a subscriber is a listener *plus what it asked for*,
|
|
191
|
+
so the three runners no longer each own a copy of the answer. Consumers that subscribe with no
|
|
192
|
+
options — parking, notifications, the queue — see everything, as they do for every rule here.
|
|
193
|
+
|
|
172
194
|
- **A declared MCP server that never connected is refused, not degraded.** If a profile's
|
|
173
195
|
`session.mcpServers` names a server and it isn't there, `createEngineSession` throws. The old
|
|
174
196
|
behaviour — start anyway, minus those tools — produced a session that reported perfectly healthy
|
package/build/index.d.mts
CHANGED
|
@@ -184,10 +184,45 @@ interface Runner {
|
|
|
184
184
|
* default must stay off: it is only sound for a consumer whose handling of
|
|
185
185
|
* those events is last-write-wins, and `parking.ts` — which subscribes from
|
|
186
186
|
* seq 0 — branches on `status_changed` instead. Live events are never
|
|
187
|
-
* affected; this touches the buffered replay alone.
|
|
187
|
+
* affected; this touches the buffered replay alone.
|
|
188
|
+
*
|
|
189
|
+
* `truncateResults` delivers an oversized `tool_result` block as its head plus
|
|
190
|
+
* the markers that say so (protocol's {@link TOOL_RESULT_HEAD_CHARS}), leaving
|
|
191
|
+
* the whole thing one fetch away. Measured, that is 68% of a long session's
|
|
192
|
+
* attach in three frames. Opt-in for the same reason and with one extra
|
|
193
|
+
* condition: **the opt-in must be issued by the unit that renders**, because
|
|
194
|
+
* `client` and `react` are separate packages an embedder can skew, and a
|
|
195
|
+
* client that asked for heads without knowing how to fetch the rest would show
|
|
196
|
+
* one as though it were the whole result. Live events are untouched — a result
|
|
197
|
+
* arriving while you watch is already on screen — and so is the stored log,
|
|
198
|
+
* which parking snapshots and the fetch route both read.
|
|
199
|
+
*
|
|
200
|
+
* `imageRefs` replaces a `tool_result`'s base64 image parts with `image_ref`
|
|
201
|
+
* addresses (protocol's {@link ImageRefPart}), their bytes one REST fetch
|
|
202
|
+
* away. Opt-in under the same rule — issued by the unit that renders — but
|
|
203
|
+
* unlike the other two it applies to **live events as well as the replay**,
|
|
204
|
+
* because the client's one render path is ref-then-fetch and bytes on a live
|
|
205
|
+
* event would only be discarded or pinned. Measured, this is 91% of all
|
|
206
|
+
* tool-result payload and 0% of what any client draws. The stored log keeps
|
|
207
|
+
* every byte, which is what the fetch route serves back. */
|
|
188
208
|
subscribe(listener: SessionEventListener, afterSeq?: number, options?: {
|
|
189
209
|
coalesceReplay?: boolean;
|
|
210
|
+
truncateResults?: boolean;
|
|
211
|
+
imageRefs?: boolean;
|
|
190
212
|
}): () => void;
|
|
213
|
+
/** One buffered event by seq, or undefined — the read side of the log the
|
|
214
|
+
* replay already walks.
|
|
215
|
+
*
|
|
216
|
+
* Optional, like every member added after `Runner` became public API: an
|
|
217
|
+
* out-of-tree runner that declines it declines only the on-demand tool result
|
|
218
|
+
* with it (the route 404s), which is exactly the degradation a runner with no
|
|
219
|
+
* `truncateResults` support wants anyway.
|
|
220
|
+
*
|
|
221
|
+
* Deliberately **not** a "give me the whole log" accessor. The one caller
|
|
222
|
+
* needs a single event by a seq a client is holding, and a method that handed
|
|
223
|
+
* out the array would invite a second copy of the bytes this feature exists
|
|
224
|
+
* to stop shipping. */
|
|
225
|
+
eventAt?(seq: number): SessionEvent | undefined;
|
|
191
226
|
/** Queue a user message for the session (starts the next turn when idle).
|
|
192
227
|
* `attachments` carry their bytes to the engine and their reference to the
|
|
193
228
|
* event log (see {@link AttachmentInput}). */
|
|
@@ -226,12 +261,39 @@ interface Runner {
|
|
|
226
261
|
* doesn't: the CLI owns its own process state).
|
|
227
262
|
*/
|
|
228
263
|
park?(): RunnerSnapshot | undefined;
|
|
264
|
+
/**
|
|
265
|
+
* The same snapshot, taken **without ending anything** — the runner stays live,
|
|
266
|
+
* attached and warm.
|
|
267
|
+
*
|
|
268
|
+
* Park and snapshot are two operations that happen to produce the same value,
|
|
269
|
+
* and separating them is what makes restart-survival possible for an engine
|
|
270
|
+
* that has no on-disk session of its own. A park is for a session with nothing
|
|
271
|
+
* to do for possibly days; this is for one whose user is mid-conversation and
|
|
272
|
+
* whose process might be redeployed out from under it. The host writes it
|
|
273
|
+
* through after each turn — never on a shutdown hook, because a `kill -9` runs
|
|
274
|
+
* no hook and that is precisely the case worth surviving — and rebuilds from
|
|
275
|
+
* the last write through the ordinary `restore` path.
|
|
276
|
+
*
|
|
277
|
+
* Returns undefined when a snapshot would capture a half-happened turn: one in
|
|
278
|
+
* flight, or pending in-process executions whose results die with the process.
|
|
279
|
+
* Optional for the same reason `park()` is — claude and codex run behind a
|
|
280
|
+
* binary that owns its process state, and have engine-side resume instead.
|
|
281
|
+
*/
|
|
282
|
+
snapshot?(): RunnerSnapshot | undefined;
|
|
229
283
|
/** Emit a session_error and terminate. For host-enforced policy (e.g. requireApiKey). */
|
|
230
284
|
fail(message: string): void;
|
|
231
285
|
/** Terminate the session and any underlying engine process. */
|
|
232
286
|
close(reason?: 'client' | 'server' | 'error'): void;
|
|
233
287
|
}
|
|
234
288
|
//#endregion
|
|
289
|
+
//#region src/lib/subscribers.d.ts
|
|
290
|
+
/** What a subscriber asked for. Absent fields mean the untransformed stream. */
|
|
291
|
+
type SubscribeOptions = {
|
|
292
|
+
coalesceReplay?: boolean;
|
|
293
|
+
truncateResults?: boolean;
|
|
294
|
+
imageRefs?: boolean;
|
|
295
|
+
};
|
|
296
|
+
//#endregion
|
|
235
297
|
//#region src/engines/claude/runner.d.ts
|
|
236
298
|
type QueryFn = (params: {
|
|
237
299
|
prompt: AsyncIterable<SDKUserMessage>;
|
|
@@ -302,6 +364,10 @@ declare class SessionRunner implements Runner {
|
|
|
302
364
|
fail(message: string): void;
|
|
303
365
|
/** Terminate the session and the underlying CLI subprocess. */
|
|
304
366
|
close(reason?: 'client' | 'server' | 'error'): void;
|
|
367
|
+
/** See `Runner.eventAt`. A linear scan: the one caller is a reader pressing
|
|
368
|
+
* "show everything" on one row, so a per-runner seq index would be a map
|
|
369
|
+
* maintained on every emit to save a walk nobody makes twice a minute. */
|
|
370
|
+
eventAt(seq: number): SessionEvent | undefined;
|
|
305
371
|
/**
|
|
306
372
|
* Replay buffered events with seq > afterSeq, then deliver live events.
|
|
307
373
|
* Returns an unsubscribe function.
|
|
@@ -315,11 +381,64 @@ declare class SessionRunner implements Runner {
|
|
|
315
381
|
* reconnecting client still holding pre-reset rows; superseded resets are
|
|
316
382
|
* content below the newer one and are skipped with what they cleared.
|
|
317
383
|
*/
|
|
318
|
-
subscribe(listener: SessionEventListener, afterSeq?: number, options?:
|
|
319
|
-
coalesceReplay?: boolean;
|
|
320
|
-
}): () => void;
|
|
384
|
+
subscribe(listener: SessionEventListener, afterSeq?: number, options?: SubscribeOptions): () => void;
|
|
321
385
|
}
|
|
322
386
|
//#endregion
|
|
387
|
+
//#region src/lib/replay.d.ts
|
|
388
|
+
/**
|
|
389
|
+
* The one replay body, and what a socket receives from it.
|
|
390
|
+
*
|
|
391
|
+
* Every runner had a byte-identical copy of this loop — three spellings of four
|
|
392
|
+
* rules, one of which ("never drop the highest-seq event, whatever the rule
|
|
393
|
+
* says") is load-bearing and was three copies of a comment. Not a base class:
|
|
394
|
+
* the runners share nothing else, and a base class would have to own `#emit`,
|
|
395
|
+
* the most engine-specific method each of them has.
|
|
396
|
+
*
|
|
397
|
+
* The rules, in the order they are applied:
|
|
398
|
+
*
|
|
399
|
+
* 1. `afterSeq` — the caller already holds everything at or below it.
|
|
400
|
+
* 2. `resetSeq` — transcript *content* strictly below the latest
|
|
401
|
+
* `conversation_reset` is skipped, so a re-attach cannot resurrect a cleared
|
|
402
|
+
* conversation while state events still replay. Claude's alone; the other
|
|
403
|
+
* engines pass 0.
|
|
404
|
+
* 3. `coalesceReplay` — last-write-wins state readings superseded later in the
|
|
405
|
+
* same replay (`staleReplaySeqs`), plus everything `replayRetains` says the
|
|
406
|
+
* reducer reads and discards. Opt-in, and only sound for a consumer whose
|
|
407
|
+
* handling of those events is last-write-wins.
|
|
408
|
+
* 4. `imageRefs` — a base64 image part is delivered as an `image_ref` address
|
|
409
|
+
* (protocol's {@link imagePartRef}), its bytes one REST fetch away. Applied
|
|
410
|
+
* **before** rule 5, because it stamps indices from the stored part array
|
|
411
|
+
* which rule 5 then reshapes. Unlike rule 5 this also applies to the live
|
|
412
|
+
* path (see `SubscriberSet`), which is the one place these two rules differ.
|
|
413
|
+
* 5. `truncateResults` — a huge `tool_result` block is delivered as its head
|
|
414
|
+
* plus the markers that say so. **Never mutates the stored event**: the live
|
|
415
|
+
* path, the parking snapshot and the fetch route all need the whole thing,
|
|
416
|
+
* so this builds a copy and the log stays the log.
|
|
417
|
+
*
|
|
418
|
+
* The highest-seq event is delivered whatever rules 2 and 3 say — a client's
|
|
419
|
+
* replay hold waits for `state.lastSeq` to reach the attach's and would
|
|
420
|
+
* otherwise hang forever — but it is still *truncated* when rule 4 applies. A
|
|
421
|
+
* session that ends on a `find /` puts its 641 KB frame exactly there.
|
|
422
|
+
*/
|
|
423
|
+
declare function replaySlice(events: readonly SessionEvent[], options: {
|
|
424
|
+
afterSeq: number;
|
|
425
|
+
resetSeq?: number;
|
|
426
|
+
coalesceReplay?: boolean;
|
|
427
|
+
truncateResults?: boolean;
|
|
428
|
+
imageRefs?: boolean;
|
|
429
|
+
}): SessionEvent[];
|
|
430
|
+
/**
|
|
431
|
+
* A copy of `event` whose oversized `tool_result` blocks carry their head and
|
|
432
|
+
* say so — or `event` itself, unchanged and un-copied, when nothing is over the
|
|
433
|
+
* budget. That identity matters: an attach is mostly small events, and a fresh
|
|
434
|
+
* object for every one of them would cost more than the feature saves.
|
|
435
|
+
*
|
|
436
|
+
* Blocks are measured and cut **individually**. A message answering three calls
|
|
437
|
+
* where one is a `find /` keeps the two small results whole, which is what makes
|
|
438
|
+
* the per-block marker (rather than a per-event one) honest.
|
|
439
|
+
*/
|
|
440
|
+
declare function truncateResultBlocks(event: SessionEvent): SessionEvent;
|
|
441
|
+
//#endregion
|
|
323
442
|
//#region src/engines/provider/runner.d.ts
|
|
324
443
|
/** `cwd` is optional for this engine: the loop has no host-filesystem coupling
|
|
325
444
|
* (tools get scoped VFS handles instead). Defaults to process.cwd() for display. */
|
|
@@ -457,6 +576,34 @@ declare class AiSdkRunner implements Runner {
|
|
|
457
576
|
* or an already-closed/parked runner.
|
|
458
577
|
*/
|
|
459
578
|
park(): RunnerSnapshot | undefined;
|
|
579
|
+
/**
|
|
580
|
+
* The same snapshot, taken without ending anything.
|
|
581
|
+
*
|
|
582
|
+
* `park()` and this are two operations that happen to produce the same value,
|
|
583
|
+
* and the difference is the whole point: `park()` *ends* the live runner
|
|
584
|
+
* (inert, listeners dropped, `onClose` called), which is right for deferred
|
|
585
|
+
* execution — the session has nothing to do for possibly days — and wrong for
|
|
586
|
+
* restart-survival, where the session is active and someone is mid-
|
|
587
|
+
* conversation. This one changes nothing at all: no status emit, no listener
|
|
588
|
+
* clear, no disposer. The host writes the value through to durable storage
|
|
589
|
+
* after each turn and keeps the runner live and warm, so a restart rebuilds
|
|
590
|
+
* from the last write through the existing `restore` path and the next message
|
|
591
|
+
* costs no wake.
|
|
592
|
+
*
|
|
593
|
+
* The gate is `park()`'s minus the requirement that there be something parked:
|
|
594
|
+
*
|
|
595
|
+
* - `#abort` set is refused for the reason it always was — a `generate()` in
|
|
596
|
+
* flight has produced messages that are not in the history yet, so the
|
|
597
|
+
* snapshot would be of a turn that half-happened.
|
|
598
|
+
* - Pending calls that are **not** all deferred are refused, which is
|
|
599
|
+
* `park()`'s rule wearing a different hat. An in-process execution's result
|
|
600
|
+
* is coming back to *this* runner and dies with the process; a restore would
|
|
601
|
+
* wait on it forever, and `state.dispatched` is what would stop the rebuilt
|
|
602
|
+
* runner from simply calling it again.
|
|
603
|
+
* - Idle with nothing pending — the case `park()` exists to refuse — is
|
|
604
|
+
* exactly the case this exists to allow.
|
|
605
|
+
*/
|
|
606
|
+
snapshot(): RunnerSnapshot | undefined;
|
|
460
607
|
sendMessage(text: string, attachments?: readonly AttachmentInput[]): void;
|
|
461
608
|
/**
|
|
462
609
|
* Deliver the result of an external (execute-less) tool call. Appends the
|
|
@@ -485,9 +632,11 @@ declare class AiSdkRunner implements Runner {
|
|
|
485
632
|
setModel(model?: string): Promise<void>;
|
|
486
633
|
fail(message: string): void;
|
|
487
634
|
close(reason?: 'client' | 'server' | 'error'): void;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
635
|
+
/** See `Runner.eventAt`. A linear scan: the one caller is a reader pressing
|
|
636
|
+
* "show everything" on one row, so a per-runner seq index would be a map
|
|
637
|
+
* maintained on every emit to save a walk nobody makes twice a minute. */
|
|
638
|
+
eventAt(seq: number): SessionEvent | undefined;
|
|
639
|
+
subscribe(listener: SessionEventListener, afterSeq?: number, options?: SubscribeOptions): () => void;
|
|
491
640
|
/**
|
|
492
641
|
* Deliver the result of an execution this runner dispatched. Used by the host
|
|
493
642
|
* when a backend settled out-of-band (a browser bridge answering later, a
|
|
@@ -1528,9 +1677,11 @@ declare class CodexRunner implements Runner {
|
|
|
1528
1677
|
setModel(model?: string): Promise<void>;
|
|
1529
1678
|
fail(message: string): void;
|
|
1530
1679
|
close(reason?: 'client' | 'server' | 'error'): void;
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1680
|
+
/** See `Runner.eventAt`. A linear scan: the one caller is a reader pressing
|
|
1681
|
+
* "show everything" on one row, so a per-runner seq index would be a map
|
|
1682
|
+
* maintained on every emit to save a walk nobody makes twice a minute. */
|
|
1683
|
+
eventAt(seq: number): SessionEvent | undefined;
|
|
1684
|
+
subscribe(listener: SessionEventListener, afterSeq?: number, options?: SubscribeOptions): () => void;
|
|
1534
1685
|
/**
|
|
1535
1686
|
* The session's MCP servers, live from the binary.
|
|
1536
1687
|
*
|
|
@@ -1625,5 +1776,5 @@ declare class JsonRpcStdioConnection {
|
|
|
1625
1776
|
*/
|
|
1626
1777
|
declare const providerAdapter: EngineAdapter;
|
|
1627
1778
|
//#endregion
|
|
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 };
|
|
1779
|
+
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, replaySlice, resolveBundledClaudeExecutable, resolveBundledCodexExecutable, toApiMessage, toExecutionResult, truncateResultBlocks, withHostTools, withMcpTools };
|
|
1629
1780
|
//# sourceMappingURL=index.d.mts.map
|