diff --git a/model/parsers/qwen3coder.go b/model/parsers/qwen3coder.go index dfa604acc..1779a2c67 100644 --- a/model/parsers/qwen3coder.go +++ b/model/parsers/qwen3coder.go @@ -392,7 +392,9 @@ func parseValue(raw string, paramType api.PropertyType) any { } var ( - qwenTagRegex = regexp.MustCompile(`<(\w+)=([^>]+)>`) + qwenTagRegex = regexp.MustCompile(`<(\w+)=([^>\r\n]+)>`) + // [^"] is safe here because xml.EscapeText has already encoded any literal + // newlines in attribute values as before this regex runs. qwenXMLTagRegex = regexp.MustCompile(``) ) diff --git a/model/parsers/qwen3coder_test.go b/model/parsers/qwen3coder_test.go index 7142567ed..9bee7f431 100644 --- a/model/parsers/qwen3coder_test.go +++ b/model/parsers/qwen3coder_test.go @@ -1121,6 +1121,34 @@ func TestQwen3CoderParserToolCallIndexResetOnInit(t *testing.T) { } } +func TestQwen3CoderParserAngleBracketsInParameterValue(t *testing.T) { + tools := []api.Tool{tool("run_code", map[string]api.ToolProperty{ + "source": {Type: api.PropertyType{"string"}}, + })} + parser := Qwen3CoderParser{} + parser.Init(tools, nil, nil) + + // Parameter value contains " closing tag + // and producing "element closed by " from xml.Unmarshal. + input := "\n\n\nIF C<1w=P-SQR(1-C)\nNEXT\n\n\n" + _, _, calls, err := parser.Add(input, true) + if err != nil { + t.Fatalf("parse failed: %v", err) + } + if len(calls) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(calls)) + } + want := "IF C<1w=P-SQR(1-C)\nNEXT" + got, ok := calls[0].Function.Arguments.Get("source") + if !ok { + t.Fatal("missing source argument") + } + if got != want { + t.Errorf("source: got %q, want %q", got, want) + } +} + func TestQwenXMLTransform(t *testing.T) { cases := []struct { desc string @@ -1181,6 +1209,41 @@ celsius `, }, + { + // qwenTagRegex must not match across newlines: a pattern like <1w=expr + // with no ">" on the same line should not greedily consume the real + // and closing tags that appear on later lines. + desc: "angle brackets in parameter values do not corrupt closing tags", + raw: ` + +IF C<1w=P-SQR(1-C) +NEXT + +`, + want: ` + +IF C<1w=P-SQR(1-C) +NEXT + +`, + }, + { + desc: "multiple angle-bracket patterns in same parameter value", + raw: ` + +IF A<1x=FOO(A) +IF B<2y=BAR(B) +NEXT + +`, + want: ` + +IF A<1x=FOO(A) +IF B<2y=BAR(B) +NEXT + +`, + }, } for _, tc := range cases {