Skip to content

Commit b4d85b9

Browse files
ersinkocclaude
andcommitted
✨ feat(tools): add tool name alias system for LLM hallucination recovery
TOOL_ALIASES map auto-resolves 30+ common misspellings/variants: - get_current_time → get_current_datetime - create_task → add_task, get_tasks → list_tasks - read_file → file_read, write_file → file_write - search_web → web_search, fetch_url → web_fetch - remember → add_memory, recall → search_memories - And 20+ more across all tool categories Resolution happens silently in executeUseTool() and executeBatchUseTool() before the "not found" check. Supports both bare and namespaced names (e.g., "core.get_current_time" → "core.get_current_datetime"). 3 new tests: bare alias, namespaced alias, unknown tool still errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0e8f1a8 commit b4d85b9

2 files changed

Lines changed: 178 additions & 2 deletions

File tree

packages/gateway/src/tools/agent-tool-registry.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,4 +2001,59 @@ describe('agent-tools helpers', () => {
20012001
expect(result.content).toContain('store_memory');
20022002
});
20032003
});
2004+
2005+
// =========================================================================
2006+
// Tool Alias Resolution
2007+
// =========================================================================
2008+
describe('tool alias resolution', () => {
2009+
it('resolves get_current_time to get_current_datetime', async () => {
2010+
const registry = createMockRegistry({
2011+
get_current_datetime: {
2012+
name: 'get_current_datetime',
2013+
description: 'Get current date and time',
2014+
category: 'utility',
2015+
},
2016+
});
2017+
const result = await executeUseTool(
2018+
registry as any,
2019+
{ tool_name: 'get_current_time', arguments: {} },
2020+
{ userId: 'test' }
2021+
);
2022+
// Should NOT return an error — alias resolved silently
2023+
expect(result.isError).toBeFalsy();
2024+
});
2025+
2026+
it('resolves namespaced alias core.get_current_time', async () => {
2027+
const registry = createMockRegistry({
2028+
'core.get_current_datetime': {
2029+
name: 'core.get_current_datetime',
2030+
description: 'Get current date and time',
2031+
category: 'utility',
2032+
},
2033+
});
2034+
const result = await executeUseTool(
2035+
registry as any,
2036+
{ tool_name: 'core.get_current_time', arguments: {} },
2037+
{ userId: 'test' }
2038+
);
2039+
expect(result.isError).toBeFalsy();
2040+
});
2041+
2042+
it('still returns error for truly unknown tools', async () => {
2043+
const registry = createMockRegistry({
2044+
get_current_datetime: {
2045+
name: 'get_current_datetime',
2046+
description: 'Get current date and time',
2047+
category: 'utility',
2048+
},
2049+
});
2050+
const result = await executeUseTool(
2051+
registry as any,
2052+
{ tool_name: 'totally_fake_tool', arguments: {} },
2053+
{ userId: 'test' }
2054+
);
2055+
expect(result.isError).toBe(true);
2056+
expect(result.content).toContain('not found');
2057+
});
2058+
});
20042059
});

packages/gateway/src/tools/agent-tool-registry.ts

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,109 @@ import { checkToolPermission } from '../services/tool-permission-service.js';
9292

9393
const log = getLog('AgentTools');
9494

95+
// =============================================================================
96+
// Tool Name Aliases — auto-resolve common LLM hallucinations
97+
// =============================================================================
98+
99+
/**
100+
* Maps commonly hallucinated tool names to their correct counterparts.
101+
* Keys are the wrong names (without namespace prefix), values are the correct names.
102+
* Both namespaced ("core.get_current_time") and bare ("get_current_time") forms are resolved.
103+
*/
104+
const TOOL_ALIASES: Record<string, string> = {
105+
// Time/Date
106+
get_current_time: 'get_current_datetime',
107+
get_time: 'get_current_datetime',
108+
current_time: 'get_current_datetime',
109+
get_date: 'get_current_datetime',
110+
get_datetime: 'get_current_datetime',
111+
112+
// Tasks
113+
get_tasks: 'list_tasks',
114+
create_task: 'add_task',
115+
new_task: 'add_task',
116+
remove_task: 'delete_task',
117+
118+
// Notes
119+
get_notes: 'list_notes',
120+
create_note: 'add_note',
121+
new_note: 'add_note',
122+
remove_note: 'delete_note',
123+
124+
// Memory
125+
get_memories: 'list_memories',
126+
save_memory: 'add_memory',
127+
remember: 'add_memory',
128+
recall: 'search_memories',
129+
130+
// Calendar
131+
get_events: 'list_events',
132+
create_event: 'add_event',
133+
new_event: 'add_event',
134+
135+
// Contacts
136+
get_contacts: 'list_contacts',
137+
create_contact: 'add_contact',
138+
new_contact: 'add_contact',
139+
140+
// Bookmarks
141+
get_bookmarks: 'list_bookmarks',
142+
create_bookmark: 'add_bookmark',
143+
new_bookmark: 'add_bookmark',
144+
145+
// Files
146+
read_file: 'file_read',
147+
write_file: 'file_write',
148+
list_files: 'file_list',
149+
delete_file: 'file_delete',
150+
151+
// Web
152+
fetch_url: 'web_fetch',
153+
browse: 'web_search',
154+
google: 'web_search',
155+
search: 'web_search',
156+
search_web: 'web_search',
157+
158+
// Email
159+
send_mail: 'send_email',
160+
compose_email: 'send_email',
161+
162+
// Goals
163+
get_goals: 'list_goals',
164+
create_goal: 'add_goal',
165+
new_goal: 'add_goal',
166+
167+
// Git
168+
git_log: 'git_history',
169+
170+
// Code
171+
run_code: 'execute_code',
172+
exec_code: 'execute_code',
173+
eval_code: 'execute_code',
174+
};
175+
176+
/**
177+
* Resolve a tool name through the alias map.
178+
* Handles both namespaced ("core.get_current_time") and bare ("get_current_time") forms.
179+
* Returns the resolved name (with original namespace preserved) or null if no alias found.
180+
*/
181+
function resolveToolAlias(toolName: string): string | null {
182+
// Try bare name first
183+
const alias = TOOL_ALIASES[toolName];
184+
if (alias) return alias;
185+
186+
// Try stripping namespace prefix (e.g., "core.get_current_time" → "get_current_time")
187+
const dotIdx = toolName.indexOf('.');
188+
if (dotIdx > 0) {
189+
const prefix = toolName.slice(0, dotIdx + 1); // "core."
190+
const baseName = toolName.slice(dotIdx + 1); // "get_current_time"
191+
const baseAlias = TOOL_ALIASES[baseName];
192+
if (baseAlias) return prefix + baseAlias; // "core.get_current_datetime"
193+
}
194+
195+
return null;
196+
}
197+
95198
// =============================================================================
96199
// Helpers
97200
// =============================================================================
@@ -670,11 +773,20 @@ export async function executeUseTool(
670773
args: Record<string, unknown>,
671774
context: ToolContext
672775
): Promise<CoreToolResult> {
673-
const { tool_name, arguments: toolArgs } = args as {
776+
let { tool_name, arguments: toolArgs } = args as {
674777
tool_name: string;
675778
arguments: Record<string, unknown>;
676779
};
677780

781+
// Alias resolution — auto-fix common LLM hallucinations
782+
if (!tools.has(tool_name)) {
783+
const resolved = resolveToolAlias(tool_name);
784+
if (resolved && tools.has(resolved)) {
785+
log.info(`Tool alias resolved: ${tool_name}${resolved}`);
786+
tool_name = resolved;
787+
}
788+
}
789+
678790
// Check if tool exists — suggest similar names if not
679791
if (!tools.has(tool_name)) {
680792
const similar = findSimilarTools(tools, tool_name);
@@ -749,7 +861,16 @@ export async function executeBatchUseTool(
749861
// Execute all tool calls in parallel
750862
const results = await Promise.allSettled(
751863
calls.map(async (call, idx) => {
752-
const { tool_name, arguments: toolArgs } = call;
864+
let { tool_name, arguments: toolArgs } = call;
865+
866+
// Alias resolution
867+
if (!tools.has(tool_name)) {
868+
const resolved = resolveToolAlias(tool_name);
869+
if (resolved && tools.has(resolved)) {
870+
log.info(`Tool alias resolved: ${tool_name}${resolved}`);
871+
tool_name = resolved;
872+
}
873+
}
753874

754875
// Check tool exists
755876
if (!tools.has(tool_name)) {

0 commit comments

Comments
 (0)