feat: add agent tools and sanitize tool names for Anthropic API

Add 8 new agent-callable tools (sessions.list/history/create/delete,
agents.list, message.send, cron.list/trigger) and sanitize tool names
at the API boundary (dots → underscores) to comply with Anthropic's
`^[a-zA-Z0-9_-]{1,128}` requirement. Reverse-maps sanitized names
back to internal names for hook callbacks and tool execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-07 12:23:09 -08:00
parent f0e3987d1c
commit 6bb424cddc
13 changed files with 656 additions and 124 deletions
+35 -2
View File
@@ -56,7 +56,7 @@ describe('ToolRegistry', () => {
const anthropicTools = registry.toAnthropicFormat();
expect(anthropicTools).toEqual([{
name: 'test.echo',
name: 'test_echo',
description: 'Echoes input back',
input_schema: echoTool.inputSchema,
}]);
@@ -86,7 +86,7 @@ describe('ToolRegistry', () => {
expect(openaiTools).toEqual([{
type: 'function',
function: {
name: 'test.echo',
name: 'test_echo',
description: 'Echoes input back',
parameters: echoTool.inputSchema,
},
@@ -161,4 +161,37 @@ describe('ToolRegistry', () => {
expect(() => reg.replace(makeTool('nonexistent'))).toThrow('not registered');
});
});
describe('ToolRegistry — API name sanitization', () => {
it('sanitizeToolName converts dots to underscores', () => {
expect(ToolRegistry.sanitizeToolName('shell.exec')).toBe('shell_exec');
expect(ToolRegistry.sanitizeToolName('file.read')).toBe('file_read');
expect(ToolRegistry.sanitizeToolName('no_dots')).toBe('no_dots');
});
it('getByApiName resolves sanitized names back to internal tools', () => {
const registry = new ToolRegistry();
registry.register(echoTool); // name is 'test.echo'
expect(registry.getByApiName('test_echo')).toBe(echoTool);
expect(registry.getByApiName('test.echo')).toBe(echoTool);
expect(registry.getByApiName('nonexistent')).toBeUndefined();
});
it('toAnthropicFormat outputs sanitized names', () => {
const registry = new ToolRegistry();
registry.register(echoTool);
const tools = registry.toAnthropicFormat();
expect(tools[0].name).toBe('test_echo');
});
it('toOpenAIFormat outputs sanitized names', () => {
const registry = new ToolRegistry();
registry.register(echoTool);
const tools = registry.toOpenAIFormat();
expect(tools[0].function.name).toBe('test_echo');
});
});
});