Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,9 @@ export class NativeToolCallParser {
params,
partial: false, // Native tool calls are always complete when yielded
nativeArgs,
// Preserve original args for API history to maintain format consistency
// This ensures line_ranges stays as [[1, 50]] instead of being converted to lineRanges
rawInput: args,
}

// Preserve original name for API history when an alias was used
Expand Down
43 changes: 43 additions & 0 deletions src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,49 @@ describe("NativeToolCallParser", () => {
])
}
})

it("should preserve rawInput with original line_ranges format for API history", () => {
const toolCall = {
id: "toolu_123",
name: "read_file" as const,
arguments: JSON.stringify({
files: [
{
path: "src/core/task/Task.ts",
line_ranges: [
[1920, 1990],
[2060, 2120],
],
},
],
}),
}

const result = NativeToolCallParser.parseToolCall(toolCall)

expect(result).not.toBeNull()
expect(result?.type).toBe("tool_use")
if (result?.type === "tool_use") {
// Verify nativeArgs has converted format (lineRanges with objects)
const nativeArgs = result.nativeArgs as {
files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
}
expect(nativeArgs.files[0].lineRanges).toEqual([
{ start: 1920, end: 1990 },
{ start: 2060, end: 2120 },
])

// Verify rawInput preserves original format (line_ranges with tuples)
expect(result.rawInput).toBeDefined()
const rawInput = result.rawInput as {
files: Array<{ path: string; line_ranges?: Array<[number, number]> }>
}
expect(rawInput.files[0].line_ranges).toEqual([
[1920, 1990],
[2060, 2120],
])
}
})
})
})

Expand Down
7 changes: 5 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3556,8 +3556,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const toolUse = block as import("../../shared/tools").ToolUse
const toolCallId = toolUse.id
if (toolCallId) {
// nativeArgs is already in the correct API format for all tools
const input = toolUse.nativeArgs || toolUse.params
// Use rawInput to preserve original API format for history consistency.
// This ensures parameters like line_ranges stay as [[1, 50]] instead of
// being converted to lineRanges with object format [{ start: 1, end: 50 }].
// Fall back to nativeArgs for tools that don't have rawInput, then to params for legacy.
const input = toolUse.rawInput || toolUse.nativeArgs || toolUse.params

// Use originalName (alias) if present for API history consistency.
// When tool aliases are used (e.g., "edit_file" -> "search_and_replace"),
Expand Down
6 changes: 6 additions & 0 deletions src/shared/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ export interface ToolUse<TName extends ToolName = ToolName> {
toolUseId?: string // kilocode_change
// nativeArgs is properly typed based on TName if it's in NativeToolArgs, otherwise never
nativeArgs?: TName extends keyof NativeToolArgs ? NativeToolArgs[TName] : never
/**
* The raw input object from the API, preserving original parameter names and formats.
* Used for saving to conversation history to maintain API format consistency.
* For example, read_file keeps `line_ranges` as `[[1, 50]]` instead of converting to `lineRanges`.
*/
rawInput?: Record<string, unknown>
}

/**
Expand Down