Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/types/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export const modelInfoSchema = z.object({
// These tools will be added if they belong to an allowed group in the current mode
// Cannot force-add tools from groups the mode doesn't allow
includedTools: z.array(z.string()).optional(),
// Define aliases for specific tools in this model (only applies to native protocol)
// Format: ["originalName:aliasName", ...] e.g., ["apply_diff:edit"]
// The tool will be presented to the model with the alias name, but executed as the original
toolAliases: z.array(z.string()).optional(),
/**
* Service tiers with pricing information.
* Each tier can have a name (for OpenAI service tiers) and pricing overrides.
Expand Down
72 changes: 65 additions & 7 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,48 @@ export class NativeToolCallParser {
}
>()

// Reverse alias map for mapping aliased tools back to original names
// Key: alias tool name (e.g., "edit"), Value: original tool name (e.g., "apply_diff")
private static toolAliasReverseMap = new Map<string, string>()

/**
* Set the reverse alias map for the current request.
* This maps alias tool names back to their original names.
* Should be called before processing tool calls for each new request.
*
* @param reverseMap - Map from alias tool name to original tool name
*/
public static setToolAliasReverseMap(reverseMap: Map<string, string>): void {
this.toolAliasReverseMap = reverseMap
}

/**
* Clear the tool alias reverse map.
* Should be called when starting a new request to prevent stale mappings.
*/
public static clearToolAliasReverseMap(): void {
this.toolAliasReverseMap.clear()
}

/**
* Map a tool name back to its original name if it was aliased.
* Returns the original name if found, otherwise returns the input name.
*
* @param name - The tool name to resolve (possibly aliased)
* @returns The original tool name
*/
public static resolveOriginalToolName(name: string): string {
return this.toolAliasReverseMap.get(name) || name
}

/**
* Resolve and return both presented (aliased) and original tool names.
*/
public static resolveToolNames(name: string): { original: string; presented: string } {
const original = this.toolAliasReverseMap.get(name) || name
return { original, presented: name }
}

/**
* Process a raw tool call chunk from the API stream.
* Handles tracking, buffering, and emits start/delta/end events.
Expand Down Expand Up @@ -247,12 +289,17 @@ export class NativeToolCallParser {
try {
const partialArgs = parseJSON(toolCall.argumentsAccumulator)

// Resolve the tool name in case it was renamed and retain presented name
const { original, presented } = this.resolveToolNames(toolCall.name)
const resolvedName = original as ToolName

// Create partial ToolUse with extracted values
return this.createPartialToolUse(
toolCall.id,
toolCall.name as ToolName,
resolvedName,
partialArgs || {},
true, // partial
presented,
)
} catch {
// Even partial-json-parser can fail on severely malformed JSON
Expand Down Expand Up @@ -334,6 +381,7 @@ export class NativeToolCallParser {
name: ToolName,
partialArgs: Record<string, any>,
partial: boolean,
presentedName?: string,
): ToolUse | null {
// Build legacy params for display
// NOTE: For streaming partial updates, we MUST populate params even for complex types
Expand Down Expand Up @@ -525,11 +573,13 @@ export class NativeToolCallParser {
params,
partial,
nativeArgs,
...(presentedName ? { presentedName } : {}),
}
}

/**
* Convert a native tool call chunk to a ToolUse object.
* Handles renamed tools by resolving to their original names for execution.
*
* @param toolCall - The native tool call from the API stream
* @returns A properly typed ToolUse object
Expand All @@ -544,9 +594,16 @@ export class NativeToolCallParser {
return this.parseDynamicMcpTool(toolCall)
}

// Validate tool name
if (!toolNames.includes(toolCall.name as ToolName)) {
// Resolve renamed tools back to their original names
// This handles cases where a tool was renamed via modelInfo.renamedTools
// e.g., if "apply_diff" was renamed to "edit_file", we resolve "edit_file" back to "apply_diff"
const { original, presented } = this.resolveToolNames(toolCall.name as string)
const resolvedName = original as TName

// Validate tool name (using resolved name)
if (!toolNames.includes(resolvedName as ToolName)) {
console.error(`Invalid tool name: ${toolCall.name}`)
console.error(`Resolved name: ${resolvedName}`)
console.error(`Valid tool names:`, toolNames)
return null
}
Expand All @@ -563,13 +620,13 @@ export class NativeToolCallParser {
// Skip complex parameters that have been migrated to nativeArgs.
// For read_file, the 'files' parameter is a FileEntry[] array that can't be
// meaningfully stringified. The properly typed data is in nativeArgs instead.
if (toolCall.name === "read_file" && key === "files") {
if (resolvedName === "read_file" && key === "files") {
continue
}

// Validate parameter name
if (!toolParamNames.includes(key as ToolParamName)) {
console.warn(`Unknown parameter '${key}' for tool '${toolCall.name}'`)
console.warn(`Unknown parameter '${key}' for tool '${resolvedName}'`)
console.warn(`Valid param names:`, toolParamNames)
continue
}
Expand All @@ -589,7 +646,7 @@ export class NativeToolCallParser {
// will fall back to legacy parameter parsing if supported.
let nativeArgs: NativeArgsFor<TName> | undefined = undefined

switch (toolCall.name) {
switch (resolvedName) {
case "read_file":
if (args.files && Array.isArray(args.files)) {
nativeArgs = { files: this.convertFileEntries(args.files) } as NativeArgsFor<TName>
Expand Down Expand Up @@ -778,10 +835,11 @@ export class NativeToolCallParser {

const result: ToolUse<TName> = {
type: "tool_use" as const,
name: toolCall.name,
name: resolvedName,
params,
partial: false, // Native tool calls are always complete when yielded
nativeArgs,
...(presented !== resolvedName ? { presentedName: presented } : {}),
}

return result
Expand Down
Loading
Loading