@@ -1287,6 +1287,116 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp
12871287 return data;
12881288}
12891289
1290+ // MiroThinker - uses MCP style toolcalling
1291+ static common_chat_params common_chat_params_init_mirothinker (const common_chat_template & tmpl,
1292+ const autoparser::templates_params & inputs) {
1293+ common_chat_params data;
1294+
1295+ data.prompt = common_chat_template_direct_apply (tmpl, inputs);
1296+ data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
1297+ data.supports_thinking = true ;
1298+ data.thinking_start_tag = " <think>" ;
1299+ data.thinking_end_tag = " </think>" ;
1300+ data.preserved_tokens = {
1301+ " <think>" ,
1302+ " </think>" ,
1303+ };
1304+
1305+ auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
1306+ auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
1307+ auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE;
1308+
1309+ auto parser = build_chat_peg_parser ([&](common_chat_peg_builder & p) {
1310+ // MiroThinker Thinking format:
1311+ // - Reasoning: <think>{reasoning}</think>
1312+ // - Content: text after reasoning
1313+ // - Tool calls section:
1314+ // <use_mcp_tool>
1315+ // <server_name>{server_name}</server_name>
1316+ // <tool_name>{tool_name}</tool_name>
1317+ // <arguments>
1318+ // {json_args}
1319+ // </arguments>
1320+ // ...
1321+ // </use_mcp_tool>
1322+
1323+ auto reasoning = extract_reasoning ? p.optional (" <think>" + p.reasoning (p.until (" </think>" )) + " </think>" ) : p.eps ();
1324+
1325+ // Tool call markers
1326+ const std::string SECTION_BEGIN = " <use_mcp_tool>" ;
1327+ const std::string SECTION_END = " </use_mcp_tool>" ;
1328+ const std::string CALL_BEGIN = " <server_name>" ;
1329+ const std::string ARGS_BEGIN = " <arguments>" ;
1330+ const std::string CALL_END = " </arguments>" ;
1331+
1332+ auto end = p.end ();
1333+
1334+ // Content only parser (no tools)
1335+ if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
1336+ return reasoning + p.content (p.rest ()) + end;
1337+ }
1338+
1339+ // Build tool call parsers for each available function
1340+ // Function name format is: <tool_name>{tool_name}</tool_name>
1341+ // We need to match: {what_ever}</server_name>{spaces}<tool_name>{tool_name}</tool_name>
1342+ auto tool_choice = p.choice ();
1343+ foreach_function (inputs.tools , [&](const json & tool) {
1344+ const auto & function = tool.at (" function" );
1345+ std::string name = function.at (" name" );
1346+ const auto & schema = function.at (" parameters" );
1347+
1348+ // Match: {what_ever}</server_name>{spaces}<tool_name>{tool_name}</tool_name>
1349+ auto tool_parser = p.tool (
1350+ p.tool_open (
1351+ p.until (" </server_name>" ) +
1352+ p.literal (" </server_name>" ) +
1353+ p.space () +
1354+ p.literal (" <tool_name>" ) +
1355+ p.tool_name (p.literal (name)) +
1356+ p.literal (ARGS_BEGIN)
1357+ ) + p.space () +
1358+ p.tool_args (p.schema (p.json (), " tool-" + name + " -schema" , schema)) +
1359+ p.space () + p.tool_close (p.literal (CALL_END))
1360+ );
1361+
1362+ tool_choice |= p.rule (" tool-" + name, tool_parser);
1363+ });
1364+
1365+ // Tool calls section: <use_mcp_tool> tool_calls </use_mcp_tool>
1366+ auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0 ;
1367+ auto max_calls = inputs.parallel_tool_calls ? -1 : 1 ;
1368+ auto tool_calls = p.trigger_rule (" tool-calls" ,
1369+ p.literal (SECTION_BEGIN) + p.space () +
1370+ p.rule (" tool-call" , p.repeat (CALL_BEGIN + tool_choice, min_calls, max_calls) +
1371+ p.space () + p.literal (SECTION_END))
1372+ );
1373+
1374+ auto content_before_tools = p.content (p.until (SECTION_BEGIN));
1375+
1376+ return reasoning + content_before_tools + tool_calls + end;
1377+ });
1378+
1379+ data.parser = parser.save ();
1380+
1381+ if (include_grammar) {
1382+ data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
1383+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1384+ foreach_function (inputs.tools , [&](const json & tool) {
1385+ const auto & function = tool.at (" function" );
1386+ auto schema = function.at (" parameters" );
1387+ builder.resolve_refs (schema);
1388+ });
1389+ parser.build_grammar (builder, data.grammar_lazy );
1390+ });
1391+
1392+ data.grammar_triggers = {
1393+ { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <use_mcp_tool>" }
1394+ };
1395+ }
1396+
1397+ return data;
1398+ }
1399+
12901400// LFM2 format:
12911401// - Reasoning: <think>{reasoning}</think> (optional, only if enable_thinking is true)
12921402// - Content: text after reasoning (optional)
@@ -1526,6 +1636,14 @@ static common_chat_params common_chat_templates_apply_jinja(const struct common_
15261636 return common_chat_params_init_kimi_k2 (tmpl, params);
15271637 }
15281638
1639+ // MiroThinker - uses MCP style toolcalling <use_mcp_tool> ... </use_mcp_tool>
1640+ // Detection: template has "</use_mcp_tool>" and "</server_name>"
1641+ if (src.find (" </use_mcp_tool>" ) != std::string::npos &&
1642+ src.find (" </server_name>" ) != std::string::npos) {
1643+ LOG_DBG (" Using specialized template: MiroThinker\n " );
1644+ return common_chat_params_init_mirothinker (tmpl, params);
1645+ }
1646+
15291647 // LFM2 - uses <|tool_list_start|>/<|tool_list_end|> markers and <|tool_call_start|>[name(args)]<|tool_call_end|> format
15301648 // Detection: template has "<|tool_list_start|>" and "<|tool_list_end|>" markers
15311649 if (src.find (" <|tool_list_start|>" ) != std::string::npos &&
0 commit comments