@ai-me-chat/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +654 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +243 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +621 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI-Me Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
executeTool: () => executeTool,
|
|
24
|
+
extractSchemasFromFile: () => extractSchemasFromFile,
|
|
25
|
+
fetchOpenAPISpec: () => fetchOpenAPISpec,
|
|
26
|
+
generateToolDefinitions: () => generateToolDefinitions,
|
|
27
|
+
generateToolDefinitionsWithSchemas: () => generateToolDefinitionsWithSchemas,
|
|
28
|
+
generateToolsFromOpenAPI: () => generateToolsFromOpenAPI,
|
|
29
|
+
parseOpenAPISpec: () => parseOpenAPISpec
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/tools/generate-tools.ts
|
|
34
|
+
var import_zod = require("zod");
|
|
35
|
+
var DEFAULT_WRITE_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
36
|
+
function generateToolDefinitions(routes, confirmation) {
|
|
37
|
+
const confirmMethods = confirmation?.methods ?? DEFAULT_WRITE_METHODS;
|
|
38
|
+
const tools = [];
|
|
39
|
+
for (const route of routes) {
|
|
40
|
+
for (const method of route.methods) {
|
|
41
|
+
const toolName = buildToolName(method, route.path);
|
|
42
|
+
const description = `${method} ${route.path}`;
|
|
43
|
+
tools.push({
|
|
44
|
+
name: toolName,
|
|
45
|
+
description,
|
|
46
|
+
parameters: buildParametersSchema(method, route.pathParams),
|
|
47
|
+
requiresConfirmation: confirmMethods.includes(method),
|
|
48
|
+
httpMethod: method,
|
|
49
|
+
path: route.path
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return tools;
|
|
54
|
+
}
|
|
55
|
+
function buildToolName(method, routePath) {
|
|
56
|
+
const pathPart = routePath.replace(/^\/api\//, "").replace(/:/g, "").replace(/\*/g, "").replace(/\//g, "_").replace(/_+/g, "_").replace(/_$/, "");
|
|
57
|
+
return `${method.toLowerCase()}_${pathPart}`;
|
|
58
|
+
}
|
|
59
|
+
function generateToolDefinitionsWithSchemas(routes, schemas, confirmation) {
|
|
60
|
+
const confirmMethods = confirmation?.methods ?? DEFAULT_WRITE_METHODS;
|
|
61
|
+
const tools = [];
|
|
62
|
+
for (const route of routes) {
|
|
63
|
+
const fileSchemas = schemas.get(route.filePath) ?? [];
|
|
64
|
+
for (const method of route.methods) {
|
|
65
|
+
const toolName = buildToolName(method, route.path);
|
|
66
|
+
const description = `${method} ${route.path}`;
|
|
67
|
+
const matchingSchema = pickBestSchema(fileSchemas, method);
|
|
68
|
+
tools.push({
|
|
69
|
+
name: toolName,
|
|
70
|
+
description,
|
|
71
|
+
parameters: matchingSchema ? buildParametersSchemaFromExtracted(method, route.pathParams, matchingSchema) : buildParametersSchema(method, route.pathParams),
|
|
72
|
+
requiresConfirmation: confirmMethods.includes(method),
|
|
73
|
+
httpMethod: method,
|
|
74
|
+
path: route.path
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return tools;
|
|
79
|
+
}
|
|
80
|
+
function pickBestSchema(fileSchemas, method) {
|
|
81
|
+
const exact = fileSchemas.find((s) => s.method === method);
|
|
82
|
+
if (exact) return exact;
|
|
83
|
+
if (method === "GET") {
|
|
84
|
+
return fileSchemas.find(
|
|
85
|
+
(s) => s.exportName.toLowerCase().includes("query") || s.exportName.toLowerCase().includes("search")
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
89
|
+
return fileSchemas.find(
|
|
90
|
+
(s) => s.exportName.toLowerCase().includes("body") || s.exportName.toLowerCase().includes("request")
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
function buildParametersSchemaFromExtracted(method, pathParams, extracted) {
|
|
96
|
+
const shape = {};
|
|
97
|
+
for (const param of pathParams) {
|
|
98
|
+
shape[param] = import_zod.z.string().describe(`Path parameter: ${param}`);
|
|
99
|
+
}
|
|
100
|
+
const props = extracted.jsonSchema["properties"];
|
|
101
|
+
const requiredFields = new Set(
|
|
102
|
+
Array.isArray(extracted.jsonSchema["required"]) ? extracted.jsonSchema["required"] : []
|
|
103
|
+
);
|
|
104
|
+
if (props) {
|
|
105
|
+
const innerShape = {};
|
|
106
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
107
|
+
const zodField = jsonSchemaScalarToZod(propSchema);
|
|
108
|
+
innerShape[key] = requiredFields.has(key) ? zodField : zodField.optional();
|
|
109
|
+
}
|
|
110
|
+
if (method === "GET") {
|
|
111
|
+
shape["query"] = import_zod.z.object(innerShape).optional().describe("Query parameters");
|
|
112
|
+
} else if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
113
|
+
shape["body"] = import_zod.z.object(innerShape).optional().describe("Request body");
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
if (method === "GET") {
|
|
117
|
+
shape["query"] = import_zod.z.object({}).passthrough().optional().describe("Query string parameters");
|
|
118
|
+
} else if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
119
|
+
shape["body"] = import_zod.z.object({}).passthrough().optional().describe("Request body (JSON object with any properties)");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return import_zod.z.object(shape);
|
|
123
|
+
}
|
|
124
|
+
function jsonSchemaScalarToZod(schema) {
|
|
125
|
+
const anyOf = schema["anyOf"];
|
|
126
|
+
if (Array.isArray(anyOf)) {
|
|
127
|
+
const nonNull = anyOf.filter(
|
|
128
|
+
(s) => s["type"] !== "null"
|
|
129
|
+
);
|
|
130
|
+
if (nonNull.length === 1 && nonNull[0]) {
|
|
131
|
+
return jsonSchemaScalarToZod(nonNull[0]);
|
|
132
|
+
}
|
|
133
|
+
return import_zod.z.unknown();
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(schema["enum"])) {
|
|
136
|
+
const values = schema["enum"];
|
|
137
|
+
if (values.every((v) => typeof v === "string") && values.length >= 2) {
|
|
138
|
+
const [first, second, ...rest] = values;
|
|
139
|
+
return import_zod.z.enum([first, second, ...rest]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
switch (schema["type"]) {
|
|
143
|
+
case "string":
|
|
144
|
+
return import_zod.z.string();
|
|
145
|
+
case "number":
|
|
146
|
+
case "integer":
|
|
147
|
+
return import_zod.z.number();
|
|
148
|
+
case "boolean":
|
|
149
|
+
return import_zod.z.boolean();
|
|
150
|
+
case "array": {
|
|
151
|
+
const items = schema["items"];
|
|
152
|
+
return items ? import_zod.z.array(jsonSchemaScalarToZod(items)) : import_zod.z.array(import_zod.z.unknown());
|
|
153
|
+
}
|
|
154
|
+
case "object":
|
|
155
|
+
return import_zod.z.record(import_zod.z.string(), import_zod.z.unknown());
|
|
156
|
+
default:
|
|
157
|
+
return import_zod.z.unknown();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function buildParametersSchema(method, pathParams) {
|
|
161
|
+
const shape = {};
|
|
162
|
+
for (const param of pathParams) {
|
|
163
|
+
shape[param] = import_zod.z.string().describe(`Path parameter: ${param}`);
|
|
164
|
+
}
|
|
165
|
+
if (method === "GET") {
|
|
166
|
+
shape["query"] = import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("Query string parameters");
|
|
167
|
+
} else if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
168
|
+
shape["body"] = import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional().describe("Request body");
|
|
169
|
+
}
|
|
170
|
+
return import_zod.z.object(shape);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/tools/schema-extractor.ts
|
|
174
|
+
function extractSchemasFromFile(_filePath, fileContent) {
|
|
175
|
+
const results = [];
|
|
176
|
+
const exportPattern = /export\s+const\s+(\w+)\s*=\s*z\.object\s*\(\s*\{([^]*?)\}\s*\)/g;
|
|
177
|
+
let match;
|
|
178
|
+
while ((match = exportPattern.exec(fileContent)) !== null) {
|
|
179
|
+
const exportName = match[1];
|
|
180
|
+
const objectBody = match[2];
|
|
181
|
+
if (!exportName) continue;
|
|
182
|
+
const jsonSchema = buildJsonSchema(objectBody);
|
|
183
|
+
const method = inferMethod(exportName);
|
|
184
|
+
results.push({ exportName, jsonSchema, method });
|
|
185
|
+
}
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
function inferMethod(exportName) {
|
|
189
|
+
const lower = exportName.toLowerCase();
|
|
190
|
+
if (lower.includes("body") || lower.includes("request")) return "POST";
|
|
191
|
+
if (lower.includes("query") || lower.includes("search")) return "GET";
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
function buildJsonSchema(objectBody) {
|
|
195
|
+
const properties = {};
|
|
196
|
+
const required = [];
|
|
197
|
+
const fields = splitFields(objectBody);
|
|
198
|
+
for (const field of fields) {
|
|
199
|
+
const fieldMatch = field.match(/^\s*(\w+)\s*:\s*(.+?)\s*$/s);
|
|
200
|
+
if (!fieldMatch) continue;
|
|
201
|
+
const fieldName = fieldMatch[1];
|
|
202
|
+
const typeExpr = fieldMatch[2];
|
|
203
|
+
if (!fieldName || !typeExpr) continue;
|
|
204
|
+
const { schema, isOptional } = parseZodType(typeExpr.trim());
|
|
205
|
+
properties[fieldName] = schema;
|
|
206
|
+
if (!isOptional) {
|
|
207
|
+
required.push(fieldName);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const jsonSchema = {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties
|
|
213
|
+
};
|
|
214
|
+
if (required.length > 0) {
|
|
215
|
+
jsonSchema["required"] = required;
|
|
216
|
+
}
|
|
217
|
+
return jsonSchema;
|
|
218
|
+
}
|
|
219
|
+
function splitFields(body) {
|
|
220
|
+
const fields = [];
|
|
221
|
+
let depth = 0;
|
|
222
|
+
let current = "";
|
|
223
|
+
for (const char of body) {
|
|
224
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
225
|
+
depth++;
|
|
226
|
+
current += char;
|
|
227
|
+
} else if (char === ")" || char === "]" || char === "}") {
|
|
228
|
+
depth--;
|
|
229
|
+
current += char;
|
|
230
|
+
} else if (char === "," && depth === 0) {
|
|
231
|
+
const trimmed2 = current.trim();
|
|
232
|
+
if (trimmed2) fields.push(trimmed2);
|
|
233
|
+
current = "";
|
|
234
|
+
} else {
|
|
235
|
+
current += char;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const trimmed = current.trim();
|
|
239
|
+
if (trimmed) fields.push(trimmed);
|
|
240
|
+
return fields;
|
|
241
|
+
}
|
|
242
|
+
function parseZodType(expr) {
|
|
243
|
+
const { base, modifiers } = tokeniseExpr(expr);
|
|
244
|
+
let schema = resolveBaseType(base);
|
|
245
|
+
let isOptional = false;
|
|
246
|
+
for (const mod of modifiers) {
|
|
247
|
+
if (mod.startsWith(".optional")) {
|
|
248
|
+
isOptional = true;
|
|
249
|
+
schema = { anyOf: [schema, { type: "null" }] };
|
|
250
|
+
} else if (mod.startsWith(".nullable")) {
|
|
251
|
+
schema = { anyOf: [schema, { type: "null" }] };
|
|
252
|
+
} else if (mod.startsWith(".array")) {
|
|
253
|
+
schema = { type: "array", items: schema };
|
|
254
|
+
} else if (mod.startsWith(".default")) {
|
|
255
|
+
isOptional = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { schema, isOptional };
|
|
259
|
+
}
|
|
260
|
+
function tokeniseExpr(expr) {
|
|
261
|
+
const modifiers = [];
|
|
262
|
+
let rest = expr.trim();
|
|
263
|
+
let depth = 0;
|
|
264
|
+
let baseEnd = 0;
|
|
265
|
+
let foundBase = false;
|
|
266
|
+
for (let i = 0; i < rest.length; i++) {
|
|
267
|
+
const ch = rest[i];
|
|
268
|
+
if (ch === "(") depth++;
|
|
269
|
+
else if (ch === ")") {
|
|
270
|
+
depth--;
|
|
271
|
+
if (depth === 0) {
|
|
272
|
+
baseEnd = i + 1;
|
|
273
|
+
foundBase = true;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!foundBase) {
|
|
279
|
+
return { base: rest, modifiers: [] };
|
|
280
|
+
}
|
|
281
|
+
const base = rest.slice(0, baseEnd);
|
|
282
|
+
rest = rest.slice(baseEnd);
|
|
283
|
+
while (rest.startsWith(".")) {
|
|
284
|
+
depth = 0;
|
|
285
|
+
let modEnd = 0;
|
|
286
|
+
let foundMod = false;
|
|
287
|
+
for (let i = 1; i < rest.length; i++) {
|
|
288
|
+
const ch = rest[i];
|
|
289
|
+
if (ch === "(") depth++;
|
|
290
|
+
else if (ch === ")") {
|
|
291
|
+
depth--;
|
|
292
|
+
if (depth === 0) {
|
|
293
|
+
modEnd = i + 1;
|
|
294
|
+
foundMod = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!foundMod) break;
|
|
300
|
+
modifiers.push(rest.slice(0, modEnd));
|
|
301
|
+
rest = rest.slice(modEnd);
|
|
302
|
+
}
|
|
303
|
+
return { base, modifiers };
|
|
304
|
+
}
|
|
305
|
+
function resolveBaseType(base) {
|
|
306
|
+
const trimmed = base.trim();
|
|
307
|
+
if (trimmed.startsWith("z.string")) return { type: "string" };
|
|
308
|
+
if (trimmed.startsWith("z.number")) return { type: "number" };
|
|
309
|
+
if (trimmed.startsWith("z.boolean")) return { type: "boolean" };
|
|
310
|
+
if (trimmed.startsWith("z.null")) return { type: "null" };
|
|
311
|
+
if (trimmed.startsWith("z.unknown") || trimmed.startsWith("z.any"))
|
|
312
|
+
return {};
|
|
313
|
+
if (trimmed.startsWith("z.enum")) {
|
|
314
|
+
const enumValues = extractEnumValues(trimmed);
|
|
315
|
+
return { type: "string", enum: enumValues };
|
|
316
|
+
}
|
|
317
|
+
if (trimmed.startsWith("z.array")) {
|
|
318
|
+
const inner = extractSingleArg(trimmed);
|
|
319
|
+
if (inner) {
|
|
320
|
+
const { schema: items } = parseZodType(inner);
|
|
321
|
+
return { type: "array", items };
|
|
322
|
+
}
|
|
323
|
+
return { type: "array" };
|
|
324
|
+
}
|
|
325
|
+
if (trimmed.startsWith("z.object")) {
|
|
326
|
+
return { type: "object" };
|
|
327
|
+
}
|
|
328
|
+
if (trimmed.startsWith("z.record")) return { type: "object" };
|
|
329
|
+
if (trimmed.startsWith("z.literal")) {
|
|
330
|
+
const val = extractLiteralValue(trimmed);
|
|
331
|
+
return val !== void 0 ? { const: val } : {};
|
|
332
|
+
}
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
function extractEnumValues(expr) {
|
|
336
|
+
const inner = extractSingleArg(expr);
|
|
337
|
+
if (!inner) return [];
|
|
338
|
+
const values = [];
|
|
339
|
+
const valuePattern = /["']([^"']+)["']/g;
|
|
340
|
+
let m;
|
|
341
|
+
while ((m = valuePattern.exec(inner)) !== null) {
|
|
342
|
+
if (m[1]) values.push(m[1]);
|
|
343
|
+
}
|
|
344
|
+
return values;
|
|
345
|
+
}
|
|
346
|
+
function extractSingleArg(expr) {
|
|
347
|
+
const openParen = expr.indexOf("(");
|
|
348
|
+
if (openParen === -1) return null;
|
|
349
|
+
let depth = 0;
|
|
350
|
+
for (let i = openParen; i < expr.length; i++) {
|
|
351
|
+
const ch = expr[i];
|
|
352
|
+
if (ch === "(") depth++;
|
|
353
|
+
else if (ch === ")") {
|
|
354
|
+
depth--;
|
|
355
|
+
if (depth === 0) {
|
|
356
|
+
return expr.slice(openParen + 1, i);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
function extractLiteralValue(expr) {
|
|
363
|
+
const inner = extractSingleArg(expr)?.trim();
|
|
364
|
+
if (!inner) return void 0;
|
|
365
|
+
if (inner.startsWith('"') && inner.endsWith('"') || inner.startsWith("'") && inner.endsWith("'")) {
|
|
366
|
+
return inner.slice(1, -1);
|
|
367
|
+
}
|
|
368
|
+
if (inner === "true") return true;
|
|
369
|
+
if (inner === "false") return false;
|
|
370
|
+
const num = Number(inner);
|
|
371
|
+
if (!Number.isNaN(num)) return num;
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/tools/openapi-parser.ts
|
|
376
|
+
var import_zod2 = require("zod");
|
|
377
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
378
|
+
"get",
|
|
379
|
+
"post",
|
|
380
|
+
"put",
|
|
381
|
+
"patch",
|
|
382
|
+
"delete",
|
|
383
|
+
"head",
|
|
384
|
+
"options",
|
|
385
|
+
"trace"
|
|
386
|
+
]);
|
|
387
|
+
var DEFAULT_WRITE_METHODS2 = ["POST", "PUT", "PATCH", "DELETE"];
|
|
388
|
+
function parseOpenAPISpec(spec) {
|
|
389
|
+
const routes = [];
|
|
390
|
+
for (const [rawPath, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
391
|
+
const methods = [];
|
|
392
|
+
for (const key of Object.keys(pathItem)) {
|
|
393
|
+
if (HTTP_METHODS.has(key.toLowerCase())) {
|
|
394
|
+
methods.push(key.toUpperCase());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (methods.length === 0) continue;
|
|
398
|
+
const expressPath = openApiPathToExpress(rawPath);
|
|
399
|
+
const pathParams = extractPathParams(rawPath);
|
|
400
|
+
routes.push({
|
|
401
|
+
path: expressPath,
|
|
402
|
+
methods,
|
|
403
|
+
pathParams,
|
|
404
|
+
// No real file path — use the spec path as a synthetic identifier.
|
|
405
|
+
filePath: `openapi:${rawPath}`
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return routes;
|
|
409
|
+
}
|
|
410
|
+
function generateToolsFromOpenAPI(spec, confirmation) {
|
|
411
|
+
const confirmMethods = confirmation?.methods ?? DEFAULT_WRITE_METHODS2;
|
|
412
|
+
const tools = [];
|
|
413
|
+
for (const [rawPath, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
414
|
+
for (const [methodKey, operation] of Object.entries(pathItem)) {
|
|
415
|
+
if (!HTTP_METHODS.has(methodKey.toLowerCase())) continue;
|
|
416
|
+
const method = methodKey.toUpperCase();
|
|
417
|
+
const op = operation;
|
|
418
|
+
const toolName = buildToolName2(method, rawPath, op.operationId);
|
|
419
|
+
const description = buildDescription(method, rawPath, op);
|
|
420
|
+
const expressPath = openApiPathToExpress(rawPath);
|
|
421
|
+
const parameters = buildZodSchema(method, rawPath, op, spec);
|
|
422
|
+
tools.push({
|
|
423
|
+
name: toolName,
|
|
424
|
+
description,
|
|
425
|
+
parameters,
|
|
426
|
+
requiresConfirmation: confirmMethods.includes(method),
|
|
427
|
+
httpMethod: method,
|
|
428
|
+
path: expressPath
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return tools;
|
|
433
|
+
}
|
|
434
|
+
async function fetchOpenAPISpec(url) {
|
|
435
|
+
const response = await fetch(url);
|
|
436
|
+
if (!response.ok) {
|
|
437
|
+
throw new Error(`Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`);
|
|
438
|
+
}
|
|
439
|
+
const spec = await response.json();
|
|
440
|
+
return spec;
|
|
441
|
+
}
|
|
442
|
+
function buildToolName2(method, rawPath, operationId) {
|
|
443
|
+
if (operationId) {
|
|
444
|
+
return operationId.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase().replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
445
|
+
}
|
|
446
|
+
const pathPart = rawPath.replace(/^\//, "").replace(/\{([^}]+)\}/g, "$1").replace(/\//g, "_").replace(/_+/g, "_").replace(/_$/, "");
|
|
447
|
+
return `${method.toLowerCase()}_${pathPart}`;
|
|
448
|
+
}
|
|
449
|
+
function buildDescription(method, rawPath, op) {
|
|
450
|
+
if (op.summary) return op.summary;
|
|
451
|
+
if (op.description) return op.description;
|
|
452
|
+
return `${method} ${rawPath}`;
|
|
453
|
+
}
|
|
454
|
+
function openApiPathToExpress(path) {
|
|
455
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
456
|
+
}
|
|
457
|
+
function extractPathParams(path) {
|
|
458
|
+
const params = [];
|
|
459
|
+
const re = /\{([^}]+)\}/g;
|
|
460
|
+
let m;
|
|
461
|
+
while ((m = re.exec(path)) !== null) {
|
|
462
|
+
if (m[1]) params.push(m[1]);
|
|
463
|
+
}
|
|
464
|
+
return params;
|
|
465
|
+
}
|
|
466
|
+
function buildZodSchema(method, rawPath, op, spec) {
|
|
467
|
+
const shape = {};
|
|
468
|
+
for (const paramName of extractPathParams(rawPath)) {
|
|
469
|
+
shape[paramName] = import_zod2.z.string().describe(`Path parameter: ${paramName}`);
|
|
470
|
+
}
|
|
471
|
+
for (const param of op.parameters ?? []) {
|
|
472
|
+
if (param.in !== "path" && param.in !== "query") continue;
|
|
473
|
+
if (param.in === "path") {
|
|
474
|
+
if (param.description) {
|
|
475
|
+
shape[param.name] = import_zod2.z.string().describe(param.description);
|
|
476
|
+
}
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const resolved = resolveSchema(param.schema ?? {}, spec);
|
|
480
|
+
let zodField = jsonSchemaToZod(resolved);
|
|
481
|
+
if (param.description) zodField = zodField.describe(param.description);
|
|
482
|
+
shape[param.name] = param.required ? zodField : zodField.optional();
|
|
483
|
+
}
|
|
484
|
+
if (["POST", "PUT", "PATCH"].includes(method) && op.requestBody) {
|
|
485
|
+
const jsonContent = op.requestBody.content["application/json"] ?? Object.values(op.requestBody.content)[0];
|
|
486
|
+
if (jsonContent?.schema) {
|
|
487
|
+
const resolved = resolveSchema(jsonContent.schema, spec);
|
|
488
|
+
const bodySchema = jsonSchemaObjectToZod(resolved, spec);
|
|
489
|
+
shape["body"] = op.requestBody.required ? bodySchema : bodySchema.optional();
|
|
490
|
+
} else {
|
|
491
|
+
shape["body"] = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional().describe("Request body");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return import_zod2.z.object(shape);
|
|
495
|
+
}
|
|
496
|
+
function resolveSchema(schema, spec) {
|
|
497
|
+
const ref = schema["$ref"];
|
|
498
|
+
if (typeof ref !== "string") return schema;
|
|
499
|
+
const prefix = "#/components/schemas/";
|
|
500
|
+
if (!ref.startsWith(prefix)) return schema;
|
|
501
|
+
const schemaName = ref.slice(prefix.length);
|
|
502
|
+
const resolved = spec.components?.schemas?.[schemaName];
|
|
503
|
+
return resolved ?? schema;
|
|
504
|
+
}
|
|
505
|
+
function jsonSchemaObjectToZod(schema, spec) {
|
|
506
|
+
if (schema["type"] !== "object") {
|
|
507
|
+
return jsonSchemaToZod(schema);
|
|
508
|
+
}
|
|
509
|
+
const props = schema["properties"];
|
|
510
|
+
if (!props) {
|
|
511
|
+
return import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).describe("Request body");
|
|
512
|
+
}
|
|
513
|
+
const required = new Set(
|
|
514
|
+
Array.isArray(schema["required"]) ? schema["required"] : []
|
|
515
|
+
);
|
|
516
|
+
const shape = {};
|
|
517
|
+
for (const [propName, propSchema] of Object.entries(props)) {
|
|
518
|
+
const resolved = resolveSchema(propSchema, spec);
|
|
519
|
+
const zodField = jsonSchemaToZod(resolved);
|
|
520
|
+
shape[propName] = required.has(propName) ? zodField : zodField.optional();
|
|
521
|
+
}
|
|
522
|
+
return import_zod2.z.object(shape);
|
|
523
|
+
}
|
|
524
|
+
function jsonSchemaToZod(schema) {
|
|
525
|
+
const type = schema["type"];
|
|
526
|
+
const anyOf = schema["anyOf"] ?? schema["oneOf"];
|
|
527
|
+
if (Array.isArray(anyOf) && anyOf.length >= 2) {
|
|
528
|
+
const members = anyOf.map(
|
|
529
|
+
(s) => jsonSchemaToZod(s)
|
|
530
|
+
);
|
|
531
|
+
const [first, second, ...rest] = members;
|
|
532
|
+
return import_zod2.z.union([first, second, ...rest]);
|
|
533
|
+
}
|
|
534
|
+
if (schema["enum"] !== void 0) {
|
|
535
|
+
const values = schema["enum"];
|
|
536
|
+
if (values.every((v) => typeof v === "string")) {
|
|
537
|
+
const [first, second, ...rest] = values;
|
|
538
|
+
if (first !== void 0 && second !== void 0) {
|
|
539
|
+
return import_zod2.z.enum([first, second, ...rest]);
|
|
540
|
+
}
|
|
541
|
+
if (first !== void 0) return import_zod2.z.literal(first);
|
|
542
|
+
}
|
|
543
|
+
return import_zod2.z.unknown();
|
|
544
|
+
}
|
|
545
|
+
switch (type) {
|
|
546
|
+
case "string":
|
|
547
|
+
return import_zod2.z.string();
|
|
548
|
+
case "number":
|
|
549
|
+
case "integer":
|
|
550
|
+
return import_zod2.z.number();
|
|
551
|
+
case "boolean":
|
|
552
|
+
return import_zod2.z.boolean();
|
|
553
|
+
case "null":
|
|
554
|
+
return import_zod2.z.null();
|
|
555
|
+
case "array": {
|
|
556
|
+
const items = schema["items"];
|
|
557
|
+
return items ? import_zod2.z.array(jsonSchemaToZod(items)) : import_zod2.z.array(import_zod2.z.unknown());
|
|
558
|
+
}
|
|
559
|
+
case "object": {
|
|
560
|
+
const props = schema["properties"];
|
|
561
|
+
if (!props) return import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown());
|
|
562
|
+
const required = new Set(
|
|
563
|
+
Array.isArray(schema["required"]) ? schema["required"] : []
|
|
564
|
+
);
|
|
565
|
+
const innerShape = {};
|
|
566
|
+
for (const [k, v] of Object.entries(props)) {
|
|
567
|
+
const field = jsonSchemaToZod(v);
|
|
568
|
+
innerShape[k] = required.has(k) ? field : field.optional();
|
|
569
|
+
}
|
|
570
|
+
return import_zod2.z.object(innerShape);
|
|
571
|
+
}
|
|
572
|
+
default:
|
|
573
|
+
return import_zod2.z.unknown();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/executor.ts
|
|
578
|
+
async function executeTool(tool, params, context) {
|
|
579
|
+
const id = crypto.randomUUID();
|
|
580
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
581
|
+
try {
|
|
582
|
+
let response;
|
|
583
|
+
if (tool.execute) {
|
|
584
|
+
response = await tool.execute(params);
|
|
585
|
+
} else if (tool.httpMethod && tool.path) {
|
|
586
|
+
response = await executeHttpTool(tool, params, context);
|
|
587
|
+
} else {
|
|
588
|
+
throw new Error(`Tool "${tool.name}" has no execute function or HTTP path`);
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
id,
|
|
592
|
+
toolName: tool.name,
|
|
593
|
+
parameters: params,
|
|
594
|
+
statusCode: 200,
|
|
595
|
+
response,
|
|
596
|
+
confirmed: !tool.requiresConfirmation,
|
|
597
|
+
executedAt: startTime
|
|
598
|
+
};
|
|
599
|
+
} catch (error) {
|
|
600
|
+
return {
|
|
601
|
+
id,
|
|
602
|
+
toolName: tool.name,
|
|
603
|
+
parameters: params,
|
|
604
|
+
statusCode: 500,
|
|
605
|
+
response: { error: error instanceof Error ? error.message : "Unknown error" },
|
|
606
|
+
confirmed: false,
|
|
607
|
+
executedAt: startTime
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function executeHttpTool(tool, params, context) {
|
|
612
|
+
const method = tool.httpMethod;
|
|
613
|
+
let url = `${context.baseUrl}${tool.path}`;
|
|
614
|
+
for (const [key, value] of Object.entries(params)) {
|
|
615
|
+
if (url.includes(`:${key}`)) {
|
|
616
|
+
url = url.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const fetchOptions = {
|
|
620
|
+
method,
|
|
621
|
+
headers: {
|
|
622
|
+
"Content-Type": "application/json",
|
|
623
|
+
...context.headers
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
if (method === "GET" && params.query) {
|
|
627
|
+
const queryParams = new URLSearchParams(
|
|
628
|
+
params.query
|
|
629
|
+
);
|
|
630
|
+
url += `?${queryParams.toString()}`;
|
|
631
|
+
} else if (["POST", "PUT", "PATCH"].includes(method) && params.body) {
|
|
632
|
+
fetchOptions.body = JSON.stringify(params.body);
|
|
633
|
+
}
|
|
634
|
+
const response = await fetch(url, fetchOptions);
|
|
635
|
+
if (!response.ok) {
|
|
636
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
637
|
+
}
|
|
638
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
639
|
+
if (contentType.includes("application/json")) {
|
|
640
|
+
return response.json();
|
|
641
|
+
}
|
|
642
|
+
return response.text();
|
|
643
|
+
}
|
|
644
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
645
|
+
0 && (module.exports = {
|
|
646
|
+
executeTool,
|
|
647
|
+
extractSchemasFromFile,
|
|
648
|
+
fetchOpenAPISpec,
|
|
649
|
+
generateToolDefinitions,
|
|
650
|
+
generateToolDefinitionsWithSchemas,
|
|
651
|
+
generateToolsFromOpenAPI,
|
|
652
|
+
parseOpenAPISpec
|
|
653
|
+
});
|
|
654
|
+
//# sourceMappingURL=index.cjs.map
|