vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit fd24c0329ed516cf5301e8fb736ae1ab2cb5bb35
parent 0322a7cb2dc33bbd68242de03be900534d9eeac8
Author: Andrew Laack <andrew@laack.co>
Date:   Tue, 31 Mar 2026 12:57:41 -0700

Merge pull request #187 from baahaus/fix/codex-collab-tool-call

Handle Codex collab_tool_call events
Diffstat:
Mvet/imbue_core/agents/agent_api/codex/data_types.py | 12++++++++++++
Mvet/imbue_core/agents/agent_api/codex/message_parser.py | 26++++++++++++++++++++++++++
Avet/imbue_core/agents/agent_api/codex/message_parser_test.py | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 109 insertions(+), 0 deletions(-)

diff --git a/vet/imbue_core/agents/agent_api/codex/data_types.py b/vet/imbue_core/agents/agent_api/codex/data_types.py @@ -95,6 +95,17 @@ class CodexMcpToolCallItem(SerializableModel): status: McpToolCallStatus +class CodexCollabToolCallItem(SerializableModel): + type: Literal["collab_tool_call"] = "collab_tool_call" + id: str + tool: str + status: McpToolCallStatus + sender_thread_id: str | None = None + receiver_thread_ids: list[str] = Field(default_factory=list) + prompt: str | None = None + agents_states: dict[str, Any] = Field(default_factory=dict) + + class CodexAgentMessageItem(SerializableModel): type: Literal["agent_message"] = "agent_message" id: str @@ -138,6 +149,7 @@ CodexThreadItemUnion = Annotated[ | Annotated[CodexCommandExecutionItem, Tag("command_execution")] | Annotated[CodexFileChangeItem, Tag("file_change")] | Annotated[CodexMcpToolCallItem, Tag("mcp_tool_call")] + | Annotated[CodexCollabToolCallItem, Tag("collab_tool_call")] | Annotated[CodexWebSearchItem, Tag("web_search")] | Annotated[CodexTodoListItem, Tag("todo_list")] | Annotated[CodexErrorItem, Tag("error")] diff --git a/vet/imbue_core/agents/agent_api/codex/message_parser.py b/vet/imbue_core/agents/agent_api/codex/message_parser.py @@ -4,6 +4,7 @@ from typing import assert_never from pydantic import TypeAdapter from vet.imbue_core.agents.agent_api.codex.data_types import CodexAgentMessageItem +from vet.imbue_core.agents.agent_api.codex.data_types import CodexCollabToolCallItem from vet.imbue_core.agents.agent_api.codex.data_types import CodexCommandExecutionItem from vet.imbue_core.agents.agent_api.codex.data_types import CodexErrorItem from vet.imbue_core.agents.agent_api.codex.data_types import CodexFileChangeItem @@ -180,6 +181,31 @@ def parse_codex_item( ) ] + case CodexCollabToolCallItem(): + tool_input = { + "tool": codex_item.tool, + "sender_thread_id": codex_item.sender_thread_id, + "receiver_thread_ids": codex_item.receiver_thread_ids, + "prompt": codex_item.prompt, + "agents_states": codex_item.agents_states, + } + if codex_item.status == "in_progress": + return [ + AgentToolUseBlock( + id=codex_item.id, + name=codex_item.tool, + input=tool_input, + ) + ] + return [ + AgentToolResultBlock( + tool_use_id=codex_item.id, + content=[tool_input], + is_error=codex_item.status == "failed", + exit_code=-1 if codex_item.status == "failed" else 0, + ) + ] + case CodexWebSearchItem(): # NOTE: currently (24-oct-2025) the web search item is not really well defined # i.e. it only has a query field, and no other fields like results, progress, etc. diff --git a/vet/imbue_core/agents/agent_api/codex/message_parser_test.py b/vet/imbue_core/agents/agent_api/codex/message_parser_test.py @@ -0,0 +1,71 @@ +from vet.imbue_core.agents.agent_api.codex.message_parser import parse_codex_event +from vet.imbue_core.agents.agent_api.data_types import AgentAssistantMessage +from vet.imbue_core.agents.agent_api.data_types import AgentToolResultBlock +from vet.imbue_core.agents.agent_api.data_types import AgentToolUseBlock + + +class TestParseCollabToolCall: + def test_item_started_returns_tool_use_block(self) -> None: + data = { + "type": "item.started", + "item": { + "id": "item_38", + "type": "collab_tool_call", + "tool": "spawn_agent", + "sender_thread_id": "thread_parent", + "receiver_thread_ids": [], + "prompt": "Review the diff", + "agents_states": {}, + "status": "in_progress", + }, + } + + message = parse_codex_event(data, "thread_parent") + + assert isinstance(message, AgentAssistantMessage) + assert len(message.content) == 1 + tool_use = message.content[0] + assert isinstance(tool_use, AgentToolUseBlock) + assert tool_use.id == "item_38" + assert tool_use.name == "spawn_agent" + assert tool_use.input == { + "tool": "spawn_agent", + "sender_thread_id": "thread_parent", + "receiver_thread_ids": [], + "prompt": "Review the diff", + "agents_states": {}, + } + + def test_item_completed_returns_tool_result_block(self) -> None: + data = { + "type": "item.completed", + "item": { + "id": "item_38", + "type": "collab_tool_call", + "tool": "spawn_agent", + "sender_thread_id": "thread_parent", + "receiver_thread_ids": ["thread_child"], + "prompt": "Review the diff", + "agents_states": {"thread_child": {"status": "completed"}}, + "status": "completed", + }, + } + + message = parse_codex_event(data, "thread_parent") + + assert isinstance(message, AgentAssistantMessage) + assert len(message.content) == 1 + tool_result = message.content[0] + assert isinstance(tool_result, AgentToolResultBlock) + assert tool_result.tool_use_id == "item_38" + assert tool_result.content == [ + { + "tool": "spawn_agent", + "sender_thread_id": "thread_parent", + "receiver_thread_ids": ["thread_child"], + "prompt": "Review the diff", + "agents_states": {"thread_child": {"status": "completed"}}, + } + ] + assert tool_result.is_error is False + assert tool_result.exit_code == 0