stitchkit 0.68.2 → 0.68.4

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.
Files changed (45) hide show
  1. package/dist/agent-runtime/context-refusal.d.ts +13 -0
  2. package/dist/agent-runtime/context-refusal.d.ts.map +1 -0
  3. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  4. package/dist/agent-runtime/runtime.d.ts +4 -0
  5. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  6. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  7. package/dist/agent-runtime.d.ts +1 -0
  8. package/dist/agent-runtime.d.ts.map +1 -1
  9. package/dist/agent-runtime.js +61 -17
  10. package/dist/browser/contract-stream.d.ts.map +1 -1
  11. package/dist/browser/stream.d.ts +3 -0
  12. package/dist/browser/stream.d.ts.map +1 -1
  13. package/dist/cli.js +5 -5
  14. package/dist/contract/define.d.ts +39 -6
  15. package/dist/contract/define.d.ts.map +1 -1
  16. package/dist/contract/index.d.ts +1 -1
  17. package/dist/contract/index.d.ts.map +1 -1
  18. package/dist/contract/index.js +1 -1
  19. package/dist/{index-cz32amcb.js → index-04agqrs8.js} +2 -2
  20. package/dist/{index-cszjsxy5.js → index-1bmpkhj2.js} +3 -3
  21. package/dist/{index-wqyrvhzz.js → index-3ydx9j01.js} +2 -2
  22. package/dist/{index-69m2278y.js → index-51a19y3v.js} +1 -1
  23. package/dist/{index-3vq6we8m.js → index-6tqys26z.js} +16 -6
  24. package/dist/{index-7etq650s.js → index-88yyydag.js} +1 -1
  25. package/dist/{index-g2j2m6vv.js → index-9t2tdk1x.js} +2 -2
  26. package/dist/{index-9ky9hhg3.js → index-bmmtz6r9.js} +14 -0
  27. package/dist/{index-dafax5md.js → index-hmfpjnh7.js} +1 -1
  28. package/dist/{index-cq9q73nf.js → index-j2nq04z6.js} +14 -0
  29. package/dist/{index-v5bayx1z.js → index-r5s4wqb5.js} +7 -3
  30. package/dist/{index-2ve29dzn.js → index-thcy3w8c.js} +43 -20
  31. package/dist/{index-pr0qsmjy.js → index-z575awm9.js} +1 -1
  32. package/dist/index.js +48 -21
  33. package/dist/internal/bounded-lines.d.ts +1 -1
  34. package/dist/internal/bounded-lines.d.ts.map +1 -1
  35. package/dist/node.js +4 -4
  36. package/dist/observability/index.js +3 -3
  37. package/dist/remote.js +3 -3
  38. package/dist/server/contract-stream.d.ts.map +1 -1
  39. package/dist/server/index.js +10 -7
  40. package/dist/server/openapi.d.ts.map +1 -1
  41. package/dist/testing.js +63 -7
  42. package/dist/tool-invoker.js +5 -5
  43. package/dist/tools.js +9 -9
  44. package/llms-full.txt +84 -4
  45. package/package.json +1 -1
package/dist/testing.js CHANGED
@@ -25,11 +25,11 @@ import {
25
25
  import {
26
26
  createClient,
27
27
  createClients
28
- } from "./index-2ve29dzn.js";
29
- import"./index-v5bayx1z.js";
28
+ } from "./index-thcy3w8c.js";
29
+ import"./index-r5s4wqb5.js";
30
30
  import {
31
31
  joinRoutePath
32
- } from "./index-9ky9hhg3.js";
32
+ } from "./index-bmmtz6r9.js";
33
33
  import {
34
34
  isRecord
35
35
  } from "./index-smpbdg6k.js";
@@ -145,7 +145,7 @@ function requireOutcome(actual, expected) {
145
145
  async function runAgentStoreConformance(config) {
146
146
  const run = `conformance-${crypto.randomUUID()}`;
147
147
  const context = {
148
- conversationIds: [run, `${run}-recovery`, `${run}-absorb`]
148
+ conversationIds: [run, `${run}-recovery`, `${run}-absorb`, `${run}-causal-history`]
149
149
  };
150
150
  const store = await config.createStore(context);
151
151
  let failure;
@@ -165,9 +165,14 @@ async function runAgentStoreConformance(config) {
165
165
  throw failure;
166
166
  }
167
167
  async function conformanceScenario(store, conversationIds) {
168
- const [conversationId, recoveryConversationId, absorbConversationId] = conversationIds;
169
- if (!conversationId || !recoveryConversationId || !absorbConversationId) {
170
- throw new Error("Agent store conformance requires three conversation identities");
168
+ const [
169
+ conversationId,
170
+ recoveryConversationId,
171
+ absorbConversationId,
172
+ causalHistoryConversationId
173
+ ] = conversationIds;
174
+ if (!conversationId || !recoveryConversationId || !absorbConversationId || !causalHistoryConversationId) {
175
+ throw new Error("Agent store conformance requires four conversation identities");
171
176
  }
172
177
  const absentConversationId = `${conversationId}-absent`;
173
178
  const firstInput = userMessage(conversationId, "input-1");
@@ -548,8 +553,59 @@ async function conformanceScenario(store, conversationIds) {
548
553
  if (terminalRun?.state !== "abandoned" || terminalMessage?.status !== "failed") {
549
554
  throw new Error("Abandon recovery did not atomically terminalize its assistant record");
550
555
  }
556
+ await assertCausalHistoryOrder(store, causalHistoryConversationId);
551
557
  await assertAbsorptionIsAtomic(store, absorbConversationId);
552
558
  }
559
+ async function assertCausalHistoryOrder(store, conversationId) {
560
+ const leadInput = userMessage(conversationId, "causal-input-1");
561
+ const leadRun = queuedRun(conversationId, leadInput.id, "causal-run-1");
562
+ requireOutcome(await store.acceptInputAndAssignRun({
563
+ idempotencyKey: "causal-request-1",
564
+ input: leadInput,
565
+ run: leadRun
566
+ }), "applied");
567
+ const acquired = await store.acquireRun({
568
+ conversationId,
569
+ runId: leadRun.id,
570
+ expectedRevision: leadRun.revision,
571
+ ownerId: "causal-owner"
572
+ });
573
+ requireOutcome(acquired, "applied");
574
+ const running = acquired.snapshot.runs.find((run) => run.id === leadRun.id);
575
+ if (!running)
576
+ throw new Error("Causal-order run disappeared after acquisition");
577
+ const successorInput = userMessage(conversationId, "causal-input-2");
578
+ const successorRun = queuedRun(conversationId, successorInput.id, "causal-run-2");
579
+ requireOutcome(await store.acceptInputAndAssignRun({
580
+ idempotencyKey: "causal-request-2",
581
+ input: successorInput,
582
+ run: successorRun
583
+ }), "applied");
584
+ const assistant = AgentMessageSchema.parse({
585
+ schemaVersion: 1,
586
+ id: running.assistantMessageId,
587
+ conversationId,
588
+ runId: running.id,
589
+ role: "assistant",
590
+ status: "streaming",
591
+ parts: [{ type: "text", text: "causal answer" }],
592
+ createdAt: "2026-08-28T00:00:00.000Z",
593
+ updatedAt: "2026-08-28T00:00:01.000Z"
594
+ });
595
+ const checkpoint = await store.checkpointRunAssistant({
596
+ conversationId,
597
+ runId: running.id,
598
+ expectedRevision: running.revision,
599
+ ownerId: "causal-owner",
600
+ ...running.fencingToken !== undefined && { fencingToken: running.fencingToken },
601
+ assistant
602
+ });
603
+ requireOutcome(checkpoint, "applied");
604
+ const order = checkpoint.snapshot.messages.map((message) => message.id).join(",");
605
+ if (order !== `${leadInput.id},${assistant.id},${successorInput.id}`) {
606
+ throw new Error(`Agent history is not in causal run order: ${order}`);
607
+ }
608
+ }
553
609
  async function assertAbsorptionIsAtomic(store, conversationId) {
554
610
  const leadInput = userMessage(conversationId, "absorb-input-1");
555
611
  const leadRun = queuedRun(conversationId, leadInput.id, "absorb-run-1");
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createToolInvoker
3
- } from "./index-g2j2m6vv.js";
4
- import"./index-7etq650s.js";
5
- import"./index-cz32amcb.js";
6
- import"./index-69m2278y.js";
3
+ } from "./index-9t2tdk1x.js";
4
+ import"./index-88yyydag.js";
5
+ import"./index-04agqrs8.js";
6
+ import"./index-51a19y3v.js";
7
7
  import"./index-vkk06pv1.js";
8
8
  import"./index-6djpbnda.js";
9
9
  import"./index-cby4ar3v.js";
10
- import"./index-9ky9hhg3.js";
10
+ import"./index-bmmtz6r9.js";
11
11
  import"./index-smpbdg6k.js";
12
12
  import"./index-0w9abg87.js";
13
13
  import"./index-6k1937bx.js";
package/dist/tools.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  redact
3
- } from "./index-dafax5md.js";
3
+ } from "./index-hmfpjnh7.js";
4
4
  import {
5
5
  signJwt,
6
6
  verifyPkce
7
- } from "./index-wqyrvhzz.js";
7
+ } from "./index-3ydx9j01.js";
8
8
  import {
9
9
  DEFAULT_CORS_ALLOW_HEADERS,
10
10
  DEFAULT_PROCESS_SIGNALS,
@@ -12,10 +12,10 @@ import {
12
12
  defaultSignalSource,
13
13
  guardSignalCallback,
14
14
  reportSignalError
15
- } from "./index-pr0qsmjy.js";
15
+ } from "./index-z575awm9.js";
16
16
  import {
17
17
  createToolInvoker
18
- } from "./index-g2j2m6vv.js";
18
+ } from "./index-9t2tdk1x.js";
19
19
  import {
20
20
  WaitTimeoutError,
21
21
  collectToolSurface,
@@ -27,24 +27,24 @@ import {
27
27
  fetchPinnedDocument,
28
28
  readCapped,
29
29
  runWaitOperation
30
- } from "./index-cszjsxy5.js";
30
+ } from "./index-1bmpkhj2.js";
31
31
  import {
32
32
  collectTools,
33
33
  createToolRunner,
34
34
  formatToolError
35
- } from "./index-7etq650s.js";
35
+ } from "./index-88yyydag.js";
36
36
  import {
37
37
  ToolExecutionControlError,
38
38
  coerceJsonArgs,
39
39
  executeToolMethod,
40
40
  isToolExecutionControlError,
41
41
  toolResultFromError
42
- } from "./index-cz32amcb.js";
42
+ } from "./index-04agqrs8.js";
43
43
  import {
44
44
  getRequestContext,
45
45
  getTraceId,
46
46
  runWithRequestContext
47
- } from "./index-69m2278y.js";
47
+ } from "./index-51a19y3v.js";
48
48
  import {
49
49
  ManagedFileError
50
50
  } from "./index-bfcpjw20.js";
@@ -66,7 +66,7 @@ import {
66
66
  defineContract,
67
67
  normalizeError,
68
68
  resolvePropagationContext
69
- } from "./index-9ky9hhg3.js";
69
+ } from "./index-bmmtz6r9.js";
70
70
  import {
71
71
  isRecord
72
72
  } from "./index-smpbdg6k.js";
package/llms-full.txt CHANGED
@@ -1574,6 +1574,27 @@ multipart or tool exposure. They do not provide replay, cursors or durable
1574
1574
  subscriptions. Keep using `streamingRoute` for an application-owned protocol and
1575
1575
  `rawResponse` for arbitrary response bodies. → ADR 0117.
1576
1576
 
1577
+ For an existing schema-owned NDJSON protocol, opt into direct item frames and
1578
+ terminal-owned completion:
1579
+
1580
+ ```ts
1581
+ stream: {
1582
+ item: Item,
1583
+ framing: 'item',
1584
+ completion: 'terminal',
1585
+ terminal: z.object({ kind: z.literal('complete') }).loose(),
1586
+ finalLine: 'require-newline',
1587
+ }
1588
+ ```
1589
+
1590
+ `item` framing is NDJSON-only and requires terminal completion. The server
1591
+ writes each validated item directly, stops the source after the terminal and
1592
+ never reads trailing producer values. Because this wire has no framework error
1593
+ envelope, any producer/lifetime failure before terminal closes the response;
1594
+ the typed client reports `STREAM_TERMINAL_MISSING`. The terminal item therefore
1595
+ proves success without weakening safe post-header failure semantics. Existing
1596
+ streams retain the envelope and explicit end frame by default. → ADR 0126.
1597
+
1577
1598
  ### SSE streaming
1578
1599
 
1579
1600
  `streamSSE` returns a `Response`, so its endpoint declares
@@ -2407,6 +2428,26 @@ wire `end` frame and, when declared, at least one matching terminal item; EOF is
2407
2428
  converge on the request operation. See the
2408
2429
  [server half](./server.md#contract-first-streams). → ADR 0117.
2409
2430
 
2431
+ An established NDJSON protocol may keep its item schema as the complete wire
2432
+ frame. This mode requires a terminal item because an unwrapped response has no
2433
+ separate safe error/end envelope:
2434
+
2435
+ ```ts
2436
+ stream: {
2437
+ item: Progress,
2438
+ framing: 'item',
2439
+ completion: 'terminal',
2440
+ terminal: z.object({ kind: z.literal('complete') }).loose(),
2441
+ finalLine: 'require-newline',
2442
+ }
2443
+ ```
2444
+
2445
+ The matching terminal item ends the operation. Before `next()` returns that
2446
+ item, the client aborts the owned request and cancels its body reader; trailing
2447
+ frames are not read. EOF first is `STREAM_TERMINAL_MISSING`. The defaults remain
2448
+ `framing: 'envelope'`, `completion: 'stream-end'` and `finalLine: 'allow'`.
2449
+ → ADR 0126.
2450
+
2410
2451
  ## SSE
2411
2452
 
2412
2453
  For a streaming endpoint, consume the response with `parseSSE`:
@@ -2460,6 +2501,10 @@ implementations. One line is bounded by `maxLineBytes` (default 1 MiB), UTF-8 is
2460
2501
  decoded strictly and malformed input throws. Passing `onParseError` explicitly
2461
2502
  selects tolerant skip-and-report behaviour.
2462
2503
 
2504
+ Set `finalLine: 'require-newline'` when the final newline is part of the
2505
+ protocol's truncation proof. The default `allow` continues to accept one valid
2506
+ final JSON document without a newline.
2507
+
2463
2508
 
2464
2509
  ==============================================================================
2465
2510
  # Guide: MCP & agents (docs/guide/mcp-and-agents.md)
@@ -3721,6 +3766,7 @@ bun add @openrouter/ai-sdk-provider
3721
3766
  ```ts
3722
3767
  import { z } from 'zod'
3723
3768
  import {
3769
+ AgentContextOverflowError,
3724
3770
  composeAgentPrompt,
3725
3771
  createAgentRuntime,
3726
3772
  createMemoryAgentRuntimeStore,
@@ -3882,6 +3928,13 @@ assistant, so physical product-history compaction cannot break idempotent retrie
3882
3928
  acquisition, revision-checked assistant checkpoints and one terminal CAS. The
3883
3929
  process-local coordinator releases its lane only after terminal commit.
3884
3930
 
3931
+ A queued admission is durable immediately, even if its predecessor is still awaiting ownership
3932
+ or its first checkpoint. Snapshots expose causal turn order rather than physical append order:
3933
+ the predecessor's assigned input(s) and assistant come before the successor input(s). Execution
3934
+ uses the same run boundary, so neither `prompt({ snapshot })` nor default/custom history
3935
+ projection can see inputs assigned to a later run. `inject` remains the explicit path that moves
3936
+ a successor input into the run already in flight.
3937
+
3885
3938
  ```text
3886
3939
  input + queued run → running → execution settled → terminal CAS → successor
3887
3940
  ```
@@ -4192,6 +4245,25 @@ is the reserved built-in policy name. `loop.prepareStep` is the controlled AI
4192
4245
  SDK step boundary for changing active tools, model, instructions or messages.
4193
4246
  It cannot replace the managed tool set or bypass its lifecycle fence.
4194
4247
 
4248
+ Context can grow between steps as tool results and deferred schemas enter the
4249
+ provider prompt. When application budgeting can prove that the next assembled
4250
+ step exceeds the selected model window, refuse it by type before that provider
4251
+ call:
4252
+
4253
+ ```ts
4254
+ prepareStep: (step) => {
4255
+ if (wouldExceedSelectedModelWindow(step)) {
4256
+ throw new AgentContextOverflowError('Prepared step exceeds the selected model window')
4257
+ }
4258
+ return chooseProductStepOptions(step)
4259
+ }
4260
+ ```
4261
+
4262
+ That deliberate refusal ends the run as `context_overflow` on the durable
4263
+ record, delivery terminal and operator event. Stitchkit does not inspect error
4264
+ messages: every other `prepareStep` or provider error remains
4265
+ `provider_failure`, and operator-only observability retains its original cause.
4266
+
4195
4267
  Completion validity belongs to the protocol and is checked before the terminal
4196
4268
  CAS. Protocols that require a visible answer opt in explicitly:
4197
4269
 
@@ -4398,7 +4470,7 @@ outbox.
4398
4470
  `createAgentRaceTrace`. Barriers have bounded teardown, traces assert exact partial order, and the
4399
4471
  helpers are exercised from packed Bun and Node consumers. `runAgentStoreConformance` runs duplicate,
4400
4472
  coalescing, collision, stale checkpoint, replay safety, terminal race, absorption, bounded reads,
4401
- compaction and recovery invariants against any fresh durable adapter.
4473
+ causal queued-history order, compaction and recovery invariants against any fresh durable adapter.
4402
4474
 
4403
4475
  It picks its conversation identities itself and passes them to `createStore(context)` **before the
4404
4476
  first mutation**, so an adapter whose runtime rows reference an application-owned conversation row
@@ -10902,8 +10974,10 @@ The browser-and-server entrypoint. Re-exports everything from
10902
10974
  | `bindRealtimeClient` | function | bind contract validation and typed acknowledgements to an existing Stitchkit client transport without owning its lifecycle |
10903
10975
  | `createRetainedTopics` | function | retained last-value store for sticky events — [guide](../guide/realtime.md#sticky-events) |
10904
10976
  | `parseSSE` | function | parse an SSE `Response` into an async generator — [guide](../guide/client.md#sse) |
10905
- | `parseNDJSON` | function | parse an NDJSON `Response`; blank keep-alive lines are skipped — [guide](../guide/client.md#ndjson) |
10906
- | `ContractStreamFrameSchema` / `ContractStreamFrame` | schema / _type_ | internal-on-the-wire `data` / safe `error` / `end` envelope of a contract-first stream |
10977
+ | `parseNDJSON` | function | parse bounded fatal-UTF-8 NDJSON; blank keep-alives are skipped and `finalLine: 'require-newline'` can make the delimiter mandatory — [guide](../guide/client.md#ndjson) |
10978
+ | `ContractStreamFrameSchema` / `ContractStreamFrame` | schema / _type_ | default on-the-wire `data` / safe `error` / `end` envelope of a contract-first stream |
10979
+ | `ContractStreamFraming` / `ContractStreamCompletion` | _types_ | opt-in item-vs-envelope framing and terminal-vs-stream-end completion policies |
10980
+ | `StreamFinalLinePolicy` | _type_ | permissive or newline-required final NDJSON line policy |
10907
10981
  | `DEFAULT_CONTRACT_STREAM_FRAME_BYTES` | const | default maximum encoded contract-stream frame: 256 KiB |
10908
10982
  | `SocketIOClient` | _type_ | low-level client handle; `emit` reports disconnected drops and `emitWithAck` exposes the native Promise primitive used by validated `request()` |
10909
10983
  | `SocketIOClientPeerLoaders` | _type_ | inject `socket.io-client` so a bundler can put it in a self-contained artifact |
@@ -10980,7 +11054,7 @@ from the root `stitchkit`.
10980
11054
  | `ContractDef` | _type_ | a defined contract |
10981
11055
  | `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` and `meta` (a default every endpoint shallow-merges over) |
10982
11056
  | `EndpointDef` | _type_ | a single endpoint definition; `output` declares JSON response presence (`null` is data, `undefined` is invalid) |
10983
- | `EndpointStreamDescriptor` | _type_ | HTTP-only schema-derived stream declaration: item schema, NDJSON/SSE framing, frame/lifetime/heartbeat/idle bounds and optional terminal predicate — [guide](../guide/server.md#contract-first-streams) |
11057
+ | `EndpointStreamDescriptor` | _type_ | HTTP-only schema-derived stream declaration: item schema, envelope/item framing, stream-end/terminal completion, NDJSON/SSE encoding and frame/lifetime/heartbeat/idle bounds — [guide](../guide/server.md#contract-first-streams) |
10984
11058
  | `HeadEndpointDef` | _type_ | explicit HTTP-only, bodyless `HEAD` endpoint definition |
10985
11059
  | `EndpointResponseMeta` | _type_ | static success metadata declared by an HTTP-only typed-data endpoint |
10986
11060
  | `ResponseMetadata` | _type_ | per-request outbound collector exposed as `ctx.response` only for a `responseMeta` endpoint |
@@ -11437,6 +11511,7 @@ Server-only optional application runtime. See the
11437
11511
  | `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
11438
11512
  | `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
11439
11513
  | `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
11514
+ | `AgentContextOverflowError` | class | deliberate application budget refusal thrown from `loop.prepareStep`; terminalizes as `context_overflow` without classifying arbitrary error text |
11440
11515
  | `AgentRuntimeRecordIds` | _type_ | optional caller-provided input, run and assistant IDs for stable application records |
11441
11516
  | `AgentRuntimeAdmission` | _type_ | canonical committed input, assigned run, pending assistant projection, compatibility IDs and snapshot version |
11442
11517
  | `AgentAdmissionEventSchema` | schema | post-commit admission projection; removes store rereads but does not imply exactly-once delivery |
@@ -11494,6 +11569,11 @@ ordinary queued successor. The absorbed run ends with `terminalReason: 'absorbed
11494
11569
  its own**; a submission on its idempotency key resolves through that pointer to the answer
11495
11570
  (→ ADR 0113).
11496
11571
 
11572
+ With `queue`, a durable successor admission is not prompt eligibility: the current executor sees
11573
+ only records through its own run boundary. Snapshot history is normalized to causal turn order
11574
+ (assigned input(s), assistant, then successor input(s)) even when the storage codec physically
11575
+ appended the successor before the predecessor checkpoint.
11576
+
11497
11577
  `AgentRuntimeStore` has two **bounded** reads beside `loadSnapshot`:
11498
11578
  `loadRun({ conversationId, runId })` returns an `AgentRunView` — the run, the conversation version it
11499
11579
  was read at, and the retained answer once the run is terminal — or `undefined`; `listActiveRuns(conversationId)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.68.2",
3
+ "version": "0.68.4",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",