stitchkit 0.56.1 → 0.56.2

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 (67) hide show
  1. package/README.md +12 -3
  2. package/dist/agent-runtime/compaction.d.ts +33 -0
  3. package/dist/agent-runtime/compaction.d.ts.map +1 -0
  4. package/dist/agent-runtime/coordinator.d.ts +24 -0
  5. package/dist/agent-runtime/coordinator.d.ts.map +1 -0
  6. package/dist/agent-runtime/events.d.ts +454 -0
  7. package/dist/agent-runtime/events.d.ts.map +1 -0
  8. package/dist/agent-runtime/history.d.ts +11 -0
  9. package/dist/agent-runtime/history.d.ts.map +1 -0
  10. package/dist/agent-runtime/managed-tools.d.ts +18 -0
  11. package/dist/agent-runtime/managed-tools.d.ts.map +1 -0
  12. package/dist/agent-runtime/models.d.ts +51 -0
  13. package/dist/agent-runtime/models.d.ts.map +1 -0
  14. package/dist/agent-runtime/observability.d.ts +134 -0
  15. package/dist/agent-runtime/observability.d.ts.map +1 -0
  16. package/dist/agent-runtime/prompt.d.ts +50 -0
  17. package/dist/agent-runtime/prompt.d.ts.map +1 -0
  18. package/dist/agent-runtime/protocol.d.ts +16 -0
  19. package/dist/agent-runtime/protocol.d.ts.map +1 -0
  20. package/dist/agent-runtime/runtime.d.ts +120 -0
  21. package/dist/agent-runtime/runtime.d.ts.map +1 -0
  22. package/dist/agent-runtime/schemas.d.ts +470 -0
  23. package/dist/agent-runtime/schemas.d.ts.map +1 -0
  24. package/dist/agent-runtime/store.d.ts +905 -0
  25. package/dist/agent-runtime/store.d.ts.map +1 -0
  26. package/dist/agent-runtime/testing.d.ts +17 -0
  27. package/dist/agent-runtime/testing.d.ts.map +1 -0
  28. package/dist/agent-runtime-openrouter.d.ts +6 -0
  29. package/dist/agent-runtime-openrouter.d.ts.map +1 -0
  30. package/dist/agent-runtime-openrouter.js +46 -0
  31. package/dist/agent-runtime.d.ts +13 -0
  32. package/dist/agent-runtime.d.ts.map +1 -0
  33. package/dist/agent-runtime.js +1931 -0
  34. package/dist/cli.js +8 -5
  35. package/dist/index-0nc0cddp.js +178 -0
  36. package/dist/{index-6y759j86.js → index-1tkyng3g.js} +9 -57
  37. package/dist/{index-sm2tjx06.js → index-41wm56v0.js} +2 -2
  38. package/dist/index-6djpbnda.js +56 -0
  39. package/dist/{index-j3dem06f.js → index-7c0gkyvj.js} +5 -3
  40. package/dist/{index-75njxz7p.js → index-9zn9fb4e.js} +8 -6
  41. package/dist/{index-xy8fmh6w.js → index-f6pymtqe.js} +3 -3
  42. package/dist/{index-jcc611vh.js → index-gnvzyw0k.js} +2 -2
  43. package/dist/{index-qh4xevf5.js → index-mr617n62.js} +4 -2
  44. package/dist/{index-5r13htq1.js → index-ps5pxp3h.js} +1 -1
  45. package/dist/index-sa2mbwa7.js +336 -0
  46. package/dist/index-smpbdg6k.js +27 -0
  47. package/dist/{index-7eq9va6e.js → index-y2rb7dwx.js} +1 -27
  48. package/dist/{index-escqg10p.js → index-zk5zn2nh.js} +32 -334
  49. package/dist/internal/observability-sink.d.ts +26 -0
  50. package/dist/internal/observability-sink.d.ts.map +1 -0
  51. package/dist/node.js +6 -5
  52. package/dist/observability/audit.d.ts.map +1 -1
  53. package/dist/observability/index.js +15 -178
  54. package/dist/remote.js +4 -3
  55. package/dist/server/error-hook.d.ts +9 -7
  56. package/dist/server/error-hook.d.ts.map +1 -1
  57. package/dist/server/index.js +9 -7
  58. package/dist/testing.js +5 -3
  59. package/dist/tools/agent.d.ts.map +1 -1
  60. package/dist/tools/execute.d.ts +7 -0
  61. package/dist/tools/execute.d.ts.map +1 -1
  62. package/dist/tools.d.ts +2 -1
  63. package/dist/tools.d.ts.map +1 -1
  64. package/dist/tools.js +23 -12
  65. package/llms-full.txt +365 -10
  66. package/llms.txt +1 -0
  67. package/package.json +16 -3
package/llms-full.txt CHANGED
@@ -47,6 +47,8 @@ keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
47
47
  | `stitchkit/remote` | browser **and** server | peer-free `implementRemote` for thin HTTP proxy processes |
48
48
  | `stitchkit/files` | server (Bun or Node) | peer-free managed local-file boundary |
49
49
  | `stitchkit/observability` | server | request/tool event projections — `createObservability`, trace context, sanitisation |
50
+ | `stitchkit/agent-runtime` | server | optional durable conversation/run loop, history, models, prompts, fencing and events |
51
+ | `stitchkit/agent-runtime/openrouter` | server | isolated OpenRouter language-model adapter |
50
52
  | `stitchkit/testing` | tests on Bun or Node | in-process generated clients over a real Fetch handler, without a TCP port |
51
53
  | `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
52
54
 
@@ -146,6 +148,8 @@ map — feature → packages:
146
148
  | `createServer` (Bun) | — (uses `Bun.serve`) |
147
149
  | `serveNode` (Node ≥ 22) | `srvx` (+ `@types/bun` dev) |
148
150
  | MCP / agent tools (`stitchkit/tools`) | `@modelcontextprotocol/server` `ai` |
151
+ | Agent application runtime (`stitchkit/agent-runtime`) | `ai` |
152
+ | OpenRouter runtime adapter (`stitchkit/agent-runtime/openrouter`) | `ai` `@openrouter/ai-sdk-provider` |
149
153
  | MCP host/client tests | `@modelcontextprotocol/client` |
150
154
  | MCP Apps UI widgets | `@modelcontextprotocol/ext-apps` |
151
155
  | React data layer (`stitchkit/react`) | `@tanstack/react-query` `react-query-kit` |
@@ -2836,6 +2840,10 @@ list.
2836
2840
 
2837
2841
  ## AI agents — `mountAgent`
2838
2842
 
2843
+ `mountAgent` remains the low-level path when the application owns its own model
2844
+ loop and conversation state. For the optional Stitchkit-owned run/history loop,
2845
+ see [Agent application runtime](./agent-runtime.md).
2846
+
2839
2847
  `mountAgent` turns a service into a Vercel AI SDK `ToolSet`, ready for
2840
2848
  `generateText` / `streamText`:
2841
2849
 
@@ -3340,6 +3348,288 @@ the rest of the context (`params`, `input`, anything `context` injects) is
3340
3348
  identical. Write the handler once; it serves every surface.
3341
3349
 
3342
3350
 
3351
+ ==============================================================================
3352
+ # Guide: Agent application runtime (docs/guide/agent-runtime.md)
3353
+ ==============================================================================
3354
+
3355
+ ---
3356
+ title: "Agent application runtime"
3357
+ description: Configure Stitchkit's optional durable history, stream loop, run coordination and managed-tool fencing.
3358
+ type: architecture
3359
+ status: active
3360
+ created: 2026-08-22
3361
+ updated: 2026-08-22
3362
+ ---
3363
+
3364
+ # Agent application runtime
3365
+
3366
+ `stitchkit/agent-runtime` is the server-only, opinionated layer above
3367
+ `mountAgent`. Use it when the application wants Stitchkit to own conversation
3368
+ mechanics: durable acceptance, history projection, the AI SDK stream loop,
3369
+ checkpoints, keyed interruption, terminal commit and stable application events.
3370
+
3371
+ If the application already owns that loop, continue importing `mountAgent` from
3372
+ `stitchkit/tools`. Neither path depends on the other at runtime.
3373
+
3374
+ ## Install
3375
+
3376
+ ```sh
3377
+ bun add stitchkit ai zod
3378
+ ```
3379
+
3380
+ OpenRouter is isolated so other runtime users do not resolve its package:
3381
+
3382
+ ```sh
3383
+ bun add @openrouter/ai-sdk-provider
3384
+ ```
3385
+
3386
+ ## Minimal composition
3387
+
3388
+ ```ts
3389
+ import { z } from 'zod'
3390
+ import {
3391
+ composeAgentPrompt,
3392
+ createAgentRuntime,
3393
+ createMemoryAgentRuntimeStore,
3394
+ defineAgentProtocol,
3395
+ defineModelRegistry,
3396
+ } from 'stitchkit/agent-runtime'
3397
+ import { openRouterProvider } from 'stitchkit/agent-runtime/openrouter'
3398
+ import { composeToolLifecycle, mountAgent } from 'stitchkit/tools'
3399
+
3400
+ const protocol = defineAgentProtocol({
3401
+ context: z.object({ userId: z.string() }),
3402
+ inputMetadata: z.object({}),
3403
+ })
3404
+
3405
+ const models = defineModelRegistry({
3406
+ providers: {
3407
+ openrouter: openRouterProvider({ apiKey: env.OPENROUTER_API_KEY }),
3408
+ },
3409
+ models: {
3410
+ fast: {
3411
+ provider: 'openrouter',
3412
+ modelId: 'provider/model',
3413
+ contextWindow: 128_000,
3414
+ capabilities: ['tools'],
3415
+ },
3416
+ },
3417
+ })
3418
+
3419
+ const prompt = composeAgentPrompt([
3420
+ {
3421
+ name: 'product',
3422
+ stability: 'stable',
3423
+ render: ({ context }) => `Help user ${context.userId}.`,
3424
+ },
3425
+ ])
3426
+
3427
+ const runtime = createAgentRuntime({
3428
+ protocol,
3429
+ store: createMemoryAgentRuntimeStore(),
3430
+ models: { resolve: () => models.resolve('fast', ['tools']) },
3431
+ prompt: ({ context, signal, model }) =>
3432
+ prompt({
3433
+ context,
3434
+ signal,
3435
+ budget: {
3436
+ contextWindow: model.descriptor.contextWindow,
3437
+ reservedOutput: 8_000,
3438
+ toolSchemas: { provenance: 'unavailable' },
3439
+ attachments: { value: 0, provenance: 'measured' },
3440
+ providerOverhead: { provenance: 'unavailable' },
3441
+ },
3442
+ }),
3443
+ tools: ({ context, toolFenceLifecycle }) =>
3444
+ mountAgent(service, {
3445
+ context,
3446
+ lifecycle: composeToolLifecycle(authLifecycle, toolFenceLifecycle),
3447
+ }),
3448
+ runs: { inputPolicy: 'interrupt', coalescePending: true },
3449
+ loop: {
3450
+ idleTimeoutMs: 60_000,
3451
+ prepareStep: ({ context, steps }) => ({
3452
+ activeTools: steps.some((step) => step.toolCalls.length > 0)
3453
+ ? ['lookup']
3454
+ : ['lookup', 'discover'],
3455
+ instructions: `Current account: ${context.userId}`,
3456
+ }),
3457
+ stopPolicies: [
3458
+ {
3459
+ name: 'repeated-tool-error',
3460
+ when: ({ steps }) => hasRepeatedToolError(steps),
3461
+ },
3462
+ ],
3463
+ },
3464
+ })
3465
+
3466
+ const ticket = runtime.submit({
3467
+ conversationId: 'conversation-id',
3468
+ idempotencyKey: 'request-id',
3469
+ context: { userId: 'user-id' },
3470
+ parts: [{ type: 'text', text: 'Hello' }],
3471
+ })
3472
+
3473
+ await ticket.accepted
3474
+ const terminal = await ticket.result
3475
+ ```
3476
+
3477
+ The in-memory store is a reference adapter and has process-local durability
3478
+ only. Production applications implement `AgentRuntimeStore` with their own
3479
+ database transaction and, when needed, distributed lease/fencing token.
3480
+
3481
+ ## Durable order
3482
+
3483
+ `acceptInputAndAssignRun` is one atomic operation. It is followed by ownership
3484
+ acquisition, revision-checked assistant checkpoints and one terminal CAS. The
3485
+ process-local coordinator releases its lane only after terminal commit.
3486
+
3487
+ ```text
3488
+ input + queued run → running → execution settled → terminal CAS → successor
3489
+ ```
3490
+
3491
+ With `runs.coalescePending: true`, an active lane has at most one queued
3492
+ successor. Every later accepted input is atomically appended to that successor;
3493
+ its `AgentRun.inputMessageIds` records the whole batch and every input ticket
3494
+ resolves to the same terminal run. Coalescing never mutates the active run.
3495
+
3496
+ `AbortSignal.aborted` is only a cooperation request. A successor does not begin
3497
+ while the predecessor still owns managed callbacks. A hung predecessor blocks
3498
+ the lane in the first version.
3499
+
3500
+ Use `await runtime.interrupt({ conversationId, runId })` when the interruption
3501
+ must be durable: it first commits `interrupt_requested`, then aborts the local
3502
+ coordinator signal. `runtime.stop(key)` is the process-local signal-only escape
3503
+ hatch.
3504
+
3505
+ ## Store operations
3506
+
3507
+ An adapter implements the aggregate `AgentRuntimeStore`, not separate message
3508
+ and run CRUD stores:
3509
+
3510
+ - `acceptInputAndAssignRun`
3511
+ - `acquireRun`
3512
+ - `checkpointRunAssistant`
3513
+ - `requestRunInterrupt`
3514
+ - `recoverRun`
3515
+ - `commitRunTerminal`
3516
+ - `replaceCompactedRange`
3517
+ - `loadSnapshot` and `scanRecoverable`
3518
+
3519
+ Every mutation carries an expected run revision or snapshot version. Input
3520
+ assignment additionally carries an idempotency identity. A conflict is a
3521
+ control outcome; stale data is never silently overwritten.
3522
+
3523
+ On startup, `scanRecoverable` returns queued/acquired records. `recoverRun`
3524
+ may abandon them, or requeue an already acquired run only with explicit
3525
+ `replaySafe: true` evidence. The framework never guesses that an external
3526
+ side effect is replayable. After an application reconstructs its typed context,
3527
+ `runtime.resume({ conversationId, runId, context })` admits that queued record
3528
+ through the same acquisition CAS and coordinator lane; it never creates a
3529
+ second input message.
3530
+
3531
+ Canonical records currently write `schemaVersion: 1`. A durable adapter owns
3532
+ read-time migration of older rows: migrate to the current shape at its storage
3533
+ boundary, validate with the exported schema, and write only the current
3534
+ version. Core deliberately does not guess an application's database migration
3535
+ or silently accept an unknown future version.
3536
+
3537
+ ## Events and reconnect
3538
+
3539
+ `publish` receives event classes with different guarantees:
3540
+
3541
+ - `assistant-delta` is transient and ordered by
3542
+ `(runId, runtimeEpoch, sequence)`;
3543
+ - `assistant-checkpoint` follows a successful checkpoint CAS;
3544
+ - `run-state` follows durable queue/acquire/interrupt transitions;
3545
+ - `tool-status` is transient lifecycle presentation with JSON-safe input on
3546
+ start and output on completion; internal tool failures remain generic;
3547
+ - `terminal` follows the winning terminal CAS.
3548
+
3549
+ A named custom stop condition terminalizes with `policy_stop`; its `policyName`
3550
+ is persisted on the run and included in the terminal event/result. `max-steps`
3551
+ is the reserved built-in policy name. `loop.prepareStep` is the controlled AI
3552
+ SDK step boundary for changing active tools, model, instructions or messages.
3553
+ It cannot replace the managed tool set or bypass its lifecycle fence.
3554
+
3555
+ `loop.idleTimeoutMs` is inactivity, not total duration: the deadline resets on
3556
+ every model stream event. A stalled call aborts with durable reason `timeout`;
3557
+ user interruption and shutdown remain distinct.
3558
+
3559
+ Reconnect loads the durable snapshot. Missing transient deltas do not mean the
3560
+ canonical result was lost. Exactly-once external delivery requires the
3561
+ application's transactional outbox or stable-ID deduplication.
3562
+
3563
+ ## Managed tools
3564
+
3565
+ Always compose `toolFenceLifecycle` into `mountAgent`. It checks ownership
3566
+ before a managed side effect and again before accepting its result. Fence loss
3567
+ uses an internal control signal: it stops the old loop and is not sent to the
3568
+ model as a tool error.
3569
+
3570
+ The framework cannot undo an already-started non-cooperative external effect.
3571
+ Pass the stable call/run idempotency identity into business mutations when the
3572
+ effect must be replay-safe.
3573
+
3574
+ ## History, provider metadata and files
3575
+
3576
+ `projectAgentHistory` converts canonical engine records into provider-valid AI
3577
+ SDK messages and pairs tool calls/results. Provider-required metadata is kept
3578
+ in a versioned opaque envelope and omitted from product delivery by default.
3579
+
3580
+ `ComposedAgentPrompt.instructions` accepts the AI SDK `Instructions` contract.
3581
+ Use `adaptInstructions` when a provider needs metadata on the system message:
3582
+
3583
+ ```ts
3584
+ prompt({
3585
+ context,
3586
+ signal,
3587
+ adaptInstructions: (content) => ({
3588
+ role: 'system',
3589
+ content,
3590
+ providerOptions: { openrouter: { cacheControl: { type: 'ephemeral' } } },
3591
+ }),
3592
+ })
3593
+ ```
3594
+
3595
+ Attachments and generated files remain application-owned. The runtime only
3596
+ stores a reference returned by `persistGeneratedFile`; it never silently puts
3597
+ base64 blobs into the neutral history store.
3598
+
3599
+ To send a stored attachment back to a multimodal model, configure
3600
+ `history.resolveFile`. It maps the neutral file reference to AI SDK file data
3601
+ (URL, bytes, provider reference or text). Without a resolver the explicit
3602
+ `unresolvedFile` policy is `text` by default; choose `omit` or `error` when a
3603
+ placeholder would be incorrect.
3604
+
3605
+ ## Compaction
3606
+
3607
+ `structuredCompaction` summarizes complete provider-valid turn groups outside
3608
+ the store lock, then calls `replaceCompactedRange` against the exact snapshot
3609
+ version. A concurrent input produces `conflict`; the stale summary is not
3610
+ applied. Summary records use the dedicated `summary` role and project to a
3611
+ provider system message. The consumer supplies the structured summary schema
3612
+ and prompt. Pass `previousSummary` for a direct call, or configure
3613
+ `readPreviousSummary` for runtime-managed compaction, to merge and atomically
3614
+ replace a leading summary on the next compaction.
3615
+
3616
+ ## Observability
3617
+
3618
+ `createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
3619
+ reuses the same bounded sink lifecycle as request/tool observability without
3620
+ sending new event kinds to existing request sinks. Product events omit provider
3621
+ causes; the operator terminal event may include `internalCause`, so its sink
3622
+ must use internal retention and redaction policy.
3623
+
3624
+ Usage values carry `provider-reported`, `computed`, `estimated` or
3625
+ `unavailable` provenance. Cost additionally carries an ISO currency code;
3626
+ OpenRouter-reported cost is normalized as USD. Missing values remain absent,
3627
+ never zero-filled.
3628
+
3629
+ In-memory sink delivery is at-most-once per execution. Stable event IDs allow
3630
+ consumer dedupe; cross-crash exactly-once requires a durable outbox.
3631
+
3632
+
3343
3633
  ==============================================================================
3344
3634
  # Guide: CLI (docs/guide/cli.md)
3345
3635
  ==============================================================================
@@ -4699,22 +4989,36 @@ error round-trips without adding correlation data to the response body.
4699
4989
  ### Stitch codes vs your codes
4700
4990
 
4701
4991
  A `code` is a free string — your app codes (`BOT_NOT_FOUND`, …) are yours and the
4702
- core never models them (ADR 0002). But stitchkit itself emits a fixed set:
4992
+ core never models them (ADR 0002). But stitchkit itself emits a set of its own:
4703
4993
  `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`,
4704
- `CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `INTERNAL_SERVER_ERROR`. They are
4994
+ `CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
4995
+ `FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
4996
+ `FILE_INSPECTION_REJECTED`, `FILE_TOO_LARGE`, `FILE_EXISTS`,
4997
+ `REALTIME_CONTRACT_VIOLATION`, `INTERNAL_SERVER_ERROR` — a set that grows in
4998
+ ordinary releases, each addition named in the changelog. They are
4705
4999
  published as **`STITCH_ERROR_STATUS`** (the `code → status` map) and
4706
5000
  **`StitchErrorCode`** (its `keyof`), with **`isStitchErrorCode()`** (→ ADR 0026).
4707
5001
 
4708
5002
  If you translate stitch's framework errors into your own wire codes in an
4709
- `onError` hook, key the map by `StitchErrorCode` so it stays exhaustive a code
4710
- stitch adds or renames becomes a compile error, not a silent `500`:
5003
+ `onError` hook, you choose how the set's growth reaches you. Keying the map by
5004
+ `StitchErrorCode` makes it exhaustive, so a code stitch adds or renames becomes
5005
+ a compile error rather than an unfamiliar code on your wire — worth it when the
5006
+ envelope is a published contract, at the cost of an edit on those releases:
4711
5007
 
4712
5008
  ```ts
5009
+ // Exhaustive on purpose: the annotation is what turns a new stitch code into a
5010
+ // compile error here. Drop it (or use `satisfies` on a partial map) to let an
5011
+ // unmapped code travel as itself instead.
4713
5012
  const STITCH_TO_APP: Record<StitchErrorCode, AppCode> = {
4714
5013
  NOT_FOUND: 'NOT_FOUND', METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
4715
5014
  BAD_REQUEST: 'VALIDATION_ERROR', VALIDATION_ERROR: 'VALIDATION_ERROR',
4716
5015
  UNAUTHORIZED: 'UNAUTHORIZED', FORBIDDEN: 'FORBIDDEN', CONFLICT: 'CONFLICT',
4717
5016
  RATE_LIMITED: 'RATE_LIMITED', INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
5017
+ REALTIME_CONTRACT_VIOLATION: 'INTERNAL_SERVER_ERROR',
5018
+ FILE_INVALID_PATH: 'VALIDATION_ERROR', FILE_OUTSIDE_ROOT: 'VALIDATION_ERROR',
5019
+ FILE_NOT_FOUND: 'NOT_FOUND', FILE_NOT_REGULAR: 'VALIDATION_ERROR',
5020
+ FILE_INSPECTION_REJECTED: 'VALIDATION_ERROR', FILE_TOO_LARGE: 'VALIDATION_ERROR',
5021
+ FILE_EXISTS: 'CONFLICT',
4718
5022
  }
4719
5023
  onError: (ctx, err) => {
4720
5024
  if (AppError.is(err) && isStitchErrorCode(err.code)) {
@@ -4790,20 +5094,20 @@ codes and schemas remain application-owned; Stitchkit stays domain-free.
4790
5094
 
4791
5095
  ## `createErrorHook`
4792
5096
 
4793
- `createErrorHook` is the code-map above, packaged — you supply the exhaustive
4794
- `codeMap` and the envelope shape, it does the normalisation (including the
5097
+ `createErrorHook` is the code-map above, packaged — you supply a `codeMap` and
5098
+ the envelope shape, it does the normalisation (including the
4795
5099
  never-leak-an-internal-message rule for a raw throw):
4796
5100
 
4797
5101
  ```ts
4798
5102
  const onError = createErrorHook({
5103
+ // Map the codes you have an opinion about; the rest travel as themselves.
4799
5104
  codeMap: {
4800
5105
  BAD_REQUEST: 'bad_request', VALIDATION_ERROR: 'bad_request',
4801
5106
  UNAUTHORIZED: 'unauthenticated', FORBIDDEN: 'forbidden',
4802
5107
  NOT_FOUND: 'not_found', METHOD_NOT_ALLOWED: 'not_found',
4803
5108
  CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
4804
5109
  INTERNAL_SERVER_ERROR: 'internal',
4805
- REALTIME_CONTRACT_VIOLATION: 'internal',
4806
- } satisfies Record<StitchErrorCode, string>,
5110
+ },
4807
5111
  // `ctx` is the request's RuntimeContext — read `ctx.traceId` for a
4808
5112
  // correlation id in the envelope. Declaring it is optional.
4809
5113
  render: (info, ctx) => ({
@@ -4816,8 +5120,24 @@ const onError = createErrorHook({
4816
5120
  createServer({ services, hooks: { onError } })
4817
5121
  ```
4818
5122
 
4819
- Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
4820
- `satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
5123
+ `codeMap` is partial: map the codes you have an opinion about. A stitchkit code
5124
+ you did not list travels as itself — the same thing a code you threw yourself
5125
+ always did. That is what keeps a code added by a future release from being a
5126
+ compile break for every project that translates codes at all.
5127
+
5128
+ One `satisfies` is the **opt-in** to the stricter deal:
5129
+
5130
+ ```ts
5131
+ codeMap: { /* … every code … */ } satisfies Record<StitchErrorCode, string>,
5132
+ ```
5133
+
5134
+ That makes the map exhaustive on your side, so a release that adds a code stops
5135
+ your build until you decide what the new code is called on your wire. Take it
5136
+ when your envelope is a published contract and a code surfacing in stitchkit's
5137
+ spelling would violate it; leave it off when passing one through is fine.
5138
+ Neither choice is silent — the changelog names every added code. For a single
5139
+ catch-all instead of either, decide it in `render`, where `info.code` is the
5140
+ resolved value.
4821
5141
 
4822
5142
  Both `onError` and `render` may be asynchronous and receive the matched endpoint
4823
5143
  as their final argument. The observer is awaited before rendering, so it can
@@ -7582,6 +7902,38 @@ Also re-exports the error helpers from `stitchkit/contract`.
7582
7902
 
7583
7903
  ---
7584
7904
 
7905
+ ## `stitchkit/agent-runtime`
7906
+
7907
+ Server-only optional application runtime. See the
7908
+ [agent runtime guide](../guide/agent-runtime.md).
7909
+
7910
+ | Export | Kind | Summary |
7911
+ |--------|------|---------|
7912
+ | `createAgentRuntime` | function | compose durable acceptance, stream loop, checkpoints, coordination, managed tools and terminal publication |
7913
+ | `defineAgentProtocol` | function | declare and validate context, input metadata and canonical message parts |
7914
+ | `AgentMessageSchema` / `AgentRunSchema` / `AgentSnapshotSchema` | schema | versioned canonical engine records |
7915
+ | `AgentRuntimeStore` | _type_ | aggregate CAS transaction boundary for message, run and compaction mutations |
7916
+ | `RecoverAgentRunSchema` | schema | explicit abandon/requeue recovery decision; acquired runs require replay-safe evidence |
7917
+ | `createMemoryAgentRuntimeStore` | function | process-local reference adapter, not production durability |
7918
+ | `projectAgentHistory` | function | asynchronously project canonical records and resolved multimodal files into provider-valid AI SDK messages |
7919
+ | `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
7920
+ | `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware context budget |
7921
+ | `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
7922
+ | `createAgentSessionCoordinator` | function | strict process-local queue/interrupt lifecycle |
7923
+ | `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
7924
+ | `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
7925
+ | `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver and explicit unresolved-file behavior |
7926
+ | `createAgentToolFenceLifecycle` | function | pre-effect and post-effect run ownership fence for `mountAgent` |
7927
+ | `AgentRuntimeEventSchema` | schema | transient delta, durable checkpoint/run-state/tool and terminal event union |
7928
+ | `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
7929
+
7930
+ ## `stitchkit/agent-runtime/openrouter`
7931
+
7932
+ | Export | Kind | Summary |
7933
+ |--------|------|---------|
7934
+ | `openRouterProvider` | function | isolated `@openrouter/ai-sdk-provider` language-model factory |
7935
+ | `OpenRouterProviderSettings` | _type_ | official provider settings accepted by the factory |
7936
+
7585
7937
  ## `stitchkit/observability`
7586
7938
 
7587
7939
  Server-only. The audit layer one level above the raw hooks — W3C trace context,
@@ -7755,6 +8107,9 @@ payload.
7755
8107
  | `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
7756
8108
  | `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
7757
8109
  | `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
8110
+ | `ToolExecutionControlReason` | _type_ | internal managed-execution stop reason: stale run or requested interruption |
8111
+ | `ToolExecutionControlError` | class | control-flow error used to unwind a superseded managed tool call without presenting it as a model-facing tool failure |
8112
+ | `isToolExecutionControlError` | function | narrow an unknown thrown value to the managed execution control error |
7758
8113
  | `ToolOperation` | _type_ | executable path-free operation shape shared by contract and framework-native runners |
7759
8114
  | `ToolCallHooks` | _type_ | object-shaped `beforeToolCall` / `afterToolCall` / `onToolError` observability hooks; the raw thrown value reaches the last two as `error` ([guide](../guide/observability.md#the-cause-behind-a-failed-tool-call)) |
7760
8115
  | `BeforeToolCallOptions` | _type_ | `{ toolName, args, context, endpoint }` passed before execution |
package/llms.txt CHANGED
@@ -10,6 +10,7 @@ Build with stitchkit: define a contract once, then `implement` it and serve it (
10
10
  - [HTTP server](https://github.com/max-listov/stitchkit/blob/master/docs/guide/server.md): createServer/createHandler, implement, lifecycle hooks, raw routes + raw-response endpoints + helpers, scopePrefixes, serveFile, primitives
11
11
  - [Typed client](https://github.com/max-listov/stitchkit/blob/master/docs/guide/client.md): createClient/createHttpClient, the typed call surface, scoped clients, SSE
12
12
  - [MCP & agents](https://github.com/max-listov/stitchkit/blob/master/docs/guide/mcp-and-agents.md): contracts as MCP tools (createMcpHandler) and AI-agent tools (mountAgent); tool lifecycle, extend, identity
13
+ - [Agent application runtime](https://github.com/max-listov/stitchkit/blob/master/docs/guide/agent-runtime.md): optional durable history, prompt/model composition, stream loop, coordination, fencing and events
13
14
  - [CLI](https://github.com/max-listov/stitchkit/blob/master/docs/guide/cli.md): contracts as a command-line program
14
15
  - [Realtime](https://github.com/max-listov/stitchkit/blob/master/docs/guide/realtime.md): Socket.IO server/client wrappers, handshake auth, the cache bridge, a raw WebSocket lane
15
16
  - [Auth & errors](https://github.com/max-listov/stitchkit/blob/master/docs/guide/auth-and-errors.md): scopes, createAuthHook, JWT/cookies, the AppError model, the stitch error-code registry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.56.1",
3
+ "version": "0.56.2",
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",
@@ -76,6 +76,14 @@
76
76
  "types": "./dist/observability/index.d.ts",
77
77
  "import": "./dist/observability/index.js"
78
78
  },
79
+ "./agent-runtime": {
80
+ "types": "./dist/agent-runtime.d.ts",
81
+ "import": "./dist/agent-runtime.js"
82
+ },
83
+ "./agent-runtime/openrouter": {
84
+ "types": "./dist/agent-runtime-openrouter.d.ts",
85
+ "import": "./dist/agent-runtime-openrouter.js"
86
+ },
79
87
  "./testing": {
80
88
  "types": "./dist/testing.d.ts",
81
89
  "import": "./dist/testing.js"
@@ -95,7 +103,7 @@
95
103
  "scripts": {
96
104
  "check": "bun x tsc --noEmit",
97
105
  "build:browser": "bun build src/index.ts src/react.ts src/contract/index.ts --outdir dist --target node --packages external --splitting --root src",
98
- "build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts --outdir dist --target node --packages external --splitting --root src",
106
+ "build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-openrouter.ts --outdir dist --target node --packages external --splitting --root src",
99
107
  "build:js": "bun run build:browser && bun run build:server",
100
108
  "build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly",
101
109
  "build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs",
@@ -110,6 +118,7 @@
110
118
  "peerDependencies": {
111
119
  "@modelcontextprotocol/ext-apps": "^1.7.2",
112
120
  "@modelcontextprotocol/server": "^2.0.0",
121
+ "@openrouter/ai-sdk-provider": "^3.0.0",
113
122
  "@types/bun": "^1.3.14",
114
123
  "@socket.io/bun-engine": "^0.1.1",
115
124
  "@socket.io/component-emitter": "^3.1.2",
@@ -132,6 +141,9 @@
132
141
  "@modelcontextprotocol/server": {
133
142
  "optional": true
134
143
  },
144
+ "@openrouter/ai-sdk-provider": {
145
+ "optional": true
146
+ },
135
147
  "@socket.io/bun-engine": {
136
148
  "optional": true
137
149
  },
@@ -164,9 +176,10 @@
164
176
  "ky": "^2.0.2"
165
177
  },
166
178
  "devDependencies": {
167
- "@modelcontextprotocol/ext-apps": "^1.7.5",
168
179
  "@modelcontextprotocol/client": "^2.0.0",
180
+ "@modelcontextprotocol/ext-apps": "^1.7.5",
169
181
  "@modelcontextprotocol/server": "^2.0.0",
182
+ "@openrouter/ai-sdk-provider": "^3.0.0",
170
183
  "@socket.io/bun-engine": "^0.1.1",
171
184
  "@socket.io/component-emitter": "^3.1.2",
172
185
  "@tanstack/react-query": "5.101.4",