@opengeni/tool-gateway 0.1.0-canary.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/LICENSE +190 -0
- package/README.md +69 -0
- package/dist/catalog.d.ts +7 -0
- package/dist/declarations.d.ts +21 -0
- package/dist/errors.d.ts +32 -0
- package/dist/index.d.ts +136 -0
- package/dist/index.js +663 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/src/catalog.ts +57 -0
- package/src/declarations.ts +334 -0
- package/src/errors.ts +75 -0
- package/src/index.ts +525 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
3
|
+
import Ajv from "ajv";
|
|
4
|
+
import Ajv2019 from "ajv/dist/2019.js";
|
|
5
|
+
import Ajv2020 from "ajv/dist/2020.js";
|
|
6
|
+
import {
|
|
7
|
+
TOOL_GATEWAY_CATALOG_VERSION,
|
|
8
|
+
ToolGatewayCallRequest,
|
|
9
|
+
ToolGatewayCatalog as ToolGatewayCatalog2,
|
|
10
|
+
ToolGatewayCatalogEntry,
|
|
11
|
+
ToolGatewayResult,
|
|
12
|
+
isToolResultSpilledReceipt
|
|
13
|
+
} from "@opengeni/contracts";
|
|
14
|
+
|
|
15
|
+
// src/catalog.ts
|
|
16
|
+
import { createHash } from "crypto";
|
|
17
|
+
import {
|
|
18
|
+
ATTEMPT_TOOL_CATALOG_MAX_BYTES,
|
|
19
|
+
ToolGatewayCatalog
|
|
20
|
+
} from "@opengeni/contracts";
|
|
21
|
+
|
|
22
|
+
// src/errors.ts
|
|
23
|
+
var ToolGatewayCatalogStaleError = class extends Error {
|
|
24
|
+
code = "catalog_stale";
|
|
25
|
+
constructor() {
|
|
26
|
+
super("Tool catalog is stale for the active gateway");
|
|
27
|
+
this.name = "ToolGatewayCatalogStaleError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ToolGatewayToolNotFoundError = class extends Error {
|
|
31
|
+
code = "tool_not_found";
|
|
32
|
+
constructor() {
|
|
33
|
+
super("Tool is not present in the active gateway catalog");
|
|
34
|
+
this.name = "ToolGatewayToolNotFoundError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var ToolGatewayApprovalRequiredError = class extends Error {
|
|
38
|
+
code = "approval_required";
|
|
39
|
+
constructor() {
|
|
40
|
+
super("Tool requires human approval");
|
|
41
|
+
this.name = "ToolGatewayApprovalRequiredError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ToolGatewayCatalogIntegrityError = class extends Error {
|
|
45
|
+
code = "catalog_integrity_failed";
|
|
46
|
+
constructor() {
|
|
47
|
+
super("Tool catalog digest does not match its authoritative content");
|
|
48
|
+
this.name = "ToolGatewayCatalogIntegrityError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var ToolGatewayCatalogTooLargeError = class extends Error {
|
|
52
|
+
code = "catalog_too_large";
|
|
53
|
+
constructor() {
|
|
54
|
+
super("Tool catalog exceeds the maximum serialized size");
|
|
55
|
+
this.name = "ToolGatewayCatalogTooLargeError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var ToolGatewayPathCollisionError = class extends Error {
|
|
59
|
+
code = "tool_path_collision";
|
|
60
|
+
constructor(path, kind) {
|
|
61
|
+
super(
|
|
62
|
+
kind === "extends_leaf" ? `Tool path ${path.join(".")} extends a tool leaf` : `Tool path ${path.join(".")} collides`
|
|
63
|
+
);
|
|
64
|
+
this.name = "ToolGatewayPathCollisionError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ToolGatewayInputValidationError = class extends Error {
|
|
68
|
+
code = "invalid_tool_arguments";
|
|
69
|
+
constructor() {
|
|
70
|
+
super("Tool arguments do not match the gateway catalog input schema");
|
|
71
|
+
this.name = "ToolGatewayInputValidationError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var ToolGatewayOutputValidationError = class extends Error {
|
|
75
|
+
code = "invalid_tool_result";
|
|
76
|
+
constructor() {
|
|
77
|
+
super("Tool result does not match the gateway catalog output schema");
|
|
78
|
+
this.name = "ToolGatewayOutputValidationError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/catalog.ts
|
|
83
|
+
function digestToolGatewayCatalog(catalog) {
|
|
84
|
+
const { createdAt: _createdAt, ...authoritative } = catalog;
|
|
85
|
+
return digestCanonicalJson(authoritative);
|
|
86
|
+
}
|
|
87
|
+
function parseVerifiedToolGatewayCatalog(input) {
|
|
88
|
+
const catalog = ToolGatewayCatalog.parse(input);
|
|
89
|
+
assertCatalogSize(catalog);
|
|
90
|
+
const { digest, ...unsigned } = catalog;
|
|
91
|
+
if (digestToolGatewayCatalog(unsigned) !== digest) {
|
|
92
|
+
throw new ToolGatewayCatalogIntegrityError();
|
|
93
|
+
}
|
|
94
|
+
return catalog;
|
|
95
|
+
}
|
|
96
|
+
function digestCanonicalJson(value) {
|
|
97
|
+
return createHash("sha256").update(JSON.stringify(canonicalJsonValue(value)), "utf8").digest("hex");
|
|
98
|
+
}
|
|
99
|
+
function compareCanonicalStrings(left, right) {
|
|
100
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
101
|
+
}
|
|
102
|
+
function assertToolGatewayCatalogSize(catalog) {
|
|
103
|
+
assertCatalogSize(catalog);
|
|
104
|
+
}
|
|
105
|
+
function assertCatalogSize(catalog) {
|
|
106
|
+
if (new TextEncoder().encode(JSON.stringify(catalog)).byteLength > ATTEMPT_TOOL_CATALOG_MAX_BYTES) {
|
|
107
|
+
throw new ToolGatewayCatalogTooLargeError();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function canonicalJsonValue(value) {
|
|
111
|
+
if (Array.isArray(value)) return value.map(canonicalJsonValue);
|
|
112
|
+
if (value !== null && typeof value === "object") {
|
|
113
|
+
return Object.fromEntries(
|
|
114
|
+
Object.entries(value).sort(([left], [right]) => compareCanonicalStrings(left, right)).map(([key, entry]) => [key, canonicalJsonValue(entry)])
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/declarations.ts
|
|
121
|
+
function generateToolGatewayDeclarations(catalog, options = {}) {
|
|
122
|
+
const verified = parseVerifiedToolGatewayCatalog(catalog);
|
|
123
|
+
return generateToolDeclarations(
|
|
124
|
+
{ digest: verified.digest, entries: verified.entries },
|
|
125
|
+
{
|
|
126
|
+
moduleSpecifier: options.moduleSpecifier ?? "@opengeni/sdk",
|
|
127
|
+
interfaceName: options.interfaceName ?? "OpenGeniGeneratedTools",
|
|
128
|
+
callOptionsType: "OpenGeniToolCallOptions",
|
|
129
|
+
fallbackResultType: "ToolGatewayResult",
|
|
130
|
+
generatedBy: "@opengeni/tool-gateway",
|
|
131
|
+
catalogDigestLabel: "Tool catalog digest"
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function generateToolDeclarations(catalog, options) {
|
|
136
|
+
const root = namespaceNode();
|
|
137
|
+
for (const entry of catalog.entries) insertEntry(root, entry);
|
|
138
|
+
return [
|
|
139
|
+
`// Generated by ${options.generatedBy}. Do not edit.`,
|
|
140
|
+
`// ${options.catalogDigestLabel ?? "Tool catalog digest"}: ${catalog.digest}`,
|
|
141
|
+
`import type { ${options.callOptionsType}, ${options.fallbackResultType} } from ${JSON.stringify(options.moduleSpecifier)};`,
|
|
142
|
+
"",
|
|
143
|
+
`declare module ${JSON.stringify(options.moduleSpecifier)} {`,
|
|
144
|
+
` interface ${options.interfaceName} {`,
|
|
145
|
+
...renderChildren(root, 4, options),
|
|
146
|
+
" }",
|
|
147
|
+
"}",
|
|
148
|
+
"",
|
|
149
|
+
"export {};",
|
|
150
|
+
""
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
function jsonSchemaToTypeScript(schema) {
|
|
154
|
+
return schemaType(schema, schema, /* @__PURE__ */ new Set(), 0);
|
|
155
|
+
}
|
|
156
|
+
function namespaceNode() {
|
|
157
|
+
return { children: /* @__PURE__ */ new Map(), entry: null };
|
|
158
|
+
}
|
|
159
|
+
function insertEntry(root, entry) {
|
|
160
|
+
let node = root;
|
|
161
|
+
for (const [index, segment] of entry.codemodePath.entries()) {
|
|
162
|
+
if (node.entry) {
|
|
163
|
+
throw new ToolGatewayPathCollisionError(entry.codemodePath, "extends_leaf");
|
|
164
|
+
}
|
|
165
|
+
let child = node.children.get(segment);
|
|
166
|
+
if (!child) {
|
|
167
|
+
child = namespaceNode();
|
|
168
|
+
node.children.set(segment, child);
|
|
169
|
+
}
|
|
170
|
+
node = child;
|
|
171
|
+
if (index === entry.codemodePath.length - 1) {
|
|
172
|
+
if (node.entry || node.children.size > 0) {
|
|
173
|
+
throw new ToolGatewayPathCollisionError(entry.codemodePath, "collision");
|
|
174
|
+
}
|
|
175
|
+
node.entry = entry;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function renderChildren(node, indent, options) {
|
|
180
|
+
const lines = [];
|
|
181
|
+
for (const [name, child] of [...node.children].sort(
|
|
182
|
+
([left], [right]) => compareCanonicalStrings(left, right)
|
|
183
|
+
)) {
|
|
184
|
+
if (child.entry) {
|
|
185
|
+
const entry = child.entry;
|
|
186
|
+
const input = jsonSchemaToTypeScript(entry.inputSchema);
|
|
187
|
+
const output = entry.outputSchema ? jsonSchemaToTypeScript(entry.outputSchema) : options.fallbackResultType;
|
|
188
|
+
const description = boundedDoc(entry.description ?? entry.title);
|
|
189
|
+
if (description) lines.push(...renderDoc(description, indent));
|
|
190
|
+
lines.push(`${spaces(indent)}readonly ${name}: (`);
|
|
191
|
+
lines.push(
|
|
192
|
+
`${spaces(indent + 2)}argumentsValue${rootObjectArgumentsAreOptional(entry.inputSchema) ? "?" : ""}: ${input},`
|
|
193
|
+
);
|
|
194
|
+
lines.push(`${spaces(indent + 2)}options?: ${options.callOptionsType},`);
|
|
195
|
+
lines.push(`${spaces(indent)}) => Promise<${output}>;`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
lines.push(`${spaces(indent)}readonly ${name}: {`);
|
|
199
|
+
lines.push(...renderChildren(child, indent + 2, options));
|
|
200
|
+
lines.push(`${spaces(indent)}};`);
|
|
201
|
+
}
|
|
202
|
+
return lines;
|
|
203
|
+
}
|
|
204
|
+
function rootObjectArgumentsAreOptional(schema) {
|
|
205
|
+
if (!isObject(schema)) return false;
|
|
206
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : [];
|
|
207
|
+
return required.length === 0 && (schema.type === "object" || isObject(schema.properties));
|
|
208
|
+
}
|
|
209
|
+
function schemaType(schema, root, resolving, depth) {
|
|
210
|
+
if (depth > 48 || schema === true) return "unknown";
|
|
211
|
+
if (schema === false) return "never";
|
|
212
|
+
if (!isObject(schema)) return "unknown";
|
|
213
|
+
if (typeof schema.$ref === "string") {
|
|
214
|
+
if (!schema.$ref.startsWith("#/") || resolving.has(schema.$ref)) return "unknown";
|
|
215
|
+
const resolved = resolveLocalReference(root, schema.$ref);
|
|
216
|
+
if (resolved === void 0) return "unknown";
|
|
217
|
+
const next = new Set(resolving);
|
|
218
|
+
next.add(schema.$ref);
|
|
219
|
+
return schemaType(resolved, root, next, depth + 1);
|
|
220
|
+
}
|
|
221
|
+
if (Object.hasOwn(schema, "const")) return literalType(schema.const);
|
|
222
|
+
if (Array.isArray(schema.enum)) return union(schema.enum.map(literalType));
|
|
223
|
+
const composites = [];
|
|
224
|
+
if (Array.isArray(schema.oneOf)) {
|
|
225
|
+
composites.push(
|
|
226
|
+
union(schema.oneOf.map((part) => schemaType(part, root, resolving, depth + 1)))
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(schema.anyOf)) {
|
|
230
|
+
composites.push(
|
|
231
|
+
union(schema.anyOf.map((part) => schemaType(part, root, resolving, depth + 1)))
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (Array.isArray(schema.allOf)) {
|
|
235
|
+
composites.push(
|
|
236
|
+
intersection(schema.allOf.map((part) => schemaType(part, root, resolving, depth + 1)))
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (composites.length > 0) {
|
|
240
|
+
const composed = intersection(composites);
|
|
241
|
+
return schema.nullable === true ? union([composed, "null"]) : composed;
|
|
242
|
+
}
|
|
243
|
+
const types = Array.isArray(schema.type) ? schema.type.filter((value) => typeof value === "string") : typeof schema.type === "string" ? [schema.type] : inferredTypes(schema);
|
|
244
|
+
const rendered = types.map((type) => typeType(type, schema, root, resolving, depth + 1));
|
|
245
|
+
if (schema.nullable === true) rendered.push("null");
|
|
246
|
+
return union(rendered.length > 0 ? rendered : ["unknown"]);
|
|
247
|
+
}
|
|
248
|
+
function typeType(type, schema, root, resolving, depth) {
|
|
249
|
+
if (type === "null" || type === "boolean" || type === "string") return type;
|
|
250
|
+
if (type === "integer" || type === "number") return "number";
|
|
251
|
+
if (type === "array") return arrayType(schema, root, resolving, depth);
|
|
252
|
+
if (type !== "object") return "unknown";
|
|
253
|
+
return objectType(schema, root, resolving, depth);
|
|
254
|
+
}
|
|
255
|
+
function arrayType(schema, root, resolving, depth) {
|
|
256
|
+
if (Array.isArray(schema.prefixItems)) {
|
|
257
|
+
const tuple = schema.prefixItems.map((item2) => schemaType(item2, root, resolving, depth + 1));
|
|
258
|
+
if (schema.items === false) return `readonly [${tuple.join(", ")}]`;
|
|
259
|
+
const rest = schema.items === void 0 || schema.items === true ? "unknown" : schemaType(schema.items, root, resolving, depth + 1);
|
|
260
|
+
return `readonly [${tuple.join(", ")}${tuple.length > 0 ? ", " : ""}...${rest}[]]`;
|
|
261
|
+
}
|
|
262
|
+
const item = schema.items === void 0 || schema.items === true ? "unknown" : schemaType(schema.items, root, resolving, depth + 1);
|
|
263
|
+
return `readonly (${item})[]`;
|
|
264
|
+
}
|
|
265
|
+
function objectType(schema, root, resolving, depth) {
|
|
266
|
+
const properties = isObject(schema.properties) ? schema.properties : {};
|
|
267
|
+
const required = new Set(
|
|
268
|
+
Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []
|
|
269
|
+
);
|
|
270
|
+
const entries = Object.entries(properties).sort(
|
|
271
|
+
([left], [right]) => compareCanonicalStrings(left, right)
|
|
272
|
+
);
|
|
273
|
+
const fields = entries.map(
|
|
274
|
+
([name, value]) => `readonly ${identifierOrQuoted(name)}${required.has(name) ? "" : "?"}: ${schemaType(value, root, resolving, depth + 1)}`
|
|
275
|
+
);
|
|
276
|
+
for (const missing of [...required].filter((name) => !Object.hasOwn(properties, name)).sort(compareCanonicalStrings)) {
|
|
277
|
+
fields.push(`readonly ${identifierOrQuoted(missing)}: unknown`);
|
|
278
|
+
}
|
|
279
|
+
if (schema.additionalProperties !== false) {
|
|
280
|
+
if (entries.length === 0 && schema.additionalProperties !== void 0 && schema.additionalProperties !== true) {
|
|
281
|
+
return `Readonly<Record<string, ${schemaType(
|
|
282
|
+
schema.additionalProperties,
|
|
283
|
+
root,
|
|
284
|
+
resolving,
|
|
285
|
+
depth + 1
|
|
286
|
+
)}>>`;
|
|
287
|
+
}
|
|
288
|
+
fields.push("readonly [key: string]: unknown");
|
|
289
|
+
}
|
|
290
|
+
return fields.length === 0 ? "Record<string, never>" : `{ ${fields.join("; ")} }`;
|
|
291
|
+
}
|
|
292
|
+
function inferredTypes(schema) {
|
|
293
|
+
if (isObject(schema.properties) || Object.hasOwn(schema, "additionalProperties")) {
|
|
294
|
+
return ["object"];
|
|
295
|
+
}
|
|
296
|
+
if (Object.hasOwn(schema, "items") || Array.isArray(schema.prefixItems)) return ["array"];
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
function resolveLocalReference(root, reference) {
|
|
300
|
+
let current = root;
|
|
301
|
+
for (const encoded of reference.slice(2).split("/")) {
|
|
302
|
+
if (!isObject(current)) return void 0;
|
|
303
|
+
const segment = encoded.replace(/~1/gu, "/").replace(/~0/gu, "~");
|
|
304
|
+
if (!Object.hasOwn(current, segment)) return void 0;
|
|
305
|
+
current = current[segment];
|
|
306
|
+
}
|
|
307
|
+
return current;
|
|
308
|
+
}
|
|
309
|
+
function literalType(value) {
|
|
310
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
311
|
+
return JSON.stringify(value);
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(value)) return `readonly [${value.map(literalType).join(", ")}]`;
|
|
314
|
+
if (isObject(value)) {
|
|
315
|
+
return `{ ${Object.entries(value).sort(([left], [right]) => compareCanonicalStrings(left, right)).map(([key, child]) => `readonly ${identifierOrQuoted(key)}: ${literalType(child)}`).join("; ")} }`;
|
|
316
|
+
}
|
|
317
|
+
return "unknown";
|
|
318
|
+
}
|
|
319
|
+
function union(values) {
|
|
320
|
+
const unique = [...new Set(values)];
|
|
321
|
+
if (unique.includes("unknown")) return "unknown";
|
|
322
|
+
if (unique.length === 0) return "never";
|
|
323
|
+
return unique.length === 1 ? unique[0] : unique.map(parenthesizeComposite).join(" | ");
|
|
324
|
+
}
|
|
325
|
+
function intersection(values) {
|
|
326
|
+
const unique = [...new Set(values.filter((value) => value !== "unknown"))];
|
|
327
|
+
return unique.length === 0 ? "unknown" : unique.length === 1 ? unique[0] : unique.map(parenthesizeComposite).join(" & ");
|
|
328
|
+
}
|
|
329
|
+
function parenthesizeComposite(value) {
|
|
330
|
+
return /[|&]/u.test(value) ? `(${value})` : value;
|
|
331
|
+
}
|
|
332
|
+
function identifierOrQuoted(value) {
|
|
333
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(value) ? value : JSON.stringify(value);
|
|
334
|
+
}
|
|
335
|
+
function boundedDoc(value) {
|
|
336
|
+
if (!value) return null;
|
|
337
|
+
const normalized = value.replace(/\s+/gu, " ").trim().replace(/\*\//gu, "*\\/");
|
|
338
|
+
if (!normalized) return null;
|
|
339
|
+
return normalized.length <= 512 ? normalized : `${normalized.slice(0, 509)}...`;
|
|
340
|
+
}
|
|
341
|
+
function renderDoc(value, indent) {
|
|
342
|
+
return [`${spaces(indent)}/** ${value} */`];
|
|
343
|
+
}
|
|
344
|
+
function spaces(count) {
|
|
345
|
+
return " ".repeat(count);
|
|
346
|
+
}
|
|
347
|
+
function isObject(value) {
|
|
348
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/index.ts
|
|
352
|
+
var PreparedToolGatewayDefinitions = class {
|
|
353
|
+
constructor(definitions) {
|
|
354
|
+
this.definitions = definitions;
|
|
355
|
+
this.entries = definitions.map(({ entry }) => entry);
|
|
356
|
+
}
|
|
357
|
+
entries;
|
|
358
|
+
create(input) {
|
|
359
|
+
return new ToolGateway(
|
|
360
|
+
input.catalogDigest,
|
|
361
|
+
this.definitions,
|
|
362
|
+
input.authorize,
|
|
363
|
+
input.requireApproval,
|
|
364
|
+
input.confirmModelApproval
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var ToolGateway = class {
|
|
369
|
+
constructor(catalogDigest, definitions, authorize, requireApproval, confirmModelApproval) {
|
|
370
|
+
this.catalogDigest = catalogDigest;
|
|
371
|
+
this.authorize = authorize;
|
|
372
|
+
this.requireApproval = requireApproval;
|
|
373
|
+
this.confirmModelApproval = confirmModelApproval;
|
|
374
|
+
for (const definition of definitions) {
|
|
375
|
+
this.byIdentity.set(identityKey(definition.entry.identity), definition);
|
|
376
|
+
this.byModelName.set(definition.entry.modelName, definition);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
byIdentity = /* @__PURE__ */ new Map();
|
|
380
|
+
byModelName = /* @__PURE__ */ new Map();
|
|
381
|
+
async call(input, context = {}) {
|
|
382
|
+
return await (await this.prepareCallWithModelApproval(input, context, false)).execute();
|
|
383
|
+
}
|
|
384
|
+
async prepareCall(input, context = {}) {
|
|
385
|
+
return await this.prepareCallWithModelApproval(input, context, false);
|
|
386
|
+
}
|
|
387
|
+
async prepareCallWithModelApproval(input, context, modelApprovalConfirmed) {
|
|
388
|
+
const request = ToolGatewayCallRequest.parse({
|
|
389
|
+
operationId: input.operationId,
|
|
390
|
+
catalogDigest: input.catalogDigest,
|
|
391
|
+
identity: input.identity,
|
|
392
|
+
arguments: input.arguments
|
|
393
|
+
});
|
|
394
|
+
const operationId = request.operationId ?? input.operationId;
|
|
395
|
+
const caller = input.caller;
|
|
396
|
+
if (request.catalogDigest !== this.catalogDigest) {
|
|
397
|
+
throw new ToolGatewayCatalogStaleError();
|
|
398
|
+
}
|
|
399
|
+
const definition = this.byIdentity.get(identityKey(request.identity));
|
|
400
|
+
if (!definition) {
|
|
401
|
+
throw new ToolGatewayToolNotFoundError();
|
|
402
|
+
}
|
|
403
|
+
if (this.requireApproval?.(definition.entry, caller, context)) {
|
|
404
|
+
throw new ToolGatewayApprovalRequiredError();
|
|
405
|
+
}
|
|
406
|
+
if (!definition.validateInput(request.arguments)) {
|
|
407
|
+
throw new ToolGatewayInputValidationError();
|
|
408
|
+
}
|
|
409
|
+
if (caller.kind === "model" && definition.entry.approval === "human" && !modelApprovalConfirmed) {
|
|
410
|
+
throw new ToolGatewayApprovalRequiredError();
|
|
411
|
+
}
|
|
412
|
+
const call = {
|
|
413
|
+
operationId,
|
|
414
|
+
catalogDigest: request.catalogDigest,
|
|
415
|
+
identity: request.identity,
|
|
416
|
+
arguments: request.arguments,
|
|
417
|
+
caller
|
|
418
|
+
};
|
|
419
|
+
await this.authorize?.({ call, entry: definition.entry });
|
|
420
|
+
if (definition.entry.approval === "human") {
|
|
421
|
+
await definition.preflightCall?.({
|
|
422
|
+
call,
|
|
423
|
+
entry: definition.entry,
|
|
424
|
+
context
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const lifecycle = await definition.lifecycle?.prepare({
|
|
428
|
+
call,
|
|
429
|
+
entry: definition.entry,
|
|
430
|
+
context
|
|
431
|
+
});
|
|
432
|
+
return {
|
|
433
|
+
call,
|
|
434
|
+
entry: definition.entry,
|
|
435
|
+
approvalAuthorityDigest: definition.approvalAuthorityDigest ?? digestCanonicalJson({
|
|
436
|
+
version: 1,
|
|
437
|
+
catalogDigest: this.catalogDigest,
|
|
438
|
+
identity: definition.entry.identity
|
|
439
|
+
}),
|
|
440
|
+
execute: async () => {
|
|
441
|
+
await lifecycle?.begin?.();
|
|
442
|
+
let result;
|
|
443
|
+
try {
|
|
444
|
+
result = ToolGatewayResult.parse(
|
|
445
|
+
await definition.execute(request.arguments, {
|
|
446
|
+
operationId,
|
|
447
|
+
caller,
|
|
448
|
+
...context.transportMeta === void 0 ? {} : { transportMeta: context.transportMeta },
|
|
449
|
+
...context.signal === void 0 ? {} : { signal: context.signal }
|
|
450
|
+
})
|
|
451
|
+
);
|
|
452
|
+
if (!result.isError && definition.validateOutput) {
|
|
453
|
+
const outputMatchesSchema = result.structuredContent !== void 0 && definition.validateOutput(result.structuredContent);
|
|
454
|
+
if (!outputMatchesSchema && !isToolResultSpilledReceipt(result.structuredContent)) {
|
|
455
|
+
throw new ToolGatewayOutputValidationError();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
await lifecycle?.complete?.({ outcome: "failed", error });
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
await lifecycle?.complete?.({ outcome: "completed", result });
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
async callModel(input) {
|
|
468
|
+
const definition = this.byModelName.get(input.modelName);
|
|
469
|
+
if (!definition) {
|
|
470
|
+
throw new ToolGatewayToolNotFoundError();
|
|
471
|
+
}
|
|
472
|
+
const call = {
|
|
473
|
+
operationId: input.operationId ?? randomUUID(),
|
|
474
|
+
catalogDigest: this.catalogDigest,
|
|
475
|
+
identity: definition.entry.identity,
|
|
476
|
+
arguments: input.arguments,
|
|
477
|
+
caller: { kind: "model", subjectId: input.subjectId }
|
|
478
|
+
};
|
|
479
|
+
const context = {
|
|
480
|
+
...input.transportMeta === void 0 ? {} : { transportMeta: input.transportMeta },
|
|
481
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
482
|
+
};
|
|
483
|
+
const modelApprovalConfirmed = definition.entry.approval === "human" && this.confirmModelApproval?.({
|
|
484
|
+
entry: definition.entry,
|
|
485
|
+
modelName: input.modelName,
|
|
486
|
+
subjectId: input.subjectId
|
|
487
|
+
}) === true;
|
|
488
|
+
return await (await this.prepareCallWithModelApproval(call, context, modelApprovalConfirmed)).execute();
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
function prepareToolGatewayDefinitions(definitions) {
|
|
492
|
+
const paths = allocateToolPaths(definitions);
|
|
493
|
+
const schemaValidators = createSchemaValidators();
|
|
494
|
+
const compiled = definitions.map((definition, index) => {
|
|
495
|
+
const {
|
|
496
|
+
execute,
|
|
497
|
+
approvalAuthorityDigest,
|
|
498
|
+
requiresProviderPreflight: _requiresProviderPreflight,
|
|
499
|
+
preflightCall,
|
|
500
|
+
lifecycle,
|
|
501
|
+
codemodePath: _path,
|
|
502
|
+
...entryInput
|
|
503
|
+
} = definition;
|
|
504
|
+
if (approvalAuthorityDigest !== void 0 && !/^[0-9a-f]{64}$/u.test(approvalAuthorityDigest)) {
|
|
505
|
+
throw new Error("Tool gateway approval authority digest must be lowercase SHA-256 hex");
|
|
506
|
+
}
|
|
507
|
+
const entry = ToolGatewayCatalogEntry.parse({
|
|
508
|
+
...entryInput,
|
|
509
|
+
codemodePath: paths[index]
|
|
510
|
+
});
|
|
511
|
+
return {
|
|
512
|
+
entry,
|
|
513
|
+
execute,
|
|
514
|
+
approvalAuthorityDigest,
|
|
515
|
+
preflightCall,
|
|
516
|
+
lifecycle,
|
|
517
|
+
validateInput: compileCatalogSchema(schemaValidators, entry.inputSchema),
|
|
518
|
+
validateOutput: entry.outputSchema ? compileCatalogSchema(schemaValidators, entry.outputSchema) : null
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
return new PreparedToolGatewayDefinitions(compiled);
|
|
522
|
+
}
|
|
523
|
+
function createWorkspaceToolGateway(input) {
|
|
524
|
+
const prepared = prepareToolGatewayDefinitions(input.definitions);
|
|
525
|
+
const unsigned = {
|
|
526
|
+
version: TOOL_GATEWAY_CATALOG_VERSION,
|
|
527
|
+
accountId: input.accountId,
|
|
528
|
+
workspaceId: input.workspaceId,
|
|
529
|
+
generation: input.generation,
|
|
530
|
+
createdAt: (input.createdAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
531
|
+
entries: [...prepared.entries]
|
|
532
|
+
};
|
|
533
|
+
const catalog = ToolGatewayCatalog2.parse({
|
|
534
|
+
...unsigned,
|
|
535
|
+
digest: digestToolGatewayCatalog(unsigned)
|
|
536
|
+
});
|
|
537
|
+
assertToolGatewayCatalogSize(catalog);
|
|
538
|
+
return {
|
|
539
|
+
catalog,
|
|
540
|
+
gateway: prepared.create({
|
|
541
|
+
catalogDigest: catalog.digest,
|
|
542
|
+
...input.authorize ? { authorize: input.authorize } : {},
|
|
543
|
+
...input.requireApproval ? { requireApproval: input.requireApproval } : {}
|
|
544
|
+
})
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
var COMPILED_CATALOG_SCHEMA_CACHE_MAX_ENTRIES = 512;
|
|
548
|
+
var compiledCatalogSchemaCache = /* @__PURE__ */ new Map();
|
|
549
|
+
function createSchemaValidators() {
|
|
550
|
+
const options = {
|
|
551
|
+
allErrors: false,
|
|
552
|
+
coerceTypes: false,
|
|
553
|
+
strict: false,
|
|
554
|
+
useDefaults: false,
|
|
555
|
+
validateFormats: false
|
|
556
|
+
};
|
|
557
|
+
return {
|
|
558
|
+
draft7: new Ajv(options),
|
|
559
|
+
draft2019: new Ajv2019(options),
|
|
560
|
+
draft2020: new Ajv2020(options)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function compileCatalogSchema(validators, schema) {
|
|
564
|
+
const dialect = typeof schema.$schema === "string" ? schema.$schema : "";
|
|
565
|
+
const family = dialect.includes("2020-12") ? "2020-12" : dialect.includes("2019-09") ? "2019-09" : "draft7";
|
|
566
|
+
const cacheKey = `${family}:${digestCanonicalJson(schema)}`;
|
|
567
|
+
const cached = compiledCatalogSchemaCache.get(cacheKey);
|
|
568
|
+
if (cached) {
|
|
569
|
+
compiledCatalogSchemaCache.delete(cacheKey);
|
|
570
|
+
compiledCatalogSchemaCache.set(cacheKey, cached);
|
|
571
|
+
return cached;
|
|
572
|
+
}
|
|
573
|
+
const compiled = family === "2020-12" ? validators.draft2020.compile(schema) : family === "2019-09" ? validators.draft2019.compile(schema) : validators.draft7.compile(schema);
|
|
574
|
+
while (compiledCatalogSchemaCache.size >= COMPILED_CATALOG_SCHEMA_CACHE_MAX_ENTRIES) {
|
|
575
|
+
const oldest = compiledCatalogSchemaCache.keys().next().value;
|
|
576
|
+
if (oldest === void 0) break;
|
|
577
|
+
compiledCatalogSchemaCache.delete(oldest);
|
|
578
|
+
}
|
|
579
|
+
compiledCatalogSchemaCache.set(cacheKey, compiled);
|
|
580
|
+
return compiled;
|
|
581
|
+
}
|
|
582
|
+
function allocateToolPaths(definitions) {
|
|
583
|
+
const requested = definitions.map(
|
|
584
|
+
(definition) => definition.codemodePath?.length ? [...definition.codemodePath] : [definition.identity.serverId, definition.identity.toolName]
|
|
585
|
+
);
|
|
586
|
+
const bases = requested.map((path) => path.map(safeNamespaceSegment));
|
|
587
|
+
const allocated = bases.map((base, index) => {
|
|
588
|
+
const path = requested[index];
|
|
589
|
+
if (path.every((segment, segmentIndex) => segment === base[segmentIndex])) return base;
|
|
590
|
+
const suffix = `_${shortIdentityDigest(definitions[index].identity)}`;
|
|
591
|
+
const last = base.at(-1);
|
|
592
|
+
return [...base.slice(0, -1), `${last.slice(0, 128 - suffix.length)}${suffix}`];
|
|
593
|
+
});
|
|
594
|
+
assertNoToolPathCollisions(allocated);
|
|
595
|
+
return allocated;
|
|
596
|
+
}
|
|
597
|
+
function assertNoToolPathCollisions(paths) {
|
|
598
|
+
const root = { children: /* @__PURE__ */ new Map(), leaf: false };
|
|
599
|
+
const ordered = [...paths].sort(compareToolPaths);
|
|
600
|
+
for (const path of ordered) {
|
|
601
|
+
let node = root;
|
|
602
|
+
for (const [index, segment] of path.entries()) {
|
|
603
|
+
if (node.leaf) throw new ToolGatewayPathCollisionError(path, "extends_leaf");
|
|
604
|
+
let child = node.children.get(segment);
|
|
605
|
+
if (!child) {
|
|
606
|
+
child = { children: /* @__PURE__ */ new Map(), leaf: false };
|
|
607
|
+
node.children.set(segment, child);
|
|
608
|
+
}
|
|
609
|
+
node = child;
|
|
610
|
+
if (index === path.length - 1) {
|
|
611
|
+
if (node.leaf || node.children.size > 0) {
|
|
612
|
+
throw new ToolGatewayPathCollisionError(path, "collision");
|
|
613
|
+
}
|
|
614
|
+
node.leaf = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function compareToolPaths(left, right) {
|
|
620
|
+
const length = Math.min(left.length, right.length);
|
|
621
|
+
for (let index = 0; index < length; index += 1) {
|
|
622
|
+
const compared = compareCanonicalStrings(left[index], right[index]);
|
|
623
|
+
if (compared !== 0) return compared;
|
|
624
|
+
}
|
|
625
|
+
return left.length - right.length;
|
|
626
|
+
}
|
|
627
|
+
function safeNamespaceSegment(value) {
|
|
628
|
+
let normalized = value.replace(/[^A-Za-z0-9_$]/gu, "_");
|
|
629
|
+
if (!/^[A-Za-z_$]/u.test(normalized)) normalized = `_${normalized}`;
|
|
630
|
+
if (["__proto__", "prototype", "constructor"].includes(normalized)) {
|
|
631
|
+
normalized = `_${normalized}`;
|
|
632
|
+
}
|
|
633
|
+
return normalized.slice(0, 128) || "_";
|
|
634
|
+
}
|
|
635
|
+
function shortIdentityDigest(identity) {
|
|
636
|
+
return createHash2("sha256").update(identityKey(identity), "utf8").digest("hex").slice(0, 10);
|
|
637
|
+
}
|
|
638
|
+
function identityKey(identity) {
|
|
639
|
+
return `${identity.serverId}\0${identity.toolName}`;
|
|
640
|
+
}
|
|
641
|
+
export {
|
|
642
|
+
PreparedToolGatewayDefinitions,
|
|
643
|
+
ToolGateway,
|
|
644
|
+
ToolGatewayApprovalRequiredError,
|
|
645
|
+
ToolGatewayCatalogIntegrityError,
|
|
646
|
+
ToolGatewayCatalogStaleError,
|
|
647
|
+
ToolGatewayCatalogTooLargeError,
|
|
648
|
+
ToolGatewayInputValidationError,
|
|
649
|
+
ToolGatewayOutputValidationError,
|
|
650
|
+
ToolGatewayPathCollisionError,
|
|
651
|
+
ToolGatewayToolNotFoundError,
|
|
652
|
+
assertToolGatewayCatalogSize,
|
|
653
|
+
compareCanonicalStrings,
|
|
654
|
+
createWorkspaceToolGateway,
|
|
655
|
+
digestCanonicalJson,
|
|
656
|
+
digestToolGatewayCatalog,
|
|
657
|
+
generateToolDeclarations,
|
|
658
|
+
generateToolGatewayDeclarations,
|
|
659
|
+
jsonSchemaToTypeScript,
|
|
660
|
+
parseVerifiedToolGatewayCatalog,
|
|
661
|
+
prepareToolGatewayDefinitions
|
|
662
|
+
};
|
|
663
|
+
//# sourceMappingURL=index.js.map
|