feat(codex): support gpt-5.3-codex commentary rendering (#201)
This commit is contained in:
@@ -347,6 +347,125 @@ async def test_codex_runner_reconnect_notice_updates_phase(tmp_path) -> None:
|
||||
assert isinstance(seen[3], CompletedEvent)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_codex_runner_prefers_final_answer_phase(tmp_path) -> None:
|
||||
thread_id = "019b73c4-0c3f-7701-a0bb-aac6b4d8a3bc"
|
||||
|
||||
codex_path = tmp_path / "codex"
|
||||
codex_path.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
"import json\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.stdin.read()\n"
|
||||
f"print(json.dumps({{'type': 'thread.started', 'thread_id': '{thread_id}'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.started'}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_0', 'type': 'agent_message', 'phase': 'commentary', 'text': 'Working through the task.'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_1', 'type': 'agent_message', 'phase': 'final_answer', 'text': 'Done.'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.completed', 'usage': {'input_tokens': 1, 'cached_input_tokens': 0, 'output_tokens': 1}}), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
codex_path.chmod(0o755)
|
||||
|
||||
runner = CodexRunner(codex_cmd=str(codex_path), extra_args=[])
|
||||
seen = [evt async for evt in runner.run("hi", None)]
|
||||
|
||||
assert len(seen) == 4
|
||||
assert isinstance(seen[0], StartedEvent)
|
||||
assert isinstance(seen[1], ActionEvent)
|
||||
assert seen[1].action.kind == "turn"
|
||||
assert isinstance(seen[2], ActionEvent)
|
||||
assert seen[2].action.kind == "note"
|
||||
assert seen[2].action.title == "Working through the task."
|
||||
assert seen[2].phase == "completed"
|
||||
assert seen[2].ok is True
|
||||
assert isinstance(seen[3], CompletedEvent)
|
||||
assert seen[3].answer == "Done."
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_codex_runner_legacy_agent_message_no_phase(tmp_path) -> None:
|
||||
thread_id = "019b73c4-0c3f-7701-a0bb-aac6b4d8a3bc"
|
||||
|
||||
codex_path = tmp_path / "codex"
|
||||
codex_path.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
"import json\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.stdin.read()\n"
|
||||
f"print(json.dumps({{'type': 'thread.started', 'thread_id': '{thread_id}'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.started'}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_0', 'type': 'agent_message', 'text': 'first'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_1', 'type': 'agent_message', 'text': 'second'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.completed', 'usage': {'input_tokens': 1, 'cached_input_tokens': 0, 'output_tokens': 1}}), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
codex_path.chmod(0o755)
|
||||
|
||||
runner = CodexRunner(codex_cmd=str(codex_path), extra_args=[])
|
||||
seen = [evt async for evt in runner.run("hi", None)]
|
||||
|
||||
completed = next(evt for evt in seen if isinstance(evt, CompletedEvent))
|
||||
assert completed.answer == "second"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_codex_runner_collab_tool_call_does_not_break_stream(tmp_path) -> None:
|
||||
thread_id = "019b73c4-0c3f-7701-a0bb-aac6b4d8a3bc"
|
||||
|
||||
codex_path = tmp_path / "codex"
|
||||
codex_path.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
"import json\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.stdin.read()\n"
|
||||
f"print(json.dumps({{'type': 'thread.started', 'thread_id': '{thread_id}'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.started'}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.started', 'item': {'id': 'item_0', 'type': 'collab_tool_call', 'tool': 'spawn_agent', 'sender_thread_id': 'main', 'receiver_thread_ids': ['worker'], 'prompt': 'check tests', 'agents_states': {}, 'status': 'in_progress'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_0', 'type': 'collab_tool_call', 'tool': 'spawn_agent', 'sender_thread_id': 'main', 'receiver_thread_ids': ['worker'], 'prompt': 'check tests', 'agents_states': {'worker': {'status': 'completed', 'message': 'ok'}}, 'status': 'completed'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_1', 'type': 'agent_message', 'text': 'ok'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.completed', 'usage': {'input_tokens': 1, 'cached_input_tokens': 0, 'output_tokens': 1}}), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
codex_path.chmod(0o755)
|
||||
|
||||
runner = CodexRunner(codex_cmd=str(codex_path), extra_args=[])
|
||||
seen = [evt async for evt in runner.run("hi", None)]
|
||||
|
||||
completed = next(evt for evt in seen if isinstance(evt, CompletedEvent))
|
||||
assert completed.answer == "ok"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_codex_runner_unknown_item_type_does_not_break_stream(tmp_path) -> None:
|
||||
thread_id = "019b73c4-0c3f-7701-a0bb-aac6b4d8a3bc"
|
||||
|
||||
codex_path = tmp_path / "codex"
|
||||
codex_path.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
"import json\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.stdin.read()\n"
|
||||
f"print(json.dumps({{'type': 'thread.started', 'thread_id': '{thread_id}'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.started'}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_0', 'type': 'future_item', 'foo': 'bar'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_1', 'type': 'agent_message', 'text': 'ok'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'turn.completed', 'usage': {'input_tokens': 1, 'cached_input_tokens': 0, 'output_tokens': 1}}), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
codex_path.chmod(0o755)
|
||||
|
||||
runner = CodexRunner(codex_cmd=str(codex_path), extra_args=[])
|
||||
seen = [evt async for evt in runner.run("hi", None)]
|
||||
|
||||
completed = next(evt for evt in seen if isinstance(evt, CompletedEvent))
|
||||
assert completed.ok is True
|
||||
assert completed.answer == "ok"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_codex_runner_includes_stderr_reason(tmp_path) -> None:
|
||||
codex_path = tmp_path / "codex"
|
||||
|
||||
Reference in New Issue
Block a user