Skip to content

Commit ede1a1c

Browse files
committed
fix: improve XML parameter extraction to handle malformed tags from Qwen3-Coder and other models
- Add support for XML tags with spaces in various positions - Handle malformed opening tags like '< tool>' and '<tool >' - Handle malformed closing tags like '</ param>' and '< /param>' - Check for multiple malformed tag patterns during parsing This fixes the issue where certain models (particularly Qwen3-Coder) generate slightly malformed XML tags that were not being parsed correctly, resulting in 'missing parameter' errors. Fixes #7406
1 parent 8684877 commit ede1a1c

File tree

1 file changed

+50
-5
lines changed

1 file changed

+50
-5
lines changed

src/core/assistant-message/AssistantMessageParser.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,32 @@ export class AssistantMessageParser {
8585
this.currentParamName = undefined
8686
continue
8787
} else {
88+
// Check for malformed closing tags (e.g., missing slash or spaces)
89+
// Some models might generate </path > or < /path> instead of </path>
90+
const malformedClosingPatterns = [
91+
`</ ${this.currentParamName}>`, // space after slash
92+
`< /${this.currentParamName}>`, // space before slash
93+
`</${this.currentParamName} >`, // space before closing bracket
94+
`< / ${this.currentParamName} >`, // spaces everywhere
95+
]
96+
97+
for (const pattern of malformedClosingPatterns) {
98+
if (currentParamValue.endsWith(pattern)) {
99+
// Found a malformed closing tag, treat it as valid
100+
const paramValue = currentParamValue.slice(0, -pattern.length)
101+
this.currentToolUse.params[this.currentParamName] =
102+
this.currentParamName === "content"
103+
? paramValue.replace(/^\n/, "").replace(/\n$/, "")
104+
: paramValue.trim()
105+
this.currentParamName = undefined
106+
break
107+
}
108+
}
109+
110+
if (this.currentParamName === undefined) {
111+
continue
112+
}
113+
88114
// Partial param value is accumulating.
89115
// Write the currently accumulated param content in real time
90116
this.currentToolUse.params[this.currentParamName] = currentParamValue
@@ -104,11 +130,22 @@ export class AssistantMessageParser {
104130
this.currentToolUse = undefined
105131
continue
106132
} else {
133+
// Check for parameter opening tags with various formats
107134
const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`)
108-
for (const paramOpeningTag of possibleParamOpeningTags) {
135+
// Also check for malformed opening tags
136+
const malformedOpeningTags = toolParamNames.flatMap((name) => [
137+
`< ${name}>`, // space after opening bracket
138+
`<${name} >`, // space before closing bracket
139+
`< ${name} >`, // spaces on both sides
140+
])
141+
142+
const allPossibleTags = [...possibleParamOpeningTags, ...malformedOpeningTags]
143+
144+
for (const paramOpeningTag of allPossibleTags) {
109145
if (this.accumulator.endsWith(paramOpeningTag)) {
110146
// Start of a new parameter.
111-
const paramName = paramOpeningTag.slice(1, -1)
147+
// Extract the parameter name, handling spaces
148+
const paramName = paramOpeningTag.replace(/[<>\s]/g, '')
112149
if (!toolParamNames.includes(paramName as ToolParamName)) {
113150
// Handle invalid parameter name gracefully
114151
continue
@@ -156,11 +193,19 @@ export class AssistantMessageParser {
156193

157194
let didStartToolUse = false
158195
const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`)
196+
// Also check for malformed tool opening tags
197+
const malformedToolOpeningTags = toolNames.flatMap((name) => [
198+
`< ${name}>`, // space after opening bracket
199+
`<${name} >`, // space before closing bracket
200+
`< ${name} >`, // spaces on both sides
201+
])
202+
203+
const allPossibleToolTags = [...possibleToolUseOpeningTags, ...malformedToolOpeningTags]
159204

160-
for (const toolUseOpeningTag of possibleToolUseOpeningTags) {
205+
for (const toolUseOpeningTag of allPossibleToolTags) {
161206
if (this.accumulator.endsWith(toolUseOpeningTag)) {
162-
// Extract and validate the tool name
163-
const extractedToolName = toolUseOpeningTag.slice(1, -1)
207+
// Extract and validate the tool name, handling spaces
208+
const extractedToolName = toolUseOpeningTag.replace(/[<>\s]/g, '')
164209

165210
// Check if the extracted tool name is valid
166211
if (!toolNames.includes(extractedToolName as ToolName)) {

0 commit comments

Comments
 (0)