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
+14 -4
View File
@@ -20,6 +20,11 @@ export class ToolRegistry {
private tools: Map<string, Tool> = new Map();
private _policy?: ToolPolicy;
/** Sanitize a tool name for API compatibility (dots → underscores). */
static sanitizeToolName(name: string): string {
return name.replace(/\./g, '_');
}
register(tool: Tool): void {
if (this.tools.has(tool.name)) {
throw new Error(`Tool '${tool.name}' is already registered`);
@@ -55,6 +60,11 @@ export class ToolRegistry {
return this.tools.get(name);
}
/** Resolve a tool by its API-sanitized name (underscores → dots fallback). */
getByApiName(name: string): Tool | undefined {
return this.tools.get(name) ?? this.tools.get(name.replace(/_/g, '.'));
}
list(): Tool[] {
return Array.from(this.tools.values());
}
@@ -77,7 +87,7 @@ export class ToolRegistry {
toAnthropicFormat(): AnthropicToolDef[] {
return this.list().map(t => ({
name: t.name,
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
input_schema: t.inputSchema,
}));
@@ -86,7 +96,7 @@ export class ToolRegistry {
/** Return Anthropic-format tools filtered by policy. */
filteredToAnthropicFormat(context?: ToolPolicyContext): AnthropicToolDef[] {
return this.filteredList(context).map(t => ({
name: t.name,
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
input_schema: t.inputSchema,
}));
@@ -96,7 +106,7 @@ export class ToolRegistry {
return this.list().map(t => ({
type: 'function' as const,
function: {
name: t.name,
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
parameters: t.inputSchema,
},
@@ -108,7 +118,7 @@ export class ToolRegistry {
return this.filteredList(context).map(t => ({
type: 'function' as const,
function: {
name: t.name,
name: ToolRegistry.sanitizeToolName(t.name),
description: t.description,
parameters: t.inputSchema,
},