From a51a4cb0964c0cbe5e4ac4a6998dbc3811917b0d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 8 Aug 2026 04:28:54 -0700 Subject: [PATCH] fix(api-server): mark replayed tool calls completed in Responses output items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The non-streaming /v1/responses path built function_call and function_call_output output items with no status field (and no item id), while the SSE streaming path correctly emits status in_progress -> completed. Spec-strict OpenAI clients reading the non-streaming output array could interpret the status-less function_call items as pending calls the CLIENT must execute — but these tools were already executed server-side by the Hermes agent and are replayed for structured tool UI only. Reported by a community user whose GPT-5.6 client concluded 'a server should not tell an OpenAI client to execute a tool the server already executed itself'. - _extract_output_items now stamps status: completed and spec-shaped item ids (fc_/fco_) on replayed items, matching the streaming path - test updated to pin status + id shape - docs example updated + explicit note that output tool calls are replayed, never pending --- gateway/platforms/api_server.py | 9 +++++++++ tests/gateway/test_api_server.py | 6 ++++++ website/docs/user-guide/features/api-server.md | 6 ++++-- 3 files changed, 19 insertions(+), 2 deletions(-) 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