stitchkit 0.56.1 → 0.56.3
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 +12 -3
- package/dist/agent-runtime/compaction.d.ts +33 -0
- package/dist/agent-runtime/compaction.d.ts.map +1 -0
- package/dist/agent-runtime/coordinator.d.ts +24 -0
- package/dist/agent-runtime/coordinator.d.ts.map +1 -0
- package/dist/agent-runtime/events.d.ts +454 -0
- package/dist/agent-runtime/events.d.ts.map +1 -0
- package/dist/agent-runtime/history.d.ts +11 -0
- package/dist/agent-runtime/history.d.ts.map +1 -0
- package/dist/agent-runtime/managed-tools.d.ts +18 -0
- package/dist/agent-runtime/managed-tools.d.ts.map +1 -0
- package/dist/agent-runtime/models.d.ts +51 -0
- package/dist/agent-runtime/models.d.ts.map +1 -0
- package/dist/agent-runtime/observability.d.ts +134 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -0
- package/dist/agent-runtime/prompt.d.ts +50 -0
- package/dist/agent-runtime/prompt.d.ts.map +1 -0
- package/dist/agent-runtime/protocol.d.ts +16 -0
- package/dist/agent-runtime/protocol.d.ts.map +1 -0
- package/dist/agent-runtime/runtime.d.ts +132 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -0
- package/dist/agent-runtime/schemas.d.ts +470 -0
- package/dist/agent-runtime/schemas.d.ts.map +1 -0
- package/dist/agent-runtime/store.d.ts +905 -0
- package/dist/agent-runtime/store.d.ts.map +1 -0
- package/dist/agent-runtime/testing.d.ts +17 -0
- package/dist/agent-runtime/testing.d.ts.map +1 -0
- package/dist/agent-runtime-openrouter.d.ts +6 -0
- package/dist/agent-runtime-openrouter.d.ts.map +1 -0
- package/dist/agent-runtime-openrouter.js +46 -0
- package/dist/agent-runtime.d.ts +13 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +1955 -0
- package/dist/cli.js +8 -5
- package/dist/index-0nc0cddp.js +178 -0
- package/dist/{index-6y759j86.js → index-1tkyng3g.js} +9 -57
- package/dist/{index-sm2tjx06.js → index-41wm56v0.js} +2 -2
- package/dist/index-6djpbnda.js +56 -0
- package/dist/{index-j3dem06f.js → index-7c0gkyvj.js} +5 -3
- package/dist/{index-75njxz7p.js → index-9zn9fb4e.js} +8 -6
- package/dist/{index-xy8fmh6w.js → index-f6pymtqe.js} +3 -3
- package/dist/{index-jcc611vh.js → index-gnvzyw0k.js} +2 -2
- package/dist/{index-qh4xevf5.js → index-mr617n62.js} +4 -2
- package/dist/{index-5r13htq1.js → index-ps5pxp3h.js} +1 -1
- package/dist/index-sa2mbwa7.js +336 -0
- package/dist/index-smpbdg6k.js +27 -0
- package/dist/{index-7eq9va6e.js → index-y2rb7dwx.js} +1 -27
- package/dist/{index-escqg10p.js → index-zk5zn2nh.js} +32 -334
- package/dist/internal/observability-sink.d.ts +26 -0
- package/dist/internal/observability-sink.d.ts.map +1 -0
- package/dist/node.js +6 -5
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/index.js +15 -178
- package/dist/remote.js +4 -3
- package/dist/server/error-hook.d.ts +9 -7
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.js +9 -7
- package/dist/testing.js +5 -3
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +7 -0
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools.d.ts +2 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +23 -12
- package/llms-full.txt +382 -10
- package/llms.txt +1 -0
- 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,303 @@ 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
|
+
recordIds: {
|
|
3472
|
+
inputMessageId: 'user-message-id',
|
|
3473
|
+
runId: 'run-id',
|
|
3474
|
+
assistantMessageId: 'assistant-message-id',
|
|
3475
|
+
},
|
|
3476
|
+
})
|
|
3477
|
+
|
|
3478
|
+
const admission = await ticket.admission
|
|
3479
|
+
await ticket.accepted
|
|
3480
|
+
const terminal = await ticket.result
|
|
3481
|
+
```
|
|
3482
|
+
|
|
3483
|
+
`recordIds` is optional. Supply stable application record IDs when an accepted-response transport must
|
|
3484
|
+
return durable placeholders before the run finishes. `ticket.admission` resolves after the store
|
|
3485
|
+
acceptance CAS and reports the actually assigned `runId`, `assistantMessageId` and snapshot version.
|
|
3486
|
+
Those assigned IDs can differ from the proposal when an input coalesces into an existing queued
|
|
3487
|
+
successor. Reuse the same `inputMessageId` for retries carrying the same idempotency key; input
|
|
3488
|
+
identity is caller-stable, while the receipt reports the run/assistant identities that assignment
|
|
3489
|
+
may change. Await `admission` first on the immediate accepted-response path. `ticket.accepted`
|
|
3490
|
+
remains the signal-only compatibility surface and additionally waits for admission publication.
|
|
3491
|
+
|
|
3492
|
+
The in-memory store is a reference adapter and has process-local durability
|
|
3493
|
+
only. Production applications implement `AgentRuntimeStore` with their own
|
|
3494
|
+
database transaction and, when needed, distributed lease/fencing token.
|
|
3495
|
+
|
|
3496
|
+
## Durable order
|
|
3497
|
+
|
|
3498
|
+
`acceptInputAndAssignRun` is one atomic operation. It is followed by ownership
|
|
3499
|
+
acquisition, revision-checked assistant checkpoints and one terminal CAS. The
|
|
3500
|
+
process-local coordinator releases its lane only after terminal commit.
|
|
3501
|
+
|
|
3502
|
+
```text
|
|
3503
|
+
input + queued run → running → execution settled → terminal CAS → successor
|
|
3504
|
+
```
|
|
3505
|
+
|
|
3506
|
+
With `runs.coalescePending: true`, an active lane has at most one queued
|
|
3507
|
+
successor. Every later accepted input is atomically appended to that successor;
|
|
3508
|
+
its `AgentRun.inputMessageIds` records the whole batch and every input ticket
|
|
3509
|
+
resolves to the same terminal run. Coalescing never mutates the active run.
|
|
3510
|
+
|
|
3511
|
+
`AbortSignal.aborted` is only a cooperation request. A successor does not begin
|
|
3512
|
+
while the predecessor still owns managed callbacks. A hung predecessor blocks
|
|
3513
|
+
the lane in the first version.
|
|
3514
|
+
|
|
3515
|
+
Use `await runtime.interrupt({ conversationId, runId })` when the interruption
|
|
3516
|
+
must be durable: it first commits `interrupt_requested`, then aborts the local
|
|
3517
|
+
coordinator signal. `runtime.stop(key)` is the process-local signal-only escape
|
|
3518
|
+
hatch.
|
|
3519
|
+
|
|
3520
|
+
## Store operations
|
|
3521
|
+
|
|
3522
|
+
An adapter implements the aggregate `AgentRuntimeStore`, not separate message
|
|
3523
|
+
and run CRUD stores:
|
|
3524
|
+
|
|
3525
|
+
- `acceptInputAndAssignRun`
|
|
3526
|
+
- `acquireRun`
|
|
3527
|
+
- `checkpointRunAssistant`
|
|
3528
|
+
- `requestRunInterrupt`
|
|
3529
|
+
- `recoverRun`
|
|
3530
|
+
- `commitRunTerminal`
|
|
3531
|
+
- `replaceCompactedRange`
|
|
3532
|
+
- `loadSnapshot` and `scanRecoverable`
|
|
3533
|
+
|
|
3534
|
+
Every mutation carries an expected run revision or snapshot version. Input
|
|
3535
|
+
assignment additionally carries an idempotency identity. A conflict is a
|
|
3536
|
+
control outcome; stale data is never silently overwritten.
|
|
3537
|
+
|
|
3538
|
+
On startup, `scanRecoverable` returns queued/acquired records. `recoverRun`
|
|
3539
|
+
may abandon them, or requeue an already acquired run only with explicit
|
|
3540
|
+
`replaySafe: true` evidence. The framework never guesses that an external
|
|
3541
|
+
side effect is replayable. After an application reconstructs its typed context,
|
|
3542
|
+
`runtime.resume({ conversationId, runId, context })` admits that queued record
|
|
3543
|
+
through the same acquisition CAS and coordinator lane; it never creates a
|
|
3544
|
+
second input message.
|
|
3545
|
+
|
|
3546
|
+
Canonical records currently write `schemaVersion: 1`. A durable adapter owns
|
|
3547
|
+
read-time migration of older rows: migrate to the current shape at its storage
|
|
3548
|
+
boundary, validate with the exported schema, and write only the current
|
|
3549
|
+
version. Core deliberately does not guess an application's database migration
|
|
3550
|
+
or silently accept an unknown future version.
|
|
3551
|
+
|
|
3552
|
+
## Events and reconnect
|
|
3553
|
+
|
|
3554
|
+
`publish` receives event classes with different guarantees:
|
|
3555
|
+
|
|
3556
|
+
- `assistant-delta` is transient and ordered by
|
|
3557
|
+
`(runId, runtimeEpoch, sequence)`;
|
|
3558
|
+
- `assistant-checkpoint` follows a successful checkpoint CAS;
|
|
3559
|
+
- `run-state` follows durable queue/acquire/interrupt transitions;
|
|
3560
|
+
- `tool-status` is transient lifecycle presentation with JSON-safe input on
|
|
3561
|
+
start and output on completion; internal tool failures remain generic;
|
|
3562
|
+
- `terminal` follows the winning terminal CAS.
|
|
3563
|
+
|
|
3564
|
+
A named custom stop condition terminalizes with `policy_stop`; its `policyName`
|
|
3565
|
+
is persisted on the run and included in the terminal event/result. `max-steps`
|
|
3566
|
+
is the reserved built-in policy name. `loop.prepareStep` is the controlled AI
|
|
3567
|
+
SDK step boundary for changing active tools, model, instructions or messages.
|
|
3568
|
+
It cannot replace the managed tool set or bypass its lifecycle fence.
|
|
3569
|
+
|
|
3570
|
+
`loop.idleTimeoutMs` is inactivity, not total duration: the deadline resets on
|
|
3571
|
+
every model stream event. A stalled call aborts with durable reason `timeout`;
|
|
3572
|
+
user interruption and shutdown remain distinct.
|
|
3573
|
+
|
|
3574
|
+
Reconnect loads the durable snapshot. Missing transient deltas do not mean the
|
|
3575
|
+
canonical result was lost. Exactly-once external delivery requires the
|
|
3576
|
+
application's transactional outbox or stable-ID deduplication.
|
|
3577
|
+
|
|
3578
|
+
## Managed tools
|
|
3579
|
+
|
|
3580
|
+
Always compose `toolFenceLifecycle` into `mountAgent`. It checks ownership
|
|
3581
|
+
before a managed side effect and again before accepting its result. Fence loss
|
|
3582
|
+
uses an internal control signal: it stops the old loop and is not sent to the
|
|
3583
|
+
model as a tool error.
|
|
3584
|
+
|
|
3585
|
+
The framework cannot undo an already-started non-cooperative external effect.
|
|
3586
|
+
Pass the stable call/run idempotency identity into business mutations when the
|
|
3587
|
+
effect must be replay-safe.
|
|
3588
|
+
|
|
3589
|
+
## History, provider metadata and files
|
|
3590
|
+
|
|
3591
|
+
`projectAgentHistory` converts canonical engine records into provider-valid AI
|
|
3592
|
+
SDK messages and pairs tool calls/results. Provider-required metadata is kept
|
|
3593
|
+
in a versioned opaque envelope and omitted from product delivery by default.
|
|
3594
|
+
|
|
3595
|
+
`ComposedAgentPrompt.instructions` accepts the AI SDK `Instructions` contract.
|
|
3596
|
+
Use `adaptInstructions` when a provider needs metadata on the system message:
|
|
3597
|
+
|
|
3598
|
+
```ts
|
|
3599
|
+
prompt({
|
|
3600
|
+
context,
|
|
3601
|
+
signal,
|
|
3602
|
+
adaptInstructions: (content) => ({
|
|
3603
|
+
role: 'system',
|
|
3604
|
+
content,
|
|
3605
|
+
providerOptions: { openrouter: { cacheControl: { type: 'ephemeral' } } },
|
|
3606
|
+
}),
|
|
3607
|
+
})
|
|
3608
|
+
```
|
|
3609
|
+
|
|
3610
|
+
Attachments and generated files remain application-owned. The runtime only
|
|
3611
|
+
stores a reference returned by `persistGeneratedFile`; it never silently puts
|
|
3612
|
+
base64 blobs into the neutral history store.
|
|
3613
|
+
|
|
3614
|
+
To send a stored attachment back to a multimodal model, configure
|
|
3615
|
+
`history.resolveFile`. It maps the neutral file reference to AI SDK file data
|
|
3616
|
+
(URL, bytes, provider reference or text). Without a resolver the explicit
|
|
3617
|
+
`unresolvedFile` policy is `text` by default; choose `omit` or `error` when a
|
|
3618
|
+
placeholder would be incorrect.
|
|
3619
|
+
|
|
3620
|
+
## Compaction
|
|
3621
|
+
|
|
3622
|
+
`structuredCompaction` summarizes complete provider-valid turn groups outside
|
|
3623
|
+
the store lock, then calls `replaceCompactedRange` against the exact snapshot
|
|
3624
|
+
version. A concurrent input produces `conflict`; the stale summary is not
|
|
3625
|
+
applied. Summary records use the dedicated `summary` role and project to a
|
|
3626
|
+
provider system message. The consumer supplies the structured summary schema
|
|
3627
|
+
and prompt. Pass `previousSummary` for a direct call, or configure
|
|
3628
|
+
`readPreviousSummary` for runtime-managed compaction, to merge and atomically
|
|
3629
|
+
replace a leading summary on the next compaction.
|
|
3630
|
+
|
|
3631
|
+
## Observability
|
|
3632
|
+
|
|
3633
|
+
`createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
|
|
3634
|
+
reuses the same bounded sink lifecycle as request/tool observability without
|
|
3635
|
+
sending new event kinds to existing request sinks. Product events omit provider
|
|
3636
|
+
causes; the operator terminal event may include `internalCause`, so its sink
|
|
3637
|
+
must use internal retention and redaction policy.
|
|
3638
|
+
|
|
3639
|
+
Usage values carry `provider-reported`, `computed`, `estimated` or
|
|
3640
|
+
`unavailable` provenance. Cost additionally carries an ISO currency code;
|
|
3641
|
+
OpenRouter-reported cost is normalized as USD. Missing values remain absent,
|
|
3642
|
+
never zero-filled.
|
|
3643
|
+
|
|
3644
|
+
In-memory sink delivery is at-most-once per execution. Stable event IDs allow
|
|
3645
|
+
consumer dedupe; cross-crash exactly-once requires a durable outbox.
|
|
3646
|
+
|
|
3647
|
+
|
|
3343
3648
|
==============================================================================
|
|
3344
3649
|
# Guide: CLI (docs/guide/cli.md)
|
|
3345
3650
|
==============================================================================
|
|
@@ -4699,22 +5004,36 @@ error round-trips without adding correlation data to the response body.
|
|
|
4699
5004
|
### Stitch codes vs your codes
|
|
4700
5005
|
|
|
4701
5006
|
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
|
|
5007
|
+
core never models them (ADR 0002). But stitchkit itself emits a set of its own:
|
|
4703
5008
|
`BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`,
|
|
4704
|
-
`CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `
|
|
5009
|
+
`CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
|
|
5010
|
+
`FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
|
|
5011
|
+
`FILE_INSPECTION_REJECTED`, `FILE_TOO_LARGE`, `FILE_EXISTS`,
|
|
5012
|
+
`REALTIME_CONTRACT_VIOLATION`, `INTERNAL_SERVER_ERROR` — a set that grows in
|
|
5013
|
+
ordinary releases, each addition named in the changelog. They are
|
|
4705
5014
|
published as **`STITCH_ERROR_STATUS`** (the `code → status` map) and
|
|
4706
5015
|
**`StitchErrorCode`** (its `keyof`), with **`isStitchErrorCode()`** (→ ADR 0026).
|
|
4707
5016
|
|
|
4708
5017
|
If you translate stitch's framework errors into your own wire codes in an
|
|
4709
|
-
`onError` hook,
|
|
4710
|
-
|
|
5018
|
+
`onError` hook, you choose how the set's growth reaches you. Keying the map by
|
|
5019
|
+
`StitchErrorCode` makes it exhaustive, so a code stitch adds or renames becomes
|
|
5020
|
+
a compile error rather than an unfamiliar code on your wire — worth it when the
|
|
5021
|
+
envelope is a published contract, at the cost of an edit on those releases:
|
|
4711
5022
|
|
|
4712
5023
|
```ts
|
|
5024
|
+
// Exhaustive on purpose: the annotation is what turns a new stitch code into a
|
|
5025
|
+
// compile error here. Drop it (or use `satisfies` on a partial map) to let an
|
|
5026
|
+
// unmapped code travel as itself instead.
|
|
4713
5027
|
const STITCH_TO_APP: Record<StitchErrorCode, AppCode> = {
|
|
4714
5028
|
NOT_FOUND: 'NOT_FOUND', METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
|
|
4715
5029
|
BAD_REQUEST: 'VALIDATION_ERROR', VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
4716
5030
|
UNAUTHORIZED: 'UNAUTHORIZED', FORBIDDEN: 'FORBIDDEN', CONFLICT: 'CONFLICT',
|
|
4717
5031
|
RATE_LIMITED: 'RATE_LIMITED', INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
|
|
5032
|
+
REALTIME_CONTRACT_VIOLATION: 'INTERNAL_SERVER_ERROR',
|
|
5033
|
+
FILE_INVALID_PATH: 'VALIDATION_ERROR', FILE_OUTSIDE_ROOT: 'VALIDATION_ERROR',
|
|
5034
|
+
FILE_NOT_FOUND: 'NOT_FOUND', FILE_NOT_REGULAR: 'VALIDATION_ERROR',
|
|
5035
|
+
FILE_INSPECTION_REJECTED: 'VALIDATION_ERROR', FILE_TOO_LARGE: 'VALIDATION_ERROR',
|
|
5036
|
+
FILE_EXISTS: 'CONFLICT',
|
|
4718
5037
|
}
|
|
4719
5038
|
onError: (ctx, err) => {
|
|
4720
5039
|
if (AppError.is(err) && isStitchErrorCode(err.code)) {
|
|
@@ -4790,20 +5109,20 @@ codes and schemas remain application-owned; Stitchkit stays domain-free.
|
|
|
4790
5109
|
|
|
4791
5110
|
## `createErrorHook`
|
|
4792
5111
|
|
|
4793
|
-
`createErrorHook` is the code-map above, packaged — you supply
|
|
4794
|
-
|
|
5112
|
+
`createErrorHook` is the code-map above, packaged — you supply a `codeMap` and
|
|
5113
|
+
the envelope shape, it does the normalisation (including the
|
|
4795
5114
|
never-leak-an-internal-message rule for a raw throw):
|
|
4796
5115
|
|
|
4797
5116
|
```ts
|
|
4798
5117
|
const onError = createErrorHook({
|
|
5118
|
+
// Map the codes you have an opinion about; the rest travel as themselves.
|
|
4799
5119
|
codeMap: {
|
|
4800
5120
|
BAD_REQUEST: 'bad_request', VALIDATION_ERROR: 'bad_request',
|
|
4801
5121
|
UNAUTHORIZED: 'unauthenticated', FORBIDDEN: 'forbidden',
|
|
4802
5122
|
NOT_FOUND: 'not_found', METHOD_NOT_ALLOWED: 'not_found',
|
|
4803
5123
|
CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
|
|
4804
5124
|
INTERNAL_SERVER_ERROR: 'internal',
|
|
4805
|
-
|
|
4806
|
-
} satisfies Record<StitchErrorCode, string>,
|
|
5125
|
+
},
|
|
4807
5126
|
// `ctx` is the request's RuntimeContext — read `ctx.traceId` for a
|
|
4808
5127
|
// correlation id in the envelope. Declaring it is optional.
|
|
4809
5128
|
render: (info, ctx) => ({
|
|
@@ -4816,8 +5135,24 @@ const onError = createErrorHook({
|
|
|
4816
5135
|
createServer({ services, hooks: { onError } })
|
|
4817
5136
|
```
|
|
4818
5137
|
|
|
4819
|
-
|
|
4820
|
-
|
|
5138
|
+
`codeMap` is partial: map the codes you have an opinion about. A stitchkit code
|
|
5139
|
+
you did not list travels as itself — the same thing a code you threw yourself
|
|
5140
|
+
always did. That is what keeps a code added by a future release from being a
|
|
5141
|
+
compile break for every project that translates codes at all.
|
|
5142
|
+
|
|
5143
|
+
One `satisfies` is the **opt-in** to the stricter deal:
|
|
5144
|
+
|
|
5145
|
+
```ts
|
|
5146
|
+
codeMap: { /* … every code … */ } satisfies Record<StitchErrorCode, string>,
|
|
5147
|
+
```
|
|
5148
|
+
|
|
5149
|
+
That makes the map exhaustive on your side, so a release that adds a code stops
|
|
5150
|
+
your build until you decide what the new code is called on your wire. Take it
|
|
5151
|
+
when your envelope is a published contract and a code surfacing in stitchkit's
|
|
5152
|
+
spelling would violate it; leave it off when passing one through is fine.
|
|
5153
|
+
Neither choice is silent — the changelog names every added code. For a single
|
|
5154
|
+
catch-all instead of either, decide it in `render`, where `info.code` is the
|
|
5155
|
+
resolved value.
|
|
4821
5156
|
|
|
4822
5157
|
Both `onError` and `render` may be asynchronous and receive the matched endpoint
|
|
4823
5158
|
as their final argument. The observer is awaited before rendering, so it can
|
|
@@ -7582,6 +7917,40 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
7582
7917
|
|
|
7583
7918
|
---
|
|
7584
7919
|
|
|
7920
|
+
## `stitchkit/agent-runtime`
|
|
7921
|
+
|
|
7922
|
+
Server-only optional application runtime. See the
|
|
7923
|
+
[agent runtime guide](../guide/agent-runtime.md).
|
|
7924
|
+
|
|
7925
|
+
| Export | Kind | Summary |
|
|
7926
|
+
|--------|------|---------|
|
|
7927
|
+
| `createAgentRuntime` | function | compose durable acceptance, stream loop, checkpoints, coordination, managed tools and terminal publication |
|
|
7928
|
+
| `defineAgentProtocol` | function | declare and validate context, input metadata and canonical message parts |
|
|
7929
|
+
| `AgentMessageSchema` / `AgentRunSchema` / `AgentSnapshotSchema` | schema | versioned canonical engine records |
|
|
7930
|
+
| `AgentRuntimeStore` | _type_ | aggregate CAS transaction boundary for message, run and compaction mutations |
|
|
7931
|
+
| `RecoverAgentRunSchema` | schema | explicit abandon/requeue recovery decision; acquired runs require replay-safe evidence |
|
|
7932
|
+
| `createMemoryAgentRuntimeStore` | function | process-local reference adapter, not production durability |
|
|
7933
|
+
| `projectAgentHistory` | function | asynchronously project canonical records and resolved multimodal files into provider-valid AI SDK messages |
|
|
7934
|
+
| `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
|
|
7935
|
+
| `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware context budget |
|
|
7936
|
+
| `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
|
|
7937
|
+
| `createAgentSessionCoordinator` | function | strict process-local queue/interrupt lifecycle |
|
|
7938
|
+
| `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
|
|
7939
|
+
| `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
|
|
7940
|
+
| `AgentRuntimeRecordIds` | _type_ | optional caller-provided input, run and assistant IDs for stable application records |
|
|
7941
|
+
| `AgentRuntimeAdmission` | _type_ | actual assigned run/assistant identity and snapshot version after durable admission |
|
|
7942
|
+
| `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver and explicit unresolved-file behavior |
|
|
7943
|
+
| `createAgentToolFenceLifecycle` | function | pre-effect and post-effect run ownership fence for `mountAgent` |
|
|
7944
|
+
| `AgentRuntimeEventSchema` | schema | transient delta, durable checkpoint/run-state/tool and terminal event union |
|
|
7945
|
+
| `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
|
|
7946
|
+
|
|
7947
|
+
## `stitchkit/agent-runtime/openrouter`
|
|
7948
|
+
|
|
7949
|
+
| Export | Kind | Summary |
|
|
7950
|
+
|--------|------|---------|
|
|
7951
|
+
| `openRouterProvider` | function | isolated `@openrouter/ai-sdk-provider` language-model factory |
|
|
7952
|
+
| `OpenRouterProviderSettings` | _type_ | official provider settings accepted by the factory |
|
|
7953
|
+
|
|
7585
7954
|
## `stitchkit/observability`
|
|
7586
7955
|
|
|
7587
7956
|
Server-only. The audit layer one level above the raw hooks — W3C trace context,
|
|
@@ -7755,6 +8124,9 @@ payload.
|
|
|
7755
8124
|
| `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
|
|
7756
8125
|
| `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
|
|
7757
8126
|
| `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
|
|
8127
|
+
| `ToolExecutionControlReason` | _type_ | internal managed-execution stop reason: stale run or requested interruption |
|
|
8128
|
+
| `ToolExecutionControlError` | class | control-flow error used to unwind a superseded managed tool call without presenting it as a model-facing tool failure |
|
|
8129
|
+
| `isToolExecutionControlError` | function | narrow an unknown thrown value to the managed execution control error |
|
|
7758
8130
|
| `ToolOperation` | _type_ | executable path-free operation shape shared by contract and framework-native runners |
|
|
7759
8131
|
| `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
8132
|
| `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.
|
|
3
|
+
"version": "0.56.3",
|
|
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",
|