kon-coding-agent 0.3.8__tar.gz → 0.3.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/CHANGELOG.md +35 -0
  2. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/PKG-INFO +9 -4
  3. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/README.md +7 -3
  4. kon_coding_agent-0.3.9/docs/images/kon-screenshot.png +0 -0
  5. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/pyproject.toml +2 -1
  6. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/types.py +1 -0
  7. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/__init__.py +3 -19
  8. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/__init__.py +2 -0
  9. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/copilot.py +4 -0
  10. kon_coding_agent-0.3.9/src/kon/llm/providers/__init__.py +61 -0
  11. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/copilot.py +0 -4
  12. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_codex_responses.py +101 -46
  13. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/loop.py +1 -1
  14. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/runtime.py +51 -29
  15. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/session.py +76 -14
  16. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/themes.py +44 -13
  17. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/turn.py +27 -27
  18. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/app.py +272 -44
  19. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/app_protocol.py +1 -5
  20. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/autocomplete.py +15 -17
  21. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/blocks.py +112 -22
  22. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/chat.py +87 -35
  23. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/commands.py +103 -17
  24. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/floating_list.py +13 -5
  25. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/input.py +50 -3
  26. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/selection_mode.py +2 -0
  27. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/session_ui.py +5 -2
  28. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/styles.py +16 -0
  29. kon_coding_agent-0.3.9/src/kon/ui/terminal_image.py +34 -0
  30. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/tool_output.py +3 -3
  31. kon_coding_agent-0.3.9/src/kon/ui/tree.py +437 -0
  32. kon_coding_agent-0.3.9/src/kon/ui/welcome.py +51 -0
  33. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/widgets.py +27 -9
  34. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_mock_provider.py +1 -1
  35. kon_coding_agent-0.3.9/tests/llm/test_openai_codex_provider_errors.py +371 -0
  36. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_agentic_loop.py +75 -2
  37. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_compaction.py +1 -1
  38. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_handoff.py +1 -1
  39. kon_coding_agent-0.3.9/tests/test_llm_lazy_imports.py +46 -0
  40. kon_coding_agent-0.3.9/tests/test_runtime_switch_model.py +213 -0
  41. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_resume.py +1 -1
  42. kon_coding_agent-0.3.9/tests/test_session_tree.py +43 -0
  43. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_autocomplete.py +6 -4
  44. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_info_bar_permissions.py +3 -3
  45. kon_coding_agent-0.3.9/tests/ui/test_input_approval_submit.py +47 -0
  46. kon_coding_agent-0.3.9/tests/ui/test_input_cursor_theme.py +19 -0
  47. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_keybindings.py +2 -2
  48. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_permissions_command.py +5 -5
  49. kon_coding_agent-0.3.9/tests/ui/test_queue_editing.py +154 -0
  50. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_shell_command_detection.py +32 -13
  51. kon_coding_agent-0.3.9/tests/ui/test_streaming_blocks.py +69 -0
  52. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_thinking_notifications_commands.py +6 -6
  53. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_tool_output_expansion.py +21 -0
  54. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/uv.lock +21 -1
  55. kon_coding_agent-0.3.8/docs/images/kon-screenshot.png +0 -0
  56. kon_coding_agent-0.3.8/src/kon/llm/providers/__init__.py +0 -58
  57. kon_coding_agent-0.3.8/src/kon/ui/welcome.py +0 -85
  58. kon_coding_agent-0.3.8/tests/llm/test_openai_codex_provider_errors.py +0 -79
  59. kon_coding_agent-0.3.8/tests/test_runtime_switch_model.py +0 -92
  60. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.github/workflows/test.yml +0 -0
  61. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.gitignore +0 -0
  62. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-release-publish/SKILL.md +0 -0
  63. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
  64. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
  65. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
  66. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.python-version +0 -0
  67. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/AGENTS.md +0 -0
  68. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/LICENSE +0 -0
  69. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/docs/e2e-test-coverage-review.md +0 -0
  70. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/docs/local-models.md +0 -0
  71. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/scripts/show_themes.py +0 -0
  72. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/__init__.py +0 -0
  73. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/async_utils.py +0 -0
  74. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/builtin_skills/init/SKILL.md +0 -0
  75. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/config.py +0 -0
  76. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/__init__.py +0 -0
  77. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/_xml.py +0 -0
  78. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/agent_mds.py +0 -0
  79. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/git.py +0 -0
  80. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/loader.py +0 -0
  81. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/skills.py +0 -0
  82. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/__init__.py +0 -0
  83. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/compaction.py +0 -0
  84. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/handoff.py +0 -0
  85. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/defaults/__init__.py +0 -0
  86. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/defaults/config.toml +0 -0
  87. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/events.py +0 -0
  88. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/git_branch.py +0 -0
  89. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/base.py +0 -0
  90. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/models.py +0 -0
  91. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/openai.py +0 -0
  92. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/anthropic.py +0 -0
  93. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/azure_ai_foundry.py +0 -0
  94. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/copilot_anthropic.py +0 -0
  95. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/github_copilot_headers.py +0 -0
  96. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/mock.py +0 -0
  97. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_compat.py +0 -0
  98. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_completions.py +0 -0
  99. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_responses.py +0 -0
  100. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/sanitize.py +0 -0
  101. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/notify.py +0 -0
  102. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/permissions.py +0 -0
  103. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/py.typed +0 -0
  104. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/completion.wav +0 -0
  105. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/error.wav +0 -0
  106. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/permission.wav +0 -0
  107. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/__init__.py +0 -0
  108. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/_read_image.py +0 -0
  109. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/_tool_utils.py +0 -0
  110. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/base.py +0 -0
  111. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/bash.py +0 -0
  112. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/edit.py +0 -0
  113. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/find.py +0 -0
  114. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/grep.py +0 -0
  115. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/read.py +0 -0
  116. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/web_fetch.py +0 -0
  117. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/web_search.py +0 -0
  118. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/write.py +0 -0
  119. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools_manager.py +0 -0
  120. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/__init__.py +0 -0
  121. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/clipboard.py +0 -0
  122. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/export.py +0 -0
  123. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/formatting.py +0 -0
  124. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/latex.py +0 -0
  125. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/path_complete.py +0 -0
  126. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/prompt_history.py +0 -0
  127. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/update_check.py +0 -0
  128. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/conftest.py +0 -0
  129. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/context/test_agents.py +0 -0
  130. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/context/test_skills.py +0 -0
  131. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/__init__.py +0 -0
  132. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_anthropic_provider.py +0 -0
  133. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_azure_ai_foundry_provider.py +0 -0
  134. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_openai_oauth.py +0 -0
  135. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_cli_auth_flags.py +0 -0
  136. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_cli_provider_resolution.py +0 -0
  137. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_binaries.py +0 -0
  138. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_error_fallback.py +0 -0
  139. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_injection.py +0 -0
  140. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_migration.py +0 -0
  141. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_git_branch.py +0 -0
  142. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_handoff_link_interrupt.py +0 -0
  143. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_launch_warnings.py +0 -0
  144. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_local_auth_config.py +0 -0
  145. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_model_provider_resolution.py +0 -0
  146. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_notifications_config.py +0 -0
  147. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_notify.py +0 -0
  148. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_openai_compat.py +0 -0
  149. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_permissions.py +0 -0
  150. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_persistence.py +0 -0
  151. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_queries.py +0 -0
  152. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_system_prompt.py +0 -0
  153. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_system_prompt_git_context.py +0 -0
  154. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_themes.py +0 -0
  155. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_tools_manager.py +0 -0
  156. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_ui_notifications.py +0 -0
  157. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_update_check.py +0 -0
  158. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_update_notice_behavior.py +0 -0
  159. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_bash_truncation.py +0 -0
  160. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_diff.py +0 -0
  161. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_edit.py +0 -0
  162. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_edit_display.py +0 -0
  163. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read.py +0 -0
  164. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read_image.py +0 -0
  165. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read_image_integration.py +0 -0
  166. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_subprocess_cancellation.py +0 -0
  167. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_web_fetch.py +0 -0
  168. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_write.py +0 -0
  169. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_app_approval_keys.py +0 -0
  170. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_floating_list.py +0 -0
  171. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_info_bar_clicks.py +0 -0
  172. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_handoff.py +0 -0
  173. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_paste.py +0 -0
  174. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_shell_style.py +0 -0
  175. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_latex.py +0 -0
  176. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_permission_selection_status.py +0 -0
  177. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_prompt_history.py +0 -0
  178. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_status_line.py +0 -0
  179. {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_styles.py +0 -0
@@ -6,6 +6,41 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  - No changes yet.
8
8
 
9
+ ## 0.3.9 - 2026-05-20
10
+
11
+ ### Added
12
+
13
+ - Added inline image display from tool results in chat UI.
14
+ - Added tree view for handoff navigation.
15
+ - Added ability to edit queued messages.
16
+ - Added Kanagawa Dragon theme.
17
+ - Added lazy provider loading for faster startup - @Meltedd.
18
+
19
+ ### Changed
20
+
21
+ - Swapped thinking block keybindings.
22
+ - Grouped preferences under settings.
23
+ - Refreshed startup resource display.
24
+ - Smoother streaming experience.
25
+ - Updated permission mode symbols — single tick for auto, stop icon for prompt.
26
+ - Lowercase slash command descriptions, `L` prefix for queue items.
27
+ - Updated README with ASCII art title, new screenshot, and refined styling.
28
+
29
+ ### Fixed
30
+
31
+ - Fixed Codex provider event handling, transport, and tracking - @Meltedd.
32
+ - Fixed tool expansion crash on startup - @0xku.
33
+ - Fixed Enter key not submitting permission prompts.
34
+ - Fixed manual shell output expand behavior.
35
+ - Fixed streaming cursor removal.
36
+ - Fixed tool output top padding missing blank line.
37
+ - Fixed tree selector display and empty tree state alignment.
38
+ - Fixed input cursor visibility in light themes.
39
+ - Fixed floating list popup styling.
40
+ - Fixed selected color alignment across themes and solarized-light dim color.
41
+ - Fixed legacy Shift+Enter mapping.
42
+ - Fixed Windows startup warning by delaying textual image import - @sukhbinder.
43
+
9
44
  ## 0.3.8 - 2026-05-08
10
45
 
11
46
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kon-coding-agent
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Summary: Minimal coding agent
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -15,11 +15,16 @@ Requires-Dist: pillow>=12.1.1
15
15
  Requires-Dist: pydantic>=2.12.5
16
16
  Requires-Dist: readability-lxml>=0.8.4
17
17
  Requires-Dist: rich>=14.3.2
18
+ Requires-Dist: textual-image[textual]>=0.12.0
18
19
  Requires-Dist: textual>=8.0.0
19
20
  Description-Content-Type: text/markdown
20
21
 
21
- <h1 align="center">Kon</h1>
22
- <p align="center">A minimal terminal coding agent with a small core, strong defaults, and user-owned context.</p>
22
+ <pre align="center">
23
+ ░█░█░█▀█░█▀█
24
+ ░█▀▄░█░█░█░█
25
+ ░▀░▀░▀▀▀░▀░▀
26
+ </pre>
27
+ <p align="center">Minimal coding agent harness</p>
23
28
  <p align="center">
24
29
  <a href="https://pypi.org/project/kon-coding-agent/"><img alt="PyPI" src="https://img.shields.io/pypi/v/kon-coding-agent?style=flat-square" /></a>
25
30
  <a href="https://www.python.org/downloads/release/python-3120/"><img alt="Python" src="https://img.shields.io/badge/python-3.12%2B-blue?style=flat-square" /></a>
@@ -27,7 +32,7 @@ Description-Content-Type: text/markdown
27
32
  </p>
28
33
 
29
34
  <p align="center">
30
- <img src="docs/images/kon-screenshot.png" alt="Kon terminal UI screenshot" width="490" />
35
+ <img src="docs/images/kon-screenshot.png" alt="Kon terminal UI screenshot" width="700" />
31
36
  </p>
32
37
 
33
38
  Kon is a minimal coding agent focused on a tiny core prompt, a small built-in toolset, and project-specific context layered on top only when you want it. The default system prompt stays **under 270 tokens**, and even including the built-in tool descriptions and parameter schemas, the fixed harness stays at about **~1,000 tokens**. The core experience is built around just **6 default tools** plus **2 optional web tools**.
@@ -1,5 +1,9 @@
1
- <h1 align="center">Kon</h1>
2
- <p align="center">A minimal terminal coding agent with a small core, strong defaults, and user-owned context.</p>
1
+ <pre align="center">
2
+ ░█░█░█▀█░█▀█
3
+ ░█▀▄░█░█░█░█
4
+ ░▀░▀░▀▀▀░▀░▀
5
+ </pre>
6
+ <p align="center">Minimal coding agent harness</p>
3
7
  <p align="center">
4
8
  <a href="https://pypi.org/project/kon-coding-agent/"><img alt="PyPI" src="https://img.shields.io/pypi/v/kon-coding-agent?style=flat-square" /></a>
5
9
  <a href="https://www.python.org/downloads/release/python-3120/"><img alt="Python" src="https://img.shields.io/badge/python-3.12%2B-blue?style=flat-square" /></a>
@@ -7,7 +11,7 @@
7
11
  </p>
8
12
 
9
13
  <p align="center">
10
- <img src="docs/images/kon-screenshot.png" alt="Kon terminal UI screenshot" width="490" />
14
+ <img src="docs/images/kon-screenshot.png" alt="Kon terminal UI screenshot" width="700" />
11
15
  </p>
12
16
 
13
17
  Kon is a minimal coding agent focused on a tiny core prompt, a small built-in toolset, and project-specific context layered on top only when you want it. The default system prompt stays **under 270 tokens**, and even including the built-in tool descriptions and parameter schemas, the fixed harness stays at about **~1,000 tokens**. The core experience is built around just **6 default tools** plus **2 optional web tools**.
@@ -14,7 +14,7 @@ default = true
14
14
 
15
15
  [project]
16
16
  name = "kon-coding-agent"
17
- version = "0.3.8"
17
+ version = "0.3.9"
18
18
  description = "Minimal coding agent"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.12"
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "readability-lxml>=0.8.4",
32
32
  "rich>=14.3.2",
33
33
  "textual>=8.0.0",
34
+ "textual-image[textual]>=0.12.0",
34
35
  ]
35
36
 
36
37
  [dependency-groups]
@@ -66,6 +66,7 @@ class ToolCallDelta(BaseModel):
66
66
  type: Literal["tool_call_delta"] = "tool_call_delta"
67
67
  index: int # Correlates with ToolCallStart.index
68
68
  arguments_delta: str
69
+ replace: bool = False
69
70
 
70
71
 
71
72
  class StreamDone(BaseModel):
@@ -10,6 +10,7 @@ from .models import (
10
10
  from .oauth import clear_credentials as clear_copilot_credentials
11
11
  from .oauth import (
12
12
  clear_openai_credentials,
13
+ is_copilot_logged_in,
13
14
  is_openai_logged_in,
14
15
  load_openai_credentials,
15
16
  openai_login,
@@ -18,33 +19,15 @@ from .oauth import get_valid_openai_token as get_openai_token
18
19
  from .oauth import get_valid_token as get_copilot_token
19
20
  from .oauth import load_credentials as load_copilot_credentials
20
21
  from .oauth import login as copilot_login
21
- from .providers import (
22
- API_TYPE_TO_PROVIDER_CLASS,
23
- PROVIDER_API_BY_NAME,
24
- CopilotAnthropicProvider,
25
- CopilotProvider,
26
- CopilotResponsesProvider,
27
- OpenAICodexResponsesProvider,
28
- OpenAICompletionsProvider,
29
- OpenAIResponsesProvider,
30
- is_copilot_logged_in,
31
- resolve_provider_api_type,
32
- )
22
+ from .providers import PROVIDER_API_BY_NAME, get_provider_class, resolve_provider_api_type
33
23
 
34
24
  __all__ = [
35
- "API_TYPE_TO_PROVIDER_CLASS",
36
25
  "DEFAULT_THINKING_LEVELS",
37
26
  "PROVIDER_API_BY_NAME",
38
27
  "ApiType",
39
28
  "BaseProvider",
40
- "CopilotAnthropicProvider",
41
- "CopilotProvider",
42
- "CopilotResponsesProvider",
43
29
  "LLMStream",
44
30
  "Model",
45
- "OpenAICodexResponsesProvider",
46
- "OpenAICompletionsProvider",
47
- "OpenAIResponsesProvider",
48
31
  "ProviderConfig",
49
32
  "clear_copilot_credentials",
50
33
  "clear_openai_credentials",
@@ -55,6 +38,7 @@ __all__ = [
55
38
  "get_model",
56
39
  "get_models_by_provider",
57
40
  "get_openai_token",
41
+ "get_provider_class",
58
42
  "is_copilot_logged_in",
59
43
  "is_openai_logged_in",
60
44
  "load_copilot_credentials",
@@ -5,6 +5,7 @@ from .copilot import (
5
5
  get_base_url_from_token,
6
6
  get_copilot_auth_path,
7
7
  get_valid_token,
8
+ is_copilot_logged_in,
8
9
  load_credentials,
9
10
  login,
10
11
  )
@@ -29,6 +30,7 @@ __all__ = [
29
30
  "get_openai_auth_path",
30
31
  "get_valid_openai_token",
31
32
  "get_valid_token",
33
+ "is_copilot_logged_in",
32
34
  "is_openai_logged_in",
33
35
  "load_credentials",
34
36
  "load_openai_credentials",
@@ -86,6 +86,10 @@ def clear_credentials() -> None:
86
86
  path.unlink()
87
87
 
88
88
 
89
+ def is_copilot_logged_in() -> bool:
90
+ return load_credentials() is not None
91
+
92
+
89
93
  def _get_urls(domain: str) -> dict[str, str]:
90
94
  return {
91
95
  "device_code": f"https://{domain}/login/device/code",
@@ -0,0 +1,61 @@
1
+ from ..base import BaseProvider
2
+ from ..models import ApiType
3
+
4
+ PROVIDER_API_BY_NAME: dict[str, ApiType] = {
5
+ "openai": ApiType.OPENAI_COMPLETIONS,
6
+ "zhipu": ApiType.OPENAI_COMPLETIONS,
7
+ "deepseek": ApiType.OPENAI_COMPLETIONS,
8
+ "github-copilot": ApiType.GITHUB_COPILOT,
9
+ "openai-responses": ApiType.OPENAI_RESPONSES,
10
+ "openai-codex": ApiType.OPENAI_CODEX_RESPONSES,
11
+ "azure-ai-foundry": ApiType.AZURE_AI_FOUNDRY,
12
+ }
13
+
14
+
15
+ def resolve_provider_api_type(provider: str | None) -> ApiType:
16
+ if provider is None:
17
+ return ApiType.OPENAI_COMPLETIONS
18
+
19
+ api_type = PROVIDER_API_BY_NAME.get(provider)
20
+ if api_type is None:
21
+ valid = ", ".join(sorted(PROVIDER_API_BY_NAME))
22
+ raise ValueError(f"Unknown provider '{provider}'. Valid providers: {valid}")
23
+
24
+ return api_type
25
+
26
+
27
+ def get_provider_class(api_type: ApiType) -> type[BaseProvider]:
28
+ match api_type:
29
+ case ApiType.GITHUB_COPILOT:
30
+ from .copilot import CopilotProvider
31
+
32
+ return CopilotProvider
33
+ case ApiType.GITHUB_COPILOT_RESPONSES:
34
+ from .copilot import CopilotResponsesProvider
35
+
36
+ return CopilotResponsesProvider
37
+ case ApiType.OPENAI_RESPONSES:
38
+ from .openai_responses import OpenAIResponsesProvider
39
+
40
+ return OpenAIResponsesProvider
41
+ case ApiType.OPENAI_CODEX_RESPONSES:
42
+ from .openai_codex_responses import OpenAICodexResponsesProvider
43
+
44
+ return OpenAICodexResponsesProvider
45
+ case ApiType.ANTHROPIC_COPILOT:
46
+ from .copilot_anthropic import CopilotAnthropicProvider
47
+
48
+ return CopilotAnthropicProvider
49
+ case ApiType.AZURE_AI_FOUNDRY:
50
+ from .azure_ai_foundry import AzureAIFoundryProvider
51
+
52
+ return AzureAIFoundryProvider
53
+ case ApiType.OPENAI_COMPLETIONS:
54
+ from .openai_completions import OpenAICompletionsProvider
55
+
56
+ return OpenAICompletionsProvider
57
+
58
+ raise ValueError(f"Unsupported API type: {api_type.value}")
59
+
60
+
61
+ __all__ = ["PROVIDER_API_BY_NAME", "get_provider_class", "resolve_provider_api_type"]
@@ -142,7 +142,3 @@ class CopilotResponsesProvider(OpenAIResponsesProvider):
142
142
  temperature=temperature,
143
143
  max_tokens=max_tokens,
144
144
  )
145
-
146
-
147
- def is_copilot_logged_in() -> bool:
148
- return load_credentials() is not None
@@ -22,11 +22,12 @@ from ...core.types import (
22
22
  ToolDefinition,
23
23
  Usage,
24
24
  )
25
- from ..base import BaseProvider, LLMStream, ProviderConfig
25
+ from ..base import BaseProvider, LLMStream
26
26
  from ..oauth.openai import get_valid_openai_token, load_openai_credentials
27
27
 
28
28
  _MAX_RETRIES = 3
29
29
  _BASE_DELAY_MS = 1000
30
+ _CONNECT_TIMEOUT_SECONDS = 30
30
31
  _OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06"
31
32
  _WS_FALLBACK_SESSIONS: set[str] = set()
32
33
 
@@ -36,7 +37,9 @@ class CodexTransportError(Exception):
36
37
 
37
38
 
38
39
  class CodexNonTransportError(Exception):
39
- pass
40
+ def __init__(self, message: str, *, status: int | None = None) -> None:
41
+ super().__init__(message)
42
+ self.status = status
40
43
 
41
44
 
42
45
  def _format_provider_error(error: Exception) -> str:
@@ -54,10 +57,6 @@ class OpenAICodexResponsesProvider(BaseProvider):
54
57
  name = "openai-codex"
55
58
  thinking_levels: list[str] = ["none", "minimal", "low", "medium", "high", "xhigh"] # noqa: RUF012
56
59
 
57
- def __init__(self, config: ProviderConfig):
58
- super().__init__(config)
59
- self._websocket_fallback = False
60
-
61
60
  async def _stream_impl(
62
61
  self,
63
62
  messages: list[Message],
@@ -207,9 +206,7 @@ class OpenAICodexResponsesProvider(BaseProvider):
207
206
  body = self._build_request_body(messages, system_prompt, tools, temperature)
208
207
  session_id = self.config.session_id
209
208
 
210
- if session_id in _WS_FALLBACK_SESSIONS:
211
- self._websocket_fallback = True
212
- else:
209
+ if session_id not in _WS_FALLBACK_SESSIONS:
213
210
  emitted = False
214
211
  try:
215
212
  websocket_events = self._stream_websocket_events(
@@ -222,11 +219,10 @@ class OpenAICodexResponsesProvider(BaseProvider):
222
219
  except CodexNonTransportError as e:
223
220
  yield StreamError(error=_format_provider_error(e))
224
221
  return
225
- except Exception as e:
222
+ except (CodexTransportError, aiohttp.ClientError, OSError) as e:
226
223
  if emitted:
227
224
  yield StreamError(error=_format_provider_error(e))
228
225
  return
229
- self._websocket_fallback = True
230
226
  if session_id:
231
227
  _WS_FALLBACK_SESSIONS.add(session_id)
232
228
 
@@ -241,9 +237,30 @@ class OpenAICodexResponsesProvider(BaseProvider):
241
237
  self, events: AsyncIterator[dict[str, Any]], llm_stream: LLMStream
242
238
  ) -> AsyncIterator[StreamPart]:
243
239
  current_tool_calls: dict[str, dict[str, Any]] = {}
240
+ call_key_by_item_id: dict[str, str] = {}
244
241
  last_tool_call_id: str | None = None
245
242
  tool_call_index = 0
246
243
 
244
+ def _resolve_key(item_id: Any) -> str | None:
245
+ if isinstance(item_id, str) and item_id:
246
+ return call_key_by_item_id.get(item_id)
247
+ return last_tool_call_id
248
+
249
+ def _reconcile(call_data: dict[str, Any], final_args: str) -> ToolCallDelta | None:
250
+ current_args = call_data["arguments"]
251
+ if final_args.startswith(current_args):
252
+ missing = final_args[len(current_args) :]
253
+ if not missing:
254
+ return None
255
+ call_data["arguments"] += missing
256
+ return ToolCallDelta(index=call_data["index"], arguments_delta=missing)
257
+ if final_args == current_args:
258
+ return None
259
+ call_data["arguments"] = final_args
260
+ return ToolCallDelta(
261
+ index=call_data["index"], arguments_delta=final_args, replace=True
262
+ )
263
+
247
264
  async for event in events:
248
265
  event_type = event.get("type")
249
266
  if not isinstance(event_type, str):
@@ -267,32 +284,65 @@ class OpenAICodexResponsesProvider(BaseProvider):
267
284
  name = item.get("name")
268
285
  if isinstance(call_id, str) and isinstance(name, str):
269
286
  full_id = f"{call_id}|{item_id}" if isinstance(item_id, str) else call_id
287
+ initial_args = item.get("arguments")
288
+ initial_args_text = initial_args if isinstance(initial_args, str) else ""
270
289
  current_tool_calls[full_id] = {
271
- "id": full_id,
272
- "name": name,
273
- "arguments": "",
290
+ "arguments": initial_args_text,
274
291
  "index": tool_call_index,
275
292
  }
293
+ if isinstance(item_id, str) and item_id:
294
+ call_key_by_item_id[item_id] = full_id
276
295
  last_tool_call_id = full_id
277
296
  yield ToolCallStart(index=tool_call_index, id=full_id, name=name)
297
+ if initial_args_text:
298
+ yield ToolCallDelta(
299
+ index=tool_call_index, arguments_delta=initial_args_text
300
+ )
278
301
  tool_call_index += 1
279
302
 
280
303
  elif event_type == "response.function_call_arguments.delta":
281
304
  delta = event.get("delta")
282
- if (
283
- isinstance(delta, str)
284
- and last_tool_call_id
285
- and last_tool_call_id in current_tool_calls
286
- ):
287
- current_tool_calls[last_tool_call_id]["arguments"] += delta
288
- idx = int(current_tool_calls[last_tool_call_id]["index"])
289
- yield ToolCallDelta(index=idx, arguments_delta=delta)
305
+ if not isinstance(delta, str):
306
+ continue
307
+ call_key = _resolve_key(event.get("item_id"))
308
+ if not call_key or call_key not in current_tool_calls:
309
+ continue
310
+ call_data = current_tool_calls[call_key]
311
+ call_data["arguments"] += delta
312
+ yield ToolCallDelta(index=call_data["index"], arguments_delta=delta)
313
+
314
+ elif event_type == "response.function_call_arguments.done":
315
+ final_args = event.get("arguments")
316
+ if not isinstance(final_args, str):
317
+ continue
318
+ call_key = _resolve_key(event.get("item_id"))
319
+ if not call_key or call_key not in current_tool_calls:
320
+ continue
321
+ call_data = current_tool_calls[call_key]
322
+ delta_part = _reconcile(call_data, final_args)
323
+ if delta_part is not None:
324
+ yield delta_part
325
+
326
+ elif event_type == "response.output_item.done":
327
+ item = event.get("item")
328
+ if not isinstance(item, dict) or item.get("type") != "function_call":
329
+ continue
330
+ final_args = item.get("arguments")
331
+ if not isinstance(final_args, str):
332
+ continue
333
+ call_key = _resolve_key(item.get("id"))
334
+ if not call_key or call_key not in current_tool_calls:
335
+ continue
336
+ call_data = current_tool_calls[call_key]
337
+ delta_part = _reconcile(call_data, final_args)
338
+ if delta_part is not None:
339
+ yield delta_part
290
340
 
291
341
  elif event_type in {"response.completed", "response.done", "response.incomplete"}:
292
342
  response_obj = event.get("response")
293
343
  if isinstance(response_obj, dict):
294
344
  self._apply_response_metadata(response_obj, llm_stream)
295
- stop_reason = self._map_stop_reason(response_obj.get("status"))
345
+ stop_reason = self._map_stop_reason(response_obj)
296
346
  if current_tool_calls and stop_reason == StopReason.STOP:
297
347
  stop_reason = StopReason.TOOL_USE
298
348
  yield StreamDone(stop_reason=stop_reason)
@@ -324,14 +374,14 @@ class OpenAICodexResponsesProvider(BaseProvider):
324
374
  if isinstance(usage, dict):
325
375
  input_details = usage.get("input_tokens_details")
326
376
  cached = 0
327
- cache_write = int(usage.get("cache_write_tokens") or 0)
377
+ cache_write_value = usage.get("cache_write_tokens")
328
378
  if isinstance(input_details, dict):
329
379
  cached = int(input_details.get("cached_tokens") or 0)
330
- cache_write = int(
331
- input_details.get("cache_write_tokens")
332
- or input_details.get("cache_creation_tokens")
333
- or cache_write
334
- )
380
+ if "cache_write_tokens" in input_details:
381
+ cache_write_value = input_details["cache_write_tokens"]
382
+ elif "cache_creation_tokens" in input_details:
383
+ cache_write_value = input_details["cache_creation_tokens"]
384
+ cache_write = int(cache_write_value) if cache_write_value is not None else 0
335
385
  input_tokens = int(usage.get("input_tokens") or 0)
336
386
  non_cached_input = max(input_tokens - cached, 0)
337
387
  llm_stream._usage = Usage(
@@ -348,7 +398,9 @@ class OpenAICodexResponsesProvider(BaseProvider):
348
398
  self, body: dict[str, Any], headers: dict[str, str]
349
399
  ) -> AsyncIterator[dict[str, Any]]:
350
400
  last_error: str | None = None
351
- timeout = aiohttp.ClientTimeout(total=kon_config.llm.request_timeout_seconds)
401
+ timeout = aiohttp.ClientTimeout(
402
+ sock_connect=_CONNECT_TIMEOUT_SECONDS, sock_read=kon_config.llm.request_timeout_seconds
403
+ )
352
404
  async with aiohttp.ClientSession(timeout=timeout) as session:
353
405
  response: aiohttp.ClientResponse | None = None
354
406
  for attempt in range(_MAX_RETRIES + 1):
@@ -361,7 +413,7 @@ class OpenAICodexResponsesProvider(BaseProvider):
361
413
  delay = _BASE_DELAY_MS * (2**attempt) / 1000
362
414
  await asyncio.sleep(delay)
363
415
  continue
364
- raise CodexNonTransportError(last_error)
416
+ raise CodexNonTransportError(last_error, status=response.status)
365
417
 
366
418
  if response is None or response.status >= 400:
367
419
  raise CodexNonTransportError(last_error or "Codex request failed after retries")
@@ -372,10 +424,15 @@ class OpenAICodexResponsesProvider(BaseProvider):
372
424
  async def _stream_websocket_events(
373
425
  self, body: dict[str, Any], headers: dict[str, str]
374
426
  ) -> AsyncIterator[dict[str, Any]]:
375
- timeout = aiohttp.ClientTimeout(total=kon_config.llm.request_timeout_seconds)
427
+ timeout = aiohttp.ClientTimeout(
428
+ sock_connect=_CONNECT_TIMEOUT_SECONDS, sock_read=kon_config.llm.request_timeout_seconds
429
+ )
430
+ ws_timeout = aiohttp.ClientWSTimeout(ws_receive=kon_config.llm.request_timeout_seconds) # type: ignore[call-arg]
376
431
  async with (
377
432
  aiohttp.ClientSession(timeout=timeout) as session,
378
- session.ws_connect(self._resolve_websocket_url(), headers=headers) as ws,
433
+ session.ws_connect(
434
+ self._resolve_websocket_url(), headers=headers, heartbeat=20, timeout=ws_timeout
435
+ ) as ws,
379
436
  ):
380
437
  await ws.send_json({"type": "response.create", **body})
381
438
  saw_completion = False
@@ -405,12 +462,6 @@ class OpenAICodexResponsesProvider(BaseProvider):
405
462
  elif msg.type == aiohttp.WSMsgType.ERROR:
406
463
  error = ws.exception()
407
464
  raise CodexTransportError(str(error) if error else "WebSocket error")
408
- elif msg.type in {
409
- aiohttp.WSMsgType.CLOSE,
410
- aiohttp.WSMsgType.CLOSED,
411
- aiohttp.WSMsgType.CLOSING,
412
- }:
413
- break
414
465
 
415
466
  if not saw_completion:
416
467
  raise CodexTransportError("WebSocket stream closed before response.completed")
@@ -436,19 +487,23 @@ class OpenAICodexResponsesProvider(BaseProvider):
436
487
  if isinstance(parsed, dict):
437
488
  yield parsed
438
489
 
439
- def _map_stop_reason(self, status: Any) -> StopReason:
490
+ def _map_stop_reason(self, response_obj: dict[str, Any]) -> StopReason:
491
+ status = response_obj.get("status")
440
492
  if status == "completed":
441
493
  return StopReason.STOP
442
494
  if status == "incomplete":
495
+ details = response_obj.get("incomplete_details")
496
+ reason = details.get("reason") if isinstance(details, dict) else None
497
+ if reason == "content_filter":
498
+ return StopReason.ERROR
443
499
  return StopReason.LENGTH
444
500
  if status in {"failed", "cancelled"}:
445
501
  return StopReason.ERROR
446
502
  return StopReason.STOP
447
503
 
448
504
  def should_retry_for_error(self, error: Exception) -> bool:
449
- msg = str(error).lower()
450
- return any(kw in msg for kw in ("429", "rate_limit", "server_error", "502", "503", "504"))
451
-
452
-
453
- def is_openai_logged_in() -> bool:
454
- return load_openai_credentials() is not None
505
+ if isinstance(error, CodexTransportError):
506
+ return True
507
+ if isinstance(error, CodexNonTransportError) and error.status is not None:
508
+ return _is_retryable_status(error.status)
509
+ return False
@@ -245,7 +245,7 @@ class Agent:
245
245
  # Get the latest assistant message that has usage.
246
246
  # The most recent assistant entry can be interrupted/error and have no usage.
247
247
  last_usage: Usage | None = None
248
- for entry in reversed(self.session.entries):
248
+ for entry in reversed(self.session.active_entries):
249
249
  if isinstance(entry, MessageEntry) and isinstance(entry.message, AssistantMessage):
250
250
  usage = entry.message.usage
251
251
  if usage is None: