feat(api): structured run events via /v1/runs SSE endpoint

Add POST /v1/runs to start async agent runs and GET /v1/runs/{run_id}/events
for SSE streaming of typed lifecycle events (tool.started, tool.completed,
message.delta, reasoning.available, run.completed, run.failed).

Changes the internal tool_progress_callback signature from positional
(tool_name, preview, args) to event-type-first
(event_type, tool_name, preview, args, **kwargs). Existing consumers
filter on event_type and remain backward-compatible.

Adds concurrency limit (_MAX_CONCURRENT_RUNS=10) and orphaned run sweep.

Fixes logic inversion in cli.py _on_tool_progress where the original PR
would have displayed internal tools instead of non-internal ones.

Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
This commit is contained in:
Mibayy
2026-04-05 11:52:46 -07:00
committed by Teknium
parent e167ad8f61
commit cc2b56b26a
11 changed files with 337 additions and 44 deletions

View File

@@ -96,7 +96,7 @@ class TestBuildChildProgressCallback:
cb = _build_child_progress_callback(0, parent)
assert cb is not None
cb("web_search", "quantum computing")
cb("tool.started", "web_search", "quantum computing", {})
output = buf.getvalue()
assert "web_search" in output
assert "quantum computing" in output
@@ -131,11 +131,11 @@ class TestBuildChildProgressCallback:
# Send 4 tool calls — shouldn't flush yet (BATCH_SIZE = 5)
for i in range(4):
cb(f"tool_{i}", f"arg_{i}")
cb("tool.started", f"tool_{i}", f"arg_{i}", {})
parent_cb.assert_not_called()
# 5th call should trigger flush
cb("tool_4", "arg_4")
cb("tool.started", "tool_4", "arg_4", {})
parent_cb.assert_called_once()
call_args = parent_cb.call_args
assert "tool_0" in call_args[0][1]
@@ -207,7 +207,7 @@ class TestBuildChildProgressCallback:
parent.tool_progress_callback = None
cb = _build_child_progress_callback(0, parent, task_count=1)
cb("web_search", "test")
cb("tool.started", "web_search", "test", {})
output = buf.getvalue()
assert "[" not in output
@@ -330,9 +330,9 @@ class TestBatchFlush:
cb = _build_child_progress_callback(0, parent)
# Send 3 tools (below batch size of 5)
cb("web_search", "query1")
cb("read_file", "file.txt")
cb("write_file", "out.txt")
cb("tool.started", "web_search", "query1", {})
cb("tool.started", "read_file", "file.txt", {})
cb("tool.started", "write_file", "out.txt", {})
parent_cb.assert_not_called()
# Flush should send the remaining 3
@@ -365,7 +365,7 @@ class TestBatchFlush:
parent.tool_progress_callback = None
cb = _build_child_progress_callback(0, parent)
cb("web_search", "test")
cb("tool.started", "web_search", "test", {})
cb._flush() # Should not crash