auto arg_name_parser = literal(prop_name);
common_peg_parser arg_value_parser = eps();
- auto string_value_parser = choice({
- literal("\"") + tool_arg_string_value(string_content('"')) + literal("\""),
- literal("'") + tool_arg_string_value(string_content('\'')) + literal("'")
- });
+ // Quoted literal as a value: normalize_quotes_to_json preserves escapes.
+ auto string_value_parser = tool_arg_value(choice({
+ literal("\"") + string_content('"') + literal("\""),
+ literal("'") + string_content('\'') + literal("'")
+ }));
if (is_string_type) {
arg_value_parser = string_value_parser;
.expect(simple_assist_msg("Use this format: [link text](url). Example: [Wikipedia](https://www.wikipedia.org)."))
.run();
- // Python tool with multiline code in string
+ // Python tool with multiline code in string: the \n in the literal decodes to a real
+ // newline, emitted as a JSON \n escape (not a doubled backslash).
tst.test("<|tool_call_start|>[python(code=\"def hello():\\n print('hey')\")]<|tool_call_end|>")
.tools({ python_tool })
.expect_tool_calls({
- { "python", R"#({"code": "def hello():\\n print('hey')"})#", "" }
+ { "python", R"#({"code": "def hello():\n print('hey')"})#", "" }
+ })
+ .run();
+
+ // String escape sequences decode to their actual characters (newline + tab here),
+ // so a "write a two line file" style call produces real line breaks, not literal "\n".
+ tst.test("<|tool_call_start|>[python(code=\"First line\\nSecond line\\tindented\")]<|tool_call_end|>")
+ .tools({ python_tool })
+ .expect_tool_calls({
+ { "python", R"#({"code": "First line\nSecond line\tindented"})#", "" }
+ })
+ .run();
+
+ // Escaped quotes inside a string argument survive the round-trip.
+ tst.test("<|tool_call_start|>[python(code=\"print(\\\"hi\\\")\")]<|tool_call_end|>")
+ .tools({ python_tool })
+ .expect_tool_calls({
+ { "python", R"#({"code": "print(\"hi\")"})#", "" }
})
.run();