XMLFormatParser coerces numeric XML tag values, silently dropping action inputs
Description
XMLFormatParser — the format parser that handles the <seed:tool_call> / <answer> XML output style — builds its
fast-xml-parser instance with only ignoreAttributes: false:
// multimodal/gui-agent/action-parser/src/FomatParsers.ts:37-41
const builder = new XMLBuilder();
const parser = new XMLParser({
ignoreAttributes: false,
});
const object = parser.parse(text);so fast-xml-parser's default parseTagValue: true still applies, and any tag whose text happens to look like a
number comes back as a JS number instead of a string.
Everything downstream is string-typed, so a numeric tag value is either dropped without a trace or reaches a
.trim() / .replace() call on a number:
FomatParsers.ts:63-70—content: value as stringpushes the raw value straight intofinished.inputs. The cast is unsound; this is where the number enters the typed layer.ActionParserHelper.standardizeActionInputsFromXMLObject()(ActionParserHelper.ts:536-571) declaresRecord<string, string>and branches only ontypeof value === 'string'andvalue && typeof value === 'object', so a numeric parameter tag matches neither and is discarded silently.ActionParserHelper.standardizeAction()(ActionParserHelper.ts:313and:335,roughInputStrValue.trim()) andparseCoordinates()(:379-380,params.trim()) both declarestringparameters.serializeAction()(multimodal/gui-agent/shared/src/utils/actions.ts:14-44) has no numeric branch forfinished, so the action serializes asfinished(unsupported).multimodal/gui-agent/agent-sdk/src/ToolCallEngine.ts:163,167,185-187stores the value intofinishMessage: string | nulland then callscontent.replace(...).
Reproduction
Measured on main (c2ad42e3) through the public entry point new DefaultActionParser().parsePrediction(...),
with @gui-agent/[email protected] and [email protected] as pinned by multimodal/pnpm-lock.yaml.
Input 1:
<answer>42</answer>actions: {"type":"finished","inputs":{"content":42}}
rawActionStrings: ["finished(unsupported)"]
ToolCallEngine.ts:187 -> TypeError: content.replace is not a functionInput 2:
<answer>007</answer>actions: {"type":"finished","inputs":{"content":7}} # leading zeros lost
rawActionStrings: ["finished(unsupported)"]
ToolCallEngine.ts:187 -> TypeError: content.replace is not a functionInput 3:
<seed:tool_call>
<function=type>
<parameter=content>123456</parameter>
</function>
</seed:tool_call>actions: {"type":"type","inputs":{}} # the text to type is gone
rawActionStrings: ["type()"]Input 4:
<think>42</think>
<answer>done</answer>reasoningContent: "" # XMLBuilder().build(42) === "" (FomatParsers.ts:59),
# normalized to undefined at DefaultActionParser.ts:96Expected
A model that answers with a bare number, or emits a numeric-looking parameter such as a verification code, should keep its payload:
actions: {"type":"finished","inputs":{"content":"42"}}
rawActionStrings: ["finished(content='42')"]Root cause / proposed fix
This is a single construction site. Setting parseTagValue: false on FomatParsers.ts:38-40 restores the string
contract that the rest of the pipeline already declares, and no conversion is needed anywhere downstream —
parseCoordinates() already runs its own Number.parseFloat on the string it receives.
Nothing pins the current coercing behaviour. Searching the whole multimodal/ tree for an XML tag whose text is a bare
number (an answer, a parameter=content, a duration, or anything else) matches no existing fixture or test, and none
of the 141 currently open PRs modifies anything under multimodal/gui-agent/action-parser/.
Environment
- Package:
@gui-agent/[email protected](multimodal/gui-agent/action-parser) - Branch:
mainatc2ad42e3 - Node 24, pnpm workspace,
[email protected]
Source: bytedance/UI-TARS-desktop