mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: Handle malformed reasoning blocks from x-ai/grok models
- Add sanitization for x-ai model reasoning blocks to prevent interference with tool parsing - Remove XML-like tags and diff markers from reasoning content - Add validation to skip corrupted tool calls from x-ai models - Clean reasoning artifacts from diff content before processing - Handle interleaved thinking blocks in content stream This fixes issue #9239 where x-ai/grok-code-fast-1 model was causing 'Edit Unsuccessful' errors due to malformed reasoning blocks interfering with the diff parsing logic.
This commit is contained in:
parent
5e6e601b0a
commit
6f99b2edde
3 changed files with 158 additions and 12 deletions
|
|
@ -128,6 +128,12 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
let lastUsage: RooUsage | undefined = undefined
|
||||
// Accumulate tool calls by index - similar to how reasoning accumulates
|
||||
const toolCallAccumulator = new Map<number, { id: string; name: string; arguments: string }>()
|
||||
// Track if we're currently processing reasoning to prevent interference with tool parsing
|
||||
let isProcessingReasoning = false
|
||||
|
||||
// Check if this is an x-ai model that might have malformed reasoning blocks
|
||||
const modelId = this.options.apiModelId || ""
|
||||
const isXAIModel = modelId.includes("x-ai/") || modelId.includes("grok")
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
|
@ -136,22 +142,49 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
if (delta) {
|
||||
// Check for reasoning content (similar to OpenRouter)
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
// For x-ai models, sanitize reasoning text to prevent XML-like content from interfering
|
||||
let reasoningText = delta.reasoning
|
||||
if (isXAIModel) {
|
||||
// Remove any XML-like tags that might interfere with tool parsing
|
||||
reasoningText = reasoningText
|
||||
.replace(/<\/?apply_diff[^>]*>/g, "")
|
||||
.replace(/<\/?SEARCH[^>]*>/g, "")
|
||||
.replace(/<\/?REPLACE[^>]*>/g, "")
|
||||
.replace(/<<<<<<< SEARCH/g, "[SEARCH]")
|
||||
.replace(/=======/g, "[SEPARATOR]")
|
||||
.replace(/>>>>>>> REPLACE/g, "[REPLACE]")
|
||||
}
|
||||
isProcessingReasoning = true
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning,
|
||||
text: reasoningText,
|
||||
}
|
||||
isProcessingReasoning = false
|
||||
}
|
||||
|
||||
// Also check for reasoning_content for backward compatibility
|
||||
if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
|
||||
// Apply same sanitization for x-ai models
|
||||
let reasoningText = delta.reasoning_content
|
||||
if (isXAIModel) {
|
||||
reasoningText = reasoningText
|
||||
.replace(/<\/?apply_diff[^>]*>/g, "")
|
||||
.replace(/<\/?SEARCH[^>]*>/g, "")
|
||||
.replace(/<\/?REPLACE[^>]*>/g, "")
|
||||
.replace(/<<<<<<< SEARCH/g, "[SEARCH]")
|
||||
.replace(/=======/g, "[SEPARATOR]")
|
||||
.replace(/>>>>>>> REPLACE/g, "[REPLACE]")
|
||||
}
|
||||
isProcessingReasoning = true
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning_content,
|
||||
text: reasoningText,
|
||||
}
|
||||
isProcessingReasoning = false
|
||||
}
|
||||
|
||||
// Check for tool calls in delta
|
||||
if ("tool_calls" in delta && Array.isArray(delta.tool_calls)) {
|
||||
// Check for tool calls in delta - but skip if we're processing reasoning to avoid interference
|
||||
if (!isProcessingReasoning && "tool_calls" in delta && Array.isArray(delta.tool_calls)) {
|
||||
for (const toolCall of delta.tool_calls) {
|
||||
const index = toolCall.index
|
||||
const existing = toolCallAccumulator.get(index)
|
||||
|
|
@ -159,23 +192,78 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
if (existing) {
|
||||
// Accumulate arguments for existing tool call
|
||||
if (toolCall.function?.arguments) {
|
||||
existing.arguments += toolCall.function.arguments
|
||||
// For x-ai models, validate the arguments don't contain reasoning artifacts
|
||||
let args = toolCall.function.arguments
|
||||
if (isXAIModel && args) {
|
||||
// Check if the arguments contain reasoning block artifacts
|
||||
if (
|
||||
args.includes("<think>") ||
|
||||
args.includes("</think>") ||
|
||||
args.includes("<reasoning>") ||
|
||||
args.includes("</reasoning>")
|
||||
) {
|
||||
// Skip this chunk as it's likely corrupted reasoning content
|
||||
console.warn(
|
||||
"[RooHandler] Skipping corrupted tool call arguments from x-ai model",
|
||||
{
|
||||
modelId,
|
||||
corruptedContent: args.substring(0, 100),
|
||||
},
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
existing.arguments += args
|
||||
}
|
||||
} else {
|
||||
// Start new tool call accumulation
|
||||
const toolName = toolCall.function?.name || ""
|
||||
const toolArgs = toolCall.function?.arguments || ""
|
||||
|
||||
// Validate tool name isn't corrupted by reasoning content
|
||||
if (isXAIModel && (toolName.includes("think") || toolName.includes("reasoning"))) {
|
||||
console.warn("[RooHandler] Skipping corrupted tool call from x-ai model", {
|
||||
modelId,
|
||||
corruptedName: toolName,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
toolCallAccumulator.set(index, {
|
||||
id: toolCall.id || "",
|
||||
name: toolCall.function?.name || "",
|
||||
arguments: toolCall.function?.arguments || "",
|
||||
name: toolName,
|
||||
arguments: toolArgs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delta.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
// For x-ai models, check if content contains interleaved reasoning markers
|
||||
let textContent = delta.content
|
||||
if (isXAIModel) {
|
||||
// Check for common reasoning block markers that shouldn't be in regular content
|
||||
if (textContent.includes("<think>") || textContent.includes("</think>")) {
|
||||
// Extract and handle the reasoning part separately
|
||||
const thinkMatch = textContent.match(/<think>(.*?)<\/think>/s)
|
||||
if (thinkMatch) {
|
||||
// Emit the reasoning part
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: thinkMatch[1],
|
||||
}
|
||||
// Remove the thinking block from the text
|
||||
textContent = textContent.replace(/<think>.*?<\/think>/s, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only yield text if there's content after cleaning
|
||||
if (textContent.trim()) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: textContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,38 @@ export class ApplyDiffTool extends BaseTool<"apply_diff"> {
|
|||
const { askApproval, handleError, pushToolResult } = callbacks
|
||||
let { path: relPath, diff: diffContent } = params
|
||||
|
||||
if (diffContent && !task.api.getModel().id.includes("claude")) {
|
||||
// Check if this is an x-ai model that might have reasoning artifacts
|
||||
const modelId = task.api.getModel().id
|
||||
const isXAIModel = modelId.includes("x-ai/") || modelId.includes("grok")
|
||||
|
||||
if (diffContent && !modelId.includes("claude")) {
|
||||
diffContent = unescapeHtmlEntities(diffContent)
|
||||
}
|
||||
|
||||
// Clean up reasoning artifacts from x-ai models
|
||||
if (isXAIModel && diffContent) {
|
||||
// Check for reasoning block markers that shouldn't be in diff content
|
||||
if (
|
||||
diffContent.includes("<think>") ||
|
||||
diffContent.includes("</think>") ||
|
||||
diffContent.includes("<reasoning>") ||
|
||||
diffContent.includes("</reasoning>")
|
||||
) {
|
||||
console.warn("[ApplyDiffTool] Cleaning reasoning artifacts from x-ai model diff content", {
|
||||
modelId,
|
||||
hasThinkTags: diffContent.includes("<think>"),
|
||||
hasReasoningTags: diffContent.includes("<reasoning>"),
|
||||
})
|
||||
// Remove thinking/reasoning blocks but preserve the actual diff markers
|
||||
diffContent = diffContent
|
||||
.replace(/<think>.*?<\/think>/gs, "")
|
||||
.replace(/<reasoning>.*?<\/reasoning>/gs, "")
|
||||
// Also clean up orphaned closing tags that might interfere
|
||||
.replace(/<\/think>/g, "")
|
||||
.replace(/<\/reasoning>/g, "")
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!relPath) {
|
||||
task.consecutiveMistakeCount++
|
||||
|
|
|
|||
|
|
@ -127,10 +127,28 @@ export async function applyDiffTool(
|
|||
if (argsXmlTag) {
|
||||
// Parse file entries from XML (new way)
|
||||
try {
|
||||
// Check if this might be from an x-ai model and contains reasoning artifacts
|
||||
const isXAIModel = cline.api.getModel().id.includes("x-ai/") || cline.api.getModel().id.includes("grok")
|
||||
let cleanedXml = argsXmlTag
|
||||
|
||||
if (isXAIModel) {
|
||||
// Clean up common reasoning artifacts that might have leaked into the XML
|
||||
// Remove thinking tags that might interfere with parsing
|
||||
cleanedXml = cleanedXml
|
||||
.replace(/<think>.*?<\/think>/gs, "")
|
||||
.replace(/<reasoning>.*?<\/reasoning>/gs, "")
|
||||
|
||||
// Check for corrupted XML structure due to reasoning blocks
|
||||
if (cleanedXml.includes("</think>") && !cleanedXml.includes("<think>")) {
|
||||
console.warn("[MultiApplyDiffTool] Detected orphaned reasoning closing tag in x-ai model response")
|
||||
cleanedXml = cleanedXml.replace(/<\/think>/g, "")
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: We use parseXmlForDiff here instead of parseXml to prevent HTML entity decoding
|
||||
// This ensures exact character matching when comparing parsed content against original file content
|
||||
// Without this, special characters like & would be decoded to & causing diff mismatches
|
||||
const parsed = parseXmlForDiff(argsXmlTag, ["file.diff.content"]) as ParsedXmlResult
|
||||
const parsed = parseXmlForDiff(cleanedXml, ["file.diff.content"]) as ParsedXmlResult
|
||||
const files = Array.isArray(parsed.file) ? parsed.file : [parsed.file].filter(Boolean)
|
||||
|
||||
for (const file of files) {
|
||||
|
|
@ -158,6 +176,18 @@ export async function applyDiffTool(
|
|||
diffContent = typeof diff.content === "string" ? diff.content : ""
|
||||
startLine = diff.start_line ? parseInt(diff.start_line) : undefined
|
||||
|
||||
// For x-ai models, validate diff content doesn't contain reasoning artifacts
|
||||
if (isXAIModel && diffContent) {
|
||||
// Check for reasoning block markers that shouldn't be in diff content
|
||||
if (diffContent.includes("<think>") || diffContent.includes("</think>")) {
|
||||
console.warn(
|
||||
"[MultiApplyDiffTool] Cleaning reasoning artifacts from x-ai model diff content",
|
||||
)
|
||||
// Remove thinking blocks but preserve the actual diff markers
|
||||
diffContent = diffContent.replace(/<think>.*?<\/think>/gs, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Only add to operations if we have valid content
|
||||
if (diffContent) {
|
||||
operationsMap[filePath].diff.push({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue