feat: msgspec schemas for jsonl decoding (#37)
This commit is contained in:
-5
@@ -1,5 +0,0 @@
|
||||
{"type":"system","subtype":"init","session_id":"session_02","cwd":"/Users/banteg/dev/project","model":"sonnet","permissionMode":"manual","apiKeySource":"env","tools":["Bash","Read","Write"],"mcp_servers":[{"name":"approvals","status":"connected"}]}
|
||||
{"type":"assistant","session_id":"session_02","message":{"id":"msg_10","type":"message","role":"assistant","content":[{"type":"text","text":"I need permission to run this command."}],"usage":{"input_tokens":80,"output_tokens":20}}}
|
||||
{"type":"assistant","session_id":"session_02","parent_tool_use_id":"toolu_parent","message":{"id":"msg_11","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_9","name":"Bash","input":{"command":"git fetch origin main"}}]}}
|
||||
{"type":"user","session_id":"session_02","message":{"id":"msg_12","type":"message","role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_9","content":"permission denied"}]}}
|
||||
{"type":"result","subtype":"error","session_id":"session_02","total_cost_usd":0.001,"is_error":true,"duration_ms":2000,"duration_api_ms":1800,"num_turns":1,"result":"","error":"Permission denied","permission_denials":[{"tool_name":"Bash","tool_use_id":"toolu_9","tool_input":{"command":"git fetch origin main"}}]}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
{"type":"system","subtype":"init","session_id":"session_01","cwd":"/Users/banteg/dev/project","model":"sonnet","permissionMode":"auto","apiKeySource":"env","tools":["Bash","Read","Write","WebSearch","Task"],"mcp_servers":[{"name":"approvals","status":"connected"}]}
|
||||
{"type":"assistant","session_id":"session_01","message":{"id":"msg_1","type":"message","role":"assistant","model":"claude-3-5-sonnet","content":[{"type":"text","text":"I'll inspect the repo, then add notes."}],"usage":{"input_tokens":120,"output_tokens":45}}}
|
||||
{"type":"assistant","session_id":"session_01","message":{"id":"msg_2","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_1","name":"Bash","input":{"command":"ls -la"}}],"usage":{"input_tokens":10,"output_tokens":5}}}
|
||||
{"type":"user","session_id":"session_01","message":{"id":"msg_3","type":"message","role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":[{"type":"text","text":"total 2\nREADME.md\nsrc\n"}]}]}}
|
||||
{"type":"assistant","session_id":"session_01","message":{"id":"msg_4","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_2","name":"Write","input":{"path":"notes.md","content":"hello"}}]}}
|
||||
{"type":"user","session_id":"session_01","message":{"id":"msg_5","type":"message","role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_2","content":"ok"}]}}
|
||||
{"type":"assistant","session_id":"session_01","message":{"id":"msg_6","type":"message","role":"assistant","content":[{"type":"text","text":"Done. Added notes.md."}],"usage":{"input_tokens":20,"output_tokens":12}}}
|
||||
{"type":"result","subtype":"success","session_id":"session_01","total_cost_usd":0.0123,"is_error":false,"duration_ms":12345,"duration_api_ms":12000,"num_turns":2,"result":"Done. Added notes.md.","usage":{"input_tokens":150,"output_tokens":70,"service_tier":"standard","server_tool_use":{"web_search_requests":0}},"modelUsage":{"sonnet":{"inputTokens":150,"outputTokens":70,"cacheReadInputTokens":0,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.0123,"contextWindow":200000}}}
|
||||
@@ -0,0 +1,9 @@
|
||||
{"type":"system","subtype":"init","uuid":"11111111-1111-1111-1111-111111111111","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","apiKeySource":"ANTHROPIC_API_KEY","cwd":"/home/alex/demo-project","tools":["Task","Bash","Read","Edit","Write","WebFetch","WebSearch"],"mcp_servers":[{"name":"github","status":"connected"},{"name":"sentry","status":"error"}],"model":"claude-sonnet-4-5-20250929","permissionMode":"default","slash_commands":["help","status","clear","compact","resume"],"output_style":"default","claude_code_version":"2.0.75","agents":["general-purpose","Plan","Explore"],"skills":["python","git"],"plugins":[{"name":"local-plugin-example","path":"/home/alex/.claude/plugins/cache/local-plugin-example"}]}
|
||||
{"type":"user","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","parent_tool_use_id":null,"message":{"role":"user","content":[{"type":"text","text":"List the files in the current directory, then summarize what you see."}]}}
|
||||
{"type":"assistant","uuid":"33333333-3333-3333-3333-333333333333","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","parent_tool_use_id":null,"message":{"id":"msg_02EXAMPLEASSIST","type":"message","role":"assistant","model":"claude-sonnet-4-5-20250929","content":[{"type":"text","text":"Sure - I-ll list the directory contents."},{"type":"tool_use","id":"toolu_01BASH_LS_EXAMPLE","name":"Bash","input":{"command":"ls","timeout":600000}},{"type":"tool_use","id":"toolu_02","name":"Write","input":{"file_path":"notes.md","content":"hello"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":123,"output_tokens":42}}}
|
||||
{"type":"user","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","parent_tool_use_id":null,"message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01BASH_LS_EXAMPLE","content":[{"type":"text","text":"README.md\npyproject.toml\nsrc/\n"}],"is_error":false}]},"tool_use_result":{"stdout":"README.md\npyproject.toml\nsrc/\n","stderr":"","interrupted":false,"exit_code":0}}
|
||||
{"type":"user","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","parent_tool_use_id":null,"message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_02","content":"ok","is_error":false}]}}
|
||||
{"type":"assistant","uuid":"44444444-4444-4444-4444-444444444444","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","parent_tool_use_id":null,"message":{"id":"msg_03EXAMPLEASSIST","type":"message","role":"assistant","model":"claude-sonnet-4-5-20250929","content":[{"type":"text","text":"I see README.md, pyproject.toml, and src/."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":130,"output_tokens":76}}}
|
||||
{"type":"result","subtype":"success","uuid":"77777777-7777-7777-7777-777777777777","session_id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","duration_ms":2450,"duration_api_ms":2100,"is_error":false,"num_turns":2,"result":"I see README.md, pyproject.toml, and src/.","total_cost_usd":0.012345,"usage":{"input_tokens":130,"output_tokens":76,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"service_tier":"standard"},"modelUsage":{"claude-sonnet-4-5-20250929":{"input_tokens":130,"output_tokens":76,"service_tier":"standard"}},"permission_denials":[],"structured_output":null}
|
||||
{"type":"system","subtype":"init","uuid":"aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb","session_id":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb","apiKeySource":"none","cwd":"/home/alex/demo-project","tools":["Bash","Read","Write"],"mcp_servers":[],"model":"claude-sonnet-4-5-20250929","permissionMode":"default","slash_commands":["help","status"],"output_style":"default"}
|
||||
{"type":"result","subtype":"error_during_execution","uuid":"99999999-9999-9999-9999-999999999999","session_id":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb","duration_ms":1200,"duration_api_ms":800,"is_error":true,"num_turns":1,"total_cost_usd":0.001,"usage":{"input_tokens":40,"output_tokens":12,"service_tier":"standard"},"modelUsage":{"claude-sonnet-4-5-20250929":{"input_tokens":40,"output_tokens":12,"service_tier":"standard"}},"permission_denials":[{"tool_name":"Write","tool_use_id":"toolu_01WRITE_SECRET_EXAMPLE","tool_input":{"file_path":"/root/secret.txt","content":"hello\n"}}],"errors":["Permission denied: cannot write to /root/secret.txt"],"result":""}
|
||||
Vendored
-37
File diff suppressed because one or more lines are too long
+21
-41
@@ -1,43 +1,23 @@
|
||||
{"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":"thread.started","thread_id":"0199a213-81c0-7800-8aa1-bbab2a035a53"}
|
||||
{"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":"item.started","item":{"id":"item_0","type":"todo_list","items":[{"text":"Inspect repo structure","completed":false},{"text":"Run tests","completed":false},{"text":"Fix failing tests","completed":false}]}}
|
||||
{"type":"item.updated","item":{"id":"item_0","type":"todo_list","items":[{"text":"Inspect repo structure","completed":true},{"text":"Run tests","completed":false},{"text":"Fix failing tests","completed":false}]}}
|
||||
{"type":"item.completed","item":{"id":"item_0","type":"todo_list","items":[{"text":"Inspect repo structure","completed":true},{"text":"Run tests","completed":true},{"text":"Fix failing tests","completed":true}]}}
|
||||
{"type":"item.started","item":{"id":"item_1","type":"command_execution","command":"pytest -q","aggregated_output":"","exit_code":null,"status":"in_progress"}}
|
||||
{"type":"item.completed","item":{"id":"item_1","type":"command_execution","command":"pytest -q","aggregated_output":"....\n","exit_code":0,"status":"completed"}}
|
||||
{"type":"item.started","item":{"id":"item_2","type":"command_execution","command":"pytest -q","aggregated_output":"","exit_code":null,"status":"in_progress"}}
|
||||
{"type":"item.completed","item":{"id":"item_2","type":"command_execution","command":"pytest -q","aggregated_output":"....F\n","exit_code":1,"status":"failed"}}
|
||||
{"type":"item.completed","item":{"id":"item_3","type":"file_change","changes":[{"path":"src/compute_answer.py","kind":"update"},{"path":"README.md","kind":"add"}],"status":"completed"}}
|
||||
{"type":"item.completed","item":{"id":"item_4","type":"file_change","changes":[{"path":"src/compute_answer.py","kind":"update"}],"status":"failed"}}
|
||||
{"type":"item.started","item":{"id":"item_5","type":"mcp_tool_call","server":"github","tool":"search_issues","arguments":{"q":"exec --json"},"result":null,"error":null,"status":"in_progress"}}
|
||||
{"type":"item.completed","item":{"id":"item_5","type":"mcp_tool_call","server":"github","tool":"search_issues","arguments":{"q":"exec --json"},"result":{"content":[{"type":"text","text":"Found 3 matches."}],"structured_content":{"matches":3}},"error":null,"status":"completed"}}
|
||||
{"type":"item.started","item":{"id":"item_6","type":"mcp_tool_call","server":"github","tool":"search_issues","arguments":null,"result":null,"error":null,"status":"in_progress"}}
|
||||
{"type":"item.completed","item":{"id":"item_6","type":"mcp_tool_call","server":"github","tool":"search_issues","arguments":null,"result":null,"error":{"message":"tool timeout"},"status":"failed"}}
|
||||
{"type":"item.completed","item":{"id":"item_7","type":"web_search","query":"codex exec --json schema"}}
|
||||
{"type":"item.completed","item":{"id":"item_8","type":"reasoning","text":"Root cause: compute_answer() returned 0."}}
|
||||
{"type":"item.completed","item":{"id":"item_9","type":"agent_message","text":"Updated src/compute_answer.py and tests pass."}}
|
||||
{"type":"item.completed","item":{"id":"item_10","type":"error","message":"command output truncated"}}
|
||||
{"type":"turn.completed","usage":{"input_tokens":1840,"cached_input_tokens":256,"output_tokens":732}}
|
||||
{"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}
|
||||
{"type":"turn.failed","error":{"message":"Aborted: required dependency `npm` is missing; cannot continue."}}
|
||||
{"type":"error","message":"codex exec exited non-zero after turn.failed"}
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
{"type":"agent_start"}
|
||||
{"type":"turn_start"}
|
||||
{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"Summarize README and list the main CLI commands."},{"type":"image","data":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=","mimeType":"image/png"}],"timestamp":1767401000000}}
|
||||
{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"Summarize README and list the main CLI commands."},{"type":"image","data":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=","mimeType":"image/png"}],"timestamp":1767401000000}}
|
||||
{"type":"message_start","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"thinking_start","contentIndex":0,"partial":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README"},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"thinking_delta","contentIndex":0,"delta":"Need to read README","partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README"},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"thinking_end","contentIndex":0,"content":"Need to read README for the CLI commands.","partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"text_start","contentIndex":1,"partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":""},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README"},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"text_delta","contentIndex":1,"delta":"I'll check the README","partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README"},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"text_end","contentIndex":1,"content":"I'll check the README for the CLI commands.","partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"toolcall_start","contentIndex":2,"partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"toolcall_delta","contentIndex":2,"delta":"{\"path\":\"README.md\"}","partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200},"assistantMessageEvent":{"type":"toolcall_end","contentIndex":2,"toolCall":{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}},"partial":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"toolUse","timestamp":1767401001200}}}
|
||||
{"type":"message_end","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":1240,"output":182,"cacheRead":0,"cacheWrite":0,"totalTokens":1422,"cost":{"input":0.00124,"output":0.000364,"cacheRead":0.0,"cacheWrite":0.0,"total":0.001604}},"stopReason":"toolUse","timestamp":1767401001500}}
|
||||
{"type":"tool_execution_start","toolCallId":"toolu_01HXYZREAD","toolName":"read","args":{"path":"README.md"}}
|
||||
{"type":"tool_execution_update","toolCallId":"toolu_01HXYZREAD","toolName":"read","args":{"path":"README.md"},"partialResult":{"content":[{"type":"text","text":"Reading README.md..."}],"details":{"bytesRead":512}}}
|
||||
{"type":"tool_execution_end","toolCallId":"toolu_01HXYZREAD","toolName":"read","result":{"content":[{"type":"text","text":"# Pi Coding Agent\n\nUsage:\n pi [options] [@files...] [messages...]\n\nOptions:\n --mode <mode>\n --print, -p\n"}],"details":{"path":"README.md","bytesRead":2048,"truncated":false}},"isError":false}
|
||||
{"type":"message_start","message":{"role":"toolResult","toolCallId":"toolu_01HXYZREAD","toolName":"read","content":[{"type":"text","text":"# Pi Coding Agent\n\nUsage:\n pi [options] [@files...] [messages...]\n\nOptions:\n --mode <mode>\n --print, -p\n"}],"details":{"path":"README.md","bytesRead":2048,"truncated":false},"isError":false,"timestamp":1767401002500}}
|
||||
{"type":"message_end","message":{"role":"toolResult","toolCallId":"toolu_01HXYZREAD","toolName":"read","content":[{"type":"text","text":"# Pi Coding Agent\n\nUsage:\n pi [options] [@files...] [messages...]\n\nOptions:\n --mode <mode>\n --print, -p\n"}],"details":{"path":"README.md","bytesRead":2048,"truncated":false},"isError":false,"timestamp":1767401002500}}
|
||||
{"type":"turn_end","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":1240,"output":182,"cacheRead":0,"cacheWrite":0,"totalTokens":1422,"cost":{"input":0.00124,"output":0.000364,"cacheRead":0.0,"cacheWrite":0.0,"total":0.001604}},"stopReason":"toolUse","timestamp":1767401001500},"toolResults":[{"role":"toolResult","toolCallId":"toolu_01HXYZREAD","toolName":"read","content":[{"type":"text","text":"# Pi Coding Agent\n\nUsage:\n pi [options] [@files...] [messages...]\n\nOptions:\n --mode <mode>\n --print, -p\n"}],"details":{"path":"README.md","bytesRead":2048,"truncated":false},"isError":false,"timestamp":1767401002500}]}
|
||||
{"type":"turn_start"}
|
||||
{"type":"message_start","message":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767401004500}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767401004500},"assistantMessageEvent":{"type":"text_start","contentIndex":0,"partial":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767401004500}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767401004500},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Main CLI commands","partial":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767401004500}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":680,"output":128,"cacheRead":0,"cacheWrite":0,"totalTokens":808,"cost":{"input":0.00068,"output":0.000256,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000936}},"stopReason":"stop","timestamp":1767401004500},"assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs.","partial":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":680,"output":128,"cacheRead":0,"cacheWrite":0,"totalTokens":808,"cost":{"input":0.00068,"output":0.000256,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000936}},"stopReason":"stop","timestamp":1767401004500}}}
|
||||
{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":680,"output":128,"cacheRead":0,"cacheWrite":0,"totalTokens":808,"cost":{"input":0.00068,"output":0.000256,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000936}},"stopReason":"stop","timestamp":1767401004500}}
|
||||
{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":680,"output":128,"cacheRead":0,"cacheWrite":0,"totalTokens":808,"cost":{"input":0.00068,"output":0.000256,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000936}},"stopReason":"stop","timestamp":1767401004500},"toolResults":[]}
|
||||
{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"Summarize README and list the main CLI commands."},{"type":"image","data":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=","mimeType":"image/png"}],"timestamp":1767401000000},{"role":"assistant","content":[{"type":"thinking","thinking":"Need to read README for the CLI commands."},{"type":"text","text":"I'll check the README for the CLI commands."},{"type":"toolCall","id":"toolu_01HXYZREAD","name":"read","arguments":{"path":"README.md"}}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":1240,"output":182,"cacheRead":0,"cacheWrite":0,"totalTokens":1422,"cost":{"input":0.00124,"output":0.000364,"cacheRead":0.0,"cacheWrite":0.0,"total":0.001604}},"stopReason":"toolUse","timestamp":1767401001500},{"role":"toolResult","toolCallId":"toolu_01HXYZREAD","toolName":"read","content":[{"type":"text","text":"# Pi Coding Agent\n\nUsage:\n pi [options] [@files...] [messages...]\n\nOptions:\n --mode <mode>\n --print, -p\n"}],"details":{"path":"README.md","bytesRead":2048,"truncated":false},"isError":false,"timestamp":1767401002500},{"role":"assistant","content":[{"type":"text","text":"Main CLI commands include `pi --mode <mode>` for output format and `pi --print` for non-interactive runs."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":680,"output":128,"cacheRead":0,"cacheWrite":0,"totalTokens":808,"cost":{"input":0.00068,"output":0.000256,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000936}},"stopReason":"stop","timestamp":1767401004500}]}
|
||||
{"type":"auto_compaction_start","reason":"threshold"}
|
||||
{"type":"auto_compaction_end","result":{"summary":"## Goal\nSummarize CLI usage.\n\n## Progress\n- Read README for CLI commands.","firstKeptEntryId":"b1b2c3d4-e5f6-7890-abcd-ef0123456789","tokensBefore":118432,"details":{"readFiles":["README.md"],"modifiedFiles":[]}},"aborted":false,"willRetry":false}
|
||||
{"type":"agent_start"}
|
||||
{"type":"turn_start"}
|
||||
{"type":"message_start","message":{"role":"user","content":[{"type":"text","text":"List the latest release notes for the CLI."}],"timestamp":1767402000000}}
|
||||
{"type":"message_end","message":{"role":"user","content":[{"type":"text","text":"List the latest release notes for the CLI."}],"timestamp":1767402000000}}
|
||||
{"type":"message_start","message":{"role":"assistant","content":[],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":320,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":320,"cost":{"input":0.00032,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.00032}},"stopReason":"error","errorMessage":"Rate limit exceeded","timestamp":1767402001200}}
|
||||
{"type":"message_end","message":{"role":"assistant","content":[],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":320,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":320,"cost":{"input":0.00032,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.00032}},"stopReason":"error","errorMessage":"Rate limit exceeded","timestamp":1767402001200}}
|
||||
{"type":"turn_end","message":{"role":"assistant","content":[],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":320,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":320,"cost":{"input":0.00032,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.00032}},"stopReason":"error","errorMessage":"Rate limit exceeded","timestamp":1767402001200},"toolResults":[]}
|
||||
{"type":"agent_end","messages":[{"role":"user","content":[{"type":"text","text":"List the latest release notes for the CLI."}],"timestamp":1767402000000},{"role":"assistant","content":[],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":320,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":320,"cost":{"input":0.00032,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.00032}},"stopReason":"error","errorMessage":"Rate limit exceeded","timestamp":1767402001200}]}
|
||||
{"type":"auto_retry_start","attempt":1,"maxAttempts":3,"delayMs":2000,"errorMessage":"Rate limit exceeded"}
|
||||
{"type":"agent_start"}
|
||||
{"type":"turn_start"}
|
||||
{"type":"message_start","message":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767402004200}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767402004200},"assistantMessageEvent":{"type":"text_start","contentIndex":0,"partial":{"role":"assistant","content":[{"type":"text","text":""}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767402004200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767402004200},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Retry succeeded","partial":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0.0,"output":0.0,"cacheRead":0.0,"cacheWrite":0.0,"total":0.0}},"stopReason":"stop","timestamp":1767402004200}}}
|
||||
{"type":"message_update","message":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":410,"output":96,"cacheRead":0,"cacheWrite":0,"totalTokens":506,"cost":{"input":0.00041,"output":0.000192,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000602}},"stopReason":"stop","timestamp":1767402004200},"assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export.","partial":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":410,"output":96,"cacheRead":0,"cacheWrite":0,"totalTokens":506,"cost":{"input":0.00041,"output":0.000192,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000602}},"stopReason":"stop","timestamp":1767402004200}}}
|
||||
{"type":"message_end","message":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":410,"output":96,"cacheRead":0,"cacheWrite":0,"totalTokens":506,"cost":{"input":0.00041,"output":0.000192,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000602}},"stopReason":"stop","timestamp":1767402004200}}
|
||||
{"type":"turn_end","message":{"role":"assistant","content":[{"type":"text","text":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":410,"output":96,"cacheRead":0,"cacheWrite":0,"totalTokens":506,"cost":{"input":0.00041,"output":0.000192,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000602}},"stopReason":"stop","timestamp":1767402004200},"toolResults":[]}
|
||||
{"type":"agent_end","messages":[{"role":"assistant","content":[{"type":"text","text":"Retry succeeded. Recent notes: 1) Added --mode json output. 2) Improved session export."}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":410,"output":96,"cacheRead":0,"cacheWrite":0,"totalTokens":506,"cost":{"input":0.00041,"output":0.000192,"cacheRead":0.0,"cacheWrite":0.0,"total":0.000602}},"stopReason":"stop","timestamp":1767402004200}]}
|
||||
{"type":"auto_retry_end","success":true,"attempt":1}
|
||||
+158
-26
@@ -11,11 +11,43 @@ from takopi.runners.claude import (
|
||||
ENGINE,
|
||||
translate_claude_event,
|
||||
)
|
||||
from takopi.schemas import claude as claude_schema
|
||||
|
||||
|
||||
def _load_fixture(name: str) -> list[dict]:
|
||||
def _load_fixture(
|
||||
name: str, *, session_id: str | None = None
|
||||
) -> list[claude_schema.StreamJsonMessage]:
|
||||
path = Path(__file__).parent / "fixtures" / name
|
||||
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
||||
events = [
|
||||
claude_schema.decode_stream_json_line(line)
|
||||
for line in path.read_bytes().splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
if session_id is None:
|
||||
return events
|
||||
return [
|
||||
event for event in events if getattr(event, "session_id", None) == session_id
|
||||
]
|
||||
|
||||
|
||||
def _decode_event(payload: dict) -> claude_schema.StreamJsonMessage:
|
||||
data_payload = dict(payload)
|
||||
data_payload.setdefault("uuid", "uuid")
|
||||
data_payload.setdefault("session_id", "session")
|
||||
match data_payload.get("type"):
|
||||
case "assistant":
|
||||
message = dict(data_payload.get("message", {}))
|
||||
message.setdefault("role", "assistant")
|
||||
message.setdefault("content", [])
|
||||
message.setdefault("model", "claude")
|
||||
data_payload["message"] = message
|
||||
case "user":
|
||||
message = dict(data_payload.get("message", {}))
|
||||
message.setdefault("role", "user")
|
||||
message.setdefault("content", [])
|
||||
data_payload["message"] = message
|
||||
data = json.dumps(data_payload).encode("utf-8")
|
||||
return claude_schema.decode_stream_json_line(data)
|
||||
|
||||
|
||||
def test_claude_resume_format_and_extract() -> None:
|
||||
@@ -33,8 +65,18 @@ def test_claude_resume_format_and_extract() -> None:
|
||||
def test_translate_success_fixture() -> None:
|
||||
state = ClaudeStreamState()
|
||||
events: list = []
|
||||
for event in _load_fixture("claude_stream_success.jsonl"):
|
||||
events.extend(translate_claude_event(event, title="claude", state=state))
|
||||
for event in _load_fixture(
|
||||
"claude_streamjson_session.jsonl",
|
||||
session_id="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
):
|
||||
events.extend(
|
||||
translate_claude_event(
|
||||
event,
|
||||
title="claude",
|
||||
state=state,
|
||||
factory=state.factory,
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(events[0], StartedEvent)
|
||||
started = next(evt for evt in events if isinstance(evt, StartedEvent))
|
||||
@@ -47,8 +89,10 @@ def test_translate_success_fixture() -> None:
|
||||
for evt in action_events
|
||||
if evt.phase == "started"
|
||||
}
|
||||
assert started_actions[("toolu_1", "started")].action.kind == "command"
|
||||
write_action = started_actions[("toolu_2", "started")].action
|
||||
assert (
|
||||
started_actions[("toolu_01BASH_LS_EXAMPLE", "started")].action.kind == "command"
|
||||
)
|
||||
write_action = started_actions[("toolu_02", "started")].action
|
||||
assert write_action.kind == "file_change"
|
||||
assert write_action.detail["changes"][0]["path"] == "notes.md"
|
||||
|
||||
@@ -57,34 +101,37 @@ def test_translate_success_fixture() -> None:
|
||||
for evt in action_events
|
||||
if evt.phase == "completed"
|
||||
}
|
||||
assert completed_actions[("toolu_1", "completed")].ok is True
|
||||
assert completed_actions[("toolu_2", "completed")].ok is True
|
||||
assert completed_actions[("toolu_01BASH_LS_EXAMPLE", "completed")].ok is True
|
||||
assert completed_actions[("toolu_02", "completed")].ok is True
|
||||
|
||||
completed = next(evt for evt in events if isinstance(evt, CompletedEvent))
|
||||
assert events[-1] == completed
|
||||
assert completed.ok is True
|
||||
assert completed.resume == started.resume
|
||||
assert completed.answer == "Done. Added notes.md."
|
||||
assert completed.answer == "I see README.md, pyproject.toml, and src/."
|
||||
|
||||
|
||||
def test_translate_error_fixture_permission_denials() -> None:
|
||||
state = ClaudeStreamState()
|
||||
events: list = []
|
||||
for event in _load_fixture("claude_stream_error.jsonl"):
|
||||
events.extend(translate_claude_event(event, title="claude", state=state))
|
||||
for event in _load_fixture(
|
||||
"claude_streamjson_session.jsonl",
|
||||
session_id="bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
||||
):
|
||||
events.extend(
|
||||
translate_claude_event(
|
||||
event,
|
||||
title="claude",
|
||||
state=state,
|
||||
factory=state.factory,
|
||||
)
|
||||
)
|
||||
|
||||
started = next(evt for evt in events if isinstance(evt, StartedEvent))
|
||||
completed = next(evt for evt in events if isinstance(evt, CompletedEvent))
|
||||
warnings = [
|
||||
evt
|
||||
for evt in events
|
||||
if isinstance(evt, ActionEvent) and evt.action.kind == "warning"
|
||||
]
|
||||
|
||||
assert warnings
|
||||
assert events.index(warnings[0]) < events.index(completed)
|
||||
assert completed.ok is False
|
||||
assert completed.error == "Permission denied"
|
||||
assert completed.error is not None
|
||||
assert "claude run failed" in completed.error
|
||||
assert completed.resume == started.resume
|
||||
|
||||
|
||||
@@ -120,13 +167,54 @@ def test_tool_results_pop_pending_actions() -> None:
|
||||
},
|
||||
}
|
||||
|
||||
translate_claude_event(tool_use_event, title="claude", state=state)
|
||||
translate_claude_event(
|
||||
_decode_event(tool_use_event),
|
||||
title="claude",
|
||||
state=state,
|
||||
factory=state.factory,
|
||||
)
|
||||
assert "toolu_1" in state.pending_actions
|
||||
|
||||
translate_claude_event(tool_result_event, title="claude", state=state)
|
||||
translate_claude_event(
|
||||
_decode_event(tool_result_event),
|
||||
title="claude",
|
||||
state=state,
|
||||
factory=state.factory,
|
||||
)
|
||||
assert not state.pending_actions
|
||||
|
||||
|
||||
def test_translate_thinking_block() -> None:
|
||||
state = ClaudeStreamState()
|
||||
event = {
|
||||
"type": "assistant",
|
||||
"message": {
|
||||
"id": "msg_1",
|
||||
"content": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"thinking": "Consider the options.",
|
||||
"signature": "sig",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
events = translate_claude_event(
|
||||
_decode_event(event),
|
||||
title="claude",
|
||||
state=state,
|
||||
factory=state.factory,
|
||||
)
|
||||
|
||||
assert len(events) == 1
|
||||
assert isinstance(events[0], ActionEvent)
|
||||
assert events[0].phase == "completed"
|
||||
assert events[0].action.kind == "note"
|
||||
assert events[0].action.title == "Consider the options."
|
||||
assert events[0].ok is True
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_run_serializes_same_session() -> None:
|
||||
runner = ClaudeRunner(claude_cmd="claude")
|
||||
@@ -184,15 +272,30 @@ async def test_run_serializes_new_session_after_session_is_known(
|
||||
"resume_marker = os.environ['CLAUDE_TEST_RESUME_MARKER']\n"
|
||||
"session_id = os.environ['CLAUDE_TEST_SESSION_ID']\n"
|
||||
"\n"
|
||||
"init = {\n"
|
||||
" 'type': 'system',\n"
|
||||
" 'subtype': 'init',\n"
|
||||
" 'uuid': 'uuid',\n"
|
||||
" 'session_id': session_id,\n"
|
||||
" 'apiKeySource': 'env',\n"
|
||||
" 'cwd': '.',\n"
|
||||
" 'tools': [],\n"
|
||||
" 'mcp_servers': [],\n"
|
||||
" 'model': 'claude',\n"
|
||||
" 'permissionMode': 'default',\n"
|
||||
" 'slash_commands': [],\n"
|
||||
" 'output_style': 'default',\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"args = sys.argv[1:]\n"
|
||||
"if '--resume' in args or '-r' in args:\n"
|
||||
" print(json.dumps({'type': 'system', 'subtype': 'init', 'session_id': session_id}), flush=True)\n"
|
||||
" print(json.dumps(init), flush=True)\n"
|
||||
" with open(resume_marker, 'w', encoding='utf-8') as f:\n"
|
||||
" f.write('started')\n"
|
||||
" f.flush()\n"
|
||||
" sys.exit(0)\n"
|
||||
"\n"
|
||||
"print(json.dumps({'type': 'system', 'subtype': 'init', 'session_id': session_id}), flush=True)\n"
|
||||
"print(json.dumps(init), flush=True)\n"
|
||||
"while not os.path.exists(gate):\n"
|
||||
" time.sleep(0.001)\n"
|
||||
"sys.exit(0)\n",
|
||||
@@ -252,8 +355,37 @@ async def test_run_strips_anthropic_api_key_by_default(tmp_path, monkeypatch) ->
|
||||
"\n"
|
||||
"session_id = 'session_01'\n"
|
||||
"status = 'set' if os.environ.get('ANTHROPIC_API_KEY') else 'unset'\n"
|
||||
"print(json.dumps({'type': 'system', 'subtype': 'init', 'session_id': session_id}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'result', 'subtype': 'success', 'is_error': False, 'result': f'api={status}', 'session_id': session_id}), flush=True)\n"
|
||||
"init = {\n"
|
||||
" 'type': 'system',\n"
|
||||
" 'subtype': 'init',\n"
|
||||
" 'uuid': 'uuid',\n"
|
||||
" 'session_id': session_id,\n"
|
||||
" 'apiKeySource': 'env',\n"
|
||||
" 'cwd': '.',\n"
|
||||
" 'tools': [],\n"
|
||||
" 'mcp_servers': [],\n"
|
||||
" 'model': 'claude',\n"
|
||||
" 'permissionMode': 'default',\n"
|
||||
" 'slash_commands': [],\n"
|
||||
" 'output_style': 'default',\n"
|
||||
"}\n"
|
||||
"print(json.dumps(init), flush=True)\n"
|
||||
"result = {\n"
|
||||
" 'type': 'result',\n"
|
||||
" 'subtype': 'success',\n"
|
||||
" 'uuid': 'uuid',\n"
|
||||
" 'session_id': session_id,\n"
|
||||
" 'duration_ms': 0,\n"
|
||||
" 'duration_api_ms': 0,\n"
|
||||
" 'is_error': False,\n"
|
||||
" 'num_turns': 1,\n"
|
||||
" 'result': f'api={status}',\n"
|
||||
" 'total_cost_usd': 0.0,\n"
|
||||
" 'usage': {'input_tokens': 0, 'output_tokens': 0},\n"
|
||||
" 'modelUsage': {},\n"
|
||||
" 'permission_denials': [],\n"
|
||||
"}\n"
|
||||
"print(json.dumps(result), flush=True)\n"
|
||||
"raise SystemExit(0)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from takopi.schemas import claude as claude_schema
|
||||
|
||||
|
||||
def _fixture_path(name: str) -> Path:
|
||||
return Path(__file__).parent / "fixtures" / name
|
||||
|
||||
|
||||
def _decode_fixture(name: str) -> list[str]:
|
||||
path = _fixture_path(name)
|
||||
errors: list[str] = []
|
||||
|
||||
for lineno, line in enumerate(path.read_bytes().splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
decoded = claude_schema.decode_stream_json_line(line)
|
||||
except Exception as exc:
|
||||
errors.append(f"line {lineno}: {exc.__class__.__name__}: {exc}")
|
||||
continue
|
||||
|
||||
_ = decoded
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture",
|
||||
[
|
||||
"claude_streamjson_session.jsonl",
|
||||
],
|
||||
)
|
||||
def test_claude_schema_parses_fixture(fixture: str) -> None:
|
||||
errors = _decode_fixture(fixture)
|
||||
|
||||
assert not errors, f"{fixture} had {len(errors)} errors: " + "; ".join(errors[:5])
|
||||
@@ -0,0 +1,44 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from takopi.schemas import codex as codex_schema
|
||||
|
||||
|
||||
def _fixture_path(name: str) -> Path:
|
||||
return Path(__file__).parent / "fixtures" / name
|
||||
|
||||
|
||||
def _decode_fixture(name: str) -> list[str]:
|
||||
path = _fixture_path(name)
|
||||
errors: list[str] = []
|
||||
|
||||
for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
json.loads(line)
|
||||
except Exception as exc:
|
||||
errors.append(f"line {lineno}: invalid JSON ({exc})")
|
||||
continue
|
||||
try:
|
||||
codex_schema.decode_event(line)
|
||||
except Exception as exc:
|
||||
errors.append(f"line {lineno}: {exc.__class__.__name__}: {exc}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture",
|
||||
[
|
||||
"codex_exec_json_all_formats.jsonl",
|
||||
],
|
||||
)
|
||||
def test_codex_schema_parses_fixture(fixture: str) -> None:
|
||||
errors = _decode_fixture(fixture)
|
||||
|
||||
assert not errors, f"{fixture} had {len(errors)} errors: " + "; ".join(errors[:5])
|
||||
@@ -1,5 +1,21 @@
|
||||
import json
|
||||
|
||||
from takopi.events import EventFactory
|
||||
from takopi.model import ActionEvent
|
||||
from takopi.runners.codex import translate_codex_event
|
||||
from takopi.schemas import codex as codex_schema
|
||||
|
||||
|
||||
def _decode_event(payload: dict) -> codex_schema.ThreadEvent:
|
||||
return codex_schema.decode_event(json.dumps(payload))
|
||||
|
||||
|
||||
def _translate_event(payload: dict) -> list:
|
||||
return translate_codex_event(
|
||||
_decode_event(payload),
|
||||
title="Codex",
|
||||
factory=EventFactory("codex"),
|
||||
)
|
||||
|
||||
|
||||
def test_translate_mcp_tool_call_summarizes_structured_content() -> None:
|
||||
@@ -20,7 +36,7 @@ def test_translate_mcp_tool_call_summarizes_structured_content() -> None:
|
||||
},
|
||||
}
|
||||
|
||||
out = translate_codex_event(evt, title="Codex")
|
||||
out = _translate_event(evt)
|
||||
assert len(out) == 1
|
||||
assert isinstance(out[0], ActionEvent)
|
||||
summary = out[0].action.detail["result_summary"]
|
||||
@@ -36,38 +52,19 @@ def test_translate_mcp_tool_call_summarizes_null_structured_content() -> None:
|
||||
"type": "mcp_tool_call",
|
||||
"server": "docs",
|
||||
"tool": "search",
|
||||
"arguments": None,
|
||||
"result": {"content": [], "structured_content": None},
|
||||
"error": None,
|
||||
"status": "completed",
|
||||
},
|
||||
}
|
||||
|
||||
out = translate_codex_event(evt, title="Codex")
|
||||
out = _translate_event(evt)
|
||||
assert len(out) == 1
|
||||
assert isinstance(out[0], ActionEvent)
|
||||
assert out[0].action.detail["result_summary"]["has_structured"] is False
|
||||
|
||||
|
||||
def test_translate_mcp_tool_call_summarizes_legacy_structured_key() -> None:
|
||||
evt = {
|
||||
"type": "item.completed",
|
||||
"item": {
|
||||
"id": "item_3",
|
||||
"type": "mcp_tool_call",
|
||||
"server": "docs",
|
||||
"tool": "search",
|
||||
"result": {"structured": {"matches": 3}},
|
||||
"error": None,
|
||||
"status": "completed",
|
||||
},
|
||||
}
|
||||
|
||||
out = translate_codex_event(evt, title="Codex")
|
||||
assert len(out) == 1
|
||||
assert isinstance(out[0], ActionEvent)
|
||||
assert out[0].action.detail["result_summary"]["has_structured"] is True
|
||||
|
||||
|
||||
def test_translate_mcp_tool_call_missing_error_is_ok() -> None:
|
||||
evt = {
|
||||
"type": "item.completed",
|
||||
@@ -76,18 +73,20 @@ def test_translate_mcp_tool_call_missing_error_is_ok() -> None:
|
||||
"type": "mcp_tool_call",
|
||||
"server": "docs",
|
||||
"tool": "search",
|
||||
"arguments": None,
|
||||
"status": "completed",
|
||||
"result": {"content": []},
|
||||
"result": {"content": [], "structured_content": None},
|
||||
"error": None,
|
||||
},
|
||||
}
|
||||
|
||||
out = translate_codex_event(evt, title="Codex")
|
||||
out = _translate_event(evt)
|
||||
assert len(out) == 1
|
||||
assert isinstance(out[0], ActionEvent)
|
||||
assert out[0].ok is True
|
||||
|
||||
|
||||
def test_translate_command_execution_allows_missing_exit_code() -> None:
|
||||
def test_translate_command_execution_allows_null_exit_code() -> None:
|
||||
evt = {
|
||||
"type": "item.completed",
|
||||
"item": {
|
||||
@@ -95,11 +94,12 @@ def test_translate_command_execution_allows_missing_exit_code() -> None:
|
||||
"type": "command_execution",
|
||||
"command": "ls -la",
|
||||
"aggregated_output": "",
|
||||
"exit_code": None,
|
||||
"status": "completed",
|
||||
},
|
||||
}
|
||||
|
||||
out = translate_codex_event(evt, title="Codex")
|
||||
out = _translate_event(evt)
|
||||
assert len(out) == 1
|
||||
assert isinstance(out[0], ActionEvent)
|
||||
assert out[0].ok is True
|
||||
|
||||
@@ -218,7 +218,7 @@ async def test_codex_runner_preserves_warning_order(tmp_path) -> None:
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"sys.stdin.read()\n"
|
||||
"print(json.dumps({'type': 'error', 'message': 'warning one', 'fatal': False}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'error', 'message': 'warning one'}), flush=True)\n"
|
||||
f"print(json.dumps({{'type': 'thread.started', 'thread_id': '{thread_id}'}}), flush=True)\n"
|
||||
"print(json.dumps({'type': 'item.completed', 'item': {'id': 'item_0', 'type': 'agent_message', 'text': 'ok'}}), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
@@ -335,7 +335,7 @@ async def test_codex_runner_includes_stderr_reason(tmp_path) -> None:
|
||||
assert completed.ok is False
|
||||
assert completed.error is not None
|
||||
assert "codex exec failed (rc=1)." in completed.error
|
||||
assert "\n\nNot inside a trusted directory" in completed.error
|
||||
assert "Not inside a trusted directory" not in completed.error
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
||||
@@ -11,11 +11,26 @@ from takopi.runners.opencode import (
|
||||
ENGINE,
|
||||
translate_opencode_event,
|
||||
)
|
||||
from takopi.schemas import opencode as opencode_schema
|
||||
|
||||
|
||||
def _load_fixture(name: str) -> list[dict]:
|
||||
def _load_fixture(name: str) -> list[opencode_schema.OpenCodeEvent]:
|
||||
path = Path(__file__).parent / "fixtures" / name
|
||||
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
||||
events: list[opencode_schema.OpenCodeEvent] = []
|
||||
for line in path.read_bytes().splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
events.append(opencode_schema.decode_event(line))
|
||||
except Exception as exc:
|
||||
raise AssertionError(
|
||||
f"{name} contained unparseable line: {line!r}"
|
||||
) from exc
|
||||
return events
|
||||
|
||||
|
||||
def _decode_event(payload: dict) -> opencode_schema.OpenCodeEvent:
|
||||
return opencode_schema.decode_event(json.dumps(payload).encode("utf-8"))
|
||||
|
||||
|
||||
def test_opencode_resume_format_and_extract() -> None:
|
||||
@@ -59,9 +74,6 @@ def test_translate_success_fixture() -> None:
|
||||
assert completed.resume == started.resume
|
||||
assert completed.answer == "```\nhello\n```"
|
||||
|
||||
assert completed.usage is not None
|
||||
assert "tokens" in completed.usage
|
||||
|
||||
|
||||
def test_translate_missing_reason_success() -> None:
|
||||
state = OpenCodeStreamState()
|
||||
@@ -74,7 +86,6 @@ def test_translate_missing_reason_success() -> None:
|
||||
fallback = runner.stream_end_events(
|
||||
resume=None,
|
||||
found_session=started.resume,
|
||||
stderr_tail="",
|
||||
state=state,
|
||||
)
|
||||
|
||||
@@ -82,14 +93,13 @@ def test_translate_missing_reason_success() -> None:
|
||||
assert completed.ok is True
|
||||
assert completed.resume == started.resume
|
||||
assert completed.answer == "All done."
|
||||
assert completed.usage is not None
|
||||
|
||||
|
||||
def test_translate_accumulates_text() -> None:
|
||||
state = OpenCodeStreamState()
|
||||
|
||||
events = translate_opencode_event(
|
||||
{"type": "step_start", "sessionID": "ses_test123", "part": {}},
|
||||
_decode_event({"type": "step_start", "sessionID": "ses_test123", "part": {}}),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -97,20 +107,24 @@ def test_translate_accumulates_text() -> None:
|
||||
assert isinstance(events[0], StartedEvent)
|
||||
|
||||
translate_opencode_event(
|
||||
{
|
||||
"type": "text",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"type": "text", "text": "Hello "},
|
||||
},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "text",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"type": "text", "text": "Hello "},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
translate_opencode_event(
|
||||
{
|
||||
"type": "text",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"type": "text", "text": "World"},
|
||||
},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "text",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"type": "text", "text": "World"},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -118,11 +132,13 @@ def test_translate_accumulates_text() -> None:
|
||||
assert state.last_text == "Hello World"
|
||||
|
||||
events = translate_opencode_event(
|
||||
{
|
||||
"type": "step_finish",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"reason": "stop", "tokens": {"input": 100, "output": 10}},
|
||||
},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "step_finish",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"reason": "stop", "tokens": {"input": 100, "output": 10}},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -140,22 +156,24 @@ def test_translate_tool_use_completed() -> None:
|
||||
state.emitted_started = True
|
||||
|
||||
events = translate_opencode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "bash",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"command": "ls -la"},
|
||||
"output": "file1.txt\nfile2.txt",
|
||||
"title": "List files",
|
||||
"metadata": {"exit": 0},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "bash",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"command": "ls -la"},
|
||||
"output": "file1.txt\nfile2.txt",
|
||||
"title": "List files",
|
||||
"metadata": {"exit": 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -175,22 +193,24 @@ def test_translate_tool_use_with_error() -> None:
|
||||
state.emitted_started = True
|
||||
|
||||
events = translate_opencode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "bash",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"command": "exit 1"},
|
||||
"output": "error",
|
||||
"title": "Run failing command",
|
||||
"metadata": {"exit": 1},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "bash",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"command": "exit 1"},
|
||||
"output": "error",
|
||||
"title": "Run failing command",
|
||||
"metadata": {"exit": 1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -209,21 +229,23 @@ def test_translate_tool_use_read_title_wraps_path() -> None:
|
||||
path = Path.cwd() / "src" / "takopi" / "runners" / "opencode.py"
|
||||
|
||||
events = translate_opencode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "read",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"filePath": str(path)},
|
||||
"output": "file contents",
|
||||
"title": "src/takopi/runners/opencode.py",
|
||||
_decode_event(
|
||||
{
|
||||
"type": "tool_use",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"id": "prt_123",
|
||||
"callID": "call_abc",
|
||||
"tool": "read",
|
||||
"state": {
|
||||
"status": "completed",
|
||||
"input": {"filePath": str(path)},
|
||||
"output": "file contents",
|
||||
"title": "src/takopi/runners/opencode.py",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
@@ -255,11 +277,16 @@ def test_step_finish_tool_calls_does_not_complete() -> None:
|
||||
state.emitted_started = True
|
||||
|
||||
events = translate_opencode_event(
|
||||
{
|
||||
"type": "step_finish",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {"reason": "tool-calls", "tokens": {"input": 100, "output": 10}},
|
||||
},
|
||||
_decode_event(
|
||||
{
|
||||
"type": "step_finish",
|
||||
"sessionID": "ses_test123",
|
||||
"part": {
|
||||
"reason": "tool-calls",
|
||||
"tokens": {"input": 100, "output": 10},
|
||||
},
|
||||
}
|
||||
),
|
||||
title="opencode",
|
||||
state=state,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from takopi.schemas import opencode as opencode_schema
|
||||
|
||||
|
||||
def _fixture_path(name: str) -> Path:
|
||||
return Path(__file__).parent / "fixtures" / name
|
||||
|
||||
|
||||
def _decode_fixture(name: str) -> list[str]:
|
||||
path = _fixture_path(name)
|
||||
errors: list[str] = []
|
||||
|
||||
for lineno, line in enumerate(path.read_bytes().splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
opencode_schema.decode_event(line)
|
||||
except Exception as exc:
|
||||
errors.append(f"line {lineno}: {exc.__class__.__name__}: {exc}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture",
|
||||
[
|
||||
"opencode_stream_success.jsonl",
|
||||
"opencode_stream_success_no_reason.jsonl",
|
||||
"opencode_stream_error.jsonl",
|
||||
],
|
||||
)
|
||||
def test_opencode_schema_parses_fixture(fixture: str) -> None:
|
||||
errors = _decode_fixture(fixture)
|
||||
assert not errors, f"{fixture} had {len(errors)} errors: " + "; ".join(errors[:5])
|
||||
+12
-3
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import anyio
|
||||
@@ -6,11 +5,21 @@ import pytest
|
||||
|
||||
from takopi.model import ActionEvent, CompletedEvent, ResumeToken, StartedEvent
|
||||
from takopi.runners.pi import ENGINE, PiRunner, PiStreamState, translate_pi_event
|
||||
from takopi.schemas import pi as pi_schema
|
||||
|
||||
|
||||
def _load_fixture(name: str) -> list[dict]:
|
||||
def _load_fixture(name: str) -> list[pi_schema.PiEvent]:
|
||||
path = Path(__file__).parent / "fixtures" / name
|
||||
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
||||
events: list[pi_schema.PiEvent] = []
|
||||
for line in path.read_text().splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
decoded = pi_schema.decode_event(line)
|
||||
except Exception as exc:
|
||||
raise AssertionError(f"{name} contained unparseable line: {line}") from exc
|
||||
events.append(decoded)
|
||||
return events
|
||||
|
||||
|
||||
def test_pi_resume_format_and_extract() -> None:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from takopi.schemas import pi as pi_schema
|
||||
|
||||
|
||||
def _fixture_path(name: str) -> Path:
|
||||
return Path(__file__).parent / "fixtures" / name
|
||||
|
||||
|
||||
def _decode_fixture(name: str) -> list[str]:
|
||||
path = _fixture_path(name)
|
||||
errors: list[str] = []
|
||||
|
||||
for lineno, line in enumerate(path.read_text().splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
pi_schema.decode_event(line)
|
||||
except Exception as exc:
|
||||
errors.append(f"line {lineno}: {exc.__class__.__name__}: {exc}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture",
|
||||
[
|
||||
"pi_stream_success.jsonl",
|
||||
"pi_stream_error.jsonl",
|
||||
"pi_print_mode_events.jsonl",
|
||||
],
|
||||
)
|
||||
def test_pi_schema_parses_fixture(fixture: str) -> None:
|
||||
errors = _decode_fixture(fixture)
|
||||
assert not errors, f"{fixture} had {len(errors)} errors: " + "; ".join(errors[:5])
|
||||
Reference in New Issue
Block a user