mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
feat: add multi-file apply_diff support for native tool calling protocol
- Create multi_apply_diff.ts schema with files array parameter - Add createApplyDiffTool factory function to swap schemas based on experiment flag - Update getNativeTools to accept multiFileApplyDiffEnabled parameter - Update NativeToolCallParser to handle both single-file and multi-file formats - Update presentAssistantMessage to route based on nativeArgs format - Update MultiApplyDiffTool to handle nativeArgs.files array - Update NativeToolArgs type to union type supporting both formats - Add 8 tests for apply_diff parsing in both formats
This commit is contained in:
parent
d92d729219
commit
7e52525523
9 changed files with 393 additions and 51 deletions
|
|
@ -400,7 +400,17 @@ export class NativeToolCallParser {
|
|||
break
|
||||
|
||||
case "apply_diff":
|
||||
if (partialArgs.path !== undefined || partialArgs.diff !== undefined) {
|
||||
// Multi-file format (from multi_apply_diff schema)
|
||||
if (partialArgs.files && Array.isArray(partialArgs.files)) {
|
||||
nativeArgs = {
|
||||
files: partialArgs.files.map((f: any) => ({
|
||||
path: f.path,
|
||||
diff: f.diff,
|
||||
})),
|
||||
}
|
||||
}
|
||||
// Single-file format (from apply_diff schema)
|
||||
else if (partialArgs.path !== undefined || partialArgs.diff !== undefined) {
|
||||
nativeArgs = {
|
||||
path: partialArgs.path,
|
||||
diff: partialArgs.diff,
|
||||
|
|
@ -633,7 +643,17 @@ export class NativeToolCallParser {
|
|||
break
|
||||
|
||||
case "apply_diff":
|
||||
if (args.path !== undefined && args.diff !== undefined) {
|
||||
// Multi-file format (from multi_apply_diff schema)
|
||||
if (args.files && Array.isArray(args.files)) {
|
||||
nativeArgs = {
|
||||
files: args.files.map((f: any) => ({
|
||||
path: f.path,
|
||||
diff: f.diff,
|
||||
})),
|
||||
} as NativeArgsFor<TName>
|
||||
}
|
||||
// Single-file format (from apply_diff schema)
|
||||
else if (args.path !== undefined && args.diff !== undefined) {
|
||||
nativeArgs = {
|
||||
path: args.path,
|
||||
diff: args.diff,
|
||||
|
|
|
|||
|
|
@ -237,5 +237,206 @@ describe("NativeToolCallParser", () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("parseToolCall", () => {
|
||||
describe("apply_diff tool", () => {
|
||||
it("should handle single-file format (path and diff)", () => {
|
||||
const toolCall = {
|
||||
id: "toolu_123",
|
||||
name: "apply_diff" as const,
|
||||
arguments: JSON.stringify({
|
||||
path: "src/test.ts",
|
||||
diff: "<<<<<<< SEARCH\nold code\n=======\nnew code\n>>>>>>> REPLACE",
|
||||
}),
|
||||
}
|
||||
|
||||
const result = NativeToolCallParser.parseToolCall(toolCall)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.type).toBe("tool_use")
|
||||
if (result?.type === "tool_use") {
|
||||
expect(result.nativeArgs).toBeDefined()
|
||||
const nativeArgs = result.nativeArgs as { path: string; diff: string }
|
||||
expect(nativeArgs.path).toBe("src/test.ts")
|
||||
expect(nativeArgs.diff).toContain("<<<<<<< SEARCH")
|
||||
expect(nativeArgs.diff).toContain(">>>>>>> REPLACE")
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle multi-file format (files array)", () => {
|
||||
const toolCall = {
|
||||
id: "toolu_456",
|
||||
name: "apply_diff" as const,
|
||||
arguments: JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
path: "src/file1.ts",
|
||||
diff: "<<<<<<< SEARCH\nold code 1\n=======\nnew code 1\n>>>>>>> REPLACE",
|
||||
},
|
||||
{
|
||||
path: "src/file2.ts",
|
||||
diff: "<<<<<<< SEARCH\nold code 2\n=======\nnew code 2\n>>>>>>> REPLACE",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
|
||||
const result = NativeToolCallParser.parseToolCall(toolCall)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.type).toBe("tool_use")
|
||||
if (result?.type === "tool_use") {
|
||||
expect(result.nativeArgs).toBeDefined()
|
||||
const nativeArgs = result.nativeArgs as {
|
||||
files: Array<{ path: string; diff: string }>
|
||||
}
|
||||
expect(nativeArgs.files).toHaveLength(2)
|
||||
expect(nativeArgs.files[0].path).toBe("src/file1.ts")
|
||||
expect(nativeArgs.files[0].diff).toContain("old code 1")
|
||||
expect(nativeArgs.files[1].path).toBe("src/file2.ts")
|
||||
expect(nativeArgs.files[1].diff).toContain("old code 2")
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle multi-file format with single file", () => {
|
||||
const toolCall = {
|
||||
id: "toolu_789",
|
||||
name: "apply_diff" as const,
|
||||
arguments: JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
path: "src/single.ts",
|
||||
diff: "<<<<<<< SEARCH\nold\n=======\nnew\n>>>>>>> REPLACE",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
|
||||
const result = NativeToolCallParser.parseToolCall(toolCall)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.type).toBe("tool_use")
|
||||
if (result?.type === "tool_use") {
|
||||
const nativeArgs = result.nativeArgs as {
|
||||
files: Array<{ path: string; diff: string }>
|
||||
}
|
||||
expect(nativeArgs.files).toHaveLength(1)
|
||||
expect(nativeArgs.files[0].path).toBe("src/single.ts")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("processStreamingChunk", () => {
|
||||
describe("apply_diff tool", () => {
|
||||
it("should handle single-file format during streaming", () => {
|
||||
const id = "toolu_streaming_apply_diff_single"
|
||||
NativeToolCallParser.startStreamingToolCall(id, "apply_diff")
|
||||
|
||||
const fullArgs = JSON.stringify({
|
||||
path: "streaming/test.ts",
|
||||
diff: "<<<<<<< SEARCH\nold\n=======\nnew\n>>>>>>> REPLACE",
|
||||
})
|
||||
|
||||
const result = NativeToolCallParser.processStreamingChunk(id, fullArgs)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.nativeArgs).toBeDefined()
|
||||
const nativeArgs = result?.nativeArgs as { path: string; diff: string }
|
||||
expect(nativeArgs.path).toBe("streaming/test.ts")
|
||||
expect(nativeArgs.diff).toContain("<<<<<<< SEARCH")
|
||||
})
|
||||
|
||||
it("should handle multi-file format during streaming", () => {
|
||||
const id = "toolu_streaming_apply_diff_multi"
|
||||
NativeToolCallParser.startStreamingToolCall(id, "apply_diff")
|
||||
|
||||
const fullArgs = JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
path: "streaming/file1.ts",
|
||||
diff: "<<<<<<< SEARCH\nold1\n=======\nnew1\n>>>>>>> REPLACE",
|
||||
},
|
||||
{
|
||||
path: "streaming/file2.ts",
|
||||
diff: "<<<<<<< SEARCH\nold2\n=======\nnew2\n>>>>>>> REPLACE",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const result = NativeToolCallParser.processStreamingChunk(id, fullArgs)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.nativeArgs).toBeDefined()
|
||||
const nativeArgs = result?.nativeArgs as {
|
||||
files: Array<{ path: string; diff: string }>
|
||||
}
|
||||
expect(nativeArgs.files).toHaveLength(2)
|
||||
expect(nativeArgs.files[0].path).toBe("streaming/file1.ts")
|
||||
expect(nativeArgs.files[1].path).toBe("streaming/file2.ts")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("finalizeStreamingToolCall", () => {
|
||||
describe("apply_diff tool", () => {
|
||||
it("should finalize single-file format correctly", () => {
|
||||
const id = "toolu_finalize_apply_diff_single"
|
||||
NativeToolCallParser.startStreamingToolCall(id, "apply_diff")
|
||||
|
||||
NativeToolCallParser.processStreamingChunk(
|
||||
id,
|
||||
JSON.stringify({
|
||||
path: "finalized/single.ts",
|
||||
diff: "<<<<<<< SEARCH\nold\n=======\nnew\n>>>>>>> REPLACE",
|
||||
}),
|
||||
)
|
||||
|
||||
const result = NativeToolCallParser.finalizeStreamingToolCall(id)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.type).toBe("tool_use")
|
||||
if (result?.type === "tool_use") {
|
||||
const nativeArgs = result.nativeArgs as { path: string; diff: string }
|
||||
expect(nativeArgs.path).toBe("finalized/single.ts")
|
||||
expect(nativeArgs.diff).toContain("<<<<<<< SEARCH")
|
||||
}
|
||||
})
|
||||
|
||||
it("should finalize multi-file format correctly", () => {
|
||||
const id = "toolu_finalize_apply_diff_multi"
|
||||
NativeToolCallParser.startStreamingToolCall(id, "apply_diff")
|
||||
|
||||
NativeToolCallParser.processStreamingChunk(
|
||||
id,
|
||||
JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
path: "finalized/file1.ts",
|
||||
diff: "<<<<<<< SEARCH\nold1\n=======\nnew1\n>>>>>>> REPLACE",
|
||||
},
|
||||
{
|
||||
path: "finalized/file2.ts",
|
||||
diff: "<<<<<<< SEARCH\nold2\n=======\nnew2\n>>>>>>> REPLACE",
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
const result = NativeToolCallParser.finalizeStreamingToolCall(id)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.type).toBe("tool_use")
|
||||
if (result?.type === "tool_use") {
|
||||
const nativeArgs = result.nativeArgs as {
|
||||
files: Array<{ path: string; diff: string }>
|
||||
}
|
||||
expect(nativeArgs.files).toHaveLength(2)
|
||||
expect(nativeArgs.files[0].path).toBe("finalized/file1.ts")
|
||||
expect(nativeArgs.files[1].path).toBe("finalized/file2.ts")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -377,8 +377,24 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
case "write_to_file":
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
case "apply_diff":
|
||||
// Handle both legacy format and new multi-file format
|
||||
if (block.params.path) {
|
||||
// Handle native multi-file format (from multi_apply_diff schema)
|
||||
if (block.nativeArgs?.files && Array.isArray(block.nativeArgs.files)) {
|
||||
const files = block.nativeArgs.files
|
||||
const firstPath = files[0]?.path
|
||||
if (firstPath) {
|
||||
if (files.length > 1) {
|
||||
return `[${block.name} for '${firstPath}' and ${files.length - 1} more file${files.length > 2 ? "s" : ""}]`
|
||||
} else {
|
||||
return `[${block.name} for '${firstPath}']`
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle native single-file format
|
||||
else if (block.nativeArgs?.path) {
|
||||
return `[${block.name} for '${block.nativeArgs.path}']`
|
||||
}
|
||||
// Handle XML legacy format
|
||||
else if (block.params.path) {
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
} else if (block.params.args) {
|
||||
// Try to extract first file path from args for display
|
||||
|
|
@ -817,17 +833,38 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
// Check if this tool call came from native protocol by checking for ID
|
||||
// Native calls always have IDs, XML calls never do
|
||||
if (toolProtocol === TOOL_PROTOCOL.NATIVE) {
|
||||
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
toolProtocol,
|
||||
})
|
||||
// For native protocol, route based on nativeArgs format:
|
||||
// - nativeArgs.files (array) -> multi-file tool (from multi_apply_diff schema)
|
||||
// - nativeArgs.path (string) -> single-file tool (from apply_diff schema)
|
||||
const nativeArgs = block.nativeArgs as
|
||||
| { files: Array<{ path: string; diff: string }> }
|
||||
| { path: string; diff: string }
|
||||
| undefined
|
||||
|
||||
if (nativeArgs && "files" in nativeArgs && Array.isArray(nativeArgs.files)) {
|
||||
// Multi-file format: use MultiApplyDiffTool
|
||||
await applyDiffTool(
|
||||
cline,
|
||||
block,
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
)
|
||||
} else {
|
||||
// Single-file format: use ApplyDiffTool
|
||||
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
toolProtocol,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Get the provider and state to check experiment settings
|
||||
// For XML protocol, check experiment settings to determine routing
|
||||
const provider = cline.providerRef.deref()
|
||||
let isMultiFileApplyDiffEnabled = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type OpenAI from "openai"
|
||||
import { multi_apply_diff } from "./multi_apply_diff"
|
||||
|
||||
const APPLY_DIFF_DESCRIPTION = `Apply precise, targeted modifications to an existing file using one or more search/replace blocks. This tool is for surgical edits only; the 'SEARCH' block must exactly match the existing content, including whitespace and indentation. To make multiple targeted changes, provide multiple SEARCH/REPLACE blocks in the 'diff' parameter. Use the 'read_file' tool first if you are not confident in the exact content to search for.`
|
||||
|
||||
|
|
@ -33,3 +34,14 @@ export const apply_diff = {
|
|||
},
|
||||
},
|
||||
} satisfies OpenAI.Chat.ChatCompletionTool
|
||||
|
||||
/**
|
||||
* Creates the apply_diff tool definition, selecting between single-file and multi-file
|
||||
* schemas based on whether the multi-file experiment is enabled.
|
||||
*
|
||||
* @param multiFileEnabled - Whether to use the multi-file schema (default: false)
|
||||
* @returns Native tool definition for apply_diff
|
||||
*/
|
||||
export function createApplyDiffTool(multiFileEnabled: boolean = false): OpenAI.Chat.ChatCompletionTool {
|
||||
return multiFileEnabled ? multi_apply_diff : apply_diff
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type OpenAI from "openai"
|
||||
import accessMcpResource from "./access_mcp_resource"
|
||||
import { apply_diff } from "./apply_diff"
|
||||
import { createApplyDiffTool } from "./apply_diff"
|
||||
import applyPatch from "./apply_patch"
|
||||
import askFollowupQuestion from "./ask_followup_question"
|
||||
import attemptCompletion from "./attempt_completion"
|
||||
|
|
@ -27,12 +27,16 @@ export { convertOpenAIToolToAnthropic, convertOpenAIToolsToAnthropic } from "./c
|
|||
* Get native tools array, optionally customizing based on settings.
|
||||
*
|
||||
* @param partialReadsEnabled - Whether to include line_ranges support in read_file tool (default: true)
|
||||
* @param multiFileApplyDiffEnabled - Whether to use multi-file apply_diff schema (default: false)
|
||||
* @returns Array of native tool definitions
|
||||
*/
|
||||
export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat.ChatCompletionTool[] {
|
||||
export function getNativeTools(
|
||||
partialReadsEnabled: boolean = true,
|
||||
multiFileApplyDiffEnabled: boolean = false,
|
||||
): OpenAI.Chat.ChatCompletionTool[] {
|
||||
return [
|
||||
accessMcpResource,
|
||||
apply_diff,
|
||||
createApplyDiffTool(multiFileApplyDiffEnabled),
|
||||
applyPatch,
|
||||
askFollowupQuestion,
|
||||
attemptCompletion,
|
||||
|
|
|
|||
54
src/core/prompts/tools/native-tools/multi_apply_diff.ts
Normal file
54
src/core/prompts/tools/native-tools/multi_apply_diff.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import type OpenAI from "openai"
|
||||
|
||||
const MULTI_APPLY_DIFF_DESCRIPTION = `Apply precise, targeted modifications to one or more files using search/replace blocks. This tool supports batch operations across multiple files in a single request, maximizing efficiency. For each file, the 'SEARCH' block must exactly match the existing content, including whitespace and indentation. Use the 'read_file' tool first if you are not confident in the exact content to search for.`
|
||||
|
||||
const DIFF_PARAMETER_DESCRIPTION = `A string containing one or more search/replace blocks defining the changes. The ':start_line:' is required and indicates the starting line number of the original content. You must not add a start line for the replacement content. Each block must follow this format:
|
||||
<<<<<<< SEARCH
|
||||
:start_line:[line_number]
|
||||
-------
|
||||
[exact content to find]
|
||||
=======
|
||||
[new content to replace with]
|
||||
>>>>>>> REPLACE`
|
||||
|
||||
/**
|
||||
* Multi-file apply_diff schema for native tool calling.
|
||||
* This schema is used when the MULTI_FILE_APPLY_DIFF experiment is enabled.
|
||||
* It allows batch operations across multiple files in a single tool call.
|
||||
*/
|
||||
export const multi_apply_diff = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "apply_diff", // Same name - model sees "apply_diff"
|
||||
description: MULTI_APPLY_DIFF_DESCRIPTION,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
files: {
|
||||
type: "array",
|
||||
description:
|
||||
"List of files to modify with their diffs. Include multiple files to batch related changes efficiently.",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path of the file to modify, relative to the current workspace directory.",
|
||||
},
|
||||
diff: {
|
||||
type: "string",
|
||||
description: DIFF_PARAMETER_DESCRIPTION,
|
||||
},
|
||||
},
|
||||
required: ["path", "diff"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
},
|
||||
required: ["files"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
} satisfies OpenAI.Chat.ChatCompletionTool
|
||||
|
|
@ -3,6 +3,7 @@ import type { ProviderSettings, ModeConfig, ModelInfo } from "@roo-code/types"
|
|||
import type { ClineProvider } from "../webview/ClineProvider"
|
||||
import { getNativeTools, getMcpServerTools } from "../prompts/tools/native-tools"
|
||||
import { filterNativeToolsForMode, filterMcpToolsForMode } from "../prompts/tools/filter-tools-for-mode"
|
||||
import { experiments as experimentsModule, EXPERIMENT_IDS } from "../../shared/experiments"
|
||||
|
||||
interface BuildToolsOptions {
|
||||
provider: ClineProvider
|
||||
|
|
@ -55,8 +56,14 @@ export async function buildNativeToolsArray(options: BuildToolsOptions): Promise
|
|||
// Determine if partial reads are enabled based on maxReadFileLine setting
|
||||
const partialReadsEnabled = maxReadFileLine !== -1
|
||||
|
||||
// Build native tools with dynamic read_file tool based on partialReadsEnabled
|
||||
const nativeTools = getNativeTools(partialReadsEnabled)
|
||||
// Determine if multi-file apply_diff is enabled based on experiment flag
|
||||
const multiFileApplyDiffEnabled = experimentsModule.isEnabled(
|
||||
experiments ?? {},
|
||||
EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF,
|
||||
)
|
||||
|
||||
// Build native tools with dynamic read_file and apply_diff tools based on settings
|
||||
const nativeTools = getNativeTools(partialReadsEnabled, multiFileApplyDiffEnabled)
|
||||
|
||||
// Filter native tools based on mode restrictions
|
||||
const filteredNativeTools = filterNativeToolsForMode(
|
||||
|
|
|
|||
|
|
@ -61,45 +61,20 @@ export async function applyDiffTool(
|
|||
pushToolResult: PushToolResult,
|
||||
removeClosingTag: RemoveClosingTag,
|
||||
) {
|
||||
// Check if native protocol is enabled - if so, always use single-file class-based tool
|
||||
const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
|
||||
if (isNativeProtocol(toolProtocol)) {
|
||||
return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
toolProtocol,
|
||||
})
|
||||
}
|
||||
|
||||
// Check if MULTI_FILE_APPLY_DIFF experiment is enabled
|
||||
const provider = cline.providerRef.deref()
|
||||
const state = await provider?.getState()
|
||||
if (provider && state) {
|
||||
const isMultiFileApplyDiffEnabled = experiments.isEnabled(
|
||||
state.experiments ?? {},
|
||||
EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF,
|
||||
)
|
||||
// Note: Routing between single-file and multi-file tools is now done in presentAssistantMessage.ts
|
||||
// based on nativeArgs format. This function is only called for multi-file operations.
|
||||
|
||||
// If experiment is disabled, use single-file class-based tool
|
||||
if (!isMultiFileApplyDiffEnabled) {
|
||||
return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
removeClosingTag,
|
||||
toolProtocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, continue with new multi-file implementation
|
||||
// Multi-file implementation
|
||||
const argsXmlTag: string | undefined = block.params.args
|
||||
const legacyPath: string | undefined = block.params.path
|
||||
const legacyDiffContent: string | undefined = block.params.diff
|
||||
const legacyStartLineStr: string | undefined = block.params.start_line
|
||||
|
||||
// Native multi-file format from nativeArgs.files
|
||||
const nativeFiles = (block.nativeArgs as { files?: Array<{ path: string; diff: string }> } | undefined)?.files
|
||||
|
||||
let operationsMap: Record<string, DiffOperation> = {}
|
||||
let usingLegacyParams = false
|
||||
let filteredOperationErrors: string[] = []
|
||||
|
|
@ -107,7 +82,12 @@ export async function applyDiffTool(
|
|||
// Handle partial message first
|
||||
if (block.partial) {
|
||||
let filePath = ""
|
||||
if (argsXmlTag) {
|
||||
// Native multi-file format
|
||||
if (nativeFiles && nativeFiles.length > 0) {
|
||||
filePath = nativeFiles[0].path || ""
|
||||
}
|
||||
// XML args format
|
||||
else if (argsXmlTag) {
|
||||
const match = argsXmlTag.match(/<file>.*?<path>([^<]+)<\/path>/s)
|
||||
if (match) {
|
||||
filePath = match[1]
|
||||
|
|
@ -126,7 +106,33 @@ export async function applyDiffTool(
|
|||
return
|
||||
}
|
||||
|
||||
if (argsXmlTag) {
|
||||
// Handle native multi-file format (from nativeArgs.files via multi_apply_diff schema)
|
||||
if (nativeFiles && nativeFiles.length > 0) {
|
||||
for (const file of nativeFiles) {
|
||||
if (!file.path || !file.diff) continue
|
||||
|
||||
const filePath = file.path
|
||||
|
||||
// Initialize the operation in the map if it doesn't exist
|
||||
if (!operationsMap[filePath]) {
|
||||
operationsMap[filePath] = {
|
||||
path: filePath,
|
||||
diff: [],
|
||||
}
|
||||
}
|
||||
|
||||
// Native format has a single diff content per file entry
|
||||
// The diff content contains the full SEARCH/REPLACE block(s)
|
||||
if (file.diff) {
|
||||
operationsMap[filePath].diff.push({
|
||||
content: file.diff,
|
||||
startLine: undefined, // Native format doesn't include start_line per file
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle XML args format (from XML protocol)
|
||||
else if (argsXmlTag) {
|
||||
// Parse file entries from XML (new way)
|
||||
try {
|
||||
// IMPORTANT: We use parseXmlForDiff here instead of parseXml to prevent HTML entity decoding
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@ export type NativeToolArgs = {
|
|||
read_file: { files: FileEntry[] }
|
||||
attempt_completion: { result: string }
|
||||
execute_command: { command: string; cwd?: string }
|
||||
apply_diff: { path: string; diff: string }
|
||||
// Union type to support both single-file and multi-file formats
|
||||
apply_diff: { path: string; diff: string } | { files: Array<{ path: string; diff: string }> }
|
||||
search_and_replace: { path: string; operations: Array<{ search: string; replace: string }> }
|
||||
search_replace: { file_path: string; old_string: string; new_string: string }
|
||||
apply_patch: { patch: string }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue