Skip to content

Commit eb4c324

Browse files
committed
feat(api): add tool adapter for Zod/JSON Schema/ITool normalization
1 parent f9eedae commit eb4c324

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

src/api/tool-adapter.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// packages/agentos/src/api/tool-adapter.ts
2+
import type { ITool, ToolExecutionResult, ToolExecutionContext, JSONSchemaObject } from '../core/tools/ITool.js';
3+
4+
export interface ToolDefinition {
5+
description?: string;
6+
parameters?: Record<string, unknown>;
7+
execute?: (args: any) => Promise<any>;
8+
}
9+
10+
export type ToolDefinitionMap = Record<string, ToolDefinition | ITool>;
11+
12+
/**
13+
* Adapts Zod schemas, JSON Schema objects, and ITool instances into ITool[].
14+
*/
15+
export function adaptTools(tools: ToolDefinitionMap | undefined): ITool[] {
16+
if (!tools) return [];
17+
const result: ITool[] = [];
18+
19+
for (const [name, def] of Object.entries(tools)) {
20+
// ITool pass-through (has inputSchema + execute as ITool signature)
21+
if ('inputSchema' in def && 'id' in def) {
22+
result.push(def as ITool);
23+
continue;
24+
}
25+
26+
const td = def as ToolDefinition;
27+
let schema: JSONSchemaObject;
28+
29+
if (td.parameters && '_def' in (td.parameters as any)) {
30+
// Zod schema — convert to JSON Schema
31+
try {
32+
const { zodToJsonSchema } = require('zod-to-json-schema') as any;
33+
schema = zodToJsonSchema(td.parameters) as JSONSchemaObject;
34+
} catch {
35+
// zod-to-json-schema not installed — use basic extraction
36+
schema = { type: 'object', properties: {} };
37+
}
38+
} else {
39+
schema = (td.parameters ?? { type: 'object', properties: {} }) as JSONSchemaObject;
40+
}
41+
42+
const executeFn = td.execute ?? (async () => ({ success: true }));
43+
44+
result.push({
45+
id: `${name}-v1`,
46+
name,
47+
displayName: name.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase()).trim(),
48+
description: td.description ?? '',
49+
inputSchema: schema,
50+
hasSideEffects: false,
51+
async execute(args: any, _ctx: ToolExecutionContext): Promise<ToolExecutionResult> {
52+
try {
53+
const output = await executeFn(args);
54+
return { success: true, output };
55+
} catch (err: any) {
56+
return { success: false, error: err?.message ?? String(err) };
57+
}
58+
},
59+
});
60+
}
61+
62+
return result;
63+
}

tests/api/tool-adapter.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// packages/agentos/tests/api/tool-adapter.spec.ts
2+
import { describe, it, expect } from 'vitest';
3+
import { adaptTools, type ToolDefinition } from '../src/api/tool-adapter.js';
4+
5+
describe('adaptTools', () => {
6+
it('adapts JSON Schema tool', () => {
7+
const tools = adaptTools({
8+
search: {
9+
description: 'Search the web',
10+
parameters: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
11+
execute: async ({ query }: any) => ({ results: [query] }),
12+
},
13+
});
14+
expect(tools).toHaveLength(1);
15+
expect(tools[0].name).toBe('search');
16+
expect(tools[0].description).toBe('Search the web');
17+
expect(tools[0].inputSchema.type).toBe('object');
18+
});
19+
20+
it('passes through ITool instances', () => {
21+
const tool = {
22+
id: 'test-v1', name: 'test', displayName: 'Test', description: 'A test',
23+
inputSchema: { type: 'object', properties: {} },
24+
hasSideEffects: false,
25+
execute: async () => ({ success: true, output: 'ok' }),
26+
};
27+
const tools = adaptTools({ test: tool as any });
28+
expect(tools).toHaveLength(1);
29+
expect(tools[0].id).toBe('test-v1');
30+
});
31+
32+
it('returns empty array for undefined', () => {
33+
expect(adaptTools(undefined)).toEqual([]);
34+
});
35+
});

0 commit comments

Comments
 (0)