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.
- package/README.md +4 -1
- package/dist/browser/socket-io.d.ts +25 -7
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +14 -9
- package/dist/contract/errors.d.ts +7 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/file-ref.d.ts +13 -0
- package/dist/contract/file-ref.d.ts.map +1 -0
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +21 -17
- package/dist/files/boundary.d.ts +55 -0
- package/dist/files/boundary.d.ts.map +1 -0
- package/dist/files.d.ts +3 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +16 -0
- package/dist/index-1bx83sw4.js +4 -0
- package/dist/{index-pfqjb5xy.js → index-28cqssm3.js} +30 -5
- package/dist/index-557by2db.js +34 -0
- package/dist/index-5r13htq1.js +283 -0
- package/dist/index-6k1937bx.js +20 -0
- package/dist/index-6y759j86.js +1031 -0
- package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
- package/dist/index-bfcpjw20.js +363 -0
- package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
- package/dist/{index-7vb44hy8.js → index-escqg10p.js} +193 -751
- package/dist/{index-173tvqcp.js → index-j3dem06f.js} +9 -114
- package/dist/{index-gvha32jh.js → index-jcc611vh.js} +2 -2
- package/dist/{index-z7761jk2.js → index-n4nfa7gh.js} +16 -22
- package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
- package/dist/index-sbdmyz75.js +18 -0
- package/dist/{index-b15rtha1.js → index-sm2tjx06.js} +98 -3
- package/dist/{index-yez419px.js → index-xy8fmh6w.js} +99 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +188 -40
- package/dist/node.js +31 -27
- package/dist/observability/index.js +28 -25
- package/dist/react.js +2 -2
- package/dist/realtime/contract.d.ts +9 -0
- package/dist/realtime/contract.d.ts.map +1 -1
- package/dist/realtime/index.d.ts +2 -1
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/request.d.ts +21 -0
- package/dist/realtime/request.d.ts.map +1 -0
- package/dist/realtime/socket.d.ts +3 -0
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/remote.d.ts +9 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +79 -0
- package/dist/server/context-contribution.d.ts +18 -0
- package/dist/server/context-contribution.d.ts.map +1 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +136 -88
- package/dist/server/lifecycle.d.ts +4 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/middleware/auth.d.ts +64 -9
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +180 -0
- package/dist/testing/surface-conformance.d.ts.map +1 -0
- package/dist/testing/surface-manifest.d.ts +255 -0
- package/dist/testing/surface-manifest.d.ts.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +714 -3
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/async-operation.d.ts +360 -0
- package/dist/tools/async-operation.d.ts.map +1 -0
- package/dist/tools/cli-args.d.ts +1 -1
- package/dist/tools/cli-args.d.ts.map +1 -1
- package/dist/tools/cli-format.d.ts +2 -2
- package/dist/tools/cli-format.d.ts.map +1 -1
- package/dist/tools/cli-wait.d.ts +11 -2
- package/dist/tools/cli-wait.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +2 -0
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts +6 -5
- package/dist/tools/define-download-tool.d.ts.map +1 -1
- package/dist/tools/define-upload-tool.d.ts +3 -1
- package/dist/tools/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/define-wait-tool.d.ts +1 -1
- package/dist/tools/define-wait-tool.d.ts.map +1 -1
- package/dist/tools/download-core.d.ts +5 -10
- package/dist/tools/download-core.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +101 -0
- package/dist/tools/internal/surface-projector.d.ts.map +1 -0
- package/dist/tools/lifecycle.d.ts +4 -0
- package/dist/tools/lifecycle.d.ts.map +1 -0
- package/dist/tools/managed-file-error.d.ts +6 -0
- package/dist/tools/managed-file-error.d.ts.map +1 -0
- package/dist/tools/mcp-prepare.d.ts +3 -21
- package/dist/tools/mcp-prepare.d.ts.map +1 -1
- package/dist/tools/mcp-round-policy.d.ts +9 -0
- package/dist/tools/mcp-round-policy.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +0 -4
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts +5 -4
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts +4 -2
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +5 -9
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -0
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/upload-core.d.ts +3 -2
- package/dist/tools/upload-core.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +4 -3
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools/wait-core.d.ts +6 -1
- package/dist/tools/wait-core.d.ts.map +1 -1
- package/dist/tools.d.ts +3 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +705 -599
- package/llms-full.txt +727 -34
- 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-
|
|
17
|
+
} from "./index-qh4xevf5.js";
|
|
5
18
|
import"./index-48ffdxgk.js";
|
|
6
|
-
import
|
|
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
|
-
|
|
743
|
+
createRealtimeProbeDriver,
|
|
744
|
+
defineRealtimeProbe,
|
|
745
|
+
runSurfaceProbes,
|
|
746
|
+
serializeSurfaceValue
|
|
36
747
|
};
|