Classify the current agentic step from the request body. Returns (step_type, tool_names) where step_type is one of: - "tool-result-followup": last message is a tool result - "tool-selection": tools available, last message is from user - "general": no agentic signals tool_
(body: dict)
| 1019 | |
| 1020 | |
| 1021 | def _classify_step(body: dict) -> tuple[str, list[str]]: |
| 1022 | """Classify the current agentic step from the request body. |
| 1023 | |
| 1024 | Returns (step_type, tool_names) where step_type is one of: |
| 1025 | - "tool-result-followup": last message is a tool result |
| 1026 | - "tool-selection": tools available, last message is from user |
| 1027 | - "general": no agentic signals |
| 1028 | |
| 1029 | tool_names: function names from the tools array (for hash differentiation). |
| 1030 | |
| 1031 | Checks both ``tools`` (standard OpenAI) and ``customTools`` (OpenClaw's |
| 1032 | internal format when ``compat.openaiCompletionsTools`` is not enabled). |
| 1033 | """ |
| 1034 | messages = body.get("messages", []) |
| 1035 | raw_tools: list[dict[str, Any]] = body.get("tools") or body.get("customTools") or [] |
| 1036 | has_tools = bool(raw_tools) |
| 1037 | |
| 1038 | tool_names: list[str] = [] |
| 1039 | for t in raw_tools: |
| 1040 | fn = t.get("function") or t.get("definition") or {} |
| 1041 | name = fn.get("name") or t.get("name") or "" |
| 1042 | if name: |
| 1043 | tool_names.append(name) |
| 1044 | |
| 1045 | last_message: dict[str, Any] | None = None |
| 1046 | for msg in reversed(messages): |
| 1047 | if msg.get("role") != "system": |
| 1048 | last_message = msg |
| 1049 | break |
| 1050 | |
| 1051 | last_role = last_message.get("role", "") if isinstance(last_message, dict) else "" |
| 1052 | last_content = last_message.get("content") if isinstance(last_message, dict) else None |
| 1053 | |
| 1054 | if last_role == "tool": |
| 1055 | return "tool-result-followup", tool_names |
| 1056 | |
| 1057 | if isinstance(last_content, list) and any( |
| 1058 | isinstance(block, dict) and block.get("type") == "tool_result" |
| 1059 | for block in last_content |
| 1060 | ): |
| 1061 | return "tool-result-followup", tool_names |
| 1062 | |
| 1063 | if has_tools and last_role == "user": |
| 1064 | return "tool-selection", tool_names |
| 1065 | |
| 1066 | return "general", tool_names |
| 1067 | |
| 1068 | |
| 1069 | def _has_vision_content(value: Any) -> bool: |