mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: improve XML truncation error handling for Grok-4.1-Fast model
- Add detection for XML response truncation errors - Provide context-specific error messages for truncation issues - Include Grok-specific guidance when detected (150k+ context issue) - Add suggestions for resolution (condense context, start fresh, etc) - Add unit tests for truncation detection logic Fixes #9463
This commit is contained in:
parent
038f830bf6
commit
1ca13345b9
2 changed files with 244 additions and 2 deletions
|
|
@ -171,7 +171,45 @@ export async function applyDiffTool(
|
|||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const detailedError = `Failed to parse apply_diff XML. This usually means:
|
||||
|
||||
// Check for truncation-specific errors
|
||||
const isTruncationError =
|
||||
errorMessage.includes("StopNode is not closed") ||
|
||||
errorMessage.includes("unclosed") ||
|
||||
errorMessage.includes("Unexpected end") ||
|
||||
argsXmlTag?.endsWith("<") ||
|
||||
argsXmlTag?.endsWith("</") ||
|
||||
(argsXmlTag && !argsXmlTag.includes("</args>")) // Missing closing args tag indicates truncation
|
||||
|
||||
// Get model info for context-specific advice (outside the if block for scope)
|
||||
const modelId = cline.api.getModel().id
|
||||
const isGrokModel = modelId?.includes("grok")
|
||||
|
||||
let detailedError: string
|
||||
|
||||
if (isTruncationError) {
|
||||
detailedError = `XML response was truncated (incomplete). This typically occurs when:
|
||||
1. The model's context window is too full (150k+ tokens)
|
||||
2. The response exceeds the model's output token limit
|
||||
|
||||
${isGrokModel ? "Note: Grok-4.1-Fast is particularly susceptible to this issue with large contexts.\n" : ""}
|
||||
Suggested solutions:
|
||||
• Start a new conversation with less context
|
||||
• Use the "Condense Context" feature to reduce conversation size
|
||||
• Break large operations into smaller chunks
|
||||
• Consider switching to a model with larger output limits
|
||||
|
||||
Technical details:
|
||||
- Error: ${errorMessage}
|
||||
- XML appears to be cut off mid-tag
|
||||
- Last characters received: ${argsXmlTag ? argsXmlTag.slice(-100) : "N/A"}
|
||||
|
||||
To continue:
|
||||
1. Try condensing the context using the UI button
|
||||
2. Copy essential context and start fresh
|
||||
3. Retry with smaller file edits`
|
||||
} else {
|
||||
detailedError = `Failed to parse apply_diff XML. This usually means:
|
||||
1. The XML structure is malformed or incomplete
|
||||
2. Missing required <file>, <path>, or <diff> tags
|
||||
3. Invalid characters or encoding in the XML
|
||||
|
|
@ -188,10 +226,18 @@ Expected structure:
|
|||
</args>
|
||||
|
||||
Original error: ${errorMessage}`
|
||||
}
|
||||
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("apply_diff")
|
||||
TelemetryService.instance.captureDiffApplicationError(cline.taskId, cline.consecutiveMistakeCount)
|
||||
await cline.say("diff_error", `Failed to parse apply_diff XML: ${errorMessage}`)
|
||||
|
||||
// Use a more descriptive error message for truncation issues
|
||||
const userFacingMessage = isTruncationError
|
||||
? `XML response truncated due to context/output limits. ${isGrokModel ? "(Known issue with Grok-4.1-Fast at 150k+ tokens) " : ""}Try condensing context or starting fresh.`
|
||||
: `Failed to parse apply_diff XML: ${errorMessage}`
|
||||
|
||||
await cline.say("diff_error", userFacingMessage)
|
||||
pushToolResult(detailedError)
|
||||
cline.processQueuedMessages()
|
||||
return
|
||||
|
|
|
|||
196
src/core/tools/__tests__/MultiApplyDiffTool.truncation.spec.ts
Normal file
196
src/core/tools/__tests__/MultiApplyDiffTool.truncation.spec.ts
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { applyDiffTool } from "../MultiApplyDiffTool"
|
||||
import { Task } from "../../task/Task"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { EXPERIMENT_IDS } from "../../../shared/experiments"
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@roo-code/telemetry", () => ({
|
||||
TelemetryService: {
|
||||
instance: {
|
||||
captureDiffApplicationError: vi.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
vi.mock("../../../utils/resolveToolProtocol", () => ({
|
||||
resolveToolProtocol: vi.fn().mockReturnValue("xml"),
|
||||
}))
|
||||
vi.mock("../../../shared/experiments", () => ({
|
||||
EXPERIMENT_IDS: {
|
||||
MULTI_FILE_APPLY_DIFF: "multi_file_apply_diff",
|
||||
PREVENT_FOCUS_DISRUPTION: "prevent_focus_disruption",
|
||||
},
|
||||
experiments: {
|
||||
isEnabled: vi.fn().mockReturnValue(true), // Enable multi-file experiment
|
||||
},
|
||||
}))
|
||||
|
||||
describe("MultiApplyDiffTool - XML Truncation Detection", () => {
|
||||
let mockCline: Partial<Task>
|
||||
let mockAskApproval: any
|
||||
let mockHandleError: any
|
||||
let mockPushToolResult: any
|
||||
let mockRemoveClosingTag: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all mocks before each test
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Set up mock Task instance (cline)
|
||||
mockCline = {
|
||||
api: {
|
||||
getModel: vi.fn().mockReturnValue({
|
||||
id: "openrouter/grok-4.1-fast",
|
||||
info: { id: "openrouter/grok-4.1-fast" },
|
||||
}),
|
||||
},
|
||||
say: vi.fn().mockResolvedValue(undefined),
|
||||
ask: vi.fn().mockResolvedValue({ response: "", text: "", images: [] }),
|
||||
recordToolError: vi.fn(),
|
||||
processQueuedMessages: vi.fn(),
|
||||
sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"),
|
||||
consecutiveMistakeCount: 0,
|
||||
taskId: "test-task-id",
|
||||
cwd: "/workspace",
|
||||
diffViewProvider: {
|
||||
reset: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
providerRef: {
|
||||
deref: vi.fn().mockReturnValue({
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
experiments: {},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
} as any
|
||||
|
||||
// Set up mock callbacks
|
||||
mockAskApproval = vi.fn().mockResolvedValue(true)
|
||||
mockHandleError = vi.fn()
|
||||
mockPushToolResult = vi.fn()
|
||||
mockRemoveClosingTag = vi.fn()
|
||||
})
|
||||
|
||||
it("should detect StopNode is not closed error as truncation", async () => {
|
||||
const block = {
|
||||
params: {
|
||||
args: `<args>
|
||||
<file>
|
||||
<path>test.ts</path>
|
||||
<diff>
|
||||
<content>some diff<`, // Truncated XML
|
||||
},
|
||||
}
|
||||
|
||||
// Mock parseXmlForDiff to throw the typical truncation error
|
||||
vi.doMock("../../../utils/xml", () => ({
|
||||
parseXmlForDiff: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Failed to parse XML: StopNode is not closed")
|
||||
}),
|
||||
}))
|
||||
|
||||
await applyDiffTool(
|
||||
mockCline as Task,
|
||||
block as any,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
// Verify truncation-specific error was shown
|
||||
expect(mockCline.say).toHaveBeenCalledWith(
|
||||
"diff_error",
|
||||
expect.stringContaining("XML response truncated due to context/output limits"),
|
||||
)
|
||||
|
||||
// Verify Grok-specific message was included
|
||||
expect(mockCline.say).toHaveBeenCalledWith(
|
||||
"diff_error",
|
||||
expect.stringContaining("Known issue with Grok-4.1-Fast at 150k+ tokens"),
|
||||
)
|
||||
|
||||
// Verify detailed error includes truncation-specific guidance
|
||||
expect(mockPushToolResult).toHaveBeenCalledWith(
|
||||
expect.stringContaining("XML response was truncated (incomplete)"),
|
||||
)
|
||||
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining('Use the "Condense Context" feature'))
|
||||
})
|
||||
|
||||
it("should detect XML ending with incomplete tag as truncation", async () => {
|
||||
const block = {
|
||||
params: {
|
||||
args: `<args>
|
||||
<file>
|
||||
<path>test.ts</path>
|
||||
<diff>
|
||||
<content>some diff</`, // Ends with opening tag
|
||||
},
|
||||
}
|
||||
|
||||
// Mock parseXmlForDiff to throw a generic error
|
||||
vi.doMock("../../../utils/xml", () => ({
|
||||
parseXmlForDiff: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Failed to parse XML: Unexpected end")
|
||||
}),
|
||||
}))
|
||||
|
||||
await applyDiffTool(
|
||||
mockCline as Task,
|
||||
block as any,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
// Verify truncation was detected
|
||||
expect(mockCline.say).toHaveBeenCalledWith(
|
||||
"diff_error",
|
||||
expect.stringContaining("XML response truncated due to context/output limits"),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle non-truncation XML errors normally", async () => {
|
||||
const block = {
|
||||
params: {
|
||||
args: `<args>
|
||||
<file>
|
||||
<malformed>This is not valid structure</malformed>
|
||||
</file>
|
||||
</args>`, // Complete but malformed XML
|
||||
},
|
||||
}
|
||||
|
||||
// Mock parseXmlForDiff to throw a non-truncation error
|
||||
vi.doMock("../../../utils/xml", () => ({
|
||||
parseXmlForDiff: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Invalid XML structure: missing <path> tag")
|
||||
}),
|
||||
}))
|
||||
|
||||
await applyDiffTool(
|
||||
mockCline as Task,
|
||||
block as any,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
// Verify standard error message was shown (not truncation-specific)
|
||||
expect(mockCline.say).toHaveBeenCalledWith(
|
||||
"diff_error",
|
||||
expect.stringContaining("Failed to parse apply_diff XML"),
|
||||
)
|
||||
|
||||
// Verify NO truncation-specific messages were shown
|
||||
expect(mockCline.say).not.toHaveBeenCalledWith("diff_error", expect.stringContaining("truncated"))
|
||||
|
||||
// Verify detailed error shows standard format guidance
|
||||
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Expected structure:"))
|
||||
|
||||
// Verify NO condensing context messages
|
||||
expect(mockPushToolResult).not.toHaveBeenCalledWith(expect.stringContaining("Condense Context"))
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue