stitchkit 0.121.0 → 0.122.1
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/CHANGELOG.md +62 -0
- package/dist/agent-runtime/browser-control-contract.d.ts +12 -0
- package/dist/agent-runtime/browser-control-contract.d.ts.map +1 -1
- package/dist/agent-runtime/control-schema.d.ts +27 -4
- package/dist/agent-runtime/control-schema.d.ts.map +1 -1
- package/dist/agent-runtime/event-schema.d.ts +33 -0
- package/dist/agent-runtime/event-schema.d.ts.map +1 -1
- package/dist/agent-runtime/run-acquisition.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution-state.d.ts +6 -0
- package/dist/agent-runtime/run-execution-state.d.ts.map +1 -1
- package/dist/agent-runtime/run-model-attempts.d.ts.map +1 -1
- package/dist/agent-runtime/run-stream-parts.d.ts +11 -0
- package/dist/agent-runtime/run-stream-parts.d.ts.map +1 -1
- package/dist/agent-runtime/tool-progress.d.ts +21 -0
- package/dist/agent-runtime/tool-progress.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/browser.js +7 -3
- package/dist/entrypoints/agent-runtime/harness.js +5 -5
- package/dist/entrypoints/agent-runtime/realtime.js +5 -5
- package/dist/entrypoints/agent-runtime.d.ts +1 -1
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +6 -4
- package/dist/entrypoints/cli.js +2 -1
- package/dist/entrypoints/server.d.ts +1 -0
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +107 -42
- package/dist/entrypoints/tools/agent.js +1 -1
- package/dist/entrypoints/tools/connections.js +7 -1
- package/dist/entrypoints/tools/mcp.js +2 -1
- package/dist/entrypoints/tools/toolkit.js +4 -3
- package/dist/{index-7zwtbak8.js → index-1fyea34p.js} +9 -4
- package/dist/index-332d0m9g.js +37 -0
- package/dist/{index-4qnzda76.js → index-7qyv50r4.js} +1 -1
- package/dist/{index-qctbzmg9.js → index-am1h8c9v.js} +69 -27
- package/dist/{index-0fra8xvy.js → index-aswegq1r.js} +18 -6
- package/dist/{index-yt7gyg5x.js → index-b51tzcgh.js} +1 -1
- package/dist/{index-aw6zahv1.js → index-dqpesdck.js} +2 -2
- package/dist/{index-sb9dr3tq.js → index-eh71zr32.js} +4 -4
- package/dist/{index-mnkk1ffz.js → index-hs76yzt7.js} +13 -1
- package/dist/{index-b0w7rbpt.js → index-t05a2ry4.js} +39 -3
- package/dist/{index-knmnj11j.js → index-y6ye582c.js} +7 -5
- package/dist/server/openapi-security.d.ts +73 -0
- package/dist/server/openapi-security.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +12 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli/format.d.ts +11 -2
- package/dist/tools/cli/format.d.ts.map +1 -1
- package/dist/tools/cli/run-managed.d.ts.map +1 -1
- package/dist/tools/cli/run-native.d.ts.map +1 -1
- package/dist/tools/connections/mcp.d.ts.map +1 -1
- package/dist/tools/mcp/catalog.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +1 -2
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/read-operation.d.ts +21 -0
- package/dist/tools/read-operation.d.ts.map +1 -0
- package/dist/tools/runtime-call-context.d.ts +15 -0
- package/dist/tools/runtime-call-context.d.ts.map +1 -0
- package/llms/agent-runtime-browser.txt +4 -1
- package/llms/agent-runtime.2.txt +13 -108
- package/llms/agent-runtime.3.txt +111 -1
- package/llms/cli.txt +30 -9
- package/llms/server.2.txt +48 -0
- package/llms/server.5.txt +2 -0
- package/llms/stitchkit.txt +3 -3
- package/llms/tools.2.txt +109 -0
- package/llms/tools.txt +34 -110
- package/llms/{upgrading-0.106.0-0.121.0.txt → upgrading-0.106.0-0.122.0.txt} +31 -3
- package/llms/upgrading.txt +1 -1
- package/llms-full.txt +165 -15
- package/llms.txt +6 -6
- package/package.json +2 -1
- package/dist/tools/durability-context.d.ts +0 -7
- package/dist/tools/durability-context.d.ts.map +0 -1
package/llms/agent-runtime.3.txt
CHANGED
|
@@ -14,6 +14,114 @@ Parts of this slice:
|
|
|
14
14
|
# Guide: Agent application runtime (docs/guide/agent-runtime.md) (continued)
|
|
15
15
|
==============================================================================
|
|
16
16
|
|
|
17
|
+
## Observability
|
|
18
|
+
|
|
19
|
+
`createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
|
|
20
|
+
reuses the same bounded sink lifecycle as request/tool observability without
|
|
21
|
+
sending new event kinds to existing request sinks. Product events omit provider
|
|
22
|
+
causes. Operator `internalCause` is also redacted by default; an operator-only sink must explicitly
|
|
23
|
+
set `includeInternalCause` and own its retention policy.
|
|
24
|
+
|
|
25
|
+
### What a run says it spent
|
|
26
|
+
|
|
27
|
+
Usage values carry `provider-reported`, `computed`, `estimated` or `unavailable`
|
|
28
|
+
provenance, **per field**. Cost additionally carries an ISO currency code;
|
|
29
|
+
OpenRouter-reported cost is normalized as USD.
|
|
30
|
+
|
|
31
|
+
Read the provenance before the number (→ ADR 0109):
|
|
32
|
+
|
|
33
|
+
- **`provider-reported`** — the provider handed us exactly this. On
|
|
34
|
+
`step-finished`, that is what a step's figures are.
|
|
35
|
+
- **`computed`** — a total, added up over steps. **Every figure on a terminal
|
|
36
|
+
event is this**, tokens included: the AI SDK's `totalUsage` is a sum it
|
|
37
|
+
performed, not a number a provider reported for the run. It is not a figure to
|
|
38
|
+
bill against unchanged.
|
|
39
|
+
- **`unavailable`** — nobody reported it. Not zero.
|
|
40
|
+
|
|
41
|
+
Two rules follow from that last one, and they differ by field on purpose:
|
|
42
|
+
|
|
43
|
+
- **A token total with an unreported step is a floor**, labelled `computed`. A
|
|
44
|
+
token count is a diagnostic, and a floor is a useful one.
|
|
45
|
+
- **A cost with an unreported step is `unavailable`, not a floor.** Money is what
|
|
46
|
+
people bill against, and "at least $1.00" reported as `$1.00` is the same class
|
|
47
|
+
of lie this whole section exists to remove. One step that did not report its
|
|
48
|
+
cost makes the run's cost unknown — not smaller. It also stays unknown: later
|
|
49
|
+
steps reporting normally cannot revive it.
|
|
50
|
+
|
|
51
|
+
**A terminal event always carries `usage`.** A run that ended before the provider
|
|
52
|
+
reported anything — superseded, interrupted, timed out, shut down, failed —
|
|
53
|
+
carries every field `unavailable`. That is deliberately different from a run that
|
|
54
|
+
spent nothing, and an omitted object could not tell you which one you had.
|
|
55
|
+
|
|
56
|
+
Two costs in different currencies do not add: the sum reports `unavailable`
|
|
57
|
+
rather than picking a label. The core records a currency and never converts one.
|
|
58
|
+
|
|
59
|
+
`step-finished.response` identifies the provider response for that one step. The terminal event
|
|
60
|
+
does not copy the last response: response identity remains per-step, while every terminal usage
|
|
61
|
+
figure remains a `computed` aggregate.
|
|
62
|
+
|
|
63
|
+
**A run's figure is durable, and that is where to read it when a channel loses
|
|
64
|
+
it.** `AgentRun.usage` is written at every checkpoint and again with the terminal
|
|
65
|
+
record, so a crashed process leaves behind what it had already spent and a
|
|
66
|
+
dropped event is not a lost number:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const snapshot = await store.loadSnapshot(conversationId)
|
|
70
|
+
const spent = snapshot.runs.find((run) => run.id === runId)?.usage
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Two gaps are open and are stated rather than left to be discovered:
|
|
74
|
+
|
|
75
|
+
- **Both event sinks are bounded and drop under load**, by arrival order — the
|
|
76
|
+
event carrying the money is exactly as droppable as the one carrying nothing.
|
|
77
|
+
The run record is the recovery, and this paragraph is the only place that says
|
|
78
|
+
so.
|
|
79
|
+
- **`AgentRuntimeResult.metrics` is `undefined` when this executor did not win
|
|
80
|
+
the terminal race.** Read `result.run.usage` — the durable figure — rather than
|
|
81
|
+
`result.metrics`, which is the channel that can go missing.
|
|
82
|
+
- **Compaction spend is invisible unless you report it.** `config.history.compact`
|
|
83
|
+
calls a model inside the turn and produces no step and no event of its own.
|
|
84
|
+
Return `usage` from `AgentCompactionResult` and it joins the run's figure;
|
|
85
|
+
omit it and the run under-reports by whatever summarising cost.
|
|
86
|
+
|
|
87
|
+
What stitchkit does **not** keep is a ledger: one figure, on the run that
|
|
88
|
+
produced it, never aggregated and never reconciled against a provider invoice
|
|
89
|
+
(→ ADR 0110).
|
|
90
|
+
|
|
91
|
+
### Reconciling with the provider's own accounting
|
|
92
|
+
|
|
93
|
+
Whether a provider bills for a call that was aborted mid-flight cannot be known
|
|
94
|
+
inside the process: the authoritative number arrives later, from the provider's
|
|
95
|
+
accounting. **stitchkit does not accept it back** (→ ADR 0110) — a terminal run
|
|
96
|
+
is an absorbing state, and a write that reached it through the conversation
|
|
97
|
+
aggregate could conflict a concurrent compaction into discarding a summary it had
|
|
98
|
+
just paid a model to produce.
|
|
99
|
+
|
|
100
|
+
The join is yours, and `runId` is the key:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// when the run terminates — write what the runtime observed
|
|
104
|
+
await ledger.record({
|
|
105
|
+
runId: terminal.run.id,
|
|
106
|
+
conversationId: terminal.run.conversationId,
|
|
107
|
+
// The durable figure, not `terminal.metrics` — that one is absent whenever
|
|
108
|
+
// this executor did not win the terminal race.
|
|
109
|
+
costUsd: terminal.run.usage?.cost?.value ?? null, // null when `unavailable`
|
|
110
|
+
provenance: terminal.run.usage?.cost?.provenance ?? 'unavailable',
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// later — the provider's accounting names the same generation
|
|
114
|
+
await ledger.reconcile({ runId, costUsd: billed, provenance: 'provider-reported' })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Record the row even when the figure is `unavailable`: that row is the evidence
|
|
118
|
+
that a run happened and cost something nobody has counted yet, and it is what the
|
|
119
|
+
provider's later figure attaches to. A run with no row is a run you cannot
|
|
120
|
+
reconcile.
|
|
121
|
+
|
|
122
|
+
The sink deduplicates stable event IDs by default. Cross-crash exactly-once still requires a durable
|
|
123
|
+
outbox.
|
|
124
|
+
|
|
17
125
|
## Deterministic race and adapter proof
|
|
18
126
|
|
|
19
127
|
`stitchkit/agent-runtime/testing` exports `createAgentRaceBarrier`, `createAgentRaceDriver`,
|
|
@@ -391,7 +499,9 @@ Delivery exports are `AgentAdmissionEventSchema`, `AgentAttemptResetEventSchema`
|
|
|
391
499
|
`AgentRunStateEventSchema`, `AgentRunOperationEventSchema`, `AgentTerminalEventSchema`,
|
|
392
500
|
`AgentTransientDeltaEventSchema`,
|
|
393
501
|
`AgentReasoningStartEventSchema`, `AgentReasoningDeltaEventSchema`,
|
|
394
|
-
`AgentReasoningEndEventSchema`, `AgentToolStatusEventSchema`, `
|
|
502
|
+
`AgentReasoningEndEventSchema`, `AgentToolStatusEventSchema`, `AgentToolProgressEventSchema` (what a
|
|
503
|
+
running tool reported through `ctx.reportProgress`, transient and never part of the model's input),
|
|
504
|
+
`AgentRuntimeEvent`,
|
|
395
505
|
`AgentRuntimeEventCursor`, `AgentRuntimeEventCursorSchema`, `AgentRuntimeCursorAdvance`,
|
|
396
506
|
`advanceAgentRuntimeEventCursor`, `agentDurableEventId`, `AgentRuntimeEventSink`,
|
|
397
507
|
`AgentRuntimeEventSinkConfig` and `createAgentRuntimeEventSink`. Cursor gaps require a canonical
|
package/llms/cli.txt
CHANGED
|
@@ -702,15 +702,36 @@ Rules worth knowing before you rely on them:
|
|
|
702
702
|
A failed call still reports its own error and exit code. An aggregate over an
|
|
703
703
|
error is not an answer to the question that was asked.
|
|
704
704
|
|
|
705
|
-
If the command succeeds but its requested view cannot be built, the CLI writes
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
`
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
705
|
+
If the command succeeds but its requested view cannot be built, the CLI writes a
|
|
706
|
+
structured `CLI_PRESENTATION_FAILED` diagnostic to stderr and exits with `8`,
|
|
707
|
+
configurable through `exitCodes`. Its `details.operationSucceeded: true` and
|
|
708
|
+
`retryable: false` distinguish a presentation refusal from a failed operation;
|
|
709
|
+
`details.cause` preserves the safe reason, such as the misspelled field and the
|
|
710
|
+
fields that are there. Domain retry hints are not appended to this diagnostic.
|
|
711
|
+
|
|
712
|
+
What reaches stdout depends on whether the command can be called again for its answer,
|
|
713
|
+
and `details.resultOnStdout` states which case occurred:
|
|
714
|
+
|
|
715
|
+
| Command | stdout | Recover the answer by |
|
|
716
|
+
|---|---|---|
|
|
717
|
+
| has an effect | the complete original result in the normal JSON format (`--json` still selects compact output) | reading stdout — never by repeating the mutation |
|
|
718
|
+
| only reads | nothing | repeating the command with a corrected view |
|
|
719
|
+
|
|
720
|
+
A managed command only reads when its tool declares `annotations.readOnlyHint: true`,
|
|
721
|
+
or declares no `readOnlyHint` and its method is `GET`. `readOnlyHint: false`, or
|
|
722
|
+
`destructiveHint: true` without a `readOnlyHint`, on a `GET` endpoint keeps the result on stdout. A native
|
|
723
|
+
`defineCliCommand` declares neither fact, so a failed `present` always keeps the validated
|
|
724
|
+
result on stdout.
|
|
725
|
+
|
|
726
|
+
A CLI that mounts a server's tools through `defineMcpClientConnection` never sees the
|
|
727
|
+
method — every MCP call is a `POST` — so it reads the `readOnlyHint` the server
|
|
728
|
+
publishes. A Stitchkit server publishes it for every `GET` by the rule above
|
|
729
|
+
([MCP → `readOnlyHint`](./mcp-and-agents.md#saying-which-tools-only-read--readonlyhint)),
|
|
730
|
+
so the same command behaves the same over the contract and over MCP. The hint is the
|
|
731
|
+
server's word: a server that labels a write as read-only makes the CLI withhold that
|
|
732
|
+
write's result on a refused view, and repeating the command repeats the effect.
|
|
733
|
+
|
|
734
|
+
CLI argument errors detected before execution still exit with `2`
|
|
714
735
|
and have no successful result to preserve.
|
|
715
736
|
|
|
716
737
|
## Named profiles
|
package/llms/server.2.txt
CHANGED
|
@@ -507,3 +507,51 @@ is simply never emitted, so nothing about a hidden endpoint leaks.
|
|
|
507
507
|
const internal = openApiRoute('/internal/openapi.json', fullDoc) // behind auth
|
|
508
508
|
const publicSpec = openApiRoute('/openapi.json', publicDoc) // curated
|
|
509
509
|
```
|
|
510
|
+
|
|
511
|
+
### Security schemes — `security`
|
|
512
|
+
|
|
513
|
+
A generic client reads the spec to learn how to authenticate, and a scope name
|
|
514
|
+
alone does not tell it which header to send. Pass `security` to describe that in
|
|
515
|
+
standard OpenAPI terms: named schemes become `components.securitySchemes`, and
|
|
516
|
+
every protected operation gets a `security` requirement. It is keyed by the
|
|
517
|
+
scopes your contracts already declare, so there is no second list of which
|
|
518
|
+
endpoint is protected:
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
const publicDoc = generateOpenApiDocument({
|
|
522
|
+
info: { title: 'Public API', version: '1.0.0' },
|
|
523
|
+
services: [account],
|
|
524
|
+
includeMethod: (m) => m.meta?.public === true,
|
|
525
|
+
security: {
|
|
526
|
+
schemes: {
|
|
527
|
+
apiKey: { type: 'http', scheme: 'bearer', description: 'An account API key' },
|
|
528
|
+
},
|
|
529
|
+
// Each protected scope → the schemes that satisfy it, any one of them.
|
|
530
|
+
scopes: { account: ['apiKey'] },
|
|
531
|
+
},
|
|
532
|
+
})
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
A scheme is `{ type: 'http', scheme: 'bearer' | 'basic' }` or
|
|
536
|
+
`{ type: 'apiKey', in: 'header' | 'query' | 'cookie', name }` (a session cookie
|
|
537
|
+
is the `cookie` form). An operation is described by the scope the auth hook
|
|
538
|
+
checks it under:
|
|
539
|
+
|
|
540
|
+
| The operation's scope | In the document |
|
|
541
|
+
|---|---|
|
|
542
|
+
| `'public'` | no `security`, no 401/403 |
|
|
543
|
+
| listed in `scopes` with schemes | `security: [{ apiKey: [] }, …]` and 401/403 |
|
|
544
|
+
| listed with an empty list — a custom scope whose rule is `'public'` | no `security`, no 401/403 |
|
|
545
|
+
| not listed | generation throws, naming the operation |
|
|
546
|
+
| none of its own | read as `defaultScope` — pass the same value as `createAuthHook({ defaultScope })` |
|
|
547
|
+
|
|
548
|
+
Generation refuses a protected scope it cannot describe instead of emitting the
|
|
549
|
+
operation as open — a client would otherwise call it without a credential. Only
|
|
550
|
+
the operations the document advertises need a mapping: a method `includeMethod`
|
|
551
|
+
filters out is never checked. A scheme `scopes` names but `schemes` does not
|
|
552
|
+
declare, and an entry for `'public'`, are refused the same way.
|
|
553
|
+
|
|
554
|
+
The metadata describes your policy; it does not enforce it. The auth hook is
|
|
555
|
+
still the only gate, and removing `security` changes the document, never a
|
|
556
|
+
401 or 403. Credential values never belong here: describe where the key goes,
|
|
557
|
+
not what it is.
|
package/llms/server.5.txt
CHANGED
|
@@ -236,6 +236,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
236
236
|
| `OpenApiConfig` | _type_ | config for `generateOpenApiDocument` (incl. `includeMethod` — curate a public subset) — [guide](../guide/server.md#curating-the-spec--includemethod) |
|
|
237
237
|
| `OpenApiDocument` | _type_ | the generated document |
|
|
238
238
|
| `OpenApiInfo` | _type_ | the spec `info` block |
|
|
239
|
+
| `OpenApiSecurity` | _type_ | `OpenApiConfig.security` — named schemes and the schemes each protected scope accepts — [guide](../guide/server.md#security-schemes--security) |
|
|
240
|
+
| `OpenApiSecurityScheme` | _type_ | one way a caller presents its credential: HTTP bearer or basic, or an API key in a header, query or cookie |
|
|
239
241
|
| `OpenApiServer` | _type_ | a spec `servers` entry |
|
|
240
242
|
|
|
241
243
|
---
|
package/llms/stitchkit.txt
CHANGED
|
@@ -92,13 +92,13 @@ own, recorded as an ADR.
|
|
|
92
92
|
| `stitchkit/xlsx` | browser **and** server | evolving | a workbook from rows and typed columns, with no dependency; text never runs as a formula |
|
|
93
93
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
94
94
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
95
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the
|
|
95
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the 67 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
96
96
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
|
|
97
97
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
98
98
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
99
99
|
| `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
|
|
100
100
|
| `stitchkit/react/keyboard/testing` | server (Bun or Node) | evolving | the keyboard layers and zones without a DOM — fakes for the window, elements and key events beside the two engines, to test the order of layers, `scope`/`inert` and the edges of a zone in milliseconds |
|
|
101
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 26 of the
|
|
101
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 26 of the 67 minors since 0.56.2, most recently 0.121.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
102
102
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
103
103
|
| `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
|
|
104
104
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
@@ -110,7 +110,7 @@ own, recorded as an ADR.
|
|
|
110
110
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
111
111
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
112
112
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
113
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the
|
|
113
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 67 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
114
114
|
| `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
|
|
115
115
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
116
116
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
package/llms/tools.2.txt
CHANGED
|
@@ -13,6 +13,115 @@ Parts of this slice:
|
|
|
13
13
|
# Guide: MCP & agents (docs/guide/mcp-and-agents.md) (continued)
|
|
14
14
|
==============================================================================
|
|
15
15
|
|
|
16
|
+
## OAuth 2.1 — a native remote connector
|
|
17
|
+
|
|
18
|
+
A remote MCP server is connectable from Claude (Desktop / web "custom
|
|
19
|
+
connector") only through the MCP authorization spec: OAuth 2.1 with PKCE, plus
|
|
20
|
+
the discovery documents (RFC 9728 / 8414), Client ID Metadata Documents (CIMD)
|
|
21
|
+
and resource indicators (RFC 8707). A Bearer-only server returns a
|
|
22
|
+
bare `401` and the connector never establishes.
|
|
23
|
+
|
|
24
|
+
stitchkit ships the OAuth **protocol mechanics**; the app supplies only
|
|
25
|
+
**identity and storage**. Three pieces wire it together:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { mountOAuthProvider, oauthProtectedResourceRoute } from 'stitchkit/tools'
|
|
29
|
+
import { createMcpHandler, createMcpHttpRoute } from 'stitchkit/tools/mcp'
|
|
30
|
+
import { createServer } from 'stitchkit/server'
|
|
31
|
+
|
|
32
|
+
const resource = 'https://api.example.com/mcp'
|
|
33
|
+
const issuer = 'https://api.example.com'
|
|
34
|
+
|
|
35
|
+
// 1. Resource server — the 401 now points at the metadata.
|
|
36
|
+
const mcp = createMcpHandler({
|
|
37
|
+
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
38
|
+
auth: resolveOAuthToken, // validate the Bearer JWT (verifyJwt + audience)
|
|
39
|
+
services,
|
|
40
|
+
protectedResource: { resource, authorizationServers: [issuer] },
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// 2. Authorization server — CIMD, /authorize (PKCE), /token.
|
|
44
|
+
const oauthRoutes = mountOAuthProvider({
|
|
45
|
+
issuer,
|
|
46
|
+
resource,
|
|
47
|
+
signingSecret: env.OAUTH_SECRET,
|
|
48
|
+
clientRegistration: {
|
|
49
|
+
preRegistered: { get: getFirstPartyClient },
|
|
50
|
+
// CIMD is enabled with secure defaults; DCR remains disabled.
|
|
51
|
+
},
|
|
52
|
+
codes, refreshTokens, // your stores (DB or in-memory)
|
|
53
|
+
authorizeUser, // → { userId, approvedScopes } | Response
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
createServer({
|
|
57
|
+
services,
|
|
58
|
+
rawRoutes: [
|
|
59
|
+
createMcpHttpRoute({ path: '/mcp', handler: mcp }),
|
|
60
|
+
oauthProtectedResourceRoute({ resource, authorizationServers: [issuer] }),
|
|
61
|
+
...oauthRoutes,
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Access tokens are signed HS256 JWTs (`signJwt`) whose `aud` is the resource and
|
|
67
|
+
whose `iss` is the issuer — validate both in `auth` with
|
|
68
|
+
`verifyJwt(token, secret, { audience: resource, issuer })`. `authorizeUser` is
|
|
69
|
+
where the app authenticates the user (reuse an existing session) and records
|
|
70
|
+
consent; return `{ userId, approvedScopes }`, where approved scopes are a subset
|
|
71
|
+
of the requested/supported values, or a `Response` to redirect the browser to a
|
|
72
|
+
login page first. The AS and resource server can co-locate or live
|
|
73
|
+
on separate origins. See
|
|
74
|
+
[ADR 0015](../decisions/0015-oauth-resource-server.md).
|
|
75
|
+
|
|
76
|
+
Client resolution is deterministic: exact pre-registered client, then an HTTPS
|
|
77
|
+
URL client id resolved as CIMD, then explicitly enabled DCR. CIMD is on by
|
|
78
|
+
default; DCR is off unless `clientRegistration.dcr` supplies both `register` and
|
|
79
|
+
`get`. A CIMD-only deployment therefore exposes neither `/register` nor a
|
|
80
|
+
`registration_endpoint`. The metadata document must declare a `client_id` that
|
|
81
|
+
exactly equals its URL, explicit redirect URIs and
|
|
82
|
+
`token_endpoint_auth_method: 'none'`.
|
|
83
|
+
|
|
84
|
+
The token endpoint and `/register` read at most 64 KiB of a request body, or
|
|
85
|
+
`maxRequestBodyBytes` when you set it; a larger one is answered `413` with
|
|
86
|
+
`invalid_request` and the rest is not read.
|
|
87
|
+
|
|
88
|
+
The production fetcher rejects credentials, fragments, non-HTTPS URLs and
|
|
89
|
+
private/reserved targets. Every redirect is DNS-resolved and IP-pinned again;
|
|
90
|
+
timeouts, redirect count and body size are bounded. Its bounded cache respects
|
|
91
|
+
`Cache-Control` (including `no-store`/`no-cache`), `Age`, `Expires`, `ETag` and
|
|
92
|
+
`Last-Modified`, coalesces concurrent misses and reports sanitized cache events;
|
|
93
|
+
invalid or unavailable identity receives only a short fail-closed cache entry.
|
|
94
|
+
|
|
95
|
+
Resolution itself is rate-limited on two levels (`CimdCachePolicy`):
|
|
96
|
+
`maxResolutionsPerClient` (default 10) bounds ONE `client_id` per
|
|
97
|
+
`resolutionWindowMs` window (default 60 s) — a client whose document disables
|
|
98
|
+
caching burns its own budget, not the server's — while `maxResolutions`
|
|
99
|
+
(default 120) caps the server-wide outbound rate as the flood backstop.
|
|
100
|
+
Positive and negative cache entries keep separate pools of `maxEntries` each,
|
|
101
|
+
so a flood of unresolvable ids cannot evict warmed clients and a burst of
|
|
102
|
+
successes cannot reset a failing client's backoff.
|
|
103
|
+
|
|
104
|
+
### Authorization hardening (MCP 2026-07-28)
|
|
105
|
+
|
|
106
|
+
- **`iss` on every authorization response (RFC 9207, SEP-2468).** Success *and*
|
|
107
|
+
error redirects carry `iss`, and the AS metadata advertises
|
|
108
|
+
`authorization_response_iss_parameter_supported: true`. A client that talks to
|
|
109
|
+
several authorization servers validates `iss` before redeeming the code, which
|
|
110
|
+
closes the **mix-up attack** — an attacker's server cannot pass its response
|
|
111
|
+
off as this issuer's. Additive on the wire: a client that ignores `iss` is
|
|
112
|
+
unaffected.
|
|
113
|
+
- **`application_type` on registration (SEP-837).** A client may declare
|
|
114
|
+
`"native"` (desktop / CLI) or `"web"` in its DCR body. A **native** client may
|
|
115
|
+
register an `http` loopback redirect (`http://127.0.0.1:…`, RFC 8252 §7.3); a
|
|
116
|
+
**web** client is held to `https` only — that mismatch is the usual cause of
|
|
117
|
+
the `redirect_uri` rejection CLI clients hit. Omit the field and registration
|
|
118
|
+
behaves exactly as before (loopback allowed); an unknown value is rejected
|
|
119
|
+
rather than silently defaulted.
|
|
120
|
+
|
|
121
|
+
> Dynamic Client Registration is an explicit interoperability mode, not the
|
|
122
|
+
> default. Enabling it changes public discovery and adds `/register`; keep it off
|
|
123
|
+
> unless a client that cannot publish CIMD requires it.
|
|
124
|
+
|
|
16
125
|
## Proxying a remote API — `implementRemote`
|
|
17
126
|
|
|
18
127
|
`implement` binds a contract to local handlers. `implementRemote` binds it to a
|
package/llms/tools.txt
CHANGED
|
@@ -317,6 +317,36 @@ schema and annotations, and nothing else — it moves when the contract moves an
|
|
|
317
317
|
not when a handler does. Raw SDK registrations stay consumer-owned and are
|
|
318
318
|
outside it.
|
|
319
319
|
|
|
320
|
+
### Saying which tools only read — `readOnlyHint`
|
|
321
|
+
|
|
322
|
+
A host reads a tool's `annotations` to group tools and to choose permission
|
|
323
|
+
defaults, and a CLI mounted over this surface reads them to decide what a
|
|
324
|
+
refused view may withhold ([CLI → views](./cli.md#aggregate-views)). The server publishes the
|
|
325
|
+
annotations the tool declared, and states `readOnlyHint: true` for a read that
|
|
326
|
+
declared none:
|
|
327
|
+
|
|
328
|
+
| The tool | Published `readOnlyHint` |
|
|
329
|
+
|---|---|
|
|
330
|
+
| declares `readOnlyHint`, either way | as declared |
|
|
331
|
+
| `GET` with no `readOnlyHint` | `true` |
|
|
332
|
+
| `GET` declaring `destructiveHint: true` and no `readOnlyHint` | none |
|
|
333
|
+
| any other method with no `readOnlyHint` | none |
|
|
334
|
+
|
|
335
|
+
A runtime tool follows the same rule by its `identity.method`. A raw SDK
|
|
336
|
+
registration (`mountWait`, `mountViewFile` and the like) publishes only what it
|
|
337
|
+
passes. The catalog digest hashes the published annotations, so it moves once
|
|
338
|
+
when a server starts publishing the derived hint.
|
|
339
|
+
|
|
340
|
+
> **A `GET` with an effect must say so.** Hosts may run a tool that publishes
|
|
341
|
+
> `readOnlyHint: true` without asking. An endpoint that changes something on
|
|
342
|
+
> `GET` — marks as seen, rotates a token — declares
|
|
343
|
+
> `tool: { annotations: { readOnlyHint: false } }`.
|
|
344
|
+
|
|
345
|
+
A client connection (`defineMcpClientConnection`) carries the hints a server
|
|
346
|
+
published onto the tools it mounts, field by field: a hint of the wrong type is
|
|
347
|
+
dropped and the tool stays. The mounted tool is as read-only as its server says
|
|
348
|
+
it is, and a surface that re-publishes mounted tools passes those hints on.
|
|
349
|
+
|
|
320
350
|
### Shaping arguments before validation — `beforeToolCall`
|
|
321
351
|
|
|
322
352
|
`beforeToolCall` sees the arguments as they arrived and may **replace** them:
|
|
@@ -498,7 +528,10 @@ render: async (ctx) => {
|
|
|
498
528
|
|
|
499
529
|
`reportProgress` is present on every **tool** call — MCP, agent and CLI — so a
|
|
500
530
|
handler never branches on transport, and it does nothing wherever nobody is
|
|
501
|
-
listening.
|
|
531
|
+
listening. Under the agent runtime somebody is: each report becomes a transient
|
|
532
|
+
`tool-progress` event of the run, so the person watching the agent sees the
|
|
533
|
+
stage while the model still reads only the tool's result (see
|
|
534
|
+
[Agent runtime → events](./agent-runtime.md)). It is absent on the HTTP transport, which has no channel for it, so a
|
|
502
535
|
handler that also serves HTTP writes `ctx.reportProgress?.(…)`. It never throws and never
|
|
503
536
|
rejects: a message about work must not be able to kill the work it describes, so
|
|
504
537
|
a refused notification leaves the host exactly where a host that never asked for
|
|
@@ -862,112 +895,3 @@ OS default disposition instead of reporting a fictional forced result.
|
|
|
862
895
|
Both transports build the server through the shared `buildMcpServer` — same
|
|
863
896
|
contract/runtime pipeline, same surface selection, context, hooks, raw escape
|
|
864
897
|
hatch and instructions.
|
|
865
|
-
|
|
866
|
-
## OAuth 2.1 — a native remote connector
|
|
867
|
-
|
|
868
|
-
A remote MCP server is connectable from Claude (Desktop / web "custom
|
|
869
|
-
connector") only through the MCP authorization spec: OAuth 2.1 with PKCE, plus
|
|
870
|
-
the discovery documents (RFC 9728 / 8414), Client ID Metadata Documents (CIMD)
|
|
871
|
-
and resource indicators (RFC 8707). A Bearer-only server returns a
|
|
872
|
-
bare `401` and the connector never establishes.
|
|
873
|
-
|
|
874
|
-
stitchkit ships the OAuth **protocol mechanics**; the app supplies only
|
|
875
|
-
**identity and storage**. Three pieces wire it together:
|
|
876
|
-
|
|
877
|
-
```ts
|
|
878
|
-
import { mountOAuthProvider, oauthProtectedResourceRoute } from 'stitchkit/tools'
|
|
879
|
-
import { createMcpHandler, createMcpHttpRoute } from 'stitchkit/tools/mcp'
|
|
880
|
-
import { createServer } from 'stitchkit/server'
|
|
881
|
-
|
|
882
|
-
const resource = 'https://api.example.com/mcp'
|
|
883
|
-
const issuer = 'https://api.example.com'
|
|
884
|
-
|
|
885
|
-
// 1. Resource server — the 401 now points at the metadata.
|
|
886
|
-
const mcp = createMcpHandler({
|
|
887
|
-
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
888
|
-
auth: resolveOAuthToken, // validate the Bearer JWT (verifyJwt + audience)
|
|
889
|
-
services,
|
|
890
|
-
protectedResource: { resource, authorizationServers: [issuer] },
|
|
891
|
-
})
|
|
892
|
-
|
|
893
|
-
// 2. Authorization server — CIMD, /authorize (PKCE), /token.
|
|
894
|
-
const oauthRoutes = mountOAuthProvider({
|
|
895
|
-
issuer,
|
|
896
|
-
resource,
|
|
897
|
-
signingSecret: env.OAUTH_SECRET,
|
|
898
|
-
clientRegistration: {
|
|
899
|
-
preRegistered: { get: getFirstPartyClient },
|
|
900
|
-
// CIMD is enabled with secure defaults; DCR remains disabled.
|
|
901
|
-
},
|
|
902
|
-
codes, refreshTokens, // your stores (DB or in-memory)
|
|
903
|
-
authorizeUser, // → { userId, approvedScopes } | Response
|
|
904
|
-
})
|
|
905
|
-
|
|
906
|
-
createServer({
|
|
907
|
-
services,
|
|
908
|
-
rawRoutes: [
|
|
909
|
-
createMcpHttpRoute({ path: '/mcp', handler: mcp }),
|
|
910
|
-
oauthProtectedResourceRoute({ resource, authorizationServers: [issuer] }),
|
|
911
|
-
...oauthRoutes,
|
|
912
|
-
],
|
|
913
|
-
})
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
Access tokens are signed HS256 JWTs (`signJwt`) whose `aud` is the resource and
|
|
917
|
-
whose `iss` is the issuer — validate both in `auth` with
|
|
918
|
-
`verifyJwt(token, secret, { audience: resource, issuer })`. `authorizeUser` is
|
|
919
|
-
where the app authenticates the user (reuse an existing session) and records
|
|
920
|
-
consent; return `{ userId, approvedScopes }`, where approved scopes are a subset
|
|
921
|
-
of the requested/supported values, or a `Response` to redirect the browser to a
|
|
922
|
-
login page first. The AS and resource server can co-locate or live
|
|
923
|
-
on separate origins. See
|
|
924
|
-
[ADR 0015](../decisions/0015-oauth-resource-server.md).
|
|
925
|
-
|
|
926
|
-
Client resolution is deterministic: exact pre-registered client, then an HTTPS
|
|
927
|
-
URL client id resolved as CIMD, then explicitly enabled DCR. CIMD is on by
|
|
928
|
-
default; DCR is off unless `clientRegistration.dcr` supplies both `register` and
|
|
929
|
-
`get`. A CIMD-only deployment therefore exposes neither `/register` nor a
|
|
930
|
-
`registration_endpoint`. The metadata document must declare a `client_id` that
|
|
931
|
-
exactly equals its URL, explicit redirect URIs and
|
|
932
|
-
`token_endpoint_auth_method: 'none'`.
|
|
933
|
-
|
|
934
|
-
The token endpoint and `/register` read at most 64 KiB of a request body, or
|
|
935
|
-
`maxRequestBodyBytes` when you set it; a larger one is answered `413` with
|
|
936
|
-
`invalid_request` and the rest is not read.
|
|
937
|
-
|
|
938
|
-
The production fetcher rejects credentials, fragments, non-HTTPS URLs and
|
|
939
|
-
private/reserved targets. Every redirect is DNS-resolved and IP-pinned again;
|
|
940
|
-
timeouts, redirect count and body size are bounded. Its bounded cache respects
|
|
941
|
-
`Cache-Control` (including `no-store`/`no-cache`), `Age`, `Expires`, `ETag` and
|
|
942
|
-
`Last-Modified`, coalesces concurrent misses and reports sanitized cache events;
|
|
943
|
-
invalid or unavailable identity receives only a short fail-closed cache entry.
|
|
944
|
-
|
|
945
|
-
Resolution itself is rate-limited on two levels (`CimdCachePolicy`):
|
|
946
|
-
`maxResolutionsPerClient` (default 10) bounds ONE `client_id` per
|
|
947
|
-
`resolutionWindowMs` window (default 60 s) — a client whose document disables
|
|
948
|
-
caching burns its own budget, not the server's — while `maxResolutions`
|
|
949
|
-
(default 120) caps the server-wide outbound rate as the flood backstop.
|
|
950
|
-
Positive and negative cache entries keep separate pools of `maxEntries` each,
|
|
951
|
-
so a flood of unresolvable ids cannot evict warmed clients and a burst of
|
|
952
|
-
successes cannot reset a failing client's backoff.
|
|
953
|
-
|
|
954
|
-
### Authorization hardening (MCP 2026-07-28)
|
|
955
|
-
|
|
956
|
-
- **`iss` on every authorization response (RFC 9207, SEP-2468).** Success *and*
|
|
957
|
-
error redirects carry `iss`, and the AS metadata advertises
|
|
958
|
-
`authorization_response_iss_parameter_supported: true`. A client that talks to
|
|
959
|
-
several authorization servers validates `iss` before redeeming the code, which
|
|
960
|
-
closes the **mix-up attack** — an attacker's server cannot pass its response
|
|
961
|
-
off as this issuer's. Additive on the wire: a client that ignores `iss` is
|
|
962
|
-
unaffected.
|
|
963
|
-
- **`application_type` on registration (SEP-837).** A client may declare
|
|
964
|
-
`"native"` (desktop / CLI) or `"web"` in its DCR body. A **native** client may
|
|
965
|
-
register an `http` loopback redirect (`http://127.0.0.1:…`, RFC 8252 §7.3); a
|
|
966
|
-
**web** client is held to `https` only — that mismatch is the usual cause of
|
|
967
|
-
the `redirect_uri` rejection CLI clients hit. Omit the field and registration
|
|
968
|
-
behaves exactly as before (loopback allowed); an unknown value is rejected
|
|
969
|
-
rather than silently defaulted.
|
|
970
|
-
|
|
971
|
-
> Dynamic Client Registration is an explicit interoperability mode, not the
|
|
972
|
-
> default. Enabling it changes public discovery and adds `/register`; keep it off
|
|
973
|
-
> unless a client that cannot publish CIMD requires it.
|
|
@@ -1,14 +1,42 @@
|
|
|
1
|
-
# Upgrading 0.106.0 → 0.
|
|
1
|
+
# Upgrading 0.106.0 → 0.122.0 — stitchkit docs slice
|
|
2
2
|
|
|
3
3
|
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
4
|
> `llms.txt` in this package.
|
|
5
5
|
|
|
6
|
-
Contains: Guide: Upgrading, 0.106.0 → 0.
|
|
6
|
+
Contains: Guide: Upgrading, 0.106.0 → 0.122.0 (docs/guide/upgrading.md)
|
|
7
7
|
|
|
8
8
|
==============================================================================
|
|
9
|
-
# Guide: Upgrading, 0.106.0 → 0.
|
|
9
|
+
# Guide: Upgrading, 0.106.0 → 0.122.0 (docs/guide/upgrading.md)
|
|
10
10
|
==============================================================================
|
|
11
11
|
|
|
12
|
+
## Released migration: 0.122.0
|
|
13
|
+
|
|
14
|
+
### read-view-refusal
|
|
15
|
+
|
|
16
|
+
**Who must act:** scripts and agents that read stdout after a managed CLI command exits with
|
|
17
|
+
`CLI_PRESENTATION_FAILED` (`8` by default) when that command is a `GET` endpoint or a tool
|
|
18
|
+
declaring `annotations.readOnlyHint: true`. Commands with an effect and native
|
|
19
|
+
`defineCliCommand` presenters change nothing.
|
|
20
|
+
|
|
21
|
+
A read no longer prints its complete result when the requested view is refused; stdout is
|
|
22
|
+
empty and the diagnostic carries `details.resultOnStdout: false`.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# before: the full list arrived on stdout beside the diagnostic
|
|
26
|
+
app user_list --table id,nosuchcolumn > users.json # exit 8, users.json holds every record
|
|
27
|
+
|
|
28
|
+
# after: repeat the read with a field the diagnostic names
|
|
29
|
+
app user_list --table id,nosuchcolumn # exit 8, stdout empty
|
|
30
|
+
app user_list --table id,name
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A `GET` endpoint whose call has an effect that must not be repeated declares it, and keeps
|
|
34
|
+
its result on stdout:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
tool: { name: 'token_rotate', annotations: { readOnlyHint: false } }
|
|
38
|
+
```
|
|
39
|
+
|
|
12
40
|
## Released migration: 0.121.0
|
|
13
41
|
|
|
14
42
|
### bounded-coding-search
|
package/llms/upgrading.txt
CHANGED
|
@@ -11,7 +11,7 @@ Contains: Guide: Upgrading, general (docs/guide/upgrading.md)
|
|
|
11
11
|
|
|
12
12
|
Load this slice, then every range slice between your installed version and the target:
|
|
13
13
|
|
|
14
|
-
- 0.106.0 → 0.
|
|
14
|
+
- 0.106.0 → 0.122.0: `llms/upgrading-0.106.0-0.122.0.txt`
|
|
15
15
|
- 0.96.0 → 0.105.0: `llms/upgrading-0.96.0-0.105.0.txt`
|
|
16
16
|
- 0.69.0 → 0.95.0: `llms/upgrading-0.69.0-0.95.0.txt`
|
|
17
17
|
- 0.55.0 → 0.68.0: `llms/upgrading-0.55.0-0.68.0.txt`
|