stitchkit 0.108.5 → 0.109.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/browser/cancellation.d.ts +4 -5
  3. package/dist/browser/cancellation.d.ts.map +1 -1
  4. package/dist/browser/client-executors.d.ts.map +1 -1
  5. package/dist/browser/client.d.ts +1 -1
  6. package/dist/browser/client.d.ts.map +1 -1
  7. package/dist/browser/http.d.ts +1 -1
  8. package/dist/browser/http.d.ts.map +1 -1
  9. package/dist/browser/session.d.ts +1 -1
  10. package/dist/browser/session.d.ts.map +1 -1
  11. package/dist/browser/transport.d.ts +1 -1
  12. package/dist/browser/transport.d.ts.map +1 -1
  13. package/dist/entrypoints/cli.js +1 -1
  14. package/dist/entrypoints/index.js +2 -2
  15. package/dist/entrypoints/react.js +1 -1
  16. package/dist/entrypoints/remote.js +2 -2
  17. package/dist/entrypoints/telegram.d.ts +2 -2
  18. package/dist/entrypoints/telegram.d.ts.map +1 -1
  19. package/dist/entrypoints/telegram.js +868 -137
  20. package/dist/entrypoints/testing.js +2 -2
  21. package/dist/entrypoints/tools.js +1 -1
  22. package/dist/{index-7jvwrjqa.js → index-00v1qz76.js} +20 -17
  23. package/dist/{index-05s01c94.js → index-5cw390s6.js} +96 -16
  24. package/dist/{index-p5kx5vqd.js → index-e32vy8jw.js} +22 -11
  25. package/dist/telegram/update-intake-work.d.ts +39 -0
  26. package/dist/telegram/update-intake-work.d.ts.map +1 -0
  27. package/dist/telegram/update-intake.d.ts +47 -9
  28. package/dist/telegram/update-intake.d.ts.map +1 -1
  29. package/dist/telegram/update-store-check.d.ts.map +1 -1
  30. package/dist/telegram/update-store-postgres.d.ts +2 -2
  31. package/dist/telegram/update-store-postgres.d.ts.map +1 -1
  32. package/dist/telegram/update-store-sqlite.d.ts +2 -2
  33. package/dist/telegram/update-store-sqlite.d.ts.map +1 -1
  34. package/dist/telegram/update-store.d.ts +52 -3
  35. package/dist/telegram/update-store.d.ts.map +1 -1
  36. package/dist/tools/cli/args-fields.d.ts +7 -1
  37. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  38. package/dist/tools/cli/args.d.ts.map +1 -1
  39. package/llms/cli.txt +9 -1
  40. package/llms/stitchkit.txt +10 -3
  41. package/llms/telegram.txt +59 -8
  42. package/llms/{upgrading-0.100.0-0.108.0.txt → upgrading-0.101.0-0.109.0.txt} +70 -72
  43. package/llms/upgrading-0.37.0.txt +176 -0
  44. package/llms/{upgrading-0.37.0-0.58.0.txt → upgrading-0.44.0-0.60.0.txt} +229 -169
  45. package/llms/{upgrading-0.59.0-0.71.0.txt → upgrading-0.61.0-0.73.0.txt} +122 -229
  46. package/llms/{upgrading-0.72.0-0.99.0.txt → upgrading-0.74.0-0.100.0.txt} +72 -122
  47. package/llms/upgrading.txt +5 -4
  48. package/llms-full.txt +145 -12
  49. package/llms.txt +9 -8
  50. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ # Upgrading 0.37.0 → 0.37.0 — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: Guide: Upgrading, 0.37.0 → 0.37.0 (docs/guide/upgrading.md)
7
+
8
+ ==============================================================================
9
+ # Guide: Upgrading, 0.37.0 → 0.37.0 (docs/guide/upgrading.md)
10
+ ==============================================================================
11
+
12
+ ## The 0.37 migration
13
+
14
+ Tool presentation is no longer an executable Zod parser. Replace the removed
15
+ flatten helpers with the JSON Schema compiler, and choose the explicit
16
+ `MountableTool` surface when using the advanced collection API:
17
+
18
+ ```ts
19
+ // before
20
+ const flat = flattenUnionsDeep(zodSchema)
21
+ mountable.schema
22
+
23
+ // after
24
+ const flat = flattenToolJsonSchema(
25
+ z.toJSONSchema(zodSchema, { target: 'draft-07', io: 'input' }),
26
+ )
27
+ mountable.presentationSchema // model/MCP/manifest JSON Schema
28
+ mountable.argumentSchema // executable CLI argument adapter only
29
+ ```
30
+
31
+ Contract and native handlers keep their original Zod schemas; MCP and agent SDK
32
+ adapters now forward raw arguments so defaults, coercions, refinements and
33
+ transforms execute exactly once inside Stitchkit. There are no compatibility
34
+ exports for `flattenDiscriminatedUnion`, `flattenUnionsDeep` or
35
+ `MountableTool.schema`. → ADR 0050
36
+
37
+ Tool-call hooks now take one options object. Migrate all three callbacks; there
38
+ are no positional overloads:
39
+
40
+ ```ts
41
+ // before
42
+ beforeToolCall: (toolName, args, context, endpoint) => {}
43
+ afterToolCall: (toolName, args, result, durationMs, context, endpoint, error) => {}
44
+ onToolError: (toolName, error, context, endpoint) => {}
45
+
46
+ // after
47
+ beforeToolCall: ({ toolName, args, context, endpoint }) => {}
48
+ afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {}
49
+ onToolError: ({ toolName, error, context, endpoint }) => {}
50
+ ```
51
+
52
+ ### MCP schema validation
53
+
54
+ The standalone validator now takes one object and live MCP configs carry the
55
+ same rules under `schemaValidation`. Migrate every positional call and every
56
+ `onIncompatibleSchema` field; there is no old-shape overload:
57
+
58
+ ```ts
59
+ // before
60
+ validateMcpSchemas(services, 'throw', logger, { requireTypedProperties: true })
61
+ createMcpHandler({ services, onIncompatibleSchema: 'throw' })
62
+
63
+ // after
64
+ validateMcpSchemas({ services, policy: 'throw', logger, requireTypedProperties: true })
65
+ createMcpHandler({ services, schemaValidation: { policy: 'throw' } })
66
+ ```
67
+
68
+ Put `extend` and `flattenUnionInput` beside `services`, not inside
69
+ `schemaValidation`. The handler applies the profile to the exact prepared schema
70
+ it advertises. Add `requirePortableFormats: true` when every custom JSON Schema
71
+ format must be rejected before a client sees it.
72
+
73
+ Native MCP registration also changed shape in 0.37. Move protected tools to the
74
+ framework registrar; keep an SDK-raw tool only by naming the opt-out:
75
+
76
+ ```ts
77
+ // before
78
+ nativeTools: (server, auth) => server.registerTool(name, config, handler)
79
+
80
+ // after — lifecycle, hooks and schema policy apply
81
+ nativeTools: ({ registerTool }, auth) => registerTool({
82
+ name, description, identity, input, output, handler,
83
+ })
84
+
85
+ // after — intentionally raw
86
+ nativeTools: ({ rawServer }, auth) => rawServer.registerTool(name, config, handler)
87
+ ```
88
+
89
+ If a tool hook annotated `endpoint` as `MethodDef`, remove that annotation or
90
+ use `OperationIdentity`: native operations have service/action/scope/method but
91
+ no HTTP path.
92
+
93
+ ### Node-facing server types
94
+
95
+ `stitchkit/server` remains Bun-concrete: an explicitly annotated `RawRoute`
96
+ still receives `BunServer`. `stitchkit/node` no longer drags Bun declarations
97
+ into a Node project. Its raw routes default the host server to `unknown`, and
98
+ its Socket.IO handle exposes only Node capabilities:
99
+
100
+ ```ts
101
+ // before — Node entry still leaked Bun-only fields and ambient types
102
+ const route: RawRoute = { handler: (_req, ctx) => ctx.server?.upgrade(...) }
103
+ const socket = await createSocketIOServer(config)
104
+ socket.websocket
105
+
106
+ // after — name a custom embedding host only when one exists
107
+ const route: RawRoute<MyHostServer> = {
108
+ handler: (_req, ctx) => useHost(ctx.server),
109
+ }
110
+ const socket = await createSocketIOServer(config)
111
+ socket.io
112
+ socket.attach(nodeHttpServer)
113
+ ```
114
+
115
+ Node consumers can remove `@types/bun` unless another dependency independently
116
+ requires it.
117
+
118
+ ### Managed server shutdown
119
+
120
+ `createServer()` and `serveNode()` now return the same structural managed
121
+ lifecycle. Replace every direct runtime stop and parallel Socket.IO close:
122
+
123
+ ```ts
124
+ // before — split ownership
125
+ const socket = await createSocketIOServer(config)
126
+ const server = createServer({
127
+ services,
128
+ websocket: socket.websocket,
129
+ rawRoutes: [socket.route],
130
+ })
131
+ server.stop()
132
+ await socket.io.close()
133
+
134
+ // after — one owner and one total deadline
135
+ const socket = await createSocketIOServer(config)
136
+ const server = createServer({ services, socket })
137
+ const result = await server.shutdown({ gracePeriodMs: 30_000 })
138
+ ```
139
+
140
+ On Node, keep the same `socket` field and replace `handle.close()` with
141
+ `handle.shutdown()`. Runtime-specific diagnostics move under `handle.runtime`;
142
+ do not use it as a second shutdown path. Standalone CLI/tools that create a
143
+ Socket.IO handle without an HTTP server call `await socket.close()`.
144
+
145
+ If Bun Socket.IO shares the port with a raw lane, keep the explicit composition
146
+ but let the server mount the Socket.IO route:
147
+
148
+ ```ts
149
+ createServer({
150
+ services,
151
+ socket,
152
+ websocket: composeWebSocketHandlers([
153
+ webSocketLane({ match: isRaw, handlers: rawHandlers }),
154
+ socketIoLane(socket.websocket),
155
+ ]),
156
+ rawRoutes: [rawUpgradeRoute],
157
+ })
158
+ ```
159
+
160
+ Move native Bun `routes` entries to `rawRoutes`. Native routes run before the
161
+ Fetch handler and therefore cannot participate in admission or drain. Wire
162
+ `SIGTERM`/`SIGINT` in the application; the first signal starts `shutdown()`, and
163
+ a later signal may abort the same controller. Close MCP, databases and queues
164
+ after the server result—those resources remain application-owned.
165
+
166
+ Move a handshake policy from the Node-only callback shape inside
167
+ `serverOptions` to the runtime-neutral top-level policy. It receives a Web
168
+ `Request`, may be async, and returns whether to admit the handshake:
169
+
170
+ ```ts
171
+ // before
172
+ serverOptions: { allowRequest: (request, done) => done(null, allowed(request)) }
173
+
174
+ // after
175
+ allowRequest: (request) => allowed(request)
176
+ ```
@@ -1,14 +1,240 @@
1
- # Upgrading 0.37.0 → 0.58.0 — stitchkit docs slice
1
+ # Upgrading 0.44.0 → 0.60.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.37.0 → 0.58.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.44.0 → 0.60.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.37.0 → 0.58.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.44.0 → 0.60.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.60.0
13
+
14
+ ### close() says what it achieved
15
+
16
+ `AgentRuntime.close()` returned `Promise<void>` and its contract promised three
17
+ things that cannot hold together. It now returns
18
+ `{ settled, timedOut, remaining }`.
19
+
20
+ Nothing breaks if you ignore the value:
21
+
22
+ ```ts
23
+ await runtime.close({ gracePeriodMs: 30_000, forceTimeoutMs: 5_000 })
24
+ ```
25
+
26
+ What DOES change is any code that relied on the old sentence "`close()` never
27
+ returns while a run is still in flight" while naming a `forceTimeoutMs`. That
28
+ was never true — the force budget exists precisely to stop waiting — so if your
29
+ process exits straight after `close()`, read the result:
30
+
31
+ ```ts
32
+ const closed = await runtime.close({ gracePeriodMs: 30_000, forceTimeoutMs: 5_000 })
33
+ if (!closed.settled) {
34
+ // `closed.remaining` runs were still in flight. They stay recoverable
35
+ // through `scanRecoverable`; close never marks them terminal on its own.
36
+ logger.warn({ remaining: closed.remaining }, 'exiting with runs in flight')
37
+ }
38
+ ```
39
+
40
+ If you want the old guarantee, omit `forceTimeoutMs`: that is the one
41
+ combination in which `close()` cannot return with a run in flight — and its wait
42
+ is unbounded, which is the trade.
43
+
44
+ A TypeScript consumer that wrote `const done: void = await runtime.close()` is
45
+ the only shape that stops compiling.
46
+
47
+ ### one name per concept
48
+
49
+ `AgentModelDeclaration` was a bare alias of `AgentModelDescriptor`, so the same
50
+ type reached consumers under two exported names:
51
+
52
+ ```ts
53
+ // before
54
+ defineModelRegistry<Record<string, AgentModelDeclaration>>({ … })
55
+ // after
56
+ defineModelRegistry<Record<string, AgentModelDescriptor>>({ … })
57
+ ```
58
+
59
+ A find-and-replace covers it; there is no behavioural change. Kept name is
60
+ `AgentModelDescriptor`, the one paired with `AgentModelDescriptorSchema` and
61
+ returned by `registry.descriptor()`.
62
+
63
+ Two names that look like the same case and are **not** being merged:
64
+ `SocketEventMap` aliases Socket.IO's `EventsMap` so a vendor's name stays out of
65
+ our signatures, and `AgentRun.ownerId` holds the same value a runtime publishes
66
+ as `runtimeEpoch` on its events. The latter is one identity in two roles — "who
67
+ owns this run" and "which runtime emitted this event" — and both declarations
68
+ now say so. Renaming either would make the fencing comparison read worse.
69
+
70
+ ### a reachable public surface
71
+
72
+ Three exports that the public API required and did not provide, plus one option
73
+ it accepted and ignored.
74
+
75
+ ```ts
76
+ // before — identified by string, because the class was exported nowhere
77
+ if (error instanceof Error && error.name === 'AgentRuntimeConflictError') …
78
+ // after
79
+ import { AgentRuntimeConflictError } from 'stitchkit/agent-runtime'
80
+ if (error instanceof AgentRuntimeConflictError) …
81
+ ```
82
+
83
+ `ActivityTokenBrand` is exported, so `ActivityProjection` can be implemented by
84
+ a test double. `STITCH_ERROR_STATUS` gained `APPLICATION_NOT_ACCEPTING` (503) —
85
+ only an exhaustive `satisfies Record<StitchErrorCode, …>` map stops compiling,
86
+ and the fix is one line. (`GRAMMY_WEBHOOK_NOT_ACCEPTING` joined it in 0.60.1,
87
+ under the same rule — see ADR 0105. This section briefly said the opposite: that
88
+ an adapter's code stays out of the registry. It does not, and leaving it out was
89
+ the worse of the two, because `isStitchErrorCode` then answered `false` and the
90
+ code reached the wire spelled stitchkit's way, past both `codeMap` and
91
+ `unmappedCode`.)
92
+
93
+ `application.shutdown()` no longer accepts `retryAfterSeconds`. Delete it from
94
+ the call — the kernel never read it. If you meant the HTTP `Retry-After` a
95
+ draining server sends, that lives on `managedServerResource({ retryAfterSeconds })`,
96
+ where it always did the work.
97
+
98
+ ### one shutdown vocabulary
99
+
100
+ ```ts
101
+ // before
102
+ await runtime.close({ drainTimeoutMs: 30_000, forceTimeoutMs: 5_000 })
103
+ // after
104
+ await runtime.close({ gracePeriodMs: 30_000, forceTimeoutMs: 5_000 })
105
+ ```
106
+
107
+ The rename is mechanical. The behaviour change under it is not, and it is the
108
+ reason the rename waited: two combinations were traps.
109
+ `close({ drainTimeoutMs })` with no force budget aborted the runs and returned
110
+ **without waiting for them to settle** — so naming a budget gave a weaker
111
+ guarantee than naming none — and `close({ forceTimeoutMs })` with no drain
112
+ budget never read the force budget, leaving an unbounded wait. Both now behave
113
+ as their names say.
114
+
115
+ > **Superseded.** This section once ended "and `close()` never returns while a
116
+ > run is in flight". That was never true with a force budget — the budget exists
117
+ > to stop waiting — and `close()` now returns `{ settled, timedOut, remaining }`
118
+ > instead of promising it. See *Released migration: 0.60.0 → close() says what
119
+ > it achieved*, above.
120
+
121
+ If your shutdown path measured how long `close()` took, expect it to take
122
+ longer in exactly the case where it used to return early — that is the fix, not
123
+ a regression. Defaults stay per-surface: `ShutdownOptions.gracePeriodMs`
124
+ defaults to 30 seconds, and the runtime's omitted budget still means "abort
125
+ immediately", the behaviour `close()` has always had.
126
+
127
+ ### unresolved attachments are omitted
128
+
129
+ `history.unresolvedFile` defaulted to `text`, and the placeholder it produced
130
+ carried the storage reference:
131
+
132
+ ```ts
133
+ // before — the provider received your object key
134
+ // "[attachment: s3://bucket/tenants/42/invoice.pdf]"
135
+ history: {}
136
+ // after — omitted entirely by default; ask for a placeholder explicitly
137
+ history: { unresolvedFile: 'text' } // "[attachment: invoice.pdf]"
138
+ ```
139
+
140
+ Two things to check. If a prompt relied on the model seeing *something* where an
141
+ unresolved file was, set `unresolvedFile: 'text'` — behaviour otherwise changes
142
+ silently, since an omitted part produces no error. And if any stored transcript
143
+ or provider log contains the old placeholder, it contains your storage layout;
144
+ treat those as disclosed. The `error` policy still names the reference, because
145
+ it is thrown into your process rather than sent upstream.
146
+
147
+ ### one bounded recoverable scan
148
+
149
+ `AgentRuntimeStore` had two scans: a mandatory unbounded one and an optional
150
+ paged one. The runtime only ever called the optional one, so implementing the
151
+ interface as written produced a store that threw on its first `recover()`:
152
+
153
+ ```ts
154
+ // before — the mandatory member was dead, the needed one was optional
155
+ {
156
+ scanRecoverable: () => loadEveryRecoverableSnapshot(),
157
+ scanRecoverablePage: ({ cursor, limit }) => page(cursor, limit),
158
+ }
159
+ // after — one member, the bounded signature the driver already used
160
+ { scanRecoverable: ({ cursor, limit }) => page(cursor, limit) }
161
+ ```
162
+
163
+ Delete the unbounded implementation rather than porting it: loading every
164
+ recoverable conversation to start is the shape 0.59.0 and ADR 0101 moved away
165
+ from, and nothing calls it now. If you built on `createAgentRuntimeStore()` you
166
+ have nothing to do **if you only pass it to the runtime** — it implements the
167
+ bounded page for you from the same driver member. If you CALL it yourself, the
168
+ member changed shape: `scanRecoverable()` took no argument and returned
169
+ snapshots; it now takes `{ cursor?, limit }` — `limit` is required — and returns
170
+ one page of descriptors.
171
+ ```ts
172
+ // before: const stale = await store.scanRecoverable()
173
+ // after: const { items, nextCursor } = await store.scanRecoverable({ limit: 100 })
174
+ ```
175
+
176
+ ### published application status
177
+
178
+ `createApplicationHealthHandler` and `createApplicationOperationalHandlers` no
179
+ longer serialise the whole `ApplicationSnapshot`. They publish
180
+ `ApplicationStatusProjection` — the verdict plus resource counts:
181
+
182
+ ```ts
183
+ // before — the response named every resource and its dependency edges
184
+ const { resources } = await fetch('/status').then((r) => r.json())
185
+ resources[0].dependsOn
186
+ // after — the topology is read in-process, where it always belonged
187
+ app.getSnapshot().resources[0].dependsOn
188
+ ```
189
+
190
+ The consequence to check is not compilation — it is whatever already consumes
191
+ these routes. A dashboard that drew the dependency graph from `/status`, or an
192
+ alert keyed on `admission.pending`, goes blank rather than red: the fields are
193
+ absent, not zero. Both are available from `getSnapshot()`, so the fix is to read
194
+ them in the process that owns the application and publish them on a channel you
195
+ control. If a route was reachable from outside your network, treat the previous
196
+ payload as disclosed and rotate nothing but assume the topology is known.
197
+
198
+ ## Released migration: 0.59.0
199
+
200
+ ### Normalized agent runtime persistence
201
+
202
+ `AgentRuntimeStoreDriver` no longer reads and rewrites a lifetime `AgentStoredState` JSON
203
+ aggregate. Migrate that row and its recoverable/archive projections once:
204
+
205
+ 1. Copy `conversationId` and `version` into the bounded runtime head.
206
+ 2. Write every `AgentStoredState.runs[]` entry as one normalized run record.
207
+ 3. Write every `AgentStoredState.admissions[]` entry as one admission receipt and copy its
208
+ canonical input into that receipt.
209
+ 4. For terminal runs, retain the canonical assistant on the run record before deleting any old
210
+ history/archive rows.
211
+ 5. Replace the recoverable projection with an index over normalized run `state`.
212
+ 6. Cut the adapter over atomically; there is no compatibility driver or dual-write mode.
213
+
214
+ ```ts
215
+ // before
216
+ createAgentRuntimeStore({
217
+ state: { load, compareAndSwap },
218
+ history: { load, loadById, apply },
219
+ scanRecoverable,
220
+ })
221
+
222
+ // after
223
+ createAgentRuntimeStore({
224
+ head: { load, compareAndSwap },
225
+ runs: { load, loadByAssistantMessageId, loadMany, listActive, save },
226
+ admissions: { load, loadByInputMessageId, create },
227
+ history: { load, apply },
228
+ scanRecoverable,
229
+ })
230
+ ```
231
+
232
+ `AgentRuntimeHeadSchema`, `AgentStoredRunSchema` and `AgentAdmissionReceiptSchema` replace
233
+ `AgentStoredStateSchema` and `AgentAdmissionIdentitySchema`. Run
234
+ `runAgentStoreConformance()` against the migrated adapter before switching production traffic.
235
+ If an application implements `AgentRuntimeStore` directly, its duplicate result must also include
236
+ the canonical `run` and the retained `assistant` for a terminal run.
237
+
12
238
  ## Released migration: 0.58.0
13
239
 
14
240
  ### The default history projection rejects invalid chronology
@@ -1007,169 +1233,3 @@ createHttpClient({
1007
1233
 
1008
1234
  There is no implicit `/auth/` suppression. Omit `suppressUnauthorizedFor` when
1009
1235
  every 401 should emit the global `unauthorized` event.
1010
-
1011
- ## The 0.37 migration
1012
-
1013
- Tool presentation is no longer an executable Zod parser. Replace the removed
1014
- flatten helpers with the JSON Schema compiler, and choose the explicit
1015
- `MountableTool` surface when using the advanced collection API:
1016
-
1017
- ```ts
1018
- // before
1019
- const flat = flattenUnionsDeep(zodSchema)
1020
- mountable.schema
1021
-
1022
- // after
1023
- const flat = flattenToolJsonSchema(
1024
- z.toJSONSchema(zodSchema, { target: 'draft-07', io: 'input' }),
1025
- )
1026
- mountable.presentationSchema // model/MCP/manifest JSON Schema
1027
- mountable.argumentSchema // executable CLI argument adapter only
1028
- ```
1029
-
1030
- Contract and native handlers keep their original Zod schemas; MCP and agent SDK
1031
- adapters now forward raw arguments so defaults, coercions, refinements and
1032
- transforms execute exactly once inside Stitchkit. There are no compatibility
1033
- exports for `flattenDiscriminatedUnion`, `flattenUnionsDeep` or
1034
- `MountableTool.schema`. → ADR 0050
1035
-
1036
- Tool-call hooks now take one options object. Migrate all three callbacks; there
1037
- are no positional overloads:
1038
-
1039
- ```ts
1040
- // before
1041
- beforeToolCall: (toolName, args, context, endpoint) => {}
1042
- afterToolCall: (toolName, args, result, durationMs, context, endpoint, error) => {}
1043
- onToolError: (toolName, error, context, endpoint) => {}
1044
-
1045
- // after
1046
- beforeToolCall: ({ toolName, args, context, endpoint }) => {}
1047
- afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {}
1048
- onToolError: ({ toolName, error, context, endpoint }) => {}
1049
- ```
1050
-
1051
- ### MCP schema validation
1052
-
1053
- The standalone validator now takes one object and live MCP configs carry the
1054
- same rules under `schemaValidation`. Migrate every positional call and every
1055
- `onIncompatibleSchema` field; there is no old-shape overload:
1056
-
1057
- ```ts
1058
- // before
1059
- validateMcpSchemas(services, 'throw', logger, { requireTypedProperties: true })
1060
- createMcpHandler({ services, onIncompatibleSchema: 'throw' })
1061
-
1062
- // after
1063
- validateMcpSchemas({ services, policy: 'throw', logger, requireTypedProperties: true })
1064
- createMcpHandler({ services, schemaValidation: { policy: 'throw' } })
1065
- ```
1066
-
1067
- Put `extend` and `flattenUnionInput` beside `services`, not inside
1068
- `schemaValidation`. The handler applies the profile to the exact prepared schema
1069
- it advertises. Add `requirePortableFormats: true` when every custom JSON Schema
1070
- format must be rejected before a client sees it.
1071
-
1072
- Native MCP registration also changed shape in 0.37. Move protected tools to the
1073
- framework registrar; keep an SDK-raw tool only by naming the opt-out:
1074
-
1075
- ```ts
1076
- // before
1077
- nativeTools: (server, auth) => server.registerTool(name, config, handler)
1078
-
1079
- // after — lifecycle, hooks and schema policy apply
1080
- nativeTools: ({ registerTool }, auth) => registerTool({
1081
- name, description, identity, input, output, handler,
1082
- })
1083
-
1084
- // after — intentionally raw
1085
- nativeTools: ({ rawServer }, auth) => rawServer.registerTool(name, config, handler)
1086
- ```
1087
-
1088
- If a tool hook annotated `endpoint` as `MethodDef`, remove that annotation or
1089
- use `OperationIdentity`: native operations have service/action/scope/method but
1090
- no HTTP path.
1091
-
1092
- ### Node-facing server types
1093
-
1094
- `stitchkit/server` remains Bun-concrete: an explicitly annotated `RawRoute`
1095
- still receives `BunServer`. `stitchkit/node` no longer drags Bun declarations
1096
- into a Node project. Its raw routes default the host server to `unknown`, and
1097
- its Socket.IO handle exposes only Node capabilities:
1098
-
1099
- ```ts
1100
- // before — Node entry still leaked Bun-only fields and ambient types
1101
- const route: RawRoute = { handler: (_req, ctx) => ctx.server?.upgrade(...) }
1102
- const socket = await createSocketIOServer(config)
1103
- socket.websocket
1104
-
1105
- // after — name a custom embedding host only when one exists
1106
- const route: RawRoute<MyHostServer> = {
1107
- handler: (_req, ctx) => useHost(ctx.server),
1108
- }
1109
- const socket = await createSocketIOServer(config)
1110
- socket.io
1111
- socket.attach(nodeHttpServer)
1112
- ```
1113
-
1114
- Node consumers can remove `@types/bun` unless another dependency independently
1115
- requires it.
1116
-
1117
- ### Managed server shutdown
1118
-
1119
- `createServer()` and `serveNode()` now return the same structural managed
1120
- lifecycle. Replace every direct runtime stop and parallel Socket.IO close:
1121
-
1122
- ```ts
1123
- // before — split ownership
1124
- const socket = await createSocketIOServer(config)
1125
- const server = createServer({
1126
- services,
1127
- websocket: socket.websocket,
1128
- rawRoutes: [socket.route],
1129
- })
1130
- server.stop()
1131
- await socket.io.close()
1132
-
1133
- // after — one owner and one total deadline
1134
- const socket = await createSocketIOServer(config)
1135
- const server = createServer({ services, socket })
1136
- const result = await server.shutdown({ gracePeriodMs: 30_000 })
1137
- ```
1138
-
1139
- On Node, keep the same `socket` field and replace `handle.close()` with
1140
- `handle.shutdown()`. Runtime-specific diagnostics move under `handle.runtime`;
1141
- do not use it as a second shutdown path. Standalone CLI/tools that create a
1142
- Socket.IO handle without an HTTP server call `await socket.close()`.
1143
-
1144
- If Bun Socket.IO shares the port with a raw lane, keep the explicit composition
1145
- but let the server mount the Socket.IO route:
1146
-
1147
- ```ts
1148
- createServer({
1149
- services,
1150
- socket,
1151
- websocket: composeWebSocketHandlers([
1152
- webSocketLane({ match: isRaw, handlers: rawHandlers }),
1153
- socketIoLane(socket.websocket),
1154
- ]),
1155
- rawRoutes: [rawUpgradeRoute],
1156
- })
1157
- ```
1158
-
1159
- Move native Bun `routes` entries to `rawRoutes`. Native routes run before the
1160
- Fetch handler and therefore cannot participate in admission or drain. Wire
1161
- `SIGTERM`/`SIGINT` in the application; the first signal starts `shutdown()`, and
1162
- a later signal may abort the same controller. Close MCP, databases and queues
1163
- after the server result—those resources remain application-owned.
1164
-
1165
- Move a handshake policy from the Node-only callback shape inside
1166
- `serverOptions` to the runtime-neutral top-level policy. It receives a Web
1167
- `Request`, may be async, and returns whether to admit the handshake:
1168
-
1169
- ```ts
1170
- // before
1171
- serverOptions: { allowRequest: (request, done) => done(null, allowed(request)) }
1172
-
1173
- // after
1174
- allowRequest: (request) => allowed(request)
1175
- ```