fix(api): forward subagent lifecycle on run stream

This commit is contained in:
LeonSGP43 2026-06-24 10:58:30 +08:00 committed by Teknium
parent a8c9ad0bcc
commit 4fbb86d2b8
2 changed files with 88 additions and 1 deletions

View file

@ -5975,7 +5975,43 @@ class APIServerAdapter(BasePlatformAdapter):
"timestamp": ts,
"text": preview or "",
})
# _thinking and subagent_progress are intentionally not forwarded
elif event_type in {"subagent.start", "subagent.complete"}:
event = {
"event": event_type,
"run_id": run_id,
"timestamp": ts,
}
if preview is not None:
event["preview"] = preview
for key in (
"goal",
"task_count",
"task_index",
"subagent_id",
"parent_id",
"depth",
"model",
"tool_count",
"status",
"summary",
"duration_seconds",
"input_tokens",
"output_tokens",
"reasoning_tokens",
"api_calls",
"cost_usd",
"files_read",
"files_written",
"output_tail",
):
value = kwargs.get(key)
if value is not None:
event[key] = value
_push(event)
# _thinking, subagent.tool, and subagent_progress are intentionally
# not forwarded on the /v1/runs stream: they are high-volume UI
# noise. Lifecycle boundaries (start/complete) still need to land
# so clients can observe delegate_task timeouts and failures.
return _callback

View file

@ -891,6 +891,57 @@ class TestAgentExecution:
assert captured["service_tier"] == "priority"
class TestRunEventCallback:
@pytest.mark.asyncio
async def test_forwards_subagent_lifecycle_events(self, adapter):
run_id = "run_subagent_events"
loop = asyncio.get_running_loop()
queue = asyncio.Queue()
adapter._run_streams[run_id] = queue
adapter._run_statuses.pop(run_id, None)
callback = adapter._make_run_event_callback(run_id, loop)
callback(
"subagent.start",
preview="research candidate issue",
goal="research candidate issue",
task_index=0,
task_count=1,
subagent_id="deleg_123",
)
callback(
"subagent.complete",
preview="Timed out after 300s",
goal="research candidate issue",
task_index=0,
task_count=1,
subagent_id="deleg_123",
status="timeout",
duration_seconds=300.0,
summary="Subagent timed out after 300s with 2 API call(s) completed.",
api_calls=2,
)
start_event = await asyncio.wait_for(queue.get(), timeout=1.0)
complete_event = await asyncio.wait_for(queue.get(), timeout=1.0)
assert start_event["event"] == "subagent.start"
assert start_event["preview"] == "research candidate issue"
assert start_event["task_index"] == 0
assert start_event["task_count"] == 1
assert start_event["subagent_id"] == "deleg_123"
assert complete_event["event"] == "subagent.complete"
assert complete_event["preview"] == "Timed out after 300s"
assert complete_event["status"] == "timeout"
assert complete_event["duration_seconds"] == 300.0
assert complete_event["api_calls"] == 2
assert "timed out after 300s" in complete_event["summary"]
assert adapter._run_statuses[run_id]["last_event"] == "subagent.complete"
# ---------------------------------------------------------------------------
# /health endpoint
# ---------------------------------------------------------------------------