diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 60999347f0349..9784e7f1b7d9b 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -5981,14 +5981,23 @@ class APIServerAdapter(BasePlatformAdapter): for tc in msg["tool_calls"]: func = tc.get("function", {}) items.append({ + "id": f"fc_{uuid.uuid4().hex[:24]}", "type": "function_call", + # These calls were already executed server-side by the + # Hermes agent; they are replayed for structured tool + # UI only. Mark them completed (matching the SSE + # streaming path) so OpenAI clients don't interpret + # them as pending calls the client must execute. + "status": "completed", "name": func.get("name", ""), "arguments": func.get("arguments", ""), "call_id": tc.get("id", ""), }) elif role == "tool": items.append({ + "id": f"fco_{uuid.uuid4().hex[:24]}", "type": "function_call_output", + "status": "completed", "call_id": msg.get("tool_call_id", ""), "output": msg.get("content", ""), }) diff --git a/tests/gateway/test_api_server.py b/tests/gateway/test_api_server.py index 5a17047914db0..eada62c7a505c 100644 --- a/tests/gateway/test_api_server.py +++ b/tests/gateway/test_api_server.py @@ -2033,9 +2033,15 @@ class TestToolCallsInOutput: assert output[0]["name"] == "calculator" assert output[0]["arguments"] == '{"expression": "6*7"}' assert output[0]["call_id"] == "call_abc123" + # Replayed server-executed calls must be marked completed so + # OpenAI clients don't treat them as pending calls to execute. + assert output[0]["status"] == "completed" + assert output[0]["id"].startswith("fc_") assert output[1]["type"] == "function_call_output" assert output[1]["call_id"] == "call_abc123" assert output[1]["output"] == "42" + assert output[1]["status"] == "completed" + assert output[1]["id"].startswith("fco_") assert output[2]["type"] == "message" assert output[2]["content"][0]["text"] == "The result is 42." diff --git a/website/docs/user-guide/features/api-server.md b/website/docs/user-guide/features/api-server.md index cca3d11b5cf57..ccba76e104d1c 100644 --- a/website/docs/user-guide/features/api-server.md +++ b/website/docs/user-guide/features/api-server.md @@ -134,14 +134,16 @@ OpenAI Responses API format. Supports server-side conversation state via `previo "status": "completed", "model": "hermes-agent", "output": [ - {"type": "function_call", "name": "terminal", "arguments": "{\"command\": \"ls\"}", "call_id": "call_1"}, - {"type": "function_call_output", "call_id": "call_1", "output": "README.md src/ tests/"}, + {"type": "function_call", "status": "completed", "name": "terminal", "arguments": "{\"command\": \"ls\"}", "call_id": "call_1"}, + {"type": "function_call_output", "status": "completed", "call_id": "call_1", "output": "README.md src/ tests/"}, {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Your project has..."}]} ], "usage": {"input_tokens": 50, "output_tokens": 200, "total_tokens": 250} } ``` +Tool calls in the `output` array were already executed server-side by the Hermes agent — they are replayed with `"status": "completed"` for structured tool UI, never as pending calls for the client to execute. + **Inline image input:** `input[].content` can contain `input_text` and `input_image` parts. Both remote URLs and `data:image/...` URLs are supported: ```json