stitchkit 0.54.0 → 0.56.0

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 (120) hide show
  1. package/README.md +4 -1
  2. package/dist/browser/socket-io.d.ts +25 -7
  3. package/dist/browser/socket-io.d.ts.map +1 -1
  4. package/dist/cli.js +14 -9
  5. package/dist/contract/errors.d.ts +7 -0
  6. package/dist/contract/errors.d.ts.map +1 -1
  7. package/dist/contract/file-ref.d.ts +13 -0
  8. package/dist/contract/file-ref.d.ts.map +1 -0
  9. package/dist/contract/index.d.ts +1 -0
  10. package/dist/contract/index.d.ts.map +1 -1
  11. package/dist/contract/index.js +21 -17
  12. package/dist/files/boundary.d.ts +55 -0
  13. package/dist/files/boundary.d.ts.map +1 -0
  14. package/dist/files.d.ts +3 -0
  15. package/dist/files.d.ts.map +1 -0
  16. package/dist/files.js +16 -0
  17. package/dist/index-1bx83sw4.js +4 -0
  18. package/dist/{index-pfqjb5xy.js → index-28cqssm3.js} +30 -5
  19. package/dist/index-557by2db.js +34 -0
  20. package/dist/index-5r13htq1.js +283 -0
  21. package/dist/index-6k1937bx.js +20 -0
  22. package/dist/index-6y759j86.js +1031 -0
  23. package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
  24. package/dist/index-bfcpjw20.js +363 -0
  25. package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
  26. package/dist/{index-7vb44hy8.js → index-escqg10p.js} +193 -751
  27. package/dist/{index-173tvqcp.js → index-j3dem06f.js} +9 -114
  28. package/dist/{index-gvha32jh.js → index-jcc611vh.js} +2 -2
  29. package/dist/{index-z7761jk2.js → index-n4nfa7gh.js} +16 -22
  30. package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
  31. package/dist/index-sbdmyz75.js +18 -0
  32. package/dist/{index-b15rtha1.js → index-sm2tjx06.js} +98 -3
  33. package/dist/{index-yez419px.js → index-xy8fmh6w.js} +99 -7
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +188 -40
  37. package/dist/node.js +31 -27
  38. package/dist/observability/index.js +28 -25
  39. package/dist/react.js +2 -2
  40. package/dist/realtime/contract.d.ts +9 -0
  41. package/dist/realtime/contract.d.ts.map +1 -1
  42. package/dist/realtime/index.d.ts +2 -1
  43. package/dist/realtime/index.d.ts.map +1 -1
  44. package/dist/realtime/request.d.ts +21 -0
  45. package/dist/realtime/request.d.ts.map +1 -0
  46. package/dist/realtime/socket.d.ts +3 -0
  47. package/dist/realtime/socket.d.ts.map +1 -1
  48. package/dist/remote.d.ts +9 -0
  49. package/dist/remote.d.ts.map +1 -0
  50. package/dist/remote.js +79 -0
  51. package/dist/server/context-contribution.d.ts +18 -0
  52. package/dist/server/context-contribution.d.ts.map +1 -0
  53. package/dist/server/context.d.ts.map +1 -1
  54. package/dist/server/index.d.ts +2 -1
  55. package/dist/server/index.d.ts.map +1 -1
  56. package/dist/server/index.js +136 -88
  57. package/dist/server/lifecycle.d.ts +4 -0
  58. package/dist/server/lifecycle.d.ts.map +1 -0
  59. package/dist/server/middleware/auth.d.ts +64 -9
  60. package/dist/server/middleware/auth.d.ts.map +1 -1
  61. package/dist/server/types.d.ts +1 -1
  62. package/dist/server/types.d.ts.map +1 -1
  63. package/dist/testing/surface-conformance.d.ts +180 -0
  64. package/dist/testing/surface-conformance.d.ts.map +1 -0
  65. package/dist/testing/surface-manifest.d.ts +255 -0
  66. package/dist/testing/surface-manifest.d.ts.map +1 -0
  67. package/dist/testing.d.ts +2 -0
  68. package/dist/testing.d.ts.map +1 -1
  69. package/dist/testing.js +714 -3
  70. package/dist/tools/agent.d.ts.map +1 -1
  71. package/dist/tools/async-operation.d.ts +360 -0
  72. package/dist/tools/async-operation.d.ts.map +1 -0
  73. package/dist/tools/cli-args.d.ts +1 -1
  74. package/dist/tools/cli-args.d.ts.map +1 -1
  75. package/dist/tools/cli-format.d.ts +2 -2
  76. package/dist/tools/cli-format.d.ts.map +1 -1
  77. package/dist/tools/cli-wait.d.ts +11 -2
  78. package/dist/tools/cli-wait.d.ts.map +1 -1
  79. package/dist/tools/cli.d.ts +2 -0
  80. package/dist/tools/cli.d.ts.map +1 -1
  81. package/dist/tools/define-download-tool.d.ts +6 -5
  82. package/dist/tools/define-download-tool.d.ts.map +1 -1
  83. package/dist/tools/define-upload-tool.d.ts +3 -1
  84. package/dist/tools/define-upload-tool.d.ts.map +1 -1
  85. package/dist/tools/define-wait-tool.d.ts +1 -1
  86. package/dist/tools/define-wait-tool.d.ts.map +1 -1
  87. package/dist/tools/download-core.d.ts +5 -10
  88. package/dist/tools/download-core.d.ts.map +1 -1
  89. package/dist/tools/internal/surface-projector.d.ts +101 -0
  90. package/dist/tools/internal/surface-projector.d.ts.map +1 -0
  91. package/dist/tools/lifecycle.d.ts +4 -0
  92. package/dist/tools/lifecycle.d.ts.map +1 -0
  93. package/dist/tools/managed-file-error.d.ts +6 -0
  94. package/dist/tools/managed-file-error.d.ts.map +1 -0
  95. package/dist/tools/mcp-prepare.d.ts +3 -21
  96. package/dist/tools/mcp-prepare.d.ts.map +1 -1
  97. package/dist/tools/mcp-round-policy.d.ts +9 -0
  98. package/dist/tools/mcp-round-policy.d.ts.map +1 -0
  99. package/dist/tools/mcp-round.d.ts +0 -4
  100. package/dist/tools/mcp-round.d.ts.map +1 -1
  101. package/dist/tools/mount-download.d.ts +5 -4
  102. package/dist/tools/mount-download.d.ts.map +1 -1
  103. package/dist/tools/mount-upload.d.ts +4 -2
  104. package/dist/tools/mount-upload.d.ts.map +1 -1
  105. package/dist/tools/mount.d.ts +5 -9
  106. package/dist/tools/mount.d.ts.map +1 -1
  107. package/dist/tools/runtime-tool.d.ts.map +1 -1
  108. package/dist/tools/surface.d.ts +7 -0
  109. package/dist/tools/surface.d.ts.map +1 -1
  110. package/dist/tools/upload-core.d.ts +3 -2
  111. package/dist/tools/upload-core.d.ts.map +1 -1
  112. package/dist/tools/view-file.d.ts +4 -3
  113. package/dist/tools/view-file.d.ts.map +1 -1
  114. package/dist/tools/wait-core.d.ts +6 -1
  115. package/dist/tools/wait-core.d.ts.map +1 -1
  116. package/dist/tools.d.ts +3 -2
  117. package/dist/tools.d.ts.map +1 -1
  118. package/dist/tools.js +705 -599
  119. package/llms-full.txt +727 -34
  120. package/package.json +10 -2
package/dist/testing.js CHANGED
@@ -1,9 +1,702 @@
1
+ import {
2
+ RealtimeRequestDisconnectedError,
3
+ RealtimeRequestInvalidAcknowledgementError,
4
+ RealtimeRequestTimeoutError
5
+ } from "./index-557by2db.js";
6
+ import {
7
+ mcpProjectionCandidate,
8
+ prepareProjectedMcpTools,
9
+ projectToolSurface
10
+ } from "./index-6y759j86.js";
11
+ import {
12
+ toJsonSchema
13
+ } from "./index-cby4ar3v.js";
1
14
  import {
2
15
  createClient,
3
16
  createClients
4
- } from "./index-e1es808r.js";
17
+ } from "./index-qh4xevf5.js";
5
18
  import"./index-48ffdxgk.js";
6
- import"./index-bmnhqtya.js";
19
+ import {
20
+ isRecord
21
+ } from "./index-7eq9va6e.js";
22
+ import"./index-1bx83sw4.js";
23
+
24
+ // src/testing/surface-conformance.ts
25
+ import { z as z2 } from "zod";
26
+
27
+ // src/testing/surface-manifest.ts
28
+ import { createHash } from "node:crypto";
29
+ import { z } from "zod";
30
+ var HttpMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
31
+ var ToolTransportSchema = z.enum(["MCP", "AGENT", "CLI"]);
32
+ var SurfaceTransportSchema = z.enum(["HTTP", "MCP", "AGENT", "CLI", "REALTIME"]);
33
+ var SurfaceSchemaDigestsSchema = z.object({
34
+ params: z.string().nullable(),
35
+ input: z.string().nullable(),
36
+ output: z.string().nullable(),
37
+ multipart: z.string().nullable()
38
+ });
39
+ var SurfaceManifestOperationSchema = z.object({
40
+ kind: z.enum(["contract", "runtime"]),
41
+ service: z.string(),
42
+ action: z.string(),
43
+ method: HttpMethodSchema,
44
+ scope: z.string().nullable(),
45
+ description: z.string(),
46
+ schemas: SurfaceSchemaDigestsSchema,
47
+ http: z.array(z.object({ method: HttpMethodSchema, path: z.string() }))
48
+ });
49
+ var SurfaceManifestToolSchema = z.object({
50
+ kind: z.enum(["contract", "runtime"]),
51
+ service: z.string(),
52
+ action: z.string(),
53
+ name: z.string(),
54
+ input: z.string()
55
+ });
56
+ var SurfaceManifestToolSurfaceSchema = z.object({
57
+ transport: ToolTransportSchema,
58
+ surface: z.string().nullable(),
59
+ tools: z.array(SurfaceManifestToolSchema)
60
+ });
61
+ var SurfaceManifestCliCommandSchema = z.object({
62
+ name: z.string(),
63
+ description: z.string(),
64
+ input: z.string(),
65
+ output: z.string().nullable()
66
+ });
67
+ var SurfaceManifestExtensionSchema = z.object({
68
+ name: z.string(),
69
+ transport: SurfaceTransportSchema
70
+ });
71
+ var SurfaceRealtimeSchemaPairSchema = z.object({
72
+ input: z.string(),
73
+ output: z.string()
74
+ });
75
+ var SurfaceManifestRealtimeEventSchema = z.object({
76
+ contract: z.string(),
77
+ direction: z.enum(["serverToClient", "clientToServer"]),
78
+ event: z.string(),
79
+ args: SurfaceRealtimeSchemaPairSchema,
80
+ acknowledgement: SurfaceRealtimeSchemaPairSchema.nullable()
81
+ });
82
+ var SurfaceManifestSchema = z.object({
83
+ manifestVersion: z.literal(2),
84
+ digestVersion: z.literal(1),
85
+ operations: z.array(SurfaceManifestOperationSchema),
86
+ toolSurfaces: z.array(SurfaceManifestToolSurfaceSchema),
87
+ realtimeContracts: z.array(z.string()),
88
+ realtime: z.array(SurfaceManifestRealtimeEventSchema),
89
+ cliOnly: z.array(SurfaceManifestCliCommandSchema),
90
+ extensions: z.array(SurfaceManifestExtensionSchema)
91
+ });
92
+ function compareCodepoints(left, right) {
93
+ if (left < right)
94
+ return -1;
95
+ if (left > right)
96
+ return 1;
97
+ return 0;
98
+ }
99
+ function canonicalValue(value) {
100
+ if (Array.isArray(value))
101
+ return value.map(canonicalValue);
102
+ if (!isRecord(value))
103
+ return value;
104
+ const result = {};
105
+ for (const key of Object.keys(value).sort(compareCodepoints)) {
106
+ result[key] = canonicalValue(value[key]);
107
+ }
108
+ return result;
109
+ }
110
+ function serializeSurfaceValue(value) {
111
+ return JSON.stringify(canonicalValue(value));
112
+ }
113
+ function digestValue(value) {
114
+ return createHash("sha256").update(serializeSurfaceValue(value)).digest("hex").slice(0, 16);
115
+ }
116
+ function canonicalSchemaValue(value, parentKey) {
117
+ if (Array.isArray(value)) {
118
+ const entries = value.map((entry) => canonicalSchemaValue(entry));
119
+ return parentKey === "required" && entries.every((entry) => typeof entry === "string") ? entries.sort((left, right) => compareCodepoints(String(left), String(right))) : entries;
120
+ }
121
+ if (!isRecord(value))
122
+ return value;
123
+ const result = {};
124
+ for (const key of Object.keys(value).sort(compareCodepoints)) {
125
+ result[key] = canonicalSchemaValue(value[key], key);
126
+ }
127
+ return result;
128
+ }
129
+ function schemaDigest(schema, io) {
130
+ if (!schema)
131
+ return null;
132
+ return digestValue(canonicalSchemaValue(toJsonSchema(schema, io, "any")));
133
+ }
134
+ function presentationDigest(schema) {
135
+ return digestValue(canonicalSchemaValue(schema));
136
+ }
137
+ function multipartDigest(value) {
138
+ return value === undefined ? null : digestValue(value);
139
+ }
140
+ function joinPath(...parts) {
141
+ const joined = parts.filter((part) => Boolean(part)).map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
142
+ return `/${joined}`;
143
+ }
144
+ function operationKey(kind, service, action) {
145
+ return `${kind}\x00${service}\x00${action}`;
146
+ }
147
+ function operationFrom(kind, source) {
148
+ return {
149
+ kind,
150
+ service: source.serviceName,
151
+ action: source.key,
152
+ method: source.method,
153
+ scope: source.scope ?? null,
154
+ description: source.desc,
155
+ schemas: {
156
+ params: schemaDigest(source.paramsSchema, "input"),
157
+ input: schemaDigest(source.inputSchema, "input"),
158
+ output: schemaDigest(source.outputSchema, "output"),
159
+ multipart: multipartDigest(source.multipart)
160
+ },
161
+ http: []
162
+ };
163
+ }
164
+ function operationFingerprint(source) {
165
+ const mcp = source.mcp ? {
166
+ inputRequired: source.mcp.inputRequired.map((request) => ({
167
+ key: request.key,
168
+ message: request.message,
169
+ schema: schemaDigest(request.schema, "input")
170
+ }))
171
+ } : null;
172
+ return serializeSurfaceValue({
173
+ method: source.method,
174
+ serviceName: source.serviceName,
175
+ key: source.key,
176
+ desc: source.desc,
177
+ scope: source.scope ?? null,
178
+ path: source.path ?? null,
179
+ expose: source.expose ? [...source.expose].sort(compareCodepoints) : null,
180
+ toolName: source.toolName ?? null,
181
+ annotations: source.annotations ?? null,
182
+ ui: source.ui ?? null,
183
+ mcp,
184
+ rawBody: source.rawBody ?? false,
185
+ rawResponse: source.rawResponse ?? false,
186
+ responseMeta: source.responseMeta ?? null,
187
+ maxJsonBodyBytes: source.maxJsonBodyBytes ?? null,
188
+ idempotent: source.idempotent ?? false,
189
+ contentType: source.contentType ?? null,
190
+ meta: source.meta ?? null,
191
+ schemas: {
192
+ params: schemaDigest(source.paramsSchema, "input"),
193
+ input: schemaDigest(source.inputSchema, "input"),
194
+ output: schemaDigest(source.outputSchema, "output"),
195
+ multipart: multipartDigest(source.multipart)
196
+ }
197
+ });
198
+ }
199
+ function sortTools(tools) {
200
+ return tools.sort((left, right) => compareCodepoints(left.name, right.name) || compareCodepoints(left.service, right.service) || compareCodepoints(left.action, right.action) || compareCodepoints(left.kind, right.kind));
201
+ }
202
+ function buildSurfaceManifest(config) {
203
+ const operations = new Map;
204
+ const operationFingerprints = new Map;
205
+ const operationDefinitions = new Map;
206
+ const addOperation = (kind, source) => {
207
+ const candidate = operationFrom(kind, source);
208
+ const key = operationKey(kind, candidate.service, candidate.action);
209
+ const definition = source.definitionToken ?? source;
210
+ const existing = operations.get(key);
211
+ if (!existing) {
212
+ operations.set(key, candidate);
213
+ operationFingerprints.set(key, operationFingerprint(source));
214
+ operationDefinitions.set(key, definition);
215
+ return candidate;
216
+ }
217
+ if (operationDefinitions.get(key) !== definition || operationFingerprints.get(key) !== operationFingerprint(source)) {
218
+ throw new Error(`Conflicting ${kind} operation identity ${candidate.service}.${candidate.action}`);
219
+ }
220
+ return existing;
221
+ };
222
+ const addHttp = (service, prefix) => {
223
+ for (const method of Object.values(service.methods)) {
224
+ const operation = addOperation("contract", method);
225
+ if (method.expose && !method.expose.includes("HTTP"))
226
+ continue;
227
+ const route = {
228
+ method: method.method,
229
+ path: joinPath(prefix, service.prefix, method.path)
230
+ };
231
+ if (!operation.http.some((entry) => entry.method === route.method && entry.path === route.path)) {
232
+ operation.http.push(route);
233
+ }
234
+ }
235
+ };
236
+ for (const service of config.services ?? []) {
237
+ addHttp(service, config.scopePrefixes?.[service.scope] ?? "");
238
+ }
239
+ for (const group of config.groups ?? []) {
240
+ for (const service of group.services)
241
+ addHttp(service, group.pathPrefix ?? "");
242
+ }
243
+ const toolSurfaces = [];
244
+ const addToolProjection = (transport, surface, projection, presentation = {}) => {
245
+ const projected = projectToolSurface(projection, transport, transport === "MCP" ? config.mcpPreparation ?? {} : presentation);
246
+ const collected = transport === "MCP" ? prepareProjectedMcpTools(projected.map(mcpProjectionCandidate), config.mcpPreparation ?? {}).map((prepared) => ({
247
+ ...prepared.tool,
248
+ presentationSchema: prepared.inputSchema
249
+ })) : projected;
250
+ const tools = [];
251
+ for (const entry of collected) {
252
+ const source = entry.kind === "contract" ? entry.source : {
253
+ definitionToken: entry.source,
254
+ method: entry.source.identity.method,
255
+ serviceName: entry.source.identity.serviceName,
256
+ key: entry.source.identity.action,
257
+ desc: entry.source.description,
258
+ ...entry.source.identity.scope !== undefined && {
259
+ scope: entry.source.identity.scope
260
+ },
261
+ inputSchema: entry.source.input,
262
+ ...entry.source.output !== undefined && {
263
+ outputSchema: entry.source.output
264
+ },
265
+ toolName: entry.source.name,
266
+ expose: entry.source.transports,
267
+ ...entry.source.annotations !== undefined && {
268
+ annotations: entry.source.annotations
269
+ },
270
+ ...entry.source.ui !== undefined && { ui: entry.source.ui },
271
+ ...entry.source.identity.meta !== undefined && {
272
+ meta: entry.source.identity.meta
273
+ },
274
+ ...entry.source.mcp !== undefined && { mcp: entry.source.mcp }
275
+ };
276
+ const operation = addOperation(entry.kind, source);
277
+ tools.push({
278
+ kind: entry.kind,
279
+ service: operation.service,
280
+ action: operation.action,
281
+ name: entry.name,
282
+ input: presentationDigest(entry.presentationSchema)
283
+ });
284
+ }
285
+ toolSurfaces.push({ transport, surface, tools: sortTools(tools) });
286
+ };
287
+ const shared = {
288
+ services: config.services,
289
+ runtimeTools: config.runtimeTools
290
+ };
291
+ const explicitMcp = config.toolSurfaces?.MCP;
292
+ const namedMcp = config.mcpSurfaces;
293
+ if (explicitMcp && namedMcp) {
294
+ throw new Error("Surface manifest MCP topology must use either one direct surface or named surfaces");
295
+ }
296
+ if (explicitMcp)
297
+ addToolProjection("MCP", null, explicitMcp);
298
+ if (namedMcp) {
299
+ for (const name of Object.keys(namedMcp).sort(compareCodepoints)) {
300
+ const projection = namedMcp[name];
301
+ if (projection)
302
+ addToolProjection("MCP", name, projection);
303
+ }
304
+ }
305
+ if (!explicitMcp && !namedMcp)
306
+ addToolProjection("MCP", null, shared);
307
+ const agent = config.toolSurfaces?.AGENT;
308
+ addToolProjection("AGENT", null, agent ?? shared, agent ?? {});
309
+ addToolProjection("CLI", null, config.toolSurfaces?.CLI ?? shared);
310
+ const realtime = [];
311
+ const realtimeContracts = Object.keys(config.realtime ?? {}).sort(compareCodepoints);
312
+ for (const contractName of realtimeContracts) {
313
+ const contract = config.realtime?.[contractName];
314
+ if (!contract)
315
+ continue;
316
+ for (const direction of ["serverToClient", "clientToServer"]) {
317
+ const registry = contract[direction];
318
+ for (const event of Object.keys(registry).sort(compareCodepoints)) {
319
+ const definition = registry[event];
320
+ if (!definition)
321
+ continue;
322
+ const argsInput = schemaDigest(definition.args, "input");
323
+ const argsOutput = schemaDigest(definition.args, "output");
324
+ if (!argsInput || !argsOutput)
325
+ throw new Error("Realtime args schema digest missing");
326
+ let acknowledgement = null;
327
+ if (definition.ack) {
328
+ const input = schemaDigest(definition.ack, "input");
329
+ const output = schemaDigest(definition.ack, "output");
330
+ if (!input || !output)
331
+ throw new Error("Realtime ack schema digest missing");
332
+ acknowledgement = { input, output };
333
+ }
334
+ realtime.push({
335
+ contract: contractName,
336
+ direction,
337
+ event,
338
+ args: { input: argsInput, output: argsOutput },
339
+ acknowledgement
340
+ });
341
+ }
342
+ }
343
+ }
344
+ const cliOnly = (config.cliCommands ?? []).map((command) => ({
345
+ name: command.name,
346
+ description: command.description,
347
+ input: schemaDigest(command.input, "input") ?? digestValue({}),
348
+ output: schemaDigest(command.output, "output")
349
+ })).sort((left, right) => compareCodepoints(left.name, right.name));
350
+ for (const operation of operations.values()) {
351
+ operation.http.sort((left, right) => compareCodepoints(left.path, right.path) || compareCodepoints(left.method, right.method));
352
+ }
353
+ return SurfaceManifestSchema.parse({
354
+ manifestVersion: 2,
355
+ digestVersion: 1,
356
+ operations: [...operations.values()].sort((left, right) => compareCodepoints(left.service, right.service) || compareCodepoints(left.action, right.action) || compareCodepoints(left.kind, right.kind)),
357
+ toolSurfaces: toolSurfaces.sort((left, right) => compareCodepoints(left.transport, right.transport) || compareCodepoints(left.surface ?? "", right.surface ?? "")),
358
+ realtimeContracts,
359
+ realtime,
360
+ cliOnly,
361
+ extensions: [...config.extensions ?? []].sort((left, right) => compareCodepoints(left.transport, right.transport) || compareCodepoints(left.name, right.name))
362
+ });
363
+ }
364
+ function assertSurfaceManifestSnapshot(manifest, snapshot) {
365
+ const actual = serializeSurfaceValue(SurfaceManifestSchema.parse(manifest));
366
+ const expected = serializeSurfaceValue(SurfaceManifestSchema.parse(snapshot));
367
+ if (actual !== expected) {
368
+ throw new Error(`Surface manifest diverged from its committed snapshot
369
+ expected: ${expected}
370
+ actual: ${actual}`);
371
+ }
372
+ }
373
+
374
+ // src/testing/surface-conformance.ts
375
+ var RealtimeRejectionObservationSchema = z2.object({
376
+ direction: z2.enum([
377
+ "client-inbound",
378
+ "client-outbound",
379
+ "server-inbound",
380
+ "server-outbound"
381
+ ]),
382
+ phase: z2.enum(["arguments", "acknowledgement"]),
383
+ reason: z2.enum([
384
+ "unknown-event",
385
+ "invalid-arguments",
386
+ "invalid-acknowledgement-value",
387
+ "missing-acknowledgement"
388
+ ]),
389
+ fault: z2.enum(["peer", "local"])
390
+ });
391
+ var ObservationFields = {
392
+ code: z2.string().optional(),
393
+ data: z2.unknown().optional(),
394
+ diagnostic: z2.string().optional(),
395
+ handlerCalls: z2.number().int().nonnegative().optional()
396
+ };
397
+ var RealtimeDisconnectObservationSchema = z2.object({
398
+ phase: z2.enum(["before-invoke", "in-flight"])
399
+ });
400
+ var TransportObservationSchema = z2.discriminatedUnion("outcome", [
401
+ z2.object({ outcome: z2.literal("success"), ...ObservationFields }),
402
+ z2.object({ outcome: z2.literal("validation_error"), ...ObservationFields }),
403
+ z2.object({ outcome: z2.literal("domain_error"), ...ObservationFields }),
404
+ z2.object({ outcome: z2.literal("aborted"), ...ObservationFields }),
405
+ z2.object({
406
+ outcome: z2.literal("realtime_rejected"),
407
+ ...ObservationFields,
408
+ rejection: RealtimeRejectionObservationSchema
409
+ }),
410
+ z2.object({
411
+ outcome: z2.literal("disconnected"),
412
+ ...ObservationFields,
413
+ disconnect: RealtimeDisconnectObservationSchema
414
+ }),
415
+ z2.object({ outcome: z2.literal("timeout"), ...ObservationFields })
416
+ ]);
417
+ function scenarioOutcome(scenario) {
418
+ if (scenario === "event" || scenario === "acknowledgement")
419
+ return "success";
420
+ if (scenario === "invalid_arguments" || scenario === "invalid_acknowledgement") {
421
+ return "realtime_rejected";
422
+ }
423
+ if (scenario === "disconnected" || scenario === "in_flight_disconnect") {
424
+ return "disconnected";
425
+ }
426
+ return "timeout";
427
+ }
428
+ function assertScenarioExpectation(scenario, expected) {
429
+ const outcome = scenarioOutcome(scenario);
430
+ if (expected.outcome !== outcome) {
431
+ throw new TypeError(`Realtime probe scenario "${scenario}" requires outcome "${outcome}"`);
432
+ }
433
+ if (expected.outcome === "realtime_rejected" && (scenario === "invalid_arguments" && expected.rejection.phase !== "arguments" || scenario === "invalid_acknowledgement" && expected.rejection.phase !== "acknowledgement")) {
434
+ throw new TypeError(`Realtime probe scenario "${scenario}" has incompatible rejection phase`);
435
+ }
436
+ if (scenario === "invalid_arguments" && expected.handlerCalls !== 0) {
437
+ throw new TypeError('Realtime probe scenario "invalid_arguments" requires handlerCalls: 0');
438
+ }
439
+ if (expected.outcome === "disconnected") {
440
+ const phase = scenario === "disconnected" ? "before-invoke" : "in-flight";
441
+ if (expected.disconnect.phase !== phase) {
442
+ throw new TypeError(`Realtime probe scenario "${scenario}" requires disconnect phase "${phase}"`);
443
+ }
444
+ }
445
+ }
446
+ function defineRealtimeProbe(config) {
447
+ const expected = TransportObservationSchema.parse(config.expected);
448
+ assertScenarioExpectation(config.scenario, expected);
449
+ return {
450
+ name: config.name,
451
+ fixture: { scenario: config.scenario, value: config.fixture },
452
+ transports: ["REALTIME"],
453
+ expected: { REALTIME: expected },
454
+ setup: config.setup,
455
+ teardown: config.teardown
456
+ };
457
+ }
458
+ function rejectionObservation(rejected) {
459
+ return RealtimeRejectionObservationSchema.parse(rejected);
460
+ }
461
+ function createRealtimeProbeDriver({
462
+ bind,
463
+ handlerCalls
464
+ }) {
465
+ return {
466
+ async invoke(fixture, signal) {
467
+ let rejected;
468
+ const currentRejection = () => rejected;
469
+ const adapter = bind((event) => {
470
+ rejected = event;
471
+ }, fixture);
472
+ const connectedBefore = adapter.connected();
473
+ const callsBefore = handlerCalls?.();
474
+ const withCalls = (observation) => {
475
+ if (callsBefore === undefined || !handlerCalls)
476
+ return observation;
477
+ return { ...observation, handlerCalls: handlerCalls() - callsBefore };
478
+ };
479
+ try {
480
+ signal.throwIfAborted();
481
+ const data = await adapter.invoke(fixture, signal);
482
+ const observedRejection = currentRejection();
483
+ if (observedRejection) {
484
+ return withCalls({
485
+ outcome: "realtime_rejected",
486
+ code: observedRejection.error.code,
487
+ rejection: rejectionObservation(observedRejection)
488
+ });
489
+ }
490
+ return withCalls({
491
+ outcome: "success",
492
+ ...data !== undefined && { data }
493
+ });
494
+ } catch (error) {
495
+ const observedRejection = currentRejection();
496
+ if (observedRejection) {
497
+ return withCalls({
498
+ outcome: "realtime_rejected",
499
+ code: error instanceof RealtimeRequestInvalidAcknowledgementError ? error.code : observedRejection.error.code,
500
+ rejection: rejectionObservation(observedRejection)
501
+ });
502
+ }
503
+ if (error instanceof RealtimeRequestInvalidAcknowledgementError) {
504
+ return withCalls({
505
+ outcome: "realtime_rejected",
506
+ code: error.code,
507
+ rejection: {
508
+ direction: "client-inbound",
509
+ phase: "acknowledgement",
510
+ reason: "invalid-acknowledgement-value",
511
+ fault: "peer"
512
+ }
513
+ });
514
+ }
515
+ if (error instanceof RealtimeRequestDisconnectedError) {
516
+ return withCalls({
517
+ outcome: "disconnected",
518
+ code: error.code,
519
+ disconnect: {
520
+ phase: connectedBefore ? "in-flight" : "before-invoke"
521
+ }
522
+ });
523
+ }
524
+ if (error instanceof RealtimeRequestTimeoutError) {
525
+ return withCalls({ outcome: "timeout", code: error.code });
526
+ }
527
+ if (signal.aborted && error === signal.reason) {
528
+ return withCalls({ outcome: "aborted" });
529
+ }
530
+ const parsedRejection = RealtimeRejectionObservationSchema.safeParse(typeof error === "object" && error !== null && "details" in error ? error.details : undefined);
531
+ if (parsedRejection.success) {
532
+ const code2 = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "REALTIME_CONTRACT_VIOLATION";
533
+ return withCalls({
534
+ outcome: "realtime_rejected",
535
+ code: code2,
536
+ rejection: parsedRejection.data
537
+ });
538
+ }
539
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : undefined;
540
+ return withCalls({
541
+ outcome: "domain_error",
542
+ ...code !== undefined && { code },
543
+ diagnostic: error instanceof Error ? error.message : String(error)
544
+ });
545
+ } finally {
546
+ await adapter.dispose?.();
547
+ }
548
+ }
549
+ };
550
+ }
551
+ function sorted(values) {
552
+ return [...values].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
553
+ }
554
+ function assertSameSet(label, expectedValues, actualValues) {
555
+ const expected = sorted(expectedValues);
556
+ const actual = sorted(actualValues);
557
+ if (serializeSurfaceValue(expected) === serializeSurfaceValue(actual))
558
+ return;
559
+ throw new Error(`${label} mismatch
560
+ expected: ${expected.join(", ") || "(empty)"}
561
+ actual: ${actual.join(", ") || "(empty)"}`);
562
+ }
563
+ function openApiOperations(document) {
564
+ return Object.entries(document.paths).flatMap(([path, item]) => Object.keys(item).filter((method) => ["get", "post", "put", "patch", "delete", "head"].includes(method)).map((method) => `${method.toUpperCase()}:${path}`));
565
+ }
566
+ function expectedToolNames(manifest, transport, surface) {
567
+ const projection = manifest.toolSurfaces.find((candidate) => candidate.transport === transport && candidate.surface === surface);
568
+ if (!projection) {
569
+ throw new Error(`${transport} surface "${surface ?? "(default)"}" is not declared in the manifest`);
570
+ }
571
+ return projection.tools.map((tool) => tool.name);
572
+ }
573
+ function assertSurfaceDiscovery(manifest, observed) {
574
+ if (observed.openApi) {
575
+ assertSameSet("HTTP/OpenAPI", manifest.operations.flatMap((operation) => operation.http.map((entry) => `${entry.method}:${entry.path.replace(/:([^/]+)/g, "{$1}")}`)), openApiOperations(observed.openApi));
576
+ }
577
+ for (const transport of ["MCP", "AGENT", "CLI"]) {
578
+ const names = observed[transport];
579
+ if (names) {
580
+ assertSameSet(`${transport} discovery`, expectedToolNames(manifest, transport, null), names);
581
+ }
582
+ }
583
+ for (const projection of observed.toolSurfaces ?? []) {
584
+ assertSameSet(`${projection.transport} surface "${projection.surface ?? "(default)"}" discovery`, expectedToolNames(manifest, projection.transport, projection.surface ?? null), projection.names);
585
+ }
586
+ for (const [contract, topology] of Object.entries(observed.realtime ?? {})) {
587
+ if (!manifest.realtimeContracts.includes(contract)) {
588
+ throw new Error(`REALTIME contract "${contract}" is not declared in the manifest`);
589
+ }
590
+ for (const direction of ["serverToClient", "clientToServer"]) {
591
+ assertSameSet(`REALTIME ${contract}.${direction} discovery`, manifest.realtime.filter((entry) => entry.contract === contract && entry.direction === direction).map((entry) => entry.event), topology[direction]);
592
+ }
593
+ }
594
+ if (observed.cliOnly) {
595
+ assertSameSet("CLI-only discovery", manifest.cliOnly.map((command) => command.name), observed.cliOnly);
596
+ }
597
+ for (const transport of [
598
+ "HTTP",
599
+ "MCP",
600
+ "AGENT",
601
+ "CLI",
602
+ "REALTIME"
603
+ ]) {
604
+ const names = observed.extensions?.[transport];
605
+ if (!names)
606
+ continue;
607
+ assertSameSet(`${transport} extension discovery`, manifest.extensions.filter((extension) => extension.transport === transport).map((extension) => extension.name), names);
608
+ }
609
+ }
610
+ function boundedObservation(observation, maxDiagnosticBytes) {
611
+ const parsed = TransportObservationSchema.parse(observation);
612
+ if (!parsed.diagnostic)
613
+ return parsed;
614
+ const bytes = new TextEncoder().encode(parsed.diagnostic);
615
+ if (bytes.byteLength <= maxDiagnosticBytes)
616
+ return parsed;
617
+ return {
618
+ ...parsed,
619
+ diagnostic: new TextDecoder().decode(bytes.slice(0, maxDiagnosticBytes))
620
+ };
621
+ }
622
+ async function runBounded(run, signal) {
623
+ if (signal.aborted) {
624
+ Promise.resolve().then(run).catch(() => {
625
+ return;
626
+ });
627
+ throw signal.reason;
628
+ }
629
+ let removeAbortListener = () => {
630
+ return;
631
+ };
632
+ const aborted = new Promise((_resolve, reject) => {
633
+ const onAbort = () => reject(signal.reason);
634
+ signal.addEventListener("abort", onAbort, { once: true });
635
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
636
+ });
637
+ try {
638
+ return await Promise.race([Promise.resolve().then(run), aborted]);
639
+ } finally {
640
+ removeAbortListener();
641
+ }
642
+ }
643
+ async function runSurfaceProbes({
644
+ probes,
645
+ drivers,
646
+ signal,
647
+ timeoutMs = 1e4,
648
+ maxDiagnosticBytes = 4096
649
+ }) {
650
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
651
+ throw new TypeError("Surface probe timeoutMs must be a positive integer");
652
+ }
653
+ if (!Number.isInteger(maxDiagnosticBytes) || maxDiagnosticBytes <= 0) {
654
+ throw new TypeError("Surface probe maxDiagnosticBytes must be a positive integer");
655
+ }
656
+ for (const probe of probes) {
657
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
658
+ const probeSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
659
+ let failed = false;
660
+ let failure;
661
+ try {
662
+ probeSignal.throwIfAborted();
663
+ if (probe.setup)
664
+ await runBounded(() => probe.setup?.(probeSignal), probeSignal);
665
+ for (const transport of probe.transports) {
666
+ probeSignal.throwIfAborted();
667
+ const driver = drivers[transport];
668
+ if (!driver) {
669
+ throw new Error(`Surface probe "${probe.name}" has no ${transport} driver`);
670
+ }
671
+ const actual = boundedObservation(await runBounded(() => driver.invoke(probe.fixture, probeSignal), probeSignal), maxDiagnosticBytes);
672
+ const expected = probe.expected[transport];
673
+ if (!expected) {
674
+ throw new Error(`Surface probe "${probe.name}" has no ${transport} expectation`);
675
+ }
676
+ if (serializeSurfaceValue(actual) !== serializeSurfaceValue(expected)) {
677
+ throw new Error(`Surface probe "${probe.name}" ${transport} mismatch
678
+ expected: ${serializeSurfaceValue(expected)}
679
+ actual: ${serializeSurfaceValue(actual)}`);
680
+ }
681
+ }
682
+ } catch (error) {
683
+ failed = true;
684
+ failure = error;
685
+ }
686
+ if (probe.teardown) {
687
+ try {
688
+ await runBounded(() => probe.teardown?.(probeSignal), probeSignal);
689
+ } catch (error) {
690
+ if (!failed) {
691
+ failed = true;
692
+ failure = error;
693
+ }
694
+ }
695
+ }
696
+ if (failed)
697
+ throw failure;
698
+ }
699
+ }
7
700
 
8
701
  // src/testing.ts
9
702
  function resolveTestBaseUrl(origin, pathPrefix) {
@@ -31,6 +724,24 @@ function createHandlerTestClients(config) {
31
724
  return createClients(config.contracts, createTestClientConfig(config), config.contractConfig);
32
725
  }
33
726
  export {
727
+ RealtimeDisconnectObservationSchema,
728
+ RealtimeRejectionObservationSchema,
729
+ SurfaceManifestExtensionSchema,
730
+ SurfaceManifestOperationSchema,
731
+ SurfaceManifestRealtimeEventSchema,
732
+ SurfaceManifestSchema,
733
+ SurfaceManifestToolSchema,
734
+ SurfaceManifestToolSurfaceSchema,
735
+ SurfaceRealtimeSchemaPairSchema,
736
+ SurfaceSchemaDigestsSchema,
737
+ TransportObservationSchema,
738
+ assertSurfaceDiscovery,
739
+ assertSurfaceManifestSnapshot,
740
+ buildSurfaceManifest,
741
+ createHandlerTestClient,
34
742
  createHandlerTestClients,
35
- createHandlerTestClient
743
+ createRealtimeProbeDriver,
744
+ defineRealtimeProbe,
745
+ runSurfaceProbes,
746
+ serializeSurfaceValue
36
747
  };