Fallback: detect bare function calls like "func_name(arg=val)" without tool-call tags. Matches against known action names so we don't false-positive on normal text.
| 912 | // Fallback: detect bare function calls like "func_name(arg=val)" without tool-call tags. |
| 913 | // Matches against known action names so we don't false-positive on normal text. |
| 914 | static std::vector<rastack::ToolCall> try_parse_bare_tool_calls( |
| 915 | RCLIEngine* engine, const std::string& text) |
| 916 | { |
| 917 | std::vector<rastack::ToolCall> calls; |
| 918 | auto all_names = engine->actions.list_actions(); |
| 919 | for (auto& tool_name : engine->pipeline.tools().list_tool_names()) |
| 920 | all_names.push_back(tool_name); |
| 921 | |
| 922 | for (const auto& name : all_names) { |
| 923 | std::string lower_text = text; |
| 924 | for (auto& c : lower_text) c = std::tolower(static_cast<unsigned char>(c)); |
| 925 | std::string lower_name = name; |
| 926 | for (auto& c : lower_name) c = std::tolower(static_cast<unsigned char>(c)); |
| 927 | |
| 928 | size_t pos = lower_text.find(lower_name + "("); |
| 929 | if (pos == std::string::npos) continue; |
| 930 | if (pos > 0 && std::isalnum(static_cast<unsigned char>(text[pos - 1]))) continue; |
| 931 | |
| 932 | size_t paren_open = pos + name.size(); |
| 933 | int depth = 0; |
| 934 | size_t paren_close = std::string::npos; |
| 935 | for (size_t i = paren_open; i < text.size(); i++) { |
| 936 | if (text[i] == '(') depth++; |
| 937 | if (text[i] == ')') { depth--; if (depth == 0) { paren_close = i; break; } } |
| 938 | } |
| 939 | if (paren_close == std::string::npos) continue; |
| 940 | |
| 941 | std::string params_str = text.substr(paren_open + 1, paren_close - paren_open - 1); |
| 942 | |
| 943 | std::string json = "{"; |
| 944 | bool first = true; |
| 945 | size_t p = 0; |
| 946 | while (p < params_str.size()) { |
| 947 | while (p < params_str.size() && (params_str[p] == ' ' || params_str[p] == ',')) p++; |
| 948 | if (p >= params_str.size()) break; |
| 949 | |
| 950 | auto eq = params_str.find('=', p); |
| 951 | if (eq == std::string::npos) break; |
| 952 | std::string key = params_str.substr(p, eq - p); |
| 953 | while (!key.empty() && key.back() == ' ') key.pop_back(); |
| 954 | while (!key.empty() && key.front() == ' ') key.erase(key.begin()); |
| 955 | |
| 956 | p = eq + 1; |
| 957 | while (p < params_str.size() && params_str[p] == ' ') p++; |
| 958 | if (p >= params_str.size()) break; |
| 959 | |
| 960 | std::string val; |
| 961 | if (params_str[p] == '"' || params_str[p] == '\'') { |
| 962 | char q = params_str[p++]; |
| 963 | while (p < params_str.size() && params_str[p] != q) val += params_str[p++]; |
| 964 | if (p < params_str.size()) p++; |
| 965 | } else { |
| 966 | while (p < params_str.size() && params_str[p] != ',' && params_str[p] != ')') |
| 967 | val += params_str[p++]; |
| 968 | while (!val.empty() && val.back() == ' ') val.pop_back(); |
| 969 | } |
| 970 | |
| 971 | if (!first) json += ", "; |
no test coverage detected