@spec2tools/core 0.1.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 +21 -0
- package/README.md +117 -0
- package/dist/agent.d.ts +38 -0
- package/dist/agent.js +126 -0
- package/dist/auth-manager.d.ts +67 -0
- package/dist/auth-manager.js +308 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +315 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +41 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.js +99 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +3 -0
- package/dist/openapi-parser.d.ts +26 -0
- package/dist/openapi-parser.js +368 -0
- package/dist/tool-executor.d.ts +20 -0
- package/dist/tool-executor.js +149 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +2 -0
- package/package.json +50 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { UnsupportedSchemaError, SpecLoadError } from './errors.js';
|
|
5
|
+
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
6
|
+
/**
|
|
7
|
+
* Fetch and parse an OpenAPI specification from URL or file path
|
|
8
|
+
*/
|
|
9
|
+
export async function loadOpenAPISpec(specPath) {
|
|
10
|
+
let content;
|
|
11
|
+
if (specPath.startsWith('http://') || specPath.startsWith('https://')) {
|
|
12
|
+
const response = await fetch(specPath);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new SpecLoadError(`HTTP ${response.status}: ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
content = await response.text();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
try {
|
|
20
|
+
content = await readFile(specPath, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new SpecLoadError(`Cannot read file: ${specPath}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
// Try JSON first, then YAML
|
|
28
|
+
if (specPath.endsWith('.json') || content.trim().startsWith('{')) {
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
}
|
|
31
|
+
return parseYaml(content);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new SpecLoadError('Invalid JSON or YAML format');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract the base URL from the OpenAPI spec
|
|
39
|
+
*/
|
|
40
|
+
export function extractBaseUrl(spec) {
|
|
41
|
+
if (spec.servers && spec.servers.length > 0) {
|
|
42
|
+
return spec.servers[0].url.replace(/\/$/, ''); // Remove trailing slash
|
|
43
|
+
}
|
|
44
|
+
throw new SpecLoadError('No server URL defined in OpenAPI spec');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extract authentication configuration from security schemes
|
|
48
|
+
*/
|
|
49
|
+
export function extractAuthConfig(spec) {
|
|
50
|
+
const securitySchemes = spec.components?.securitySchemes;
|
|
51
|
+
const globalSecurity = spec.security;
|
|
52
|
+
if (!securitySchemes || !globalSecurity || globalSecurity.length === 0) {
|
|
53
|
+
return { type: 'none' };
|
|
54
|
+
}
|
|
55
|
+
// Get the first security requirement
|
|
56
|
+
const securityReq = globalSecurity[0];
|
|
57
|
+
const schemeName = Object.keys(securityReq)[0];
|
|
58
|
+
const scheme = securitySchemes[schemeName];
|
|
59
|
+
if (!scheme) {
|
|
60
|
+
return { type: 'none' };
|
|
61
|
+
}
|
|
62
|
+
return parseSecurityScheme(scheme, securityReq[schemeName]);
|
|
63
|
+
}
|
|
64
|
+
function parseSecurityScheme(scheme, scopes) {
|
|
65
|
+
if (scheme.type === 'oauth2') {
|
|
66
|
+
const flow = scheme.flows?.authorizationCode;
|
|
67
|
+
if (!flow) {
|
|
68
|
+
throw new UnsupportedSchemaError('securitySchemes', 'Only OAuth2 authorization code flow is supported');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
type: 'oauth2',
|
|
72
|
+
authorizationUrl: flow.authorizationUrl,
|
|
73
|
+
tokenUrl: flow.tokenUrl,
|
|
74
|
+
scopes: scopes,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (scheme.type === 'apiKey') {
|
|
78
|
+
return {
|
|
79
|
+
type: 'apiKey',
|
|
80
|
+
apiKeyHeader: scheme.name,
|
|
81
|
+
apiKeyIn: scheme.in,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (scheme.type === 'http' && scheme.scheme === 'bearer') {
|
|
85
|
+
return { type: 'bearer' };
|
|
86
|
+
}
|
|
87
|
+
return { type: 'none' };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Convert an OpenAPI schema to a Zod schema
|
|
91
|
+
*/
|
|
92
|
+
function schemaToZod(schema, path, depth = 0) {
|
|
93
|
+
// Check for unsupported features
|
|
94
|
+
if (schema.anyOf) {
|
|
95
|
+
throw new UnsupportedSchemaError(path, 'anyOf is not supported');
|
|
96
|
+
}
|
|
97
|
+
if (schema.oneOf) {
|
|
98
|
+
throw new UnsupportedSchemaError(path, 'oneOf is not supported');
|
|
99
|
+
}
|
|
100
|
+
if (schema.allOf) {
|
|
101
|
+
throw new UnsupportedSchemaError(path, 'allOf is not supported');
|
|
102
|
+
}
|
|
103
|
+
if (schema.$ref) {
|
|
104
|
+
throw new UnsupportedSchemaError(path, '$ref is not supported');
|
|
105
|
+
}
|
|
106
|
+
// Handle array types
|
|
107
|
+
if (schema.type === 'array') {
|
|
108
|
+
if (!schema.items) {
|
|
109
|
+
throw new UnsupportedSchemaError(path, 'Array without items schema');
|
|
110
|
+
}
|
|
111
|
+
if (schema.items.type === 'object') {
|
|
112
|
+
throw new UnsupportedSchemaError(path, 'Arrays of objects are not supported');
|
|
113
|
+
}
|
|
114
|
+
const itemSchema = schemaToZod(schema.items, `${path}.items`, depth);
|
|
115
|
+
let arraySchema = z.array(itemSchema);
|
|
116
|
+
if (schema.description) {
|
|
117
|
+
arraySchema = arraySchema.describe(schema.description);
|
|
118
|
+
}
|
|
119
|
+
return arraySchema;
|
|
120
|
+
}
|
|
121
|
+
// Handle object types
|
|
122
|
+
if (schema.type === 'object' || schema.properties) {
|
|
123
|
+
if (depth > 1) {
|
|
124
|
+
throw new UnsupportedSchemaError(path, 'Nested objects beyond 1 level are not supported');
|
|
125
|
+
}
|
|
126
|
+
const properties = schema.properties || {};
|
|
127
|
+
const required = schema.required || [];
|
|
128
|
+
const shape = {};
|
|
129
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
130
|
+
let zodProp = schemaToZod(propSchema, `${path}.${propName}`, depth + 1);
|
|
131
|
+
if (!required.includes(propName)) {
|
|
132
|
+
zodProp = zodProp.optional();
|
|
133
|
+
}
|
|
134
|
+
shape[propName] = zodProp;
|
|
135
|
+
}
|
|
136
|
+
let objSchema = z.object(shape);
|
|
137
|
+
if (schema.description) {
|
|
138
|
+
objSchema = objSchema.describe(schema.description);
|
|
139
|
+
}
|
|
140
|
+
return objSchema;
|
|
141
|
+
}
|
|
142
|
+
// Handle primitive types
|
|
143
|
+
switch (schema.type) {
|
|
144
|
+
case 'string': {
|
|
145
|
+
let strSchema = z.string();
|
|
146
|
+
if (schema.enum) {
|
|
147
|
+
strSchema = z.enum(schema.enum);
|
|
148
|
+
}
|
|
149
|
+
if (schema.description) {
|
|
150
|
+
strSchema = strSchema.describe(schema.description);
|
|
151
|
+
}
|
|
152
|
+
return strSchema;
|
|
153
|
+
}
|
|
154
|
+
case 'number':
|
|
155
|
+
case 'integer': {
|
|
156
|
+
let numSchema = z.number();
|
|
157
|
+
if (schema.description) {
|
|
158
|
+
numSchema = numSchema.describe(schema.description);
|
|
159
|
+
}
|
|
160
|
+
return numSchema;
|
|
161
|
+
}
|
|
162
|
+
case 'boolean': {
|
|
163
|
+
let boolSchema = z.boolean();
|
|
164
|
+
if (schema.description) {
|
|
165
|
+
boolSchema = boolSchema.describe(schema.description);
|
|
166
|
+
}
|
|
167
|
+
return boolSchema;
|
|
168
|
+
}
|
|
169
|
+
default:
|
|
170
|
+
// Default to string for unknown types
|
|
171
|
+
return z.string();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build parameters schema from path and query parameters
|
|
176
|
+
*/
|
|
177
|
+
function buildParametersSchema(parameters, operationId) {
|
|
178
|
+
const shape = {};
|
|
179
|
+
for (const param of parameters) {
|
|
180
|
+
if (param.in !== 'path' && param.in !== 'query') {
|
|
181
|
+
continue; // Skip header and cookie parameters
|
|
182
|
+
}
|
|
183
|
+
let paramSchema;
|
|
184
|
+
if (param.schema) {
|
|
185
|
+
paramSchema = schemaToZod(param.schema, `${operationId}.parameters.${param.name}`, 0);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
paramSchema = z.string();
|
|
189
|
+
}
|
|
190
|
+
if (param.description) {
|
|
191
|
+
paramSchema = paramSchema.describe(param.description);
|
|
192
|
+
}
|
|
193
|
+
if (!param.required) {
|
|
194
|
+
paramSchema = paramSchema.optional();
|
|
195
|
+
}
|
|
196
|
+
shape[param.name] = paramSchema;
|
|
197
|
+
}
|
|
198
|
+
return shape;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Build request body schema
|
|
202
|
+
*/
|
|
203
|
+
function buildRequestBodySchema(operation, operationId) {
|
|
204
|
+
if (!operation.requestBody?.content) {
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
const jsonContent = operation.requestBody.content['application/json'];
|
|
208
|
+
if (!jsonContent?.schema) {
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
const schema = jsonContent.schema;
|
|
212
|
+
// Check for file uploads
|
|
213
|
+
if (schema.type === 'string' && schema.format === 'binary') {
|
|
214
|
+
throw new UnsupportedSchemaError(`${operationId}.requestBody`, 'File uploads are not supported');
|
|
215
|
+
}
|
|
216
|
+
// For object schemas, flatten properties into the parameter shape
|
|
217
|
+
if (schema.type === 'object' || schema.properties) {
|
|
218
|
+
const properties = schema.properties || {};
|
|
219
|
+
const required = schema.required || [];
|
|
220
|
+
const shape = {};
|
|
221
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
222
|
+
// Check for file upload in properties
|
|
223
|
+
if (propSchema.type === 'string' &&
|
|
224
|
+
propSchema.format === 'binary') {
|
|
225
|
+
throw new UnsupportedSchemaError(`${operationId}.requestBody.${propName}`, 'File uploads are not supported');
|
|
226
|
+
}
|
|
227
|
+
let zodProp = schemaToZod(propSchema, `${operationId}.requestBody.${propName}`, 1);
|
|
228
|
+
if (!required.includes(propName)) {
|
|
229
|
+
zodProp = zodProp.optional();
|
|
230
|
+
}
|
|
231
|
+
shape[propName] = zodProp;
|
|
232
|
+
}
|
|
233
|
+
return shape;
|
|
234
|
+
}
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Generate tool name from operation
|
|
239
|
+
*/
|
|
240
|
+
function generateToolName(operation, method, path) {
|
|
241
|
+
if (operation.operationId) {
|
|
242
|
+
return operation.operationId;
|
|
243
|
+
}
|
|
244
|
+
// Generate name from method and path
|
|
245
|
+
const cleanPath = path
|
|
246
|
+
.replace(/[{}]/g, '')
|
|
247
|
+
.replace(/\//g, '_')
|
|
248
|
+
.replace(/^_/, '');
|
|
249
|
+
return `${method.toLowerCase()}_${cleanPath}`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Parse all operations from the OpenAPI spec and generate tool definitions
|
|
253
|
+
*/
|
|
254
|
+
export function parseOperations(spec) {
|
|
255
|
+
const tools = [];
|
|
256
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
257
|
+
// Get path-level parameters
|
|
258
|
+
const pathParameters = pathItem.parameters || [];
|
|
259
|
+
for (const method of HTTP_METHODS) {
|
|
260
|
+
const methodKey = method.toLowerCase();
|
|
261
|
+
const operation = pathItem[methodKey];
|
|
262
|
+
if (!operation) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const operationId = generateToolName(operation, method, path);
|
|
266
|
+
// Combine path-level and operation-level parameters
|
|
267
|
+
const allParameters = [
|
|
268
|
+
...pathParameters,
|
|
269
|
+
...(operation.parameters || []),
|
|
270
|
+
];
|
|
271
|
+
// Build combined schema from parameters and request body
|
|
272
|
+
const parameterShape = buildParametersSchema(allParameters, operationId);
|
|
273
|
+
const bodyShape = buildRequestBodySchema(operation, operationId);
|
|
274
|
+
const combinedShape = { ...parameterShape, ...bodyShape };
|
|
275
|
+
const parameters = z.object(combinedShape);
|
|
276
|
+
tools.push({
|
|
277
|
+
name: operationId,
|
|
278
|
+
description: operation.summary || operation.description || `${method} ${path}`,
|
|
279
|
+
parameters,
|
|
280
|
+
httpMethod: method,
|
|
281
|
+
path,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return tools;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Format tool schema for display
|
|
289
|
+
*/
|
|
290
|
+
export function formatToolSchema(tool) {
|
|
291
|
+
const shape = tool.parameters.shape;
|
|
292
|
+
const lines = ['{'];
|
|
293
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
294
|
+
const zodSchema = schema;
|
|
295
|
+
const isOptional = zodSchema.isOptional();
|
|
296
|
+
const description = zodSchema.description;
|
|
297
|
+
let typeStr = getZodTypeString(zodSchema);
|
|
298
|
+
if (isOptional) {
|
|
299
|
+
typeStr += '.optional()';
|
|
300
|
+
}
|
|
301
|
+
if (description) {
|
|
302
|
+
typeStr += `.describe("${description}")`;
|
|
303
|
+
}
|
|
304
|
+
lines.push(` ${key}: ${typeStr},`);
|
|
305
|
+
}
|
|
306
|
+
lines.push('}');
|
|
307
|
+
return lines.join('\n');
|
|
308
|
+
}
|
|
309
|
+
function getZodTypeString(schema) {
|
|
310
|
+
if (schema instanceof z.ZodOptional) {
|
|
311
|
+
return getZodTypeString(schema.unwrap());
|
|
312
|
+
}
|
|
313
|
+
if (schema instanceof z.ZodString) {
|
|
314
|
+
return 'z.string()';
|
|
315
|
+
}
|
|
316
|
+
if (schema instanceof z.ZodNumber) {
|
|
317
|
+
return 'z.number()';
|
|
318
|
+
}
|
|
319
|
+
if (schema instanceof z.ZodBoolean) {
|
|
320
|
+
return 'z.boolean()';
|
|
321
|
+
}
|
|
322
|
+
if (schema instanceof z.ZodEnum) {
|
|
323
|
+
return `z.enum([${schema.options.map((o) => `"${o}"`).join(', ')}])`;
|
|
324
|
+
}
|
|
325
|
+
if (schema instanceof z.ZodArray) {
|
|
326
|
+
return `z.array(${getZodTypeString(schema.element)})`;
|
|
327
|
+
}
|
|
328
|
+
if (schema instanceof z.ZodObject) {
|
|
329
|
+
return 'z.object({...})';
|
|
330
|
+
}
|
|
331
|
+
return 'z.unknown()';
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Format tool signature for display
|
|
335
|
+
*/
|
|
336
|
+
export function formatToolSignature(tool) {
|
|
337
|
+
const shape = tool.parameters.shape;
|
|
338
|
+
const params = [];
|
|
339
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
340
|
+
const zodSchema = schema;
|
|
341
|
+
const isOptional = zodSchema.isOptional();
|
|
342
|
+
const typeStr = getSimpleTypeString(zodSchema);
|
|
343
|
+
params.push(`${key}${isOptional ? '?' : ''}: ${typeStr}`);
|
|
344
|
+
}
|
|
345
|
+
return `${tool.name}(${params.join(', ')})`;
|
|
346
|
+
}
|
|
347
|
+
function getSimpleTypeString(schema) {
|
|
348
|
+
if (schema instanceof z.ZodOptional) {
|
|
349
|
+
return getSimpleTypeString(schema.unwrap());
|
|
350
|
+
}
|
|
351
|
+
if (schema instanceof z.ZodString || schema instanceof z.ZodEnum) {
|
|
352
|
+
return 'string';
|
|
353
|
+
}
|
|
354
|
+
if (schema instanceof z.ZodNumber) {
|
|
355
|
+
return 'number';
|
|
356
|
+
}
|
|
357
|
+
if (schema instanceof z.ZodBoolean) {
|
|
358
|
+
return 'boolean';
|
|
359
|
+
}
|
|
360
|
+
if (schema instanceof z.ZodArray) {
|
|
361
|
+
return `${getSimpleTypeString(schema.element)}[]`;
|
|
362
|
+
}
|
|
363
|
+
if (schema instanceof z.ZodObject) {
|
|
364
|
+
return 'object';
|
|
365
|
+
}
|
|
366
|
+
return 'unknown';
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=openapi-parser.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Tool, HttpMethod } from './types.js';
|
|
3
|
+
import { AuthManager } from './auth-manager.js';
|
|
4
|
+
interface ToolDefinition {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: z.ZodObject<z.ZodRawShape>;
|
|
8
|
+
httpMethod: HttpMethod;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create executable tools from tool definitions
|
|
13
|
+
*/
|
|
14
|
+
export declare function createExecutableTools(toolDefs: ToolDefinition[], baseUrl: string, authManager: AuthManager): Tool[];
|
|
15
|
+
/**
|
|
16
|
+
* Execute a tool directly by name
|
|
17
|
+
*/
|
|
18
|
+
export declare function executeToolByName(tools: Tool[], toolName: string, params: Record<string, unknown>): Promise<unknown>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=tool-executor.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ToolExecutionError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create executable tools from tool definitions
|
|
5
|
+
*/
|
|
6
|
+
export function createExecutableTools(toolDefs, baseUrl, authManager) {
|
|
7
|
+
return toolDefs.map((def) => ({
|
|
8
|
+
...def,
|
|
9
|
+
execute: createExecutor(def, baseUrl, authManager),
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create an executor function for a tool
|
|
14
|
+
*/
|
|
15
|
+
function createExecutor(tool, baseUrl, authManager) {
|
|
16
|
+
return async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
// Validate parameters
|
|
19
|
+
const validatedParams = tool.parameters.parse(params);
|
|
20
|
+
// Build URL with path parameters replaced
|
|
21
|
+
let url = buildUrl(baseUrl, tool.path, validatedParams);
|
|
22
|
+
// Separate path, query, and body parameters
|
|
23
|
+
const { queryParams, bodyParams } = separateParams(validatedParams, tool.path);
|
|
24
|
+
// Add query parameters
|
|
25
|
+
const urlObj = new URL(url);
|
|
26
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
27
|
+
if (value !== undefined) {
|
|
28
|
+
urlObj.searchParams.set(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Add auth query params if needed
|
|
32
|
+
const authQueryParams = authManager.getAuthQueryParams();
|
|
33
|
+
for (const [key, value] of Object.entries(authQueryParams)) {
|
|
34
|
+
urlObj.searchParams.set(key, value);
|
|
35
|
+
}
|
|
36
|
+
url = urlObj.toString();
|
|
37
|
+
// Build request options
|
|
38
|
+
const headers = {
|
|
39
|
+
...authManager.getAuthHeaders(),
|
|
40
|
+
};
|
|
41
|
+
const fetchOptions = {
|
|
42
|
+
method: tool.httpMethod,
|
|
43
|
+
headers,
|
|
44
|
+
};
|
|
45
|
+
// Add body for methods that support it
|
|
46
|
+
if (['POST', 'PUT', 'PATCH'].includes(tool.httpMethod)) {
|
|
47
|
+
if (Object.keys(bodyParams).length > 0) {
|
|
48
|
+
headers['Content-Type'] = 'application/json';
|
|
49
|
+
fetchOptions.body = JSON.stringify(bodyParams);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
fetchOptions.headers = headers;
|
|
53
|
+
// Execute request
|
|
54
|
+
const response = await fetch(url, fetchOptions);
|
|
55
|
+
// Parse response
|
|
56
|
+
const contentType = response.headers.get('content-type') || '';
|
|
57
|
+
let responseData;
|
|
58
|
+
if (contentType.includes('application/json')) {
|
|
59
|
+
responseData = await response.json();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
responseData = await response.text();
|
|
63
|
+
}
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status}: ${JSON.stringify(responseData)}`);
|
|
66
|
+
}
|
|
67
|
+
return responseData;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof z.ZodError) {
|
|
71
|
+
throw new ToolExecutionError(tool.name, new Error(`Invalid parameters: ${error.message}`));
|
|
72
|
+
}
|
|
73
|
+
if (error instanceof ToolExecutionError) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
throw new ToolExecutionError(tool.name, error instanceof Error ? error : new Error(String(error)));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build URL with path parameters replaced
|
|
82
|
+
*/
|
|
83
|
+
function buildUrl(baseUrl, path, params) {
|
|
84
|
+
let finalPath = path;
|
|
85
|
+
// Replace path parameters
|
|
86
|
+
const pathParamRegex = /\{(\w+)\}/g;
|
|
87
|
+
let match;
|
|
88
|
+
while ((match = pathParamRegex.exec(path)) !== null) {
|
|
89
|
+
const paramName = match[1];
|
|
90
|
+
const paramValue = params[paramName];
|
|
91
|
+
if (paramValue !== undefined) {
|
|
92
|
+
finalPath = finalPath.replace(`{${paramName}}`, encodeURIComponent(String(paramValue)));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return `${baseUrl}${finalPath}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Separate parameters into path, query, and body params
|
|
99
|
+
*/
|
|
100
|
+
function separateParams(params, path) {
|
|
101
|
+
const pathParams = {};
|
|
102
|
+
const queryParams = {};
|
|
103
|
+
const bodyParams = {};
|
|
104
|
+
// Extract path parameter names
|
|
105
|
+
const pathParamNames = new Set();
|
|
106
|
+
const pathParamRegex = /\{(\w+)\}/g;
|
|
107
|
+
let match;
|
|
108
|
+
while ((match = pathParamRegex.exec(path)) !== null) {
|
|
109
|
+
pathParamNames.add(match[1]);
|
|
110
|
+
}
|
|
111
|
+
for (const [key, value] of Object.entries(params)) {
|
|
112
|
+
if (pathParamNames.has(key)) {
|
|
113
|
+
pathParams[key] = value;
|
|
114
|
+
}
|
|
115
|
+
else if (isPrimitive(value)) {
|
|
116
|
+
// Primitive values go to query params
|
|
117
|
+
queryParams[key] = value;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Complex values go to body
|
|
121
|
+
bodyParams[key] = value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// For simplicity, if there are non-path primitive params,
|
|
125
|
+
// we need to determine if they're query or body based on HTTP method
|
|
126
|
+
// Since we don't have that info here, we'll treat all non-path primitives
|
|
127
|
+
// as potential query params for GET/DELETE, and body params for POST/PUT/PATCH
|
|
128
|
+
// This is handled by the executor which knows the method
|
|
129
|
+
return { pathParams, queryParams, bodyParams };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if value is a primitive type
|
|
133
|
+
*/
|
|
134
|
+
function isPrimitive(value) {
|
|
135
|
+
return (typeof value === 'string' ||
|
|
136
|
+
typeof value === 'number' ||
|
|
137
|
+
typeof value === 'boolean');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Execute a tool directly by name
|
|
141
|
+
*/
|
|
142
|
+
export async function executeToolByName(tools, toolName, params) {
|
|
143
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
144
|
+
if (!tool) {
|
|
145
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
146
|
+
}
|
|
147
|
+
return tool.execute(params);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=tool-executor.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
3
|
+
export interface Tool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: z.ZodObject<z.ZodRawShape>;
|
|
7
|
+
execute: (params: unknown) => Promise<unknown>;
|
|
8
|
+
httpMethod: HttpMethod;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
export type AuthType = 'oauth2' | 'apiKey' | 'bearer' | 'none';
|
|
12
|
+
export interface AuthConfig {
|
|
13
|
+
type: AuthType;
|
|
14
|
+
authorizationUrl?: string;
|
|
15
|
+
tokenUrl?: string;
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
apiKeyHeader?: string;
|
|
18
|
+
apiKeyIn?: 'header' | 'query';
|
|
19
|
+
}
|
|
20
|
+
export interface Session {
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
tools: Tool[];
|
|
23
|
+
authConfig: AuthConfig;
|
|
24
|
+
accessToken?: string;
|
|
25
|
+
refreshToken?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface OpenAPISpec {
|
|
28
|
+
openapi: string;
|
|
29
|
+
info: {
|
|
30
|
+
title: string;
|
|
31
|
+
version: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
};
|
|
34
|
+
servers?: Array<{
|
|
35
|
+
url: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
}>;
|
|
38
|
+
paths: Record<string, PathItem>;
|
|
39
|
+
components?: {
|
|
40
|
+
schemas?: Record<string, SchemaObject>;
|
|
41
|
+
securitySchemes?: Record<string, SecurityScheme>;
|
|
42
|
+
};
|
|
43
|
+
security?: Array<Record<string, string[]>>;
|
|
44
|
+
}
|
|
45
|
+
export interface PathItem {
|
|
46
|
+
get?: Operation;
|
|
47
|
+
post?: Operation;
|
|
48
|
+
put?: Operation;
|
|
49
|
+
patch?: Operation;
|
|
50
|
+
delete?: Operation;
|
|
51
|
+
parameters?: Parameter[];
|
|
52
|
+
}
|
|
53
|
+
export interface Operation {
|
|
54
|
+
operationId?: string;
|
|
55
|
+
summary?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
parameters?: Parameter[];
|
|
58
|
+
requestBody?: RequestBody;
|
|
59
|
+
responses?: Record<string, Response>;
|
|
60
|
+
security?: Array<Record<string, string[]>>;
|
|
61
|
+
}
|
|
62
|
+
export interface Parameter {
|
|
63
|
+
name: string;
|
|
64
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
65
|
+
required?: boolean;
|
|
66
|
+
description?: string;
|
|
67
|
+
schema?: SchemaObject;
|
|
68
|
+
}
|
|
69
|
+
export interface RequestBody {
|
|
70
|
+
required?: boolean;
|
|
71
|
+
content?: Record<string, MediaType>;
|
|
72
|
+
description?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface MediaType {
|
|
75
|
+
schema?: SchemaObject;
|
|
76
|
+
}
|
|
77
|
+
export interface Response {
|
|
78
|
+
description: string;
|
|
79
|
+
content?: Record<string, MediaType>;
|
|
80
|
+
}
|
|
81
|
+
export interface SchemaObject {
|
|
82
|
+
type?: string;
|
|
83
|
+
format?: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
required?: string[];
|
|
86
|
+
properties?: Record<string, SchemaObject>;
|
|
87
|
+
items?: SchemaObject;
|
|
88
|
+
enum?: unknown[];
|
|
89
|
+
default?: unknown;
|
|
90
|
+
anyOf?: SchemaObject[];
|
|
91
|
+
oneOf?: SchemaObject[];
|
|
92
|
+
allOf?: SchemaObject[];
|
|
93
|
+
$ref?: string;
|
|
94
|
+
}
|
|
95
|
+
export interface SecurityScheme {
|
|
96
|
+
type: 'oauth2' | 'apiKey' | 'http';
|
|
97
|
+
scheme?: string;
|
|
98
|
+
bearerFormat?: string;
|
|
99
|
+
name?: string;
|
|
100
|
+
in?: 'header' | 'query' | 'cookie';
|
|
101
|
+
flows?: {
|
|
102
|
+
authorizationCode?: {
|
|
103
|
+
authorizationUrl: string;
|
|
104
|
+
tokenUrl: string;
|
|
105
|
+
scopes?: Record<string, string>;
|
|
106
|
+
};
|
|
107
|
+
clientCredentials?: {
|
|
108
|
+
tokenUrl: string;
|
|
109
|
+
scopes?: Record<string, string>;
|
|
110
|
+
};
|
|
111
|
+
implicit?: {
|
|
112
|
+
authorizationUrl: string;
|
|
113
|
+
scopes?: Record<string, string>;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED