ai-tool-adapter 1.0.0 → 1.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/README.md ADDED
@@ -0,0 +1,444 @@
1
+ # ai-tool-adapter
2
+
3
+ Universal AI tool schema adapter for function calling across multiple LLM providers.
4
+
5
+ Write your tool definitions once, convert to any provider format instantly. No more duplicating schemas for OpenAI, Anthropic, Google, and Mistral.
6
+
7
+ ## Features
8
+
9
+ - **Zero dependencies** - Lightweight and secure
10
+ - **Type-safe** - Full TypeScript support with strict typing
11
+ - **Provider-agnostic** - One schema, multiple formats
12
+ - **Pure functions** - Predictable transformations with no side effects
13
+ - **Well-tested** - Comprehensive test coverage across all adapters
14
+
15
+ ## Supported Providers
16
+
17
+ - **OpenAI** - GPT-4, GPT-3.5 function calling
18
+ - **Anthropic** - Claude tool use
19
+ - **Google Gemini** - Gemini function calling
20
+ - **Mistral** - Mistral AI function calling
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install ai-tool-adapter
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import { adapt } from 'ai-tool-adapter';
32
+
33
+ // Define your tool once using the universal schema
34
+ const weatherTool = {
35
+ name: 'get_weather',
36
+ description: 'Get current weather for a location',
37
+ params: {
38
+ location: {
39
+ type: 'string',
40
+ description: 'City name',
41
+ required: true,
42
+ },
43
+ units: {
44
+ type: 'string',
45
+ description: 'Temperature units',
46
+ enum: ['celsius', 'fahrenheit'],
47
+ },
48
+ },
49
+ };
50
+
51
+ // Convert to any provider format
52
+ const openaiTool = adapt(weatherTool, 'openai');
53
+ const anthropicTool = adapt(weatherTool, 'anthropic');
54
+ const geminiTool = adapt(weatherTool, 'gemini');
55
+ const mistralTool = adapt(weatherTool, 'mistral');
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ ### `adapt(tool, provider)`
61
+
62
+ Convert a single universal tool definition to a specific provider's format.
63
+
64
+ **Parameters:**
65
+ - `tool` (UniversalTool) - Your universal tool definition
66
+ - `provider` (Provider) - Target provider: `'openai'` | `'anthropic'` | `'gemini'` | `'mistral'`
67
+
68
+ **Returns:** Provider-specific tool format
69
+
70
+ **Throws:** Error if provider is not supported
71
+
72
+ ```typescript
73
+ const tool = {
74
+ name: 'calculate',
75
+ description: 'Perform arithmetic calculation',
76
+ params: {
77
+ operation: {
78
+ type: 'string',
79
+ enum: ['add', 'subtract', 'multiply', 'divide'],
80
+ required: true,
81
+ },
82
+ a: { type: 'number', required: true },
83
+ b: { type: 'number', required: true },
84
+ },
85
+ };
86
+
87
+ const openaiTool = adapt(tool, 'openai');
88
+ ```
89
+
90
+ ### `adaptAll(tools, provider)`
91
+
92
+ Convert multiple tools at once.
93
+
94
+ **Parameters:**
95
+ - `tools` (UniversalTool[]) - Array of universal tool definitions
96
+ - `provider` (Provider) - Target provider
97
+
98
+ **Returns:** Array of provider-specific tool formats
99
+
100
+ ```typescript
101
+ const tools = [weatherTool, calculatorTool, searchTool];
102
+ const openaiTools = adaptAll(tools, 'openai');
103
+ ```
104
+
105
+ ### `getProviders()`
106
+
107
+ Get list of all supported providers.
108
+
109
+ **Returns:** Array of provider names
110
+
111
+ ```typescript
112
+ const providers = getProviders();
113
+ console.log(providers); // ['openai', 'anthropic', 'gemini', 'mistral']
114
+ ```
115
+
116
+ ## Universal Tool Schema
117
+
118
+ ### Basic Structure
119
+
120
+ ```typescript
121
+ interface UniversalTool {
122
+ name: string; // Function name
123
+ description: string; // What the tool does
124
+ params: Record<string, ToolParam>; // Parameter definitions
125
+ }
126
+ ```
127
+
128
+ ### Parameter Definition
129
+
130
+ ```typescript
131
+ interface ToolParam {
132
+ type: 'string' | 'number' | 'boolean' | 'array' | 'object';
133
+ description?: string; // Parameter description
134
+ required?: boolean; // Whether required (default: false)
135
+ default?: unknown; // Default value
136
+ enum?: string[]; // Allowed values
137
+ items?: { // For array types
138
+ type: ParamType;
139
+ };
140
+ }
141
+ ```
142
+
143
+ ## Examples
144
+
145
+ ### Simple Tool
146
+
147
+ ```typescript
148
+ const statusTool = {
149
+ name: 'get_status',
150
+ description: 'Get current system status',
151
+ params: {}, // No parameters
152
+ };
153
+ ```
154
+
155
+ ### Tool with Required Parameters
156
+
157
+ ```typescript
158
+ const searchTool = {
159
+ name: 'search',
160
+ description: 'Search for items',
161
+ params: {
162
+ query: {
163
+ type: 'string',
164
+ description: 'Search query',
165
+ required: true,
166
+ },
167
+ },
168
+ };
169
+ ```
170
+
171
+ ### Tool with Enums
172
+
173
+ ```typescript
174
+ const sortTool = {
175
+ name: 'sort_results',
176
+ description: 'Sort search results',
177
+ params: {
178
+ order: {
179
+ type: 'string',
180
+ enum: ['asc', 'desc'],
181
+ default: 'asc',
182
+ },
183
+ },
184
+ };
185
+ ```
186
+
187
+ ### Tool with Array Parameters
188
+
189
+ ```typescript
190
+ const batchTool = {
191
+ name: 'process_batch',
192
+ description: 'Process multiple items',
193
+ params: {
194
+ items: {
195
+ type: 'array',
196
+ description: 'Items to process',
197
+ items: { type: 'string' },
198
+ required: true,
199
+ },
200
+ },
201
+ };
202
+ ```
203
+
204
+ ### Complex Tool
205
+
206
+ ```typescript
207
+ const apiTool = {
208
+ name: 'api_request',
209
+ description: 'Make an API request',
210
+ params: {
211
+ endpoint: {
212
+ type: 'string',
213
+ description: 'API endpoint path',
214
+ required: true,
215
+ },
216
+ method: {
217
+ type: 'string',
218
+ description: 'HTTP method',
219
+ enum: ['GET', 'POST', 'PUT', 'DELETE'],
220
+ default: 'GET',
221
+ },
222
+ headers: {
223
+ type: 'object',
224
+ description: 'Request headers',
225
+ },
226
+ params: {
227
+ type: 'array',
228
+ description: 'Query parameters',
229
+ items: { type: 'string' },
230
+ },
231
+ timeout: {
232
+ type: 'number',
233
+ description: 'Request timeout in milliseconds',
234
+ default: 5000,
235
+ },
236
+ },
237
+ };
238
+ ```
239
+
240
+ ## Provider Output Formats
241
+
242
+ ### OpenAI
243
+
244
+ Wraps function definition in a type object:
245
+
246
+ ```json
247
+ {
248
+ "type": "function",
249
+ "function": {
250
+ "name": "get_weather",
251
+ "description": "Get weather",
252
+ "parameters": {
253
+ "type": "object",
254
+ "properties": {...},
255
+ "required": [...]
256
+ }
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### Anthropic
262
+
263
+ Flat structure with `input_schema`:
264
+
265
+ ```json
266
+ {
267
+ "name": "get_weather",
268
+ "description": "Get weather",
269
+ "input_schema": {
270
+ "type": "object",
271
+ "properties": {...},
272
+ "required": [...]
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### Gemini
278
+
279
+ Flat structure with UPPERCASE types:
280
+
281
+ ```json
282
+ {
283
+ "name": "get_weather",
284
+ "description": "Get weather",
285
+ "parameters": {
286
+ "type": "OBJECT",
287
+ "properties": {
288
+ "location": { "type": "STRING" }
289
+ },
290
+ "required": [...]
291
+ }
292
+ }
293
+ ```
294
+
295
+ ### Mistral
296
+
297
+ OpenAI-compatible format:
298
+
299
+ ```json
300
+ {
301
+ "type": "function",
302
+ "function": {
303
+ "name": "get_weather",
304
+ "description": "Get weather",
305
+ "parameters": {...}
306
+ }
307
+ }
308
+ ```
309
+
310
+ ## Real-World Usage
311
+
312
+ ### With OpenAI SDK
313
+
314
+ ```typescript
315
+ import OpenAI from 'openai';
316
+ import { adapt } from 'ai-tool-adapter';
317
+
318
+ const client = new OpenAI();
319
+
320
+ const tools = [weatherTool, calculatorTool].map(tool =>
321
+ adapt(tool, 'openai')
322
+ );
323
+
324
+ const response = await client.chat.completions.create({
325
+ model: 'gpt-4',
326
+ messages: [{ role: 'user', content: 'What\'s the weather in Paris?' }],
327
+ tools,
328
+ });
329
+ ```
330
+
331
+ ### With Anthropic SDK
332
+
333
+ ```typescript
334
+ import Anthropic from '@anthropic-ai/sdk';
335
+ import { adaptAll } from 'ai-tool-adapter';
336
+
337
+ const client = new Anthropic();
338
+
339
+ const tools = adaptAll([weatherTool, calculatorTool], 'anthropic');
340
+
341
+ const response = await client.messages.create({
342
+ model: 'claude-3-5-sonnet-20241022',
343
+ max_tokens: 1024,
344
+ messages: [{ role: 'user', content: 'What\'s the weather in Paris?' }],
345
+ tools,
346
+ });
347
+ ```
348
+
349
+ ### Multi-Provider Support
350
+
351
+ ```typescript
352
+ import { adapt } from 'ai-tool-adapter';
353
+
354
+ // Single source of truth for your tools
355
+ const myTools = [weatherTool, calculatorTool, searchTool];
356
+
357
+ // Support all providers effortlessly
358
+ const providerClients = {
359
+ openai: { tools: myTools.map(t => adapt(t, 'openai')) },
360
+ anthropic: { tools: myTools.map(t => adapt(t, 'anthropic')) },
361
+ gemini: { tools: myTools.map(t => adapt(t, 'gemini')) },
362
+ mistral: { tools: myTools.map(t => adapt(t, 'mistral')) },
363
+ };
364
+
365
+ // Use whichever provider you need
366
+ function callLLM(provider: string, prompt: string) {
367
+ const config = providerClients[provider];
368
+ // Make API call with provider-specific tools
369
+ }
370
+ ```
371
+
372
+ ## TypeScript Support
373
+
374
+ Full type definitions included:
375
+
376
+ ```typescript
377
+ import type {
378
+ UniversalTool,
379
+ ToolParam,
380
+ Provider,
381
+ ParamType
382
+ } from 'ai-tool-adapter';
383
+
384
+ const tool: UniversalTool = {
385
+ name: 'my_tool',
386
+ description: 'My custom tool',
387
+ params: {
388
+ param1: {
389
+ type: 'string',
390
+ required: true,
391
+ },
392
+ },
393
+ };
394
+ ```
395
+
396
+ ## Development
397
+
398
+ ```bash
399
+ # Install dependencies
400
+ npm install
401
+
402
+ # Build
403
+ npm run build
404
+
405
+ # Run tests
406
+ npm test
407
+
408
+ # Watch mode for tests
409
+ npm run test:watch
410
+ ```
411
+
412
+ ## Architecture
413
+
414
+ This library follows the **Strategy Pattern** with each provider adapter as an independent, interchangeable strategy. This design ensures:
415
+
416
+ - **Orthogonality** - Adapters are completely independent
417
+ - **Open/Closed Principle** - Easy to add new providers without modifying existing code
418
+ - **Single Responsibility** - Each adapter handles exactly one provider's format
419
+ - **Pure Functions** - Predictable, testable transformations
420
+
421
+ For detailed architecture documentation, see [CLAUDE.md](./CLAUDE.md).
422
+
423
+ ## Contributing
424
+
425
+ Contributions are welcome! To add a new provider:
426
+
427
+ 1. Create adapter in `src/adapters/yourprovider.ts`
428
+ 2. Add provider to `Provider` type in `src/types.ts`
429
+ 3. Register adapter in `src/index.ts`
430
+ 4. Add comprehensive tests
431
+ 5. Update documentation
432
+
433
+ See [CLAUDE.md](./CLAUDE.md) for detailed guidelines.
434
+
435
+ ## License
436
+
437
+ ISC
438
+
439
+ ## Related
440
+
441
+ - [OpenAI Function Calling](https://platform.openai.com/docs/guides/function-calling)
442
+ - [Anthropic Tool Use](https://docs.anthropic.com/claude/docs/tool-use)
443
+ - [Google Gemini Function Calling](https://ai.google.dev/docs/function_calling)
444
+ - [Mistral AI Function Calling](https://docs.mistral.ai/capabilities/function_calling/)
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Anthropic (Claude) Tool Adapter
3
+ *
4
+ * Converts UniversalTool to Anthropic's tool format.
5
+ * Output structure is flat with input_schema instead of parameters.
6
+ *
7
+ * @see https://docs.anthropic.com/claude/docs/tool-use
8
+ */
9
+ import { UniversalTool } from '../types';
10
+ interface AnthropicProperty {
11
+ type: string;
12
+ description?: string;
13
+ enum?: string[];
14
+ default?: unknown;
15
+ items?: {
16
+ type: string;
17
+ };
18
+ }
19
+ interface AnthropicInputSchema {
20
+ type: 'object';
21
+ properties: Record<string, AnthropicProperty>;
22
+ required: string[];
23
+ }
24
+ interface AnthropicTool {
25
+ name: string;
26
+ description: string;
27
+ input_schema: AnthropicInputSchema;
28
+ }
29
+ /**
30
+ * Converts a UniversalTool to Anthropic tool format
31
+ *
32
+ * @param tool - Universal tool definition
33
+ * @returns Anthropic-formatted tool object
34
+ */
35
+ export declare function anthropicAdapter(tool: UniversalTool): AnthropicTool;
36
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * Anthropic (Claude) Tool Adapter
4
+ *
5
+ * Converts UniversalTool to Anthropic's tool format.
6
+ * Output structure is flat with input_schema instead of parameters.
7
+ *
8
+ * @see https://docs.anthropic.com/claude/docs/tool-use
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.anthropicAdapter = anthropicAdapter;
12
+ /**
13
+ * Convert a single parameter to Anthropic property format
14
+ */
15
+ function convertParam(param) {
16
+ const property = {
17
+ type: param.type,
18
+ };
19
+ if (param.description) {
20
+ property.description = param.description;
21
+ }
22
+ if (param.enum) {
23
+ property.enum = param.enum;
24
+ }
25
+ if (param.default !== undefined) {
26
+ property.default = param.default;
27
+ }
28
+ if (param.type === 'array' && param.items) {
29
+ property.items = { type: param.items.type };
30
+ }
31
+ return property;
32
+ }
33
+ /**
34
+ * Converts a UniversalTool to Anthropic tool format
35
+ *
36
+ * @param tool - Universal tool definition
37
+ * @returns Anthropic-formatted tool object
38
+ */
39
+ function anthropicAdapter(tool) {
40
+ const properties = {};
41
+ const required = [];
42
+ for (const [paramName, paramDef] of Object.entries(tool.params)) {
43
+ properties[paramName] = convertParam(paramDef);
44
+ if (paramDef.required) {
45
+ required.push(paramName);
46
+ }
47
+ }
48
+ return {
49
+ name: tool.name,
50
+ description: tool.description,
51
+ input_schema: {
52
+ type: 'object',
53
+ properties,
54
+ required,
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for Anthropic (Claude) adapter
3
+ */
4
+ export {};
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for Anthropic (Claude) adapter
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const anthropic_1 = require("./anthropic");
8
+ (0, vitest_1.describe)('anthropicAdapter', () => {
9
+ (0, vitest_1.it)('should convert a basic tool with required params', () => {
10
+ const tool = {
11
+ name: 'get_weather',
12
+ description: 'Get current weather for a location',
13
+ params: {
14
+ location: {
15
+ type: 'string',
16
+ description: 'City name',
17
+ required: true,
18
+ },
19
+ },
20
+ };
21
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
22
+ (0, vitest_1.expect)(result).toEqual({
23
+ name: 'get_weather',
24
+ description: 'Get current weather for a location',
25
+ input_schema: {
26
+ type: 'object',
27
+ properties: {
28
+ location: {
29
+ type: 'string',
30
+ description: 'City name',
31
+ },
32
+ },
33
+ required: ['location'],
34
+ },
35
+ });
36
+ });
37
+ (0, vitest_1.it)('should handle enum values', () => {
38
+ const tool = {
39
+ name: 'set_temperature',
40
+ description: 'Set temperature unit',
41
+ params: {
42
+ unit: {
43
+ type: 'string',
44
+ enum: ['celsius', 'fahrenheit', 'kelvin'],
45
+ required: true,
46
+ },
47
+ },
48
+ };
49
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
50
+ (0, vitest_1.expect)(result.input_schema.properties.unit).toEqual({
51
+ type: 'string',
52
+ enum: ['celsius', 'fahrenheit', 'kelvin'],
53
+ });
54
+ });
55
+ (0, vitest_1.it)('should handle array types with items', () => {
56
+ const tool = {
57
+ name: 'process_items',
58
+ description: 'Process a list of items',
59
+ params: {
60
+ items: {
61
+ type: 'array',
62
+ description: 'List of items',
63
+ items: { type: 'string' },
64
+ required: true,
65
+ },
66
+ },
67
+ };
68
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
69
+ (0, vitest_1.expect)(result.input_schema.properties.items).toEqual({
70
+ type: 'array',
71
+ description: 'List of items',
72
+ items: { type: 'string' },
73
+ });
74
+ });
75
+ (0, vitest_1.it)('should handle default values', () => {
76
+ const tool = {
77
+ name: 'paginate',
78
+ description: 'Get paginated results',
79
+ params: {
80
+ page: {
81
+ type: 'number',
82
+ default: 1,
83
+ },
84
+ limit: {
85
+ type: 'number',
86
+ default: 10,
87
+ },
88
+ },
89
+ };
90
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
91
+ (0, vitest_1.expect)(result.input_schema.properties.page).toEqual({
92
+ type: 'number',
93
+ default: 1,
94
+ });
95
+ (0, vitest_1.expect)(result.input_schema.properties.limit).toEqual({
96
+ type: 'number',
97
+ default: 10,
98
+ });
99
+ });
100
+ (0, vitest_1.it)('should handle mixed required and optional params', () => {
101
+ const tool = {
102
+ name: 'search',
103
+ description: 'Search for items',
104
+ params: {
105
+ query: {
106
+ type: 'string',
107
+ description: 'Search query',
108
+ required: true,
109
+ },
110
+ limit: {
111
+ type: 'number',
112
+ description: 'Result limit',
113
+ default: 10,
114
+ },
115
+ exact: {
116
+ type: 'boolean',
117
+ description: 'Exact match',
118
+ },
119
+ },
120
+ };
121
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
122
+ (0, vitest_1.expect)(result.input_schema.required).toEqual(['query']);
123
+ (0, vitest_1.expect)(result.input_schema.properties).toHaveProperty('query');
124
+ (0, vitest_1.expect)(result.input_schema.properties).toHaveProperty('limit');
125
+ (0, vitest_1.expect)(result.input_schema.properties).toHaveProperty('exact');
126
+ });
127
+ (0, vitest_1.it)('should handle empty params', () => {
128
+ const tool = {
129
+ name: 'get_status',
130
+ description: 'Get current status',
131
+ params: {},
132
+ };
133
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
134
+ (0, vitest_1.expect)(result.input_schema).toEqual({
135
+ type: 'object',
136
+ properties: {},
137
+ required: [],
138
+ });
139
+ });
140
+ (0, vitest_1.it)('should use lowercase type names', () => {
141
+ const tool = {
142
+ name: 'test_types',
143
+ description: 'Test all types',
144
+ params: {
145
+ str: { type: 'string' },
146
+ num: { type: 'number' },
147
+ bool: { type: 'boolean' },
148
+ arr: { type: 'array', items: { type: 'string' } },
149
+ obj: { type: 'object' },
150
+ },
151
+ };
152
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
153
+ const props = result.input_schema.properties;
154
+ (0, vitest_1.expect)(props.str.type).toBe('string');
155
+ (0, vitest_1.expect)(props.num.type).toBe('number');
156
+ (0, vitest_1.expect)(props.bool.type).toBe('boolean');
157
+ (0, vitest_1.expect)(props.arr.type).toBe('array');
158
+ (0, vitest_1.expect)(props.obj.type).toBe('object');
159
+ });
160
+ (0, vitest_1.it)('should use flat structure with input_schema', () => {
161
+ const tool = {
162
+ name: 'test',
163
+ description: 'Test tool',
164
+ params: {},
165
+ };
166
+ const result = (0, anthropic_1.anthropicAdapter)(tool);
167
+ (0, vitest_1.expect)(result).toHaveProperty('name');
168
+ (0, vitest_1.expect)(result).toHaveProperty('description');
169
+ (0, vitest_1.expect)(result).toHaveProperty('input_schema');
170
+ (0, vitest_1.expect)(result).not.toHaveProperty('type');
171
+ (0, vitest_1.expect)(result).not.toHaveProperty('function');
172
+ });
173
+ });