# Todo 43 Typed Provider Tool Calls Plan **Status:** pending ## Goal Preserve native provider tool calls as a distinct stream item so direct ChatAgent tools can retain provider-generated IDs and arguments without changing the existing hidden text-event protocol. ## Scope - Keep text protocol output as name-only events for dual-agent mode. - Represent provider-native tool calls with a separate stream kind. - Preserve `id`, `name`, parsed arguments, and raw arguments from the provider. - Flush buffered provider tool calls when a compatible provider ends with `[DONE]` but omits the expected tool-call finish reason. - Do not change Runtime tool routing or provider transcript serialization in this todo. ## Files - Modify: `src/agent_lab/domain/messages.py` - Modify: `src/agent_lab/infrastructure/openai_compatible.py` - Modify: `tests/test_openai_stream_parser.py` - Modify: `tests/test_debug_runtime.py` - Modify: `docs/plans/todos.md` ## Plan ### Plan 1. Add failing parser tests for distinct event sources. 2. Verify the tests fail because provider calls and text events currently share `kind="event"`. 3. Add explicit `StreamItem.text_event(...)` and `StreamItem.provider_tool_call(...)` constructors while keeping the event payload as `ToolCallEvent`. 4. Update the text-event parser to emit `text_event` and the provider tool-call accumulator to emit `provider_tool_call`. 5. Make parser flush drain buffered provider calls exactly once when no finish reason was received. 6. Update existing Runtime regression fixtures to accept only `text_event` in the current dual-agent path; direct routing remains out of scope. ### Act Follow TDD in this order: - Add `test_parser_distinguishes_text_events_from_provider_tool_calls` asserting: - visible text remains `message_delta`; - `mock_search` produces `kind="text_event"`; - provider `delta.tool_calls` produces `kind="provider_tool_call"` with intact arguments. - Add `test_parser_flushes_provider_tool_calls_without_finish_reason` using fragmented name/argument chunks followed by `flush()` and asserting one call is emitted. - Add `test_parser_does_not_emit_provider_tool_call_twice_after_finish_and_flush` to protect normal providers. - Run `uv run pytest tests/test_openai_stream_parser.py -q` and confirm the new tests fail for the missing typed kinds/flush behavior. - Implement the minimum domain/parser changes. - Run the focused parser tests until green. - Run `uv run pytest tests/test_debug_runtime.py tests/test_event_agent.py tests/test_openai_stream_parser.py -q` to verify the existing dual-agent path still passes. ### Observe Confirm from test output and inspection that: - text events remain name-only; - provider calls retain raw and parsed arguments; - multiple provider calls preserve provider order/index; - flush emits only buffered calls that were not already drained; - no Runtime direct-tools behavior was added early. ### Update - Mark only todo 43 as `done` after focused and regression tests pass. - If review finds a defect after the commit, add a new `43.1` row directly below todo 43 instead of modifying any completed historical todo row. - Commit with a message explaining that the previous shared event kind discarded the distinction required for direct tools; mention compatibility risk if any provider-specific flush behavior remains. ## Verification - `uv run pytest tests/test_openai_stream_parser.py -q` - `uv run pytest tests/test_debug_runtime.py tests/test_event_agent.py tests/test_openai_stream_parser.py -q` - `git diff --check`