@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
package/dist/cli.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import * as readline from 'readline';
|
|
5
|
+
import { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, formatToolSchema, formatToolSignature, } from './openapi-parser.js';
|
|
6
|
+
import { AuthManager } from './auth-manager.js';
|
|
7
|
+
import { createExecutableTools, executeToolByName } from './tool-executor.js';
|
|
8
|
+
import { Agent } from './agent.js';
|
|
9
|
+
import { UnsupportedSchemaError, AuthenticationError, ToolExecutionError, SpecLoadError, } from './errors.js';
|
|
10
|
+
const VERSION = '0.1.5';
|
|
11
|
+
export function createCLI() {
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name('spec2tools')
|
|
15
|
+
.description('Dynamically convert OpenAPI specs into AI agent tools at runtime')
|
|
16
|
+
.version(VERSION);
|
|
17
|
+
program
|
|
18
|
+
.command('start')
|
|
19
|
+
.description('Start the agent with an OpenAPI specification')
|
|
20
|
+
.requiredOption('-s, --spec <path>', 'Path or URL to OpenAPI specification')
|
|
21
|
+
.option('--no-auth', 'Skip authentication even if required by spec')
|
|
22
|
+
.option('--token <token>', 'Provide access token directly')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
await startAgent(options);
|
|
25
|
+
});
|
|
26
|
+
return program;
|
|
27
|
+
}
|
|
28
|
+
async function startAgent(options) {
|
|
29
|
+
const spinner = ora();
|
|
30
|
+
try {
|
|
31
|
+
// Load OpenAPI spec
|
|
32
|
+
spinner.start('Loading OpenAPI specification...');
|
|
33
|
+
const spec = await loadOpenAPISpec(options.spec);
|
|
34
|
+
spinner.succeed(`Loaded: ${spec.info.title} v${spec.info.version}`);
|
|
35
|
+
// Extract base URL
|
|
36
|
+
const baseUrl = extractBaseUrl(spec);
|
|
37
|
+
console.log(chalk.dim(`Base URL: ${baseUrl}`));
|
|
38
|
+
// Extract auth config
|
|
39
|
+
const authConfig = extractAuthConfig(spec);
|
|
40
|
+
// Parse operations
|
|
41
|
+
spinner.start('Parsing operations...');
|
|
42
|
+
const toolDefs = parseOperations(spec);
|
|
43
|
+
spinner.succeed(`Found ${toolDefs.length} operations`);
|
|
44
|
+
if (toolDefs.length === 0) {
|
|
45
|
+
console.log(chalk.yellow('No operations found in the specification.'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Initialize auth manager
|
|
49
|
+
const authManager = new AuthManager(authConfig);
|
|
50
|
+
// Handle authentication
|
|
51
|
+
if (options.token) {
|
|
52
|
+
authManager.setAccessToken(options.token);
|
|
53
|
+
console.log(chalk.green('Using provided access token'));
|
|
54
|
+
}
|
|
55
|
+
else if (options.auth && authManager.requiresAuth()) {
|
|
56
|
+
console.log(chalk.yellow(`\nAuthentication required (${authConfig.type})`));
|
|
57
|
+
await authManager.authenticate();
|
|
58
|
+
console.log(chalk.green('Authentication successful!'));
|
|
59
|
+
}
|
|
60
|
+
else if (authManager.requiresAuth()) {
|
|
61
|
+
console.log(chalk.yellow('\nWarning: API requires authentication but --no-auth was specified'));
|
|
62
|
+
}
|
|
63
|
+
// Create executable tools
|
|
64
|
+
const tools = createExecutableTools(toolDefs, baseUrl, authManager);
|
|
65
|
+
// Initialize session
|
|
66
|
+
const session = {
|
|
67
|
+
baseUrl,
|
|
68
|
+
tools,
|
|
69
|
+
authConfig,
|
|
70
|
+
accessToken: authManager.getAccessToken(),
|
|
71
|
+
};
|
|
72
|
+
// Start chat loop
|
|
73
|
+
await startChatLoop(session);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
spinner.fail();
|
|
77
|
+
if (error instanceof UnsupportedSchemaError) {
|
|
78
|
+
console.error(chalk.red(`\nSchema Error: ${error.message}`));
|
|
79
|
+
console.error(chalk.dim('This OpenAPI specification contains features that are not supported.'));
|
|
80
|
+
}
|
|
81
|
+
else if (error instanceof AuthenticationError) {
|
|
82
|
+
console.error(chalk.red(`\nAuth Error: ${error.message}`));
|
|
83
|
+
}
|
|
84
|
+
else if (error instanceof SpecLoadError) {
|
|
85
|
+
console.error(chalk.red(`\nSpec Error: ${error.message}`));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : String(error)}`));
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function startChatLoop(session) {
|
|
94
|
+
// Initialize agent
|
|
95
|
+
const agent = new Agent({ tools: session.tools });
|
|
96
|
+
console.log(chalk.bold('\n--- Spec2Tools ---'));
|
|
97
|
+
console.log(chalk.dim('Type your message or use special commands:'));
|
|
98
|
+
console.log(chalk.dim(' /tools - List available tools'));
|
|
99
|
+
console.log(chalk.dim(' /call - Call a tool directly'));
|
|
100
|
+
console.log(chalk.dim(' /schema - Show tool schema'));
|
|
101
|
+
console.log(chalk.dim(' /help - Show help'));
|
|
102
|
+
console.log(chalk.dim(' /exit - Exit the CLI'));
|
|
103
|
+
console.log('');
|
|
104
|
+
const rl = readline.createInterface({
|
|
105
|
+
input: process.stdin,
|
|
106
|
+
output: process.stdout,
|
|
107
|
+
});
|
|
108
|
+
const prompt = () => {
|
|
109
|
+
rl.question(chalk.cyan('> '), async (input) => {
|
|
110
|
+
const trimmedInput = input.trim();
|
|
111
|
+
if (!trimmedInput) {
|
|
112
|
+
prompt();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Pause readline during async operations to prevent corruption
|
|
116
|
+
rl.pause();
|
|
117
|
+
try {
|
|
118
|
+
await handleInput(trimmedInput, session, agent);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
rl.resume();
|
|
125
|
+
prompt();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
rl.on('close', () => {
|
|
130
|
+
console.log(chalk.dim('\nGoodbye!'));
|
|
131
|
+
process.exit(0);
|
|
132
|
+
});
|
|
133
|
+
prompt();
|
|
134
|
+
}
|
|
135
|
+
async function handleInput(input, session, agent) {
|
|
136
|
+
// Handle special commands
|
|
137
|
+
if (input.startsWith('/')) {
|
|
138
|
+
await handleCommand(input, session, agent);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Regular chat message
|
|
142
|
+
const spinner = ora('Thinking...').start();
|
|
143
|
+
try {
|
|
144
|
+
const response = await agent.chat(input);
|
|
145
|
+
spinner.stop();
|
|
146
|
+
console.log(chalk.white('\n' + response + '\n'));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
spinner.fail('Failed to get response');
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function handleCommand(input, session, agent) {
|
|
154
|
+
const parts = input.slice(1).split(/\s+/);
|
|
155
|
+
const command = parts[0].toLowerCase();
|
|
156
|
+
const args = parts.slice(1);
|
|
157
|
+
switch (command) {
|
|
158
|
+
case 'tools':
|
|
159
|
+
listTools(session.tools);
|
|
160
|
+
break;
|
|
161
|
+
case 'call':
|
|
162
|
+
await callTool(session.tools, args);
|
|
163
|
+
break;
|
|
164
|
+
case 'schema':
|
|
165
|
+
showSchema(session.tools, args[0]);
|
|
166
|
+
break;
|
|
167
|
+
case 'help':
|
|
168
|
+
showHelp();
|
|
169
|
+
break;
|
|
170
|
+
case 'exit':
|
|
171
|
+
case 'quit':
|
|
172
|
+
console.log(chalk.dim('Goodbye!'));
|
|
173
|
+
process.exit(0);
|
|
174
|
+
case 'clear':
|
|
175
|
+
agent.clearHistory();
|
|
176
|
+
console.log(chalk.dim('Conversation history cleared.'));
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
console.log(chalk.yellow(`Unknown command: ${command}`));
|
|
180
|
+
console.log(chalk.dim('Type /help for available commands.'));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function listTools(tools) {
|
|
184
|
+
console.log(chalk.bold('\nAvailable tools:'));
|
|
185
|
+
tools.forEach((tool, index) => {
|
|
186
|
+
const signature = formatToolSignature(tool);
|
|
187
|
+
console.log(chalk.cyan(`${index + 1}. ${signature}`));
|
|
188
|
+
console.log(chalk.dim(` ${tool.description}`));
|
|
189
|
+
});
|
|
190
|
+
console.log('');
|
|
191
|
+
}
|
|
192
|
+
async function callTool(tools, args) {
|
|
193
|
+
if (args.length === 0) {
|
|
194
|
+
console.log(chalk.yellow('Usage: /call <toolName> [--param value ...]'));
|
|
195
|
+
console.log(chalk.dim('Example: /call createUser --name "John" --email "john@example.com"'));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const toolName = args[0];
|
|
199
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
200
|
+
if (!tool) {
|
|
201
|
+
console.log(chalk.red(`Tool not found: ${toolName}`));
|
|
202
|
+
console.log(chalk.dim('Use /tools to see available tools.'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Parse remaining args as --key value pairs
|
|
206
|
+
const params = parseCallArgs(args.slice(1));
|
|
207
|
+
const spinner = ora(`Calling ${toolName}...`).start();
|
|
208
|
+
try {
|
|
209
|
+
const result = await executeToolByName(tools, toolName, params);
|
|
210
|
+
spinner.succeed(`${toolName} completed`);
|
|
211
|
+
console.log(chalk.white('\nResult:'));
|
|
212
|
+
console.log(JSON.stringify(result, null, 2));
|
|
213
|
+
console.log('');
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
spinner.fail(`${toolName} failed`);
|
|
217
|
+
if (error instanceof ToolExecutionError) {
|
|
218
|
+
console.error(chalk.red(error.message));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function parseCallArgs(args) {
|
|
226
|
+
const params = {};
|
|
227
|
+
let i = 0;
|
|
228
|
+
while (i < args.length) {
|
|
229
|
+
const arg = args[i];
|
|
230
|
+
if (arg.startsWith('--')) {
|
|
231
|
+
const key = arg.slice(2);
|
|
232
|
+
const value = args[i + 1];
|
|
233
|
+
if (value === undefined) {
|
|
234
|
+
params[key] = true;
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
else if (value.startsWith('--')) {
|
|
238
|
+
params[key] = true;
|
|
239
|
+
i++;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Try to parse as JSON, number, or boolean
|
|
243
|
+
params[key] = parseValue(value);
|
|
244
|
+
i += 2;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
i++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return params;
|
|
252
|
+
}
|
|
253
|
+
function parseValue(value) {
|
|
254
|
+
// Remove quotes if present
|
|
255
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
256
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
257
|
+
return value.slice(1, -1);
|
|
258
|
+
}
|
|
259
|
+
// Try parsing as number
|
|
260
|
+
const num = Number(value);
|
|
261
|
+
if (!isNaN(num)) {
|
|
262
|
+
return num;
|
|
263
|
+
}
|
|
264
|
+
// Check for boolean
|
|
265
|
+
if (value.toLowerCase() === 'true')
|
|
266
|
+
return true;
|
|
267
|
+
if (value.toLowerCase() === 'false')
|
|
268
|
+
return false;
|
|
269
|
+
// Try parsing as JSON
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(value);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function showSchema(tools, toolName) {
|
|
278
|
+
if (!toolName) {
|
|
279
|
+
console.log(chalk.yellow('Usage: /schema <toolName>'));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
283
|
+
if (!tool) {
|
|
284
|
+
console.log(chalk.red(`Tool not found: ${toolName}`));
|
|
285
|
+
console.log(chalk.dim('Use /tools to see available tools.'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.log(chalk.bold(`\nSchema for ${toolName}:`));
|
|
289
|
+
console.log(chalk.white(formatToolSchema(tool)));
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
function showHelp() {
|
|
293
|
+
console.log(chalk.bold('\nAgent CLI Help'));
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log(chalk.cyan('Chat Mode:'));
|
|
296
|
+
console.log(' Just type your message to chat with the AI agent.');
|
|
297
|
+
console.log(' The agent can use available tools to help you.');
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log(chalk.cyan('Special Commands:'));
|
|
300
|
+
console.log(chalk.white(' /tools') + chalk.dim(' - List all available tools'));
|
|
301
|
+
console.log(chalk.white(' /call <tool> [args]') +
|
|
302
|
+
chalk.dim(' - Call a tool directly'));
|
|
303
|
+
console.log(chalk.white(' /schema <tool>') + chalk.dim(' - Show tool parameter schema'));
|
|
304
|
+
console.log(chalk.white(' /clear') + chalk.dim(' - Clear conversation history'));
|
|
305
|
+
console.log(chalk.white(' /help') + chalk.dim(' - Show this help message'));
|
|
306
|
+
console.log(chalk.white(' /exit') + chalk.dim(' - Exit the CLI'));
|
|
307
|
+
console.log('');
|
|
308
|
+
console.log(chalk.cyan('Examples:'));
|
|
309
|
+
console.log(chalk.dim(' > What can you do?'));
|
|
310
|
+
console.log(chalk.dim(' > Create a user named John with email john@example.com'));
|
|
311
|
+
console.log(chalk.dim(' > /call createUser --name "John" --email "john@example.com"'));
|
|
312
|
+
console.log(chalk.dim(' > /schema createUser'));
|
|
313
|
+
console.log('');
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when encountering unsupported OpenAPI schema features
|
|
3
|
+
*/
|
|
4
|
+
export declare class UnsupportedSchemaError extends Error {
|
|
5
|
+
constructor(path: string, reason: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when authentication fails
|
|
9
|
+
*/
|
|
10
|
+
export declare class AuthenticationError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when tool execution fails
|
|
15
|
+
*/
|
|
16
|
+
export declare class ToolExecutionError extends Error {
|
|
17
|
+
readonly toolName: string;
|
|
18
|
+
readonly cause: Error;
|
|
19
|
+
constructor(toolName: string, cause: Error);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when OpenAPI spec is invalid or cannot be fetched
|
|
23
|
+
*/
|
|
24
|
+
export declare class SpecLoadError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when encountering unsupported OpenAPI schema features
|
|
3
|
+
*/
|
|
4
|
+
export class UnsupportedSchemaError extends Error {
|
|
5
|
+
constructor(path, reason) {
|
|
6
|
+
super(`Unsupported schema at ${path}: ${reason}`);
|
|
7
|
+
this.name = 'UnsupportedSchemaError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Error thrown when authentication fails
|
|
12
|
+
*/
|
|
13
|
+
export class AuthenticationError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(`Authentication failed: ${message}`);
|
|
16
|
+
this.name = 'AuthenticationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Error thrown when tool execution fails
|
|
21
|
+
*/
|
|
22
|
+
export class ToolExecutionError extends Error {
|
|
23
|
+
toolName;
|
|
24
|
+
cause;
|
|
25
|
+
constructor(toolName, cause) {
|
|
26
|
+
super(`Tool ${toolName} failed: ${cause.message}`);
|
|
27
|
+
this.name = 'ToolExecutionError';
|
|
28
|
+
this.toolName = toolName;
|
|
29
|
+
this.cause = cause;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Error thrown when OpenAPI spec is invalid or cannot be fetched
|
|
34
|
+
*/
|
|
35
|
+
export class SpecLoadError extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(`Failed to load OpenAPI spec: ${message}`);
|
|
38
|
+
this.name = 'SpecLoadError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=errors.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { UnsupportedSchemaError, AuthenticationError, ToolExecutionError, SpecLoadError, } from './errors.js';
|
|
2
|
+
export type { HttpMethod, Tool, AuthType, AuthConfig, Session, OpenAPISpec, PathItem, Operation, Parameter, RequestBody, MediaType, Response, SchemaObject, SecurityScheme, } from './types.js';
|
|
3
|
+
export { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, formatToolSchema, formatToolSignature, } from './openapi-parser.js';
|
|
4
|
+
export { AuthManager } from './auth-manager.js';
|
|
5
|
+
export { createExecutableTools, executeToolByName } from './tool-executor.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Error classes
|
|
2
|
+
export { UnsupportedSchemaError, AuthenticationError, ToolExecutionError, SpecLoadError, } from './errors.js';
|
|
3
|
+
// OpenAPI Parser
|
|
4
|
+
export { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, formatToolSchema, formatToolSignature, } from './openapi-parser.js';
|
|
5
|
+
// Auth Manager
|
|
6
|
+
export { AuthManager } from './auth-manager.js';
|
|
7
|
+
// Tool Executor
|
|
8
|
+
export { createExecutableTools, executeToolByName } from './tool-executor.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
type ToolSet = NonNullable<Parameters<typeof generateText>[0]['tools']>;
|
|
3
|
+
export interface Spec2ToolsOptions {
|
|
4
|
+
/** Path or URL to OpenAPI specification */
|
|
5
|
+
spec: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create AI SDK tools from an OpenAPI specification.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createTools } from 'spec2tools';
|
|
13
|
+
* import { generateText } from 'ai';
|
|
14
|
+
* import { openai } from '@ai-sdk/openai';
|
|
15
|
+
*
|
|
16
|
+
* const tools = await createTools({ spec: './openapi.yaml' });
|
|
17
|
+
*
|
|
18
|
+
* const result = await generateText({
|
|
19
|
+
* model: openai('gpt-4o'),
|
|
20
|
+
* tools,
|
|
21
|
+
* prompt: 'List all users',
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @throws Error if the API requires authentication
|
|
26
|
+
*/
|
|
27
|
+
export declare function createTools(options: Spec2ToolsOptions): Promise<ToolSet>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=lib.d.ts.map
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import { loadOpenAPISpec, extractBaseUrl, extractAuthConfig, parseOperations, } from './openapi-parser.js';
|
|
3
|
+
import { AuthManager } from './auth-manager.js';
|
|
4
|
+
import { ToolExecutionError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create AI SDK tools from an OpenAPI specification.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createTools } from 'spec2tools';
|
|
11
|
+
* import { generateText } from 'ai';
|
|
12
|
+
* import { openai } from '@ai-sdk/openai';
|
|
13
|
+
*
|
|
14
|
+
* const tools = await createTools({ spec: './openapi.yaml' });
|
|
15
|
+
*
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: openai('gpt-4o'),
|
|
18
|
+
* tools,
|
|
19
|
+
* prompt: 'List all users',
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @throws Error if the API requires authentication
|
|
24
|
+
*/
|
|
25
|
+
export async function createTools(options) {
|
|
26
|
+
const spec = await loadOpenAPISpec(options.spec);
|
|
27
|
+
const baseUrl = extractBaseUrl(spec);
|
|
28
|
+
const authConfig = extractAuthConfig(spec);
|
|
29
|
+
// Check if auth is required
|
|
30
|
+
if (authConfig.type !== 'none') {
|
|
31
|
+
throw new Error(`This API requires authentication (${authConfig.type}). ` +
|
|
32
|
+
`The createTools() function only supports APIs without authentication. ` +
|
|
33
|
+
`Use the CLI for authenticated APIs: npx spec2tools start --spec ${options.spec}`);
|
|
34
|
+
}
|
|
35
|
+
const toolDefs = parseOperations(spec);
|
|
36
|
+
const authManager = new AuthManager(authConfig);
|
|
37
|
+
// Build AI SDK tools
|
|
38
|
+
const tools = {};
|
|
39
|
+
for (const toolDef of toolDefs) {
|
|
40
|
+
const { name, description, parameters, httpMethod, path } = toolDef;
|
|
41
|
+
tools[name] = tool({
|
|
42
|
+
description,
|
|
43
|
+
inputSchema: parameters,
|
|
44
|
+
execute: async (params) => {
|
|
45
|
+
// Build URL with path parameters
|
|
46
|
+
let url = `${baseUrl}${path}`;
|
|
47
|
+
const queryParams = {};
|
|
48
|
+
const bodyParams = {};
|
|
49
|
+
for (const [key, value] of Object.entries(params)) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
continue;
|
|
52
|
+
if (url.includes(`{${key}}`)) {
|
|
53
|
+
// Path parameter
|
|
54
|
+
url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
|
|
55
|
+
}
|
|
56
|
+
else if (httpMethod === 'GET' || httpMethod === 'DELETE') {
|
|
57
|
+
// Query parameter for GET/DELETE
|
|
58
|
+
queryParams[key] = String(value);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Body parameter for POST/PUT/PATCH
|
|
62
|
+
bodyParams[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Add query parameters
|
|
66
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
67
|
+
if (queryString) {
|
|
68
|
+
url += `?${queryString}`;
|
|
69
|
+
}
|
|
70
|
+
// Build request options
|
|
71
|
+
const fetchOptions = {
|
|
72
|
+
method: httpMethod,
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
...authManager.getAuthHeaders(),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
// Add body for non-GET/DELETE requests
|
|
79
|
+
if (Object.keys(bodyParams).length > 0 &&
|
|
80
|
+
httpMethod !== 'GET' &&
|
|
81
|
+
httpMethod !== 'DELETE') {
|
|
82
|
+
fetchOptions.body = JSON.stringify(bodyParams);
|
|
83
|
+
}
|
|
84
|
+
const response = await fetch(url, fetchOptions);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
const errorText = await response.text();
|
|
87
|
+
throw new ToolExecutionError(name, new Error(`HTTP ${response.status}: ${errorText}`));
|
|
88
|
+
}
|
|
89
|
+
const contentType = response.headers.get('content-type');
|
|
90
|
+
if (contentType?.includes('application/json')) {
|
|
91
|
+
return await response.json();
|
|
92
|
+
}
|
|
93
|
+
return await response.text();
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return tools;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OpenAPISpec, Tool, AuthConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch and parse an OpenAPI specification from URL or file path
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadOpenAPISpec(specPath: string): Promise<OpenAPISpec>;
|
|
6
|
+
/**
|
|
7
|
+
* Extract the base URL from the OpenAPI spec
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractBaseUrl(spec: OpenAPISpec): string;
|
|
10
|
+
/**
|
|
11
|
+
* Extract authentication configuration from security schemes
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractAuthConfig(spec: OpenAPISpec): AuthConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Parse all operations from the OpenAPI spec and generate tool definitions
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseOperations(spec: OpenAPISpec): Omit<Tool, 'execute'>[];
|
|
18
|
+
/**
|
|
19
|
+
* Format tool schema for display
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatToolSchema(tool: Omit<Tool, 'execute'>): string;
|
|
22
|
+
/**
|
|
23
|
+
* Format tool signature for display
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatToolSignature(tool: Omit<Tool, 'execute'>): string;
|
|
26
|
+
//# sourceMappingURL=openapi-parser.d.ts.map
|