stitchkit 0.36.1 → 0.37.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 -2
- package/dist/browser/http.d.ts +2 -2
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/contract/define.d.ts +28 -2
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-dvrn81q4.js → index-4pvtq6h2.js} +83 -34
- package/dist/{index-0ed3bx43.js → index-frfyw9fa.js} +2 -2
- package/dist/{index-pmftwk2a.js → index-g3jrbd0z.js} +26 -0
- package/dist/{index-82gncajj.js → index-s6yhmg1k.js} +555 -337
- package/dist/{index-bx49hskg.js → index-xq45akyd.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +7 -3
- package/dist/observability/index.js +3 -3
- package/dist/server/bun.d.ts +32 -0
- package/dist/server/bun.d.ts.map +1 -0
- package/dist/server/context.d.ts +1 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts +2 -3
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +25 -5
- package/dist/server/logger.d.ts +5 -5
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logging.d.ts +4 -1
- package/dist/server/logging.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +2 -2
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/request-body.d.ts +4 -0
- package/dist/server/request-body.d.ts.map +1 -0
- package/dist/server/router.d.ts +3 -3
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/socket-io-config.d.ts +20 -0
- package/dist/server/socket-io-config.d.ts.map +1 -0
- package/dist/server/socket-io-node.d.ts +14 -0
- package/dist/server/socket-io-node.d.ts.map +1 -0
- package/dist/server/socket-io.d.ts +5 -32
- package/dist/server/socket-io.d.ts.map +1 -1
- package/dist/server/types.d.ts +54 -49
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +46 -16
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/flatten-join.d.ts +8 -0
- package/dist/tools/flatten-join.d.ts.map +1 -0
- package/dist/tools/flatten.d.ts +7 -44
- package/dist/tools/flatten.d.ts.map +1 -1
- package/dist/tools/json-schema.d.ts +1 -1
- package/dist/tools/json-schema.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp-handler.d.ts +7 -13
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +61 -24
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +9 -6
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/names.d.ts +1 -1
- package/dist/tools/native-mcp.d.ts +57 -0
- package/dist/tools/native-mcp.d.ts.map +1 -0
- package/dist/tools/portable-formats.d.ts +15 -0
- package/dist/tools/portable-formats.d.ts.map +1 -0
- package/dist/tools/presentation.d.ts +15 -0
- package/dist/tools/presentation.d.ts.map +1 -0
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/schema.d.ts +3 -8
- package/dist/tools/schema.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +5 -3
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +7 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +357 -95
- package/llms-full.txt +371 -77
- package/package.json +14 -11
- /package/dist/{index-17rdjw68.js → index-e497hxcy.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
jsonSchemaFields,
|
|
3
3
|
toJsonSchema
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-frfyw9fa.js";
|
|
5
5
|
import {
|
|
6
6
|
isWithinDir
|
|
7
7
|
} from "./index-x3fcszf8.js";
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
runWithRequestContext,
|
|
15
15
|
safeJsonParse,
|
|
16
16
|
validateHandlerOutput
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-e497hxcy.js";
|
|
18
18
|
|
|
19
19
|
// src/tools/coerce.ts
|
|
20
20
|
import { z } from "zod";
|
|
@@ -77,8 +77,325 @@ function coerceJsonArgs(args, schema) {
|
|
|
77
77
|
return isRecord(coerced) ? coerced : args;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// src/tools/flatten-join.ts
|
|
81
|
+
var ANNOTATION_KEYS = new Set(["$schema", "default", "description", "examples", "title"]);
|
|
82
|
+
var TYPE_KEYWORDS = {
|
|
83
|
+
array: [
|
|
84
|
+
"contains",
|
|
85
|
+
"items",
|
|
86
|
+
"maxContains",
|
|
87
|
+
"maxItems",
|
|
88
|
+
"minContains",
|
|
89
|
+
"minItems",
|
|
90
|
+
"prefixItems",
|
|
91
|
+
"uniqueItems"
|
|
92
|
+
],
|
|
93
|
+
integer: ["exclusiveMaximum", "exclusiveMinimum", "maximum", "minimum", "multipleOf"],
|
|
94
|
+
number: ["exclusiveMaximum", "exclusiveMinimum", "maximum", "minimum", "multipleOf"],
|
|
95
|
+
object: [
|
|
96
|
+
"additionalProperties",
|
|
97
|
+
"dependentRequired",
|
|
98
|
+
"dependentSchemas",
|
|
99
|
+
"maxProperties",
|
|
100
|
+
"minProperties",
|
|
101
|
+
"patternProperties",
|
|
102
|
+
"properties",
|
|
103
|
+
"propertyNames",
|
|
104
|
+
"required"
|
|
105
|
+
],
|
|
106
|
+
string: [
|
|
107
|
+
"contentEncoding",
|
|
108
|
+
"contentMediaType",
|
|
109
|
+
"format",
|
|
110
|
+
"maxLength",
|
|
111
|
+
"minLength",
|
|
112
|
+
"pattern"
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
function nullableProjection(schema) {
|
|
116
|
+
if (!Array.isArray(schema.anyOf) || schema.anyOf.length !== 2)
|
|
117
|
+
return schema;
|
|
118
|
+
const branches = schema.anyOf.filter(isRecord);
|
|
119
|
+
if (branches.length !== 2)
|
|
120
|
+
return schema;
|
|
121
|
+
const nullBranch = branches.find((branch) => branch.type === "null");
|
|
122
|
+
const value = branches.find((branch) => typeof branch.type === "string" && branch.type !== "null");
|
|
123
|
+
if (!nullBranch || !value || Object.keys(nullBranch).some((key) => key !== "type")) {
|
|
124
|
+
return schema;
|
|
125
|
+
}
|
|
126
|
+
const type = value.type;
|
|
127
|
+
if (typeof type !== "string")
|
|
128
|
+
return schema;
|
|
129
|
+
const projected = { type: [type, "null"] };
|
|
130
|
+
for (const key of [...ANNOTATION_KEYS, ...TYPE_KEYWORDS[type] ?? []]) {
|
|
131
|
+
if (value[key] !== undefined)
|
|
132
|
+
projected[key] = value[key];
|
|
133
|
+
else if (schema[key] !== undefined)
|
|
134
|
+
projected[key] = schema[key];
|
|
135
|
+
}
|
|
136
|
+
if (value.const !== undefined)
|
|
137
|
+
projected.enum = [value.const, null];
|
|
138
|
+
else if (Array.isArray(value.enum))
|
|
139
|
+
projected.enum = [...value.enum, null];
|
|
140
|
+
return projected;
|
|
141
|
+
}
|
|
142
|
+
function withoutAnnotations(schema) {
|
|
143
|
+
const result = {};
|
|
144
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
145
|
+
if (!ANNOTATION_KEYS.has(key))
|
|
146
|
+
result[key] = value;
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function stableJson(value) {
|
|
151
|
+
if (Array.isArray(value))
|
|
152
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
153
|
+
if (!isRecord(value))
|
|
154
|
+
return JSON.stringify(value) ?? "undefined";
|
|
155
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
156
|
+
}
|
|
157
|
+
function stringValues(schema) {
|
|
158
|
+
if (typeof schema.const === "string")
|
|
159
|
+
return [schema.const];
|
|
160
|
+
if (!Array.isArray(schema.enum))
|
|
161
|
+
return null;
|
|
162
|
+
const values = schema.enum.filter((value) => typeof value === "string");
|
|
163
|
+
return values.length === schema.enum.length ? values : null;
|
|
164
|
+
}
|
|
165
|
+
function typeNames(schema) {
|
|
166
|
+
if (typeof schema.type === "string")
|
|
167
|
+
return [schema.type];
|
|
168
|
+
if (Array.isArray(schema.type)) {
|
|
169
|
+
const values2 = schema.type.filter((value) => typeof value === "string");
|
|
170
|
+
if (values2.length === schema.type.length)
|
|
171
|
+
return values2;
|
|
172
|
+
}
|
|
173
|
+
const values = stringValues(schema);
|
|
174
|
+
if (values)
|
|
175
|
+
return ["string"];
|
|
176
|
+
if (typeof schema.const === "number")
|
|
177
|
+
return ["number"];
|
|
178
|
+
if (typeof schema.const === "boolean")
|
|
179
|
+
return ["boolean"];
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
function typeSchema(type, nullable) {
|
|
183
|
+
const schema = { type: nullable ? [type, "null"] : type };
|
|
184
|
+
if (type === "array")
|
|
185
|
+
schema.items = {};
|
|
186
|
+
if (type === "object")
|
|
187
|
+
schema.additionalProperties = {};
|
|
188
|
+
return schema;
|
|
189
|
+
}
|
|
190
|
+
function commonBaseType(schemas) {
|
|
191
|
+
const names = schemas.map(typeNames);
|
|
192
|
+
if (names.some((value) => value === null))
|
|
193
|
+
return {};
|
|
194
|
+
let nullable = false;
|
|
195
|
+
const bases = new Set;
|
|
196
|
+
for (const branch of names) {
|
|
197
|
+
if (!branch)
|
|
198
|
+
return {};
|
|
199
|
+
for (const name of branch) {
|
|
200
|
+
if (name === "null")
|
|
201
|
+
nullable = true;
|
|
202
|
+
else
|
|
203
|
+
bases.add(name);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (bases.size === 1) {
|
|
207
|
+
const [only] = bases;
|
|
208
|
+
return only ? typeSchema(only, nullable) : {};
|
|
209
|
+
}
|
|
210
|
+
if ([...bases].every((name) => name === "integer" || name === "number")) {
|
|
211
|
+
return typeSchema("number", nullable);
|
|
212
|
+
}
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
function mergePropertySchemas(properties) {
|
|
216
|
+
const schemas = properties.map((property) => nullableProjection(property.schema));
|
|
217
|
+
const structural = schemas.map(withoutAnnotations);
|
|
218
|
+
const [first] = structural;
|
|
219
|
+
let merged;
|
|
220
|
+
if (first && structural.every((schema) => stableJson(schema) === stableJson(first))) {
|
|
221
|
+
merged = structuredClone(first);
|
|
222
|
+
} else {
|
|
223
|
+
const values = schemas.map(stringValues);
|
|
224
|
+
const allStrings = values.every((value) => value !== null);
|
|
225
|
+
merged = allStrings ? { type: "string", enum: [...new Set(values.flatMap((value) => value ?? []))] } : commonBaseType(schemas);
|
|
226
|
+
}
|
|
227
|
+
const descriptions = [
|
|
228
|
+
...new Set(schemas.map((schema) => schema.description).filter((value) => typeof value === "string"))
|
|
229
|
+
];
|
|
230
|
+
if (descriptions.length === 1)
|
|
231
|
+
merged.description = descriptions[0];
|
|
232
|
+
return merged;
|
|
233
|
+
}
|
|
234
|
+
|
|
80
235
|
// src/tools/flatten.ts
|
|
81
|
-
|
|
236
|
+
function rebuild(value) {
|
|
237
|
+
if (Array.isArray(value))
|
|
238
|
+
return value.map(rebuild);
|
|
239
|
+
if (!isRecord(value))
|
|
240
|
+
return value;
|
|
241
|
+
const node = {};
|
|
242
|
+
for (const [key, child] of Object.entries(value))
|
|
243
|
+
node[key] = rebuild(child);
|
|
244
|
+
return flattenNode(node);
|
|
245
|
+
}
|
|
246
|
+
function stringValues2(schema) {
|
|
247
|
+
if (typeof schema.const === "string")
|
|
248
|
+
return [schema.const];
|
|
249
|
+
if (!Array.isArray(schema.enum))
|
|
250
|
+
return null;
|
|
251
|
+
const values = schema.enum.filter((value) => typeof value === "string");
|
|
252
|
+
return values.length === schema.enum.length ? values : null;
|
|
253
|
+
}
|
|
254
|
+
function objectVariants(node) {
|
|
255
|
+
if (!Array.isArray(node.oneOf) || node.oneOf.length < 2)
|
|
256
|
+
return null;
|
|
257
|
+
const variants = node.oneOf.filter(isRecord);
|
|
258
|
+
if (variants.length !== node.oneOf.length)
|
|
259
|
+
return null;
|
|
260
|
+
if (variants.some((variant) => variant.type !== "object" || !isRecord(variant.properties))) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return variants;
|
|
264
|
+
}
|
|
265
|
+
function findDiscriminator(variants) {
|
|
266
|
+
const [first] = variants;
|
|
267
|
+
if (!first || !isRecord(first.properties))
|
|
268
|
+
return null;
|
|
269
|
+
for (const key of Object.keys(first.properties).sort()) {
|
|
270
|
+
const values = [];
|
|
271
|
+
const seen = new Set;
|
|
272
|
+
let valid = true;
|
|
273
|
+
for (const variant of variants) {
|
|
274
|
+
if (!requiredKeys(variant).has(key)) {
|
|
275
|
+
valid = false;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
const properties = variant.properties;
|
|
279
|
+
const field = isRecord(properties) ? properties[key] : undefined;
|
|
280
|
+
const branchValues = isRecord(field) ? stringValues2(field) : null;
|
|
281
|
+
if (!branchValues || branchValues.length === 0) {
|
|
282
|
+
valid = false;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
for (const value of branchValues) {
|
|
286
|
+
if (seen.has(value)) {
|
|
287
|
+
valid = false;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
seen.add(value);
|
|
291
|
+
}
|
|
292
|
+
if (!valid)
|
|
293
|
+
break;
|
|
294
|
+
values.push(branchValues);
|
|
295
|
+
}
|
|
296
|
+
if (valid)
|
|
297
|
+
return { key, values };
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function requiredKeys(variant) {
|
|
302
|
+
if (!Array.isArray(variant.required))
|
|
303
|
+
return new Set;
|
|
304
|
+
return new Set(variant.required.filter((key) => typeof key === "string"));
|
|
305
|
+
}
|
|
306
|
+
function appendHint(schema, hint) {
|
|
307
|
+
const description = typeof schema.description === "string" ? schema.description : undefined;
|
|
308
|
+
return { ...schema, description: description ? `${description} ${hint}` : hint };
|
|
309
|
+
}
|
|
310
|
+
function flattenNode(node) {
|
|
311
|
+
const variants = objectVariants(node);
|
|
312
|
+
if (!variants)
|
|
313
|
+
return node;
|
|
314
|
+
const discriminator = findDiscriminator(variants);
|
|
315
|
+
if (!discriminator)
|
|
316
|
+
return node;
|
|
317
|
+
const perKey = new Map;
|
|
318
|
+
for (let index = 0;index < variants.length; index++) {
|
|
319
|
+
const variant = variants[index];
|
|
320
|
+
if (!variant)
|
|
321
|
+
continue;
|
|
322
|
+
const labels = discriminator.values[index] ?? [];
|
|
323
|
+
const required2 = requiredKeys(variant);
|
|
324
|
+
if (!isRecord(variant.properties))
|
|
325
|
+
continue;
|
|
326
|
+
for (const [key, rawSchema] of Object.entries(variant.properties)) {
|
|
327
|
+
if (key === discriminator.key || !isRecord(rawSchema))
|
|
328
|
+
continue;
|
|
329
|
+
const entries = perKey.get(key) ?? [];
|
|
330
|
+
entries.push({ schema: rawSchema, labels, required: required2.has(key) });
|
|
331
|
+
perKey.set(key, entries);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const discriminatorProperties = [];
|
|
335
|
+
for (let index = 0;index < variants.length; index++) {
|
|
336
|
+
const variant = variants[index];
|
|
337
|
+
if (!variant || !isRecord(variant.properties))
|
|
338
|
+
continue;
|
|
339
|
+
const schema = variant.properties[discriminator.key];
|
|
340
|
+
if (!isRecord(schema))
|
|
341
|
+
continue;
|
|
342
|
+
discriminatorProperties.push({
|
|
343
|
+
schema,
|
|
344
|
+
labels: discriminator.values[index] ?? [],
|
|
345
|
+
required: true
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const properties = {
|
|
349
|
+
[discriminator.key]: mergePropertySchemas(discriminatorProperties)
|
|
350
|
+
};
|
|
351
|
+
const required = [discriminator.key];
|
|
352
|
+
for (const [key, entries] of perKey) {
|
|
353
|
+
let merged = mergePropertySchemas(entries);
|
|
354
|
+
const requiredEverywhere = entries.length === variants.length && entries.every((entry) => entry.required);
|
|
355
|
+
if (requiredEverywhere) {
|
|
356
|
+
required.push(key);
|
|
357
|
+
} else {
|
|
358
|
+
const requiredLabels = entries.filter((entry) => entry.required).flatMap((entry) => entry.labels);
|
|
359
|
+
const presentLabels = entries.flatMap((entry) => entry.labels);
|
|
360
|
+
const labels = requiredLabels.length > 0 ? requiredLabels : presentLabels;
|
|
361
|
+
const verb = requiredLabels.length > 0 ? "Required" : "Available";
|
|
362
|
+
merged = appendHint(merged, `${verb} if ${discriminator.key} = ${[...new Set(labels)].join(" | ")}`);
|
|
363
|
+
}
|
|
364
|
+
properties[key] = merged;
|
|
365
|
+
}
|
|
366
|
+
const result = {};
|
|
367
|
+
for (const [key, value] of Object.entries(node)) {
|
|
368
|
+
if (key !== "oneOf" && key !== "anyOf" && key !== "properties" && key !== "required") {
|
|
369
|
+
result[key] = value;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
result.type = "object";
|
|
373
|
+
result.properties = properties;
|
|
374
|
+
result.required = required;
|
|
375
|
+
result.additionalProperties = variants.every((variant) => variant.additionalProperties === false) ? false : {};
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
function flattenToolJsonSchema(schema) {
|
|
379
|
+
const result = rebuild(schema);
|
|
380
|
+
return isRecord(result) ? result : {};
|
|
381
|
+
}
|
|
382
|
+
function freezeToolJsonSchema(schema) {
|
|
383
|
+
for (const value of Object.values(schema))
|
|
384
|
+
freezeJsonValue(value);
|
|
385
|
+
return Object.freeze(schema);
|
|
386
|
+
}
|
|
387
|
+
function freezeJsonValue(value) {
|
|
388
|
+
if (Array.isArray(value)) {
|
|
389
|
+
for (const item of value)
|
|
390
|
+
freezeJsonValue(item);
|
|
391
|
+
Object.freeze(value);
|
|
392
|
+
} else if (isRecord(value)) {
|
|
393
|
+
freezeToolJsonSchema(value);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/tools/mount.ts
|
|
398
|
+
import { z as z4 } from "zod";
|
|
82
399
|
|
|
83
400
|
// src/tools/schema.ts
|
|
84
401
|
import { z as z2 } from "zod";
|
|
@@ -131,300 +448,6 @@ function mergeSchemas(paramsSchema, inputSchema) {
|
|
|
131
448
|
return paramsObject ? z2.intersection(paramsObject, inputSchema) : inputSchema;
|
|
132
449
|
}
|
|
133
450
|
|
|
134
|
-
// src/tools/flatten.ts
|
|
135
|
-
function flattenDiscriminatedUnion(union) {
|
|
136
|
-
const discriminator = union.def.discriminator;
|
|
137
|
-
const allDiscValues = [];
|
|
138
|
-
const perKey = new Map;
|
|
139
|
-
for (const opt of union.def.options) {
|
|
140
|
-
if (!(opt instanceof z3.ZodObject)) {
|
|
141
|
-
throw new Error(`flattenDiscriminatedUnion: variant for discriminator '${discriminator}' is not a ZodObject`);
|
|
142
|
-
}
|
|
143
|
-
const discValues = stringLiteralOrEnumValues(opt.shape[discriminator]);
|
|
144
|
-
if (discValues === null) {
|
|
145
|
-
throw new Error(`flattenDiscriminatedUnion: discriminator '${discriminator}' must be a string z.literal() or z.enum()`);
|
|
146
|
-
}
|
|
147
|
-
allDiscValues.push(...discValues);
|
|
148
|
-
const label = discValues.join(" | ");
|
|
149
|
-
for (const [key, field] of Object.entries(opt.shape)) {
|
|
150
|
-
if (key === discriminator)
|
|
151
|
-
continue;
|
|
152
|
-
const entry = perKey.get(key) ?? { schemas: [], variants: [] };
|
|
153
|
-
entry.schemas.push(unwrapField(field));
|
|
154
|
-
entry.variants.push(label);
|
|
155
|
-
perKey.set(key, entry);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const uniqDisc = [...new Set(allDiscValues)];
|
|
159
|
-
const [firstLiteral, ...restLiterals] = uniqDisc;
|
|
160
|
-
if (firstLiteral === undefined) {
|
|
161
|
-
throw new Error("flattenDiscriminatedUnion: union has no options");
|
|
162
|
-
}
|
|
163
|
-
const shape = {
|
|
164
|
-
[discriminator]: z3.enum([firstLiteral, ...restLiterals])
|
|
165
|
-
};
|
|
166
|
-
for (const [key, entry] of perKey) {
|
|
167
|
-
const advertised = mergeCollidingFields(entry.schemas);
|
|
168
|
-
const hint = `Required if ${discriminator} = ${[...new Set(entry.variants)].join(" | ")}`;
|
|
169
|
-
shape[key] = z3.optional(advertised).describe(hint);
|
|
170
|
-
}
|
|
171
|
-
return withKeyPolicy(z3.object(shape), mergeVariantKeyPolicies(union.def.options));
|
|
172
|
-
}
|
|
173
|
-
function mergeVariantKeyPolicies(options) {
|
|
174
|
-
let strict = 0;
|
|
175
|
-
let counted = 0;
|
|
176
|
-
for (const option of options) {
|
|
177
|
-
const policy = option instanceof z3.ZodObject ? keyPolicyOf(option) : undefined;
|
|
178
|
-
if (policy !== undefined && !(policy instanceof z3.ZodNever))
|
|
179
|
-
return z3.unknown();
|
|
180
|
-
counted++;
|
|
181
|
-
if (policy instanceof z3.ZodNever)
|
|
182
|
-
strict++;
|
|
183
|
-
}
|
|
184
|
-
if (counted === 0 || strict === 0)
|
|
185
|
-
return;
|
|
186
|
-
return strict === counted ? z3.never() : z3.unknown();
|
|
187
|
-
}
|
|
188
|
-
function unwrapField(field) {
|
|
189
|
-
if (field instanceof z3.ZodOptional || field instanceof z3.ZodDefault) {
|
|
190
|
-
return unwrapField(field.unwrap());
|
|
191
|
-
}
|
|
192
|
-
return field;
|
|
193
|
-
}
|
|
194
|
-
function preserveDescription(from, to) {
|
|
195
|
-
return from.description === undefined ? to : to.describe(from.description);
|
|
196
|
-
}
|
|
197
|
-
function stringLiteralOrEnumValues(field) {
|
|
198
|
-
let raw = null;
|
|
199
|
-
if (field instanceof z3.ZodLiteral)
|
|
200
|
-
raw = field.def.values;
|
|
201
|
-
else if (field instanceof z3.ZodEnum)
|
|
202
|
-
raw = field.options;
|
|
203
|
-
if (raw === null)
|
|
204
|
-
return null;
|
|
205
|
-
const strings = raw.filter((v) => typeof v === "string");
|
|
206
|
-
return strings.length === raw.length ? strings : null;
|
|
207
|
-
}
|
|
208
|
-
function isFlattenableDU(union) {
|
|
209
|
-
const disc = union.def.discriminator;
|
|
210
|
-
if (union.def.options.length === 0)
|
|
211
|
-
return false;
|
|
212
|
-
for (const opt of union.def.options) {
|
|
213
|
-
if (!(opt instanceof z3.ZodObject))
|
|
214
|
-
return false;
|
|
215
|
-
if (stringLiteralOrEnumValues(opt.shape[disc]) === null)
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
function stripAnnotations(node) {
|
|
221
|
-
if (Array.isArray(node)) {
|
|
222
|
-
for (const item of node)
|
|
223
|
-
stripAnnotations(item);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (typeof node === "object" && node !== null) {
|
|
227
|
-
Reflect.deleteProperty(node, "description");
|
|
228
|
-
Reflect.deleteProperty(node, "default");
|
|
229
|
-
Reflect.deleteProperty(node, "$schema");
|
|
230
|
-
for (const value of Object.values(node))
|
|
231
|
-
stripAnnotations(value);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function hasInvisibleConstraint(schema) {
|
|
235
|
-
if (!(schema instanceof z3.ZodType))
|
|
236
|
-
return false;
|
|
237
|
-
const def = schema.def;
|
|
238
|
-
if (isRecord(def) && def.type !== "unknown" && def.type !== "any") {
|
|
239
|
-
if (normalizedJson(schema) === "{}")
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
if (schema instanceof z3.ZodCatch)
|
|
243
|
-
return true;
|
|
244
|
-
if (isRecord(def) && def.coerce === true)
|
|
245
|
-
return true;
|
|
246
|
-
if (isRecord(def) && Array.isArray(def.checks)) {
|
|
247
|
-
for (const check of def.checks) {
|
|
248
|
-
if (isInvisibleCheck(check))
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodNullable || schema instanceof z3.ZodDefault) {
|
|
253
|
-
return hasInvisibleConstraint(schema.unwrap());
|
|
254
|
-
}
|
|
255
|
-
if (schema instanceof z3.ZodPipe)
|
|
256
|
-
return true;
|
|
257
|
-
if (schema instanceof z3.ZodObject) {
|
|
258
|
-
return Object.values(schema.shape).some(hasInvisibleConstraint);
|
|
259
|
-
}
|
|
260
|
-
if (schema instanceof z3.ZodArray)
|
|
261
|
-
return hasInvisibleConstraint(schema.element);
|
|
262
|
-
if (schema instanceof z3.ZodUnion)
|
|
263
|
-
return schema.def.options.some(hasInvisibleConstraint);
|
|
264
|
-
if (schema instanceof z3.ZodRecord)
|
|
265
|
-
return hasInvisibleConstraint(schema.valueType);
|
|
266
|
-
if (schema instanceof z3.ZodIntersection) {
|
|
267
|
-
return hasInvisibleConstraint(schema.def.left) || hasInvisibleConstraint(schema.def.right);
|
|
268
|
-
}
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
var INVISIBLE_CHECK_KINDS = new Set(["custom", "overwrite"]);
|
|
272
|
-
function acceptsMoreThanItsType(schema) {
|
|
273
|
-
if (!(schema instanceof z3.ZodType))
|
|
274
|
-
return false;
|
|
275
|
-
if (schema instanceof z3.ZodCatch)
|
|
276
|
-
return true;
|
|
277
|
-
if (isRecord(schema.def) && schema.def.coerce === true)
|
|
278
|
-
return true;
|
|
279
|
-
if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodNullable || schema instanceof z3.ZodDefault) {
|
|
280
|
-
return acceptsMoreThanItsType(schema.unwrap());
|
|
281
|
-
}
|
|
282
|
-
if (schema instanceof z3.ZodPipe)
|
|
283
|
-
return acceptsMoreThanItsType(schema.def.in);
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
function isInvisibleCheck(check) {
|
|
287
|
-
if (!isRecord(check))
|
|
288
|
-
return false;
|
|
289
|
-
const inner = check._zod;
|
|
290
|
-
const def = isRecord(inner) ? inner.def : undefined;
|
|
291
|
-
const kind = isRecord(def) ? def.check : undefined;
|
|
292
|
-
return typeof kind === "string" && INVISIBLE_CHECK_KINDS.has(kind);
|
|
293
|
-
}
|
|
294
|
-
function projectToBaseType(schema) {
|
|
295
|
-
if (!(schema instanceof z3.ZodType))
|
|
296
|
-
return z3.unknown();
|
|
297
|
-
if (schema instanceof z3.ZodNullable) {
|
|
298
|
-
return z3.nullable(projectToBaseType(schema.unwrap()));
|
|
299
|
-
}
|
|
300
|
-
if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodDefault) {
|
|
301
|
-
return projectToBaseType(schema.unwrap());
|
|
302
|
-
}
|
|
303
|
-
if (schema instanceof z3.ZodPipe)
|
|
304
|
-
return projectToBaseType(schema.def.in);
|
|
305
|
-
if (schema instanceof z3.ZodCatch)
|
|
306
|
-
return z3.unknown();
|
|
307
|
-
const coerces = isRecord(schema.def) && schema.def.coerce === true;
|
|
308
|
-
if (schema instanceof z3.ZodNumber)
|
|
309
|
-
return coerces ? z3.coerce.number() : z3.number();
|
|
310
|
-
if (schema instanceof z3.ZodString)
|
|
311
|
-
return coerces ? z3.coerce.string() : z3.string();
|
|
312
|
-
if (schema instanceof z3.ZodBoolean && coerces)
|
|
313
|
-
return z3.coerce.boolean();
|
|
314
|
-
if (schema instanceof z3.ZodEnum || schema instanceof z3.ZodLiteral) {
|
|
315
|
-
return stringLiteralOrEnumValues(schema) === null ? z3.unknown() : z3.string();
|
|
316
|
-
}
|
|
317
|
-
if (schema instanceof z3.ZodBoolean)
|
|
318
|
-
return z3.boolean();
|
|
319
|
-
if (schema instanceof z3.ZodArray)
|
|
320
|
-
return z3.array(z3.unknown());
|
|
321
|
-
if (schema instanceof z3.ZodObject)
|
|
322
|
-
return z3.looseObject({});
|
|
323
|
-
return z3.unknown();
|
|
324
|
-
}
|
|
325
|
-
function normalizedJson(schema) {
|
|
326
|
-
if (!(schema instanceof z3.ZodType))
|
|
327
|
-
return "{}";
|
|
328
|
-
const json = toJsonSchema(schema, "input", "any");
|
|
329
|
-
stripAnnotations(json);
|
|
330
|
-
return JSON.stringify(json);
|
|
331
|
-
}
|
|
332
|
-
function mergeCollidingFields(schemas) {
|
|
333
|
-
const distinct = new Map;
|
|
334
|
-
for (const schema of schemas) {
|
|
335
|
-
const key = normalizedJson(schema);
|
|
336
|
-
if (!distinct.has(key))
|
|
337
|
-
distinct.set(key, schema);
|
|
338
|
-
}
|
|
339
|
-
const values = [...distinct.values()];
|
|
340
|
-
const only = values[0];
|
|
341
|
-
if (values.length <= 1) {
|
|
342
|
-
if (only === undefined)
|
|
343
|
-
return z3.unknown();
|
|
344
|
-
if (schemas.length <= 1)
|
|
345
|
-
return only;
|
|
346
|
-
const hazards2 = schemas.map(acceptsMoreThanItsType);
|
|
347
|
-
if (hazards2.some(Boolean) && !hazards2.every(Boolean))
|
|
348
|
-
return z3.unknown();
|
|
349
|
-
if (schemas.some(hasInvisibleConstraint)) {
|
|
350
|
-
if (normalizedJson(only) === "{}")
|
|
351
|
-
return only;
|
|
352
|
-
return projectToBaseType(only);
|
|
353
|
-
}
|
|
354
|
-
return only;
|
|
355
|
-
}
|
|
356
|
-
const merged = [];
|
|
357
|
-
let allEnum = true;
|
|
358
|
-
for (const schema of values) {
|
|
359
|
-
const vals = stringLiteralOrEnumValues(schema);
|
|
360
|
-
if (vals === null) {
|
|
361
|
-
allEnum = false;
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
merged.push(...vals);
|
|
365
|
-
}
|
|
366
|
-
if (allEnum) {
|
|
367
|
-
const uniq = [...new Set(merged)];
|
|
368
|
-
const [first2, ...rest] = uniq;
|
|
369
|
-
if (first2 !== undefined)
|
|
370
|
-
return z3.enum([first2, ...rest]);
|
|
371
|
-
}
|
|
372
|
-
const hazards = schemas.map(acceptsMoreThanItsType);
|
|
373
|
-
if (hazards.some(Boolean) && !hazards.every(Boolean))
|
|
374
|
-
return z3.unknown();
|
|
375
|
-
const projected = values.map(projectToBaseType);
|
|
376
|
-
const first = projected[0];
|
|
377
|
-
if (first !== undefined) {
|
|
378
|
-
const shape = normalizedJson(first);
|
|
379
|
-
if (shape !== "{}" && projected.every((p) => normalizedJson(p) === shape))
|
|
380
|
-
return first;
|
|
381
|
-
}
|
|
382
|
-
return z3.unknown();
|
|
383
|
-
}
|
|
384
|
-
function flattenUnionsDeep(schema) {
|
|
385
|
-
if (!(schema instanceof z3.ZodType))
|
|
386
|
-
return z3.unknown();
|
|
387
|
-
if (schema instanceof z3.ZodDiscriminatedUnion) {
|
|
388
|
-
if (!isFlattenableDU(schema))
|
|
389
|
-
return schema;
|
|
390
|
-
return preserveDescription(schema, flattenUnionsDeep(flattenDiscriminatedUnion(schema)));
|
|
391
|
-
}
|
|
392
|
-
if (schema instanceof z3.ZodObject) {
|
|
393
|
-
const shape = {};
|
|
394
|
-
for (const [key, field] of Object.entries(schema.shape)) {
|
|
395
|
-
shape[key] = flattenUnionsDeep(field);
|
|
396
|
-
}
|
|
397
|
-
return preserveDescription(schema, rebuildObject(schema, shape));
|
|
398
|
-
}
|
|
399
|
-
if (schema instanceof z3.ZodArray) {
|
|
400
|
-
return preserveDescription(schema, z3.array(flattenUnionsDeep(schema.element)));
|
|
401
|
-
}
|
|
402
|
-
if (schema instanceof z3.ZodUnion) {
|
|
403
|
-
const opts = schema.def.options.map((option) => flattenUnionsDeep(option));
|
|
404
|
-
const [a, b, ...rest] = opts;
|
|
405
|
-
return a && b ? preserveDescription(schema, z3.union([a, b, ...rest])) : schema;
|
|
406
|
-
}
|
|
407
|
-
if (schema instanceof z3.ZodRecord) {
|
|
408
|
-
return preserveDescription(schema, z3.record(schema.keyType, flattenUnionsDeep(schema.valueType)));
|
|
409
|
-
}
|
|
410
|
-
if (schema instanceof z3.ZodOptional) {
|
|
411
|
-
return preserveDescription(schema, z3.optional(flattenUnionsDeep(schema.unwrap())));
|
|
412
|
-
}
|
|
413
|
-
if (schema instanceof z3.ZodNullable) {
|
|
414
|
-
return preserveDescription(schema, z3.nullable(flattenUnionsDeep(schema.unwrap())));
|
|
415
|
-
}
|
|
416
|
-
if (schema instanceof z3.ZodDefault) {
|
|
417
|
-
return preserveDescription(schema, flattenUnionsDeep(schema.unwrap()).default(schema.def.defaultValue));
|
|
418
|
-
}
|
|
419
|
-
if (schema instanceof z3.ZodIntersection) {
|
|
420
|
-
return preserveDescription(schema, z3.intersection(flattenUnionsDeep(schema.def.left), flattenUnionsDeep(schema.def.right)));
|
|
421
|
-
}
|
|
422
|
-
return schema;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/tools/mount.ts
|
|
426
|
-
import { z as z4 } from "zod";
|
|
427
|
-
|
|
428
451
|
// src/tools/execute.ts
|
|
429
452
|
function toolResultFromError(err) {
|
|
430
453
|
const appErr = normalizeError(err);
|
|
@@ -435,8 +458,8 @@ function toolResultFromError(err) {
|
|
|
435
458
|
...appErr.hint && { hint: appErr.hint }
|
|
436
459
|
};
|
|
437
460
|
}
|
|
438
|
-
async function executeToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson = false, onOutputStrip) {
|
|
439
|
-
return inToolCallContext({ source: context.source, toolName, method }, () => runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip));
|
|
461
|
+
async function executeToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson = false, onOutputStrip, extension) {
|
|
462
|
+
return inToolCallContext({ source: context.source, toolName, method }, () => runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip, extension));
|
|
440
463
|
}
|
|
441
464
|
function inToolCallContext(call, body) {
|
|
442
465
|
const parent = getRequestContext();
|
|
@@ -454,23 +477,93 @@ function inToolCallContext(call, body) {
|
|
|
454
477
|
action: call.method.key
|
|
455
478
|
}, body);
|
|
456
479
|
}
|
|
457
|
-
async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip) {
|
|
480
|
+
async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip, extension) {
|
|
458
481
|
const startedAt = Date.now();
|
|
482
|
+
let hookContext = context;
|
|
459
483
|
const finish = async (result, thrown) => {
|
|
460
|
-
await hooks?.afterToolCall?.(
|
|
484
|
+
await hooks?.afterToolCall?.({
|
|
485
|
+
toolName,
|
|
486
|
+
args: rawArgs,
|
|
487
|
+
result,
|
|
488
|
+
durationMs: Date.now() - startedAt,
|
|
489
|
+
context: hookContext,
|
|
490
|
+
endpoint: method,
|
|
491
|
+
...thrown !== undefined && { error: thrown }
|
|
492
|
+
});
|
|
461
493
|
return result;
|
|
462
494
|
};
|
|
463
|
-
|
|
495
|
+
let beforeRan = false;
|
|
496
|
+
const runBefore = async () => {
|
|
497
|
+
if (beforeRan || !hooks?.beforeToolCall)
|
|
498
|
+
return null;
|
|
499
|
+
beforeRan = true;
|
|
464
500
|
try {
|
|
465
|
-
await hooks.beforeToolCall(
|
|
501
|
+
await hooks.beforeToolCall({
|
|
502
|
+
toolName,
|
|
503
|
+
args: rawArgs,
|
|
504
|
+
context: hookContext,
|
|
505
|
+
endpoint: method
|
|
506
|
+
});
|
|
507
|
+
return null;
|
|
466
508
|
} catch (err) {
|
|
467
509
|
return finish(toolResultFromError(err));
|
|
468
510
|
}
|
|
511
|
+
};
|
|
512
|
+
const finishThrown = async (err) => {
|
|
513
|
+
const beforeFailure2 = await runBefore();
|
|
514
|
+
if (beforeFailure2)
|
|
515
|
+
return beforeFailure2;
|
|
516
|
+
if (hooks?.onToolError) {
|
|
517
|
+
try {
|
|
518
|
+
await hooks.onToolError({
|
|
519
|
+
toolName,
|
|
520
|
+
error: err,
|
|
521
|
+
context: hookContext,
|
|
522
|
+
endpoint: method
|
|
523
|
+
});
|
|
524
|
+
} catch (hookErr) {
|
|
525
|
+
console.error("[stitchkit] onToolError hook failed:", hookErr);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return finish(toolResultFromError(err), err);
|
|
529
|
+
};
|
|
530
|
+
let callArgs = rawArgs;
|
|
531
|
+
let callContext = context;
|
|
532
|
+
if (extension) {
|
|
533
|
+
const extensionKeys = new Set(Object.keys(extension.schema.shape));
|
|
534
|
+
const extensionArgs = Object.fromEntries(Object.entries(rawArgs).filter(([key]) => extensionKeys.has(key)));
|
|
535
|
+
let parsed;
|
|
536
|
+
try {
|
|
537
|
+
parsed = extension.schema.safeParse(extensionArgs);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return finishThrown(err);
|
|
540
|
+
}
|
|
541
|
+
if (!parsed.success) {
|
|
542
|
+
const beforeFailure2 = await runBefore();
|
|
543
|
+
if (beforeFailure2)
|
|
544
|
+
return beforeFailure2;
|
|
545
|
+
return finish({
|
|
546
|
+
ok: false,
|
|
547
|
+
code: "VALIDATION_ERROR",
|
|
548
|
+
details: { message: `Invalid tool extension: ${formatZodError(parsed.error)}` }
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const resolved = await extension.resolve({ ...rawArgs, ...parsed.data });
|
|
553
|
+
callContext = { ...context, ...resolved, source: context.source };
|
|
554
|
+
hookContext = callContext;
|
|
555
|
+
callArgs = Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extensionKeys.has(key)));
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return finishThrown(err);
|
|
558
|
+
}
|
|
469
559
|
}
|
|
560
|
+
const beforeFailure = await runBefore();
|
|
561
|
+
if (beforeFailure)
|
|
562
|
+
return beforeFailure;
|
|
470
563
|
const paramKeys = new Set(objectShapeKeys(method.paramsSchema));
|
|
471
564
|
let paramArgs = {};
|
|
472
565
|
let inputArgs = {};
|
|
473
|
-
for (const [key, value] of Object.entries(
|
|
566
|
+
for (const [key, value] of Object.entries(callArgs)) {
|
|
474
567
|
if (isUnsafeKey(key))
|
|
475
568
|
continue;
|
|
476
569
|
if (paramKeys.has(key))
|
|
@@ -484,7 +577,12 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
|
|
|
484
577
|
}
|
|
485
578
|
let params;
|
|
486
579
|
if (method.paramsSchema) {
|
|
487
|
-
|
|
580
|
+
let result;
|
|
581
|
+
try {
|
|
582
|
+
result = method.paramsSchema.safeParse(paramArgs);
|
|
583
|
+
} catch (err) {
|
|
584
|
+
return finishThrown(err);
|
|
585
|
+
}
|
|
488
586
|
if (!result.success) {
|
|
489
587
|
return finish({
|
|
490
588
|
ok: false,
|
|
@@ -496,7 +594,12 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
|
|
|
496
594
|
}
|
|
497
595
|
let input;
|
|
498
596
|
if (method.inputSchema) {
|
|
499
|
-
|
|
597
|
+
let result;
|
|
598
|
+
try {
|
|
599
|
+
result = method.inputSchema.safeParse(inputArgs);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
return finishThrown(err);
|
|
602
|
+
}
|
|
500
603
|
if (!result.success) {
|
|
501
604
|
return finish({
|
|
502
605
|
ok: false,
|
|
@@ -507,7 +610,12 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
|
|
|
507
610
|
input = result.data;
|
|
508
611
|
}
|
|
509
612
|
try {
|
|
510
|
-
const ctx = {
|
|
613
|
+
const ctx = {
|
|
614
|
+
...callContext,
|
|
615
|
+
params,
|
|
616
|
+
input,
|
|
617
|
+
source: context.source
|
|
618
|
+
};
|
|
511
619
|
if (lifecycle?.beforeHandle) {
|
|
512
620
|
await lifecycle.beforeHandle(ctx, method);
|
|
513
621
|
}
|
|
@@ -531,14 +639,7 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
|
|
|
531
639
|
const output = (data === undefined || data === null) && !method.outputSchema ? { status: "ok" } : data;
|
|
532
640
|
return finish({ ok: true, data: output });
|
|
533
641
|
} catch (err) {
|
|
534
|
-
|
|
535
|
-
try {
|
|
536
|
-
await hooks.onToolError(toolName, err, context, method);
|
|
537
|
-
} catch (hookErr) {
|
|
538
|
-
console.error("[stitchkit] onToolError hook failed:", hookErr);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return finish(toolResultFromError(err), err);
|
|
642
|
+
return finishThrown(err);
|
|
542
643
|
}
|
|
543
644
|
}
|
|
544
645
|
|
|
@@ -607,6 +708,121 @@ function assertUniqueToolName(name, taken, surface) {
|
|
|
607
708
|
}
|
|
608
709
|
}
|
|
609
710
|
|
|
711
|
+
// src/tools/presentation.ts
|
|
712
|
+
import { z as z3 } from "zod";
|
|
713
|
+
function withoutDialect(schema) {
|
|
714
|
+
const result = {};
|
|
715
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
716
|
+
if (key !== "$schema")
|
|
717
|
+
result[key] = value;
|
|
718
|
+
}
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
function hasLocalReference(value) {
|
|
722
|
+
if (Array.isArray(value))
|
|
723
|
+
return value.some(hasLocalReference);
|
|
724
|
+
if (!isRecord(value))
|
|
725
|
+
return false;
|
|
726
|
+
if (typeof value.$ref === "string" && value.$ref.startsWith("#"))
|
|
727
|
+
return true;
|
|
728
|
+
return Object.values(value).some(hasLocalReference);
|
|
729
|
+
}
|
|
730
|
+
function rewriteLocalReferences(value, namespace) {
|
|
731
|
+
if (Array.isArray(value)) {
|
|
732
|
+
return value.map((child) => rewriteLocalReferences(child, namespace));
|
|
733
|
+
}
|
|
734
|
+
if (!isRecord(value))
|
|
735
|
+
return value;
|
|
736
|
+
const rewritten = {};
|
|
737
|
+
for (const [key, child] of Object.entries(value)) {
|
|
738
|
+
rewritten[key] = key === "$ref" && typeof child === "string" && child.startsWith("#") ? `#/definitions/${namespace}${child.slice(1)}` : rewriteLocalReferences(child, namespace);
|
|
739
|
+
}
|
|
740
|
+
return rewritten;
|
|
741
|
+
}
|
|
742
|
+
function namespaceLocalReferences(schema, namespace) {
|
|
743
|
+
if (!schema || !hasLocalReference(schema))
|
|
744
|
+
return schema;
|
|
745
|
+
const rewritten = rewriteLocalReferences(schema, namespace);
|
|
746
|
+
if (!isRecord(rewritten))
|
|
747
|
+
return schema;
|
|
748
|
+
return { ...rewritten, definitions: { [namespace]: rewritten } };
|
|
749
|
+
}
|
|
750
|
+
function objectProperties(schema) {
|
|
751
|
+
return schema.type === "object" && isRecord(schema.properties) ? schema.properties : null;
|
|
752
|
+
}
|
|
753
|
+
function requiredKeys2(schema) {
|
|
754
|
+
return Array.isArray(schema.required) ? schema.required.filter((key) => typeof key === "string") : [];
|
|
755
|
+
}
|
|
756
|
+
function mergeObjectSchemas(left, right, rightOwnsUnknownKeys) {
|
|
757
|
+
const leftProperties = objectProperties(left);
|
|
758
|
+
const rightProperties = objectProperties(right);
|
|
759
|
+
if (!leftProperties || !rightProperties)
|
|
760
|
+
return { allOf: [left, right] };
|
|
761
|
+
const conflicts = Object.keys(leftProperties).filter((key) => (key in rightProperties));
|
|
762
|
+
if (conflicts.length > 0) {
|
|
763
|
+
throw new Error(`Schema merge conflict: ${conflicts.join(", ")} appear in both params and input`);
|
|
764
|
+
}
|
|
765
|
+
const merged = {
|
|
766
|
+
type: "object",
|
|
767
|
+
properties: { ...leftProperties, ...rightProperties },
|
|
768
|
+
required: [...new Set([...requiredKeys2(left), ...requiredKeys2(right)])]
|
|
769
|
+
};
|
|
770
|
+
const leftDefinitions = isRecord(left.definitions) ? left.definitions : {};
|
|
771
|
+
const rightDefinitions = isRecord(right.definitions) ? right.definitions : {};
|
|
772
|
+
const definitions = { ...leftDefinitions, ...rightDefinitions };
|
|
773
|
+
if (Object.keys(definitions).length > 0)
|
|
774
|
+
merged.definitions = definitions;
|
|
775
|
+
const unknownKeys = rightOwnsUnknownKeys ? right.additionalProperties : left.additionalProperties;
|
|
776
|
+
if (unknownKeys !== undefined)
|
|
777
|
+
merged.additionalProperties = unknownKeys;
|
|
778
|
+
return merged;
|
|
779
|
+
}
|
|
780
|
+
function schemaFromZod(schema, unrepresentable, flatten) {
|
|
781
|
+
if (!schema)
|
|
782
|
+
return;
|
|
783
|
+
const json = toJsonSchema(schema, "input", unrepresentable, "draft-07");
|
|
784
|
+
return flatten ? flattenToolJsonSchema(json) : json;
|
|
785
|
+
}
|
|
786
|
+
function buildToolPresentationSchema(config) {
|
|
787
|
+
const unrepresentable = config.unrepresentable ?? "any";
|
|
788
|
+
const flatten = config.flattenUnionInput ?? false;
|
|
789
|
+
const params = namespaceLocalReferences(schemaFromZod(config.paramsSchema, unrepresentable, flatten), "params");
|
|
790
|
+
const input = namespaceLocalReferences(schemaFromZod(config.inputSchema, unrepresentable, flatten), "input");
|
|
791
|
+
let presentation;
|
|
792
|
+
if (params && input) {
|
|
793
|
+
presentation = mergeObjectSchemas(withoutDialect(params), withoutDialect(input), true);
|
|
794
|
+
} else if (input) {
|
|
795
|
+
presentation = input;
|
|
796
|
+
} else if (params) {
|
|
797
|
+
presentation = params;
|
|
798
|
+
} else {
|
|
799
|
+
presentation = {
|
|
800
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
if (config.extendSchema) {
|
|
806
|
+
const extra = namespaceLocalReferences(schemaFromZod(z3.object(config.extendSchema), unrepresentable, flatten), "extend");
|
|
807
|
+
if (extra) {
|
|
808
|
+
presentation = mergeObjectSchemas(withoutDialect(extra), withoutDialect(presentation), true);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (presentation.$schema === undefined) {
|
|
812
|
+
presentation = {
|
|
813
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
814
|
+
...presentation
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
return freezeToolJsonSchema(presentation);
|
|
818
|
+
}
|
|
819
|
+
function isObjectPresentationSchema(schema) {
|
|
820
|
+
return schema.type === "object" && isRecord(schema.properties);
|
|
821
|
+
}
|
|
822
|
+
function presentationMetadata(schema) {
|
|
823
|
+
return withoutDialect(schema);
|
|
824
|
+
}
|
|
825
|
+
|
|
610
826
|
// src/tools/mount.ts
|
|
611
827
|
function applyExtend(base, extra) {
|
|
612
828
|
if (base instanceof z4.ZodObject) {
|
|
@@ -630,6 +846,8 @@ function collectTools(service, transport, config = {}) {
|
|
|
630
846
|
}
|
|
631
847
|
if (method.multipart)
|
|
632
848
|
continue;
|
|
849
|
+
if (method.rawBody)
|
|
850
|
+
continue;
|
|
633
851
|
if (method.rawResponse)
|
|
634
852
|
continue;
|
|
635
853
|
const name = method.toolName ?? toToolName(service.name, methodName);
|
|
@@ -639,24 +857,28 @@ function collectTools(service, transport, config = {}) {
|
|
|
639
857
|
}
|
|
640
858
|
assertToolName(name, service.name, methodName);
|
|
641
859
|
}
|
|
642
|
-
const baseSchema = flattenUnionInput ? mergeSchemas(method.paramsSchema ? flattenUnionsDeep(method.paramsSchema) : undefined, method.inputSchema ? flattenUnionsDeep(method.inputSchema) : undefined) : mergeSchemas(method.paramsSchema, method.inputSchema);
|
|
643
860
|
const shouldExtend = !!extend && (!extend.filter || extend.filter(service, method));
|
|
644
|
-
const
|
|
645
|
-
|
|
861
|
+
const baseArgumentSchema = mergeSchemas(method.paramsSchema, method.inputSchema);
|
|
862
|
+
const argumentSchema = shouldExtend && extend ? applyExtend(baseArgumentSchema, extend.schema) : baseArgumentSchema;
|
|
863
|
+
const presentationSchema = buildToolPresentationSchema({
|
|
864
|
+
paramsSchema: method.paramsSchema,
|
|
865
|
+
inputSchema: method.inputSchema,
|
|
866
|
+
extendSchema: shouldExtend ? extend?.schema : undefined,
|
|
867
|
+
flattenUnionInput,
|
|
868
|
+
unrepresentable: "any"
|
|
869
|
+
});
|
|
870
|
+
tools.push({ method, name, argumentSchema, presentationSchema, shouldExtend });
|
|
646
871
|
}
|
|
647
872
|
return tools;
|
|
648
873
|
}
|
|
649
874
|
function createToolRunner(config) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const cleanArgs = tool.shouldExtend && extendKeys ? Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extendKeys.has(key))) : rawArgs;
|
|
658
|
-
return executeToolMethod(tool.method, tool.name, cleanArgs, { ...config.context, ...extraContext, source: config.source }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(tool.name, paths) : undefined);
|
|
659
|
-
}
|
|
875
|
+
const extension = config.extend ? {
|
|
876
|
+
schema: z4.object(config.extend.schema),
|
|
877
|
+
resolve: config.extend.resolve
|
|
878
|
+
} : undefined;
|
|
879
|
+
return async function runOneToolCall(tool, rawArgs) {
|
|
880
|
+
return executeToolMethod(tool.method, tool.name, rawArgs, { ...config.context, source: config.source }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(tool.name, paths) : undefined, tool.shouldExtend ? extension : undefined);
|
|
881
|
+
};
|
|
660
882
|
}
|
|
661
883
|
function formatToolError(result, toolName, errorHint) {
|
|
662
884
|
const err = { error: result.code };
|
|
@@ -1227,7 +1449,7 @@ function renderCommandHelp(name, command, tool) {
|
|
|
1227
1449
|
const lines = [tool.method.desc, "", `Usage: ${name} ${command} [args] [--flags]`, ""];
|
|
1228
1450
|
let fields = [];
|
|
1229
1451
|
try {
|
|
1230
|
-
fields = jsonSchemaFields(
|
|
1452
|
+
fields = jsonSchemaFields(tool.presentationSchema);
|
|
1231
1453
|
} catch {
|
|
1232
1454
|
lines.push("(arguments: pass a JSON object — this command has a complex schema)", "");
|
|
1233
1455
|
}
|
|
@@ -1307,7 +1529,7 @@ async function createCli(config) {
|
|
|
1307
1529
|
`);
|
|
1308
1530
|
return exit(1);
|
|
1309
1531
|
}
|
|
1310
|
-
const { toolArgs, options } = parseCliArgs(commandArgv, tool.
|
|
1532
|
+
const { toolArgs, options } = parseCliArgs(commandArgv, tool.argumentSchema);
|
|
1311
1533
|
if (options.help) {
|
|
1312
1534
|
stdout(renderCommandHelp(config.name, command, tool));
|
|
1313
1535
|
return exit(0);
|
|
@@ -1321,7 +1543,7 @@ async function createCli(config) {
|
|
|
1321
1543
|
}
|
|
1322
1544
|
const passthroughField = config.passthrough?.[command];
|
|
1323
1545
|
if (passthroughField) {
|
|
1324
|
-
collectPassthrough(toolArgs, passthroughField, objectShapeKeys(tool.
|
|
1546
|
+
collectPassthrough(toolArgs, passthroughField, objectShapeKeys(tool.argumentSchema));
|
|
1325
1547
|
}
|
|
1326
1548
|
if (options.dryRun) {
|
|
1327
1549
|
stdout(`${JSON.stringify({ command, args: toolArgs }, null, 2)}
|
|
@@ -1367,11 +1589,7 @@ async function createCli(config) {
|
|
|
1367
1589
|
return exit(exitCode);
|
|
1368
1590
|
}
|
|
1369
1591
|
function safeInputSchema(tool) {
|
|
1370
|
-
|
|
1371
|
-
return toJsonSchema(tool.schema, "input");
|
|
1372
|
-
} catch {
|
|
1373
|
-
return {};
|
|
1374
|
-
}
|
|
1592
|
+
return tool.presentationSchema;
|
|
1375
1593
|
}
|
|
1376
1594
|
|
|
1377
|
-
export { coerceJsonArgs, toolResultFromError,
|
|
1595
|
+
export { coerceJsonArgs, toolResultFromError, executeToolMethod, assertToolName, assertUniqueToolName, flattenToolJsonSchema, buildToolPresentationSchema, isObjectPresentationSchema, presentationMetadata, collectTools, createToolRunner, formatToolError, fetchGuarded, readCapped, writeDownload, parseCliArgs, DEFAULT_EXIT_CODES, emitResult, pollUntil, pollUntilDone, createCli };
|