fix: handle legacy item types in exec render
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
{"type":"error","message":"Failed to load optional config file ~/.codex/local.toml (ENOENT); continuing with defaults","code":"CONFIG_NOT_FOUND","fatal":false}
|
||||||
|
{"type":"thread.started","thread_id":"thread_01JHEM1P9M8Z7Y2YQJ4G6N2C3D","cli_version":"0.56.0","model":"gpt-5-codex","sandbox_mode":"workspace-write","cwd":"/home/user/project"}
|
||||||
|
{"type":"turn.started","turn_id":"turn_01JHEM1P9M8Z7Y2YQJ4G6N2C3E"}
|
||||||
|
{"type":"item.started","item":{"id":"item_0001","type":"todo_list","items":[{"text":"Inspect repo structure","completed":false},{"text":"Run tests","completed":false},{"text":"Fix failing tests","completed":false},{"text":"Summarize changes","completed":false}]}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0001","type":"todo_list","items":[{"text":"Inspect repo structure","completed":true},{"text":"Run tests","completed":false},{"text":"Fix failing tests","completed":false},{"text":"Summarize changes","completed":false}]}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0001","type":"todo_list","items":[{"text":"Inspect repo structure","completed":true},{"text":"Run tests","completed":true},{"text":"Fix failing tests","completed":true},{"text":"Summarize changes","completed":false}]}}
|
||||||
|
{"type":"item.started","item":{"id":"item_0002","type":"web_search","query":"python jsonlines parser handle unknown fields"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0002","type":"web_search","query":"python jsonlines parser handle unknown fields"}}
|
||||||
|
{"type":"error","message":"Web search disabled by policy; returned cached results only","code":"WEB_SEARCH_POLICY","fatal":false}
|
||||||
|
{"type":"item.started","item":{"id":"item_0003","type":"mcp_tool_call","server":"github","tool":"search_issues","status":"in_progress"}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0003","type":"mcp_tool_call","server":"github","tool":"search_issues","status":"completed"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0003","type":"mcp_tool_call","server":"github","tool":"search_issues","status":"completed"}}
|
||||||
|
{"type":"item.started","item":{"id":"item_0004","type":"command_execution","command":"pytest -q","aggregated_output":"","exit_code":null,"status":"in_progress"}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0004","type":"command_execution","command":"pytest -q","aggregated_output":"....F\n","exit_code":null,"status":"in_progress"}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0004","type":"command_execution","command":"pytest -q","aggregated_output":"....F....\nFAILURES\n_________________________________ test_beta __________________________________\nE AssertionError: expected 42, got 0\n","exit_code":null,"status":"in_progress"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0004","type":"command_execution","command":"pytest -q","aggregated_output":"....F....\n\nFAILURES\n_________________________________ test_beta __________________________________\nE AssertionError: expected 42, got 0\n\n=========================== short test summary info ===========================\nFAILED tests/test_beta.py::test_beta - AssertionError: expected 42, got 0\n1 failed, 11 passed in 0.98s\n","exit_code":1,"status":"failed"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0005","type":"file_change","changes":[{"path":"src/compute_answer.py","kind":"update"},{"path":"tests/test_beta.py","kind":"update"}],"status":"completed"}}
|
||||||
|
{"type":"item.started","item":{"id":"item_0006","type":"command_execution","command":"pytest -q","aggregated_output":"","status":"in_progress","exit_code":null}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0006","type":"command_execution","command":"pytest -q","aggregated_output":"............\n","status":"in_progress"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0006","type":"command_execution","command":"pytest -q","aggregated_output":"............\n12 passed in 1.23s\n","status":"completed","exit_code":0}}
|
||||||
|
{"type":"item.started","item":{"id":"item_0007","type":"reasoning","text":"Root cause: compute_answer() returned 0. Updated logic to return 42 for the valid input path. Re-ran pytest to confirm all tests pass."}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0007","type":"reasoning","text":"Root cause: compute_answer() returned 0. Updated logic to return 42 for the valid input path. Re-ran pytest to confirm all tests pass."}}
|
||||||
|
{"type":"item.started","item":{"id":"item_0008","type":"agent_message","text":"I found the failing assertion in "}}
|
||||||
|
{"type":"item.updated","item":{"id":"item_0008","type":"agent_message","text":"I found the failing assertion in tests/test_beta.py and updated src/compute_answer.py to return the expected value."}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0008","type":"agent_message","text":"I found the failing assertion in tests/test_beta.py and updated src/compute_answer.py to return the expected value (42). After the change, `pytest -q` reports 12 passed."}}
|
||||||
|
{"type":"turn.completed","usage":{"input_tokens":1840,"cached_input_tokens":256,"output_tokens":732},"latency_ms":8421}
|
||||||
|
{"type":"turn.started"}
|
||||||
|
{"type":"item.started","item":{"id":"item_0009","type":"command_execution","command":"npm test","aggregated_output":"","exit_code":null,"status":"in_progress"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0009","type":"command_execution","command":"npm test","aggregated_output":"sh: npm: command not found\n","exit_code":127,"status":"failed"}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_0010","type":"error","message":"Command `npm` not found in PATH (exit 127)."}}
|
||||||
|
{"type":"turn.failed","error":{"message":"Aborted: required dependency `npm` is missing; cannot continue."},"exit_code":1}
|
||||||
|
{"type":"error","message":"codex exec exited non-zero (1) after turn.failed"}
|
||||||
|
{"type":"thread.started","thread_id":"thread_legacy_7f9c2d3e"}
|
||||||
|
{"type":"turn.started"}
|
||||||
|
{"type":"item.completed","item":{"id":"item_l_0001","type":"agent_message","item_type":"assistant_message","text":"Legacy schema example: hello (item_type=assistant_message)."}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_l_0002","item_type":"command_execution","command":"echo legacy","output":"legacy\n","exit_code":0,"status":"completed"}}
|
||||||
|
{"type":"turn.completed","usage":{"input_tokens":12,"output_tokens":9}}
|
||||||
|
{"type":"thread.started","thread_id":"thread_future_01JK0Y6F8K6C7R3N1MGZ9G9A2B"}
|
||||||
|
{"type":"turn.started"}
|
||||||
|
{"type":"item.completed","item":{"id":"item_f_0001","type":"tool_call","name":"my_custom_tool","arguments":{"foo":"bar","n":3},"status":"completed","result":{"ok":true}}}
|
||||||
|
{"type":"item.completed","item":{"id":"item_f_0002","type":"file_change","changes":[{"path":"README.md","kind":"add"}],"status":"failed","error":"permission denied"}}
|
||||||
|
{"type":"turn.rate_limited","retry_after_ms":1200}
|
||||||
|
{"type":"turn.completed","usage":null}
|
||||||
@@ -90,14 +90,19 @@ def format_event(
|
|||||||
return last_item, [f"stream error: {event['message']}"], None, None
|
return last_item, [f"stream error: {event['message']}"], None, None
|
||||||
case "item.started" | "item.updated" | "item.completed" as etype:
|
case "item.started" | "item.updated" | "item.completed" as etype:
|
||||||
item = event["item"]
|
item = event["item"]
|
||||||
item_num = extract_numeric_id(item["id"], last_item)
|
item_type = item.get("type") or item.get("item_type")
|
||||||
|
if item_type == "assistant_message":
|
||||||
|
item_type = "agent_message"
|
||||||
|
if item_type is None:
|
||||||
|
return last_item, [], None, None
|
||||||
|
item_num = extract_numeric_id(item.get("id"), last_item)
|
||||||
last_item = item_num if item_num is not None else last_item
|
last_item = item_num if item_num is not None else last_item
|
||||||
prefix = f"{item_num}. "
|
prefix = f"{item_num}. "
|
||||||
if escape_markdown and item_num is not None:
|
if escape_markdown and item_num is not None:
|
||||||
# Avoid ordered-list parsing which renumbers items in MarkdownIt/CommonMark.
|
# Avoid ordered-list parsing which renumbers items in MarkdownIt/CommonMark.
|
||||||
prefix = f"{item_num}\\." + " "
|
prefix = f"{item_num}\\." + " "
|
||||||
|
|
||||||
match (item["type"], etype):
|
match (item_type, etype):
|
||||||
case ("agent_message", "item.completed"):
|
case ("agent_message", "item.completed"):
|
||||||
lines.append("assistant:")
|
lines.append("assistant:")
|
||||||
lines.extend(indent(item["text"], " ").splitlines())
|
lines.extend(indent(item["text"], " ").splitlines())
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ def _loads(lines: str) -> list[dict]:
|
|||||||
|
|
||||||
|
|
||||||
FIXTURE_PATH = Path(__file__).resolve().parent / "fixtures" / "codex.jsonl"
|
FIXTURE_PATH = Path(__file__).resolve().parent / "fixtures" / "codex.jsonl"
|
||||||
|
ALL_FORMATS_FIXTURE_PATH = (
|
||||||
|
Path(__file__).resolve().parent.parent / "codex_exec_json_all_formats.jsonl"
|
||||||
|
)
|
||||||
|
|
||||||
SAMPLE_STREAM = """
|
SAMPLE_STREAM = """
|
||||||
{"type":"thread.started","thread_id":"0199a213-81c0-7800-8aa1-bbab2a035a53"}
|
{"type":"thread.started","thread_id":"0199a213-81c0-7800-8aa1-bbab2a035a53"}
|
||||||
@@ -62,6 +65,32 @@ def test_render_event_cli_real_run_fixture() -> None:
|
|||||||
assert out[-1] == "turn completed"
|
assert out[-1] == "turn completed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_event_cli_all_formats_fixture() -> None:
|
||||||
|
events = _loads(ALL_FORMATS_FIXTURE_PATH.read_text(encoding="utf-8"))
|
||||||
|
last_turn = None
|
||||||
|
out: list[str] = []
|
||||||
|
for evt in events:
|
||||||
|
last_turn, lines = render_event_cli(evt, last_turn)
|
||||||
|
out.extend(lines)
|
||||||
|
|
||||||
|
assert "thread started" in out
|
||||||
|
assert "turn started" in out
|
||||||
|
assert any(line.startswith("stream error:") for line in out)
|
||||||
|
assert any(line.startswith("4. ▸ `pytest -q`") for line in out)
|
||||||
|
assert any("✗ `pytest -q` (exit 1)" in line for line in out)
|
||||||
|
assert any("searched: python jsonlines parser handle unknown fields" in line for line in out)
|
||||||
|
assert any("tool: github.search_issues" in line for line in out)
|
||||||
|
assert any("updated `src/compute_answer.py`" in line for line in out)
|
||||||
|
assert any(
|
||||||
|
line.startswith(
|
||||||
|
"turn failed: Aborted: required dependency `npm` is missing; cannot continue."
|
||||||
|
)
|
||||||
|
for line in out
|
||||||
|
)
|
||||||
|
assert "assistant:" in out
|
||||||
|
assert any("Legacy schema example" in line for line in out)
|
||||||
|
|
||||||
|
|
||||||
def test_progress_renderer_renders_progress_and_final() -> None:
|
def test_progress_renderer_renders_progress_and_final() -> None:
|
||||||
r = ExecProgressRenderer(max_actions=5)
|
r = ExecProgressRenderer(max_actions=5)
|
||||||
for evt in _loads(SAMPLE_STREAM):
|
for evt in _loads(SAMPLE_STREAM):
|
||||||
|
|||||||
Reference in New Issue
Block a user