Repository navigation
[WRONG BRANCH] fix(responses): repair MiMo tool-call envelope corruption causing infinite retry loops #7214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -66,6 +66,39 @@ function repairJsonObjectEnvelope(text: string): Record<string, unknown> | undef | |||||||||||||||||
| return undefined; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Repair the observed MiMo tool-call corruption: the model emits its proprietary | ||||||||||||||||||
| * `</function></tool_call>` closing tags INSIDE the JSON string value of the | ||||||||||||||||||
| * function arguments, and drops the JSON object's closing brace. Strip the | ||||||||||||||||||
| * suffix, restore the brace, and only accept the repair when the result parses | ||||||||||||||||||
| * into an object. | ||||||||||||||||||
| */ | ||||||||||||||||||
| function repairMimoToolCallEnvelope(text: string): Record<string, unknown> | undefined { | ||||||||||||||||||
| const suffix = "</function></tool_call>"; | ||||||||||||||||||
| if (!text.includes(suffix)) return undefined; | ||||||||||||||||||
| const stripped = text.replace(/\s*<\/function><\/tool_call>\s*$/u, "").trimEnd(); | ||||||||||||||||||
| if (!stripped.startsWith("{")) return undefined; | ||||||||||||||||||
| // The observed corruption replaces the JSON string's closing quote and the | ||||||||||||||||||
| // object's closing brace with the tags. Restore what is missing: an object | ||||||||||||||||||
| // that already closes keeps nothing, a closed string value needs only `}`, | ||||||||||||||||||
| // and an unterminated string value needs `"}`. | ||||||||||||||||||
| let candidate: string; | ||||||||||||||||||
| if (stripped.endsWith("}")) { | ||||||||||||||||||
| candidate = stripped; | ||||||||||||||||||
| } else if (stripped.endsWith('"')) { | ||||||||||||||||||
| candidate = `${stripped}}`; | ||||||||||||||||||
|
Comment on lines
+88
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Detect an escaped quote before selecting the repair. If the argument value ends with an escaped quote before 🤖 Prompt for AI Agents |
||||||||||||||||||
| } else { | ||||||||||||||||||
| candidate = `${stripped}"}`; | ||||||||||||||||||
| } | ||||||||||||||||||
| try { | ||||||||||||||||||
| const parsed: unknown = JSON.parse(candidate); | ||||||||||||||||||
| if (isObj(parsed)) return parsed; | ||||||||||||||||||
| } catch { | ||||||||||||||||||
| /* fall through to the tolerated-{} path */ | ||||||||||||||||||
| } | ||||||||||||||||||
| return undefined; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Decode a function call's serialized arguments the way history replay tolerates them: empty | ||||||||||||||||||
| * or non-JSON text becomes `{}` instead of failing every later turn, and the one bounded | ||||||||||||||||||
|
|
@@ -80,12 +113,24 @@ export function functionCallArgumentsFromText(callId: string, rawArguments: stri | |||||||||||||||||
| const parsed: unknown = JSON.parse(rawArgs); | ||||||||||||||||||
| if (isObj(parsed)) args = parsed; | ||||||||||||||||||
| } catch { | ||||||||||||||||||
| // Corruptions observed on Xiaomi MiMo routed requests, each with a bounded | ||||||||||||||||||
| // repair so a serialized call the model can still see is restored instead | ||||||||||||||||||
| // of replaying {} forever. | ||||||||||||||||||
| const mimoRepaired = repairMimoToolCallEnvelope(rawArgs); | ||||||||||||||||||
| if (mimoRepaired !== undefined) { | ||||||||||||||||||
| args = mimoRepaired; | ||||||||||||||||||
| console.warn(`[parser] function_call ${callId} repaired MiMo tool-call envelope (tags inside string)`); | ||||||||||||||||||
| return args; | ||||||||||||||||||
| } | ||||||||||||||||||
| // One observed serialization corruption loses exactly the JSON object's opening | ||||||||||||||||||
| // brace; the closed envelope is tight enough to repair back into a call the | ||||||||||||||||||
| // routed model can still see and retry, instead of replaying {} forever. | ||||||||||||||||||
| const repaired = repairJsonObjectEnvelope(rawArgs); | ||||||||||||||||||
| if (repaired === undefined) { | ||||||||||||||||||
| console.warn(`[parser] function_call ${callId} has non-JSON arguments; defaulting to {}`); | ||||||||||||||||||
| console.warn( | ||||||||||||||||||
| `[parser] function_call ${callId} has non-JSON arguments; defaulting to {}. ` + | ||||||||||||||||||
| `len=${rawArgs.length} head=${JSON.stringify(rawArgs.slice(0, 200))} tail=${JSON.stringify(rawArgs.slice(-100))}` | ||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Keep malformed tool arguments out of service logs Every malformed function-call replay that reaches this fallback now prints the first 200 and last 100 characters of raw tool arguments. Tool arguments can contain file content, shell commands and credentials; JSON.stringify escapes syntax but does not redact any of that content. Previously this warning was content-free. Remove the head/tail payload and retain only bounded non-content diagnostics. Add a regression with a synthetic secret/content marker that asserts it never reaches console output. |
||||||||||||||||||
| ); | ||||||||||||||||||
|
Comment on lines
+130
to
+133
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Do not log raw tool arguments. When JSON parsing fails, this warning writes the first 200 and last 100 characters of Proposed change--- "a/src/responses/parser.ts"
+++ "b/src/responses/parser.ts"
@@ -127,10 +127,10 @@
// routed model can still see and retry, instead of replaying {} forever.
const repaired = repairJsonObjectEnvelope(rawArgs);
if (repaired === undefined) {
console.warn(
`[parser] function_call ${callId} has non-JSON arguments; defaulting to {}. ` +
- `len=${rawArgs.length} head=${JSON.stringify(rawArgs.slice(0, 200))} tail=${JSON.stringify(rawArgs.slice(-100))}`
+ `len=${rawArgs.length}`
);
} else {
args = repaired;
console.warn(`[parser] function_call ${callId} arguments lost the JSON opening brace; repaired from history`);📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSources: Path instructions, Learnings |
||||||||||||||||||
| } else { | ||||||||||||||||||
| args = repaired; | ||||||||||||||||||
| console.warn(`[parser] function_call ${callId} arguments lost the JSON opening brace; repaired from history`); | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Distinguish string contents from JSON delimiters during repair
An unterminated string whose final content character is } (for example the truncated prefix {"command":"printf }) enters this branch, is treated as a closed object, then fails JSON.parse and falls back to {}. A string ending in an escaped quote has the same problem in the next branch: endsWith(quote) does not mean that the string closed. Both are instances of the envelope corruption this helper claims to repair and lose otherwise recoverable arguments. Use a bounded string/escape-aware envelope check and add cases for braces inside strings, escaped terminal quotes, valid JSON containing literal tag text, and irreparable inputs.