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:
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