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.
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/CHANGELOG.md +35 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/PKG-INFO +9 -4
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/README.md +7 -3
- kon_coding_agent-0.3.9/docs/images/kon-screenshot.png +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/pyproject.toml +2 -1
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/types.py +1 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/__init__.py +3 -19
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/__init__.py +2 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/copilot.py +4 -0
- kon_coding_agent-0.3.9/src/kon/llm/providers/__init__.py +61 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/copilot.py +0 -4
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_codex_responses.py +101 -46
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/loop.py +1 -1
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/runtime.py +51 -29
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/session.py +76 -14
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/themes.py +44 -13
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/turn.py +27 -27
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/app.py +272 -44
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/app_protocol.py +1 -5
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/autocomplete.py +15 -17
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/blocks.py +112 -22
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/chat.py +87 -35
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/commands.py +103 -17
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/floating_list.py +13 -5
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/input.py +50 -3
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/selection_mode.py +2 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/session_ui.py +5 -2
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/styles.py +16 -0
- kon_coding_agent-0.3.9/src/kon/ui/terminal_image.py +34 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/tool_output.py +3 -3
- kon_coding_agent-0.3.9/src/kon/ui/tree.py +437 -0
- kon_coding_agent-0.3.9/src/kon/ui/welcome.py +51 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/widgets.py +27 -9
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_mock_provider.py +1 -1
- kon_coding_agent-0.3.9/tests/llm/test_openai_codex_provider_errors.py +371 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_agentic_loop.py +75 -2
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_compaction.py +1 -1
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_handoff.py +1 -1
- kon_coding_agent-0.3.9/tests/test_llm_lazy_imports.py +46 -0
- kon_coding_agent-0.3.9/tests/test_runtime_switch_model.py +213 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_resume.py +1 -1
- kon_coding_agent-0.3.9/tests/test_session_tree.py +43 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_autocomplete.py +6 -4
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_info_bar_permissions.py +3 -3
- kon_coding_agent-0.3.9/tests/ui/test_input_approval_submit.py +47 -0
- kon_coding_agent-0.3.9/tests/ui/test_input_cursor_theme.py +19 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_keybindings.py +2 -2
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_permissions_command.py +5 -5
- kon_coding_agent-0.3.9/tests/ui/test_queue_editing.py +154 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_shell_command_detection.py +32 -13
- kon_coding_agent-0.3.9/tests/ui/test_streaming_blocks.py +69 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_thinking_notifications_commands.py +6 -6
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_tool_output_expansion.py +21 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/uv.lock +21 -1
- kon_coding_agent-0.3.8/docs/images/kon-screenshot.png +0 -0
- kon_coding_agent-0.3.8/src/kon/llm/providers/__init__.py +0 -58
- kon_coding_agent-0.3.8/src/kon/ui/welcome.py +0 -85
- kon_coding_agent-0.3.8/tests/llm/test_openai_codex_provider_errors.py +0 -79
- kon_coding_agent-0.3.8/tests/test_runtime_switch_model.py +0 -92
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.github/workflows/test.yml +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.gitignore +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-release-publish/SKILL.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/.python-version +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/AGENTS.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/LICENSE +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/docs/e2e-test-coverage-review.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/docs/local-models.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/scripts/show_themes.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/async_utils.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/builtin_skills/init/SKILL.md +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/config.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/_xml.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/agent_mds.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/git.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/loader.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/context/skills.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/compaction.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/core/handoff.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/defaults/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/defaults/config.toml +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/events.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/git_branch.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/base.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/models.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/oauth/openai.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/anthropic.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/azure_ai_foundry.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/copilot_anthropic.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/github_copilot_headers.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/mock.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_compat.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_completions.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_responses.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/sanitize.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/notify.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/permissions.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/py.typed +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/completion.wav +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/error.wav +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/sounds/permission.wav +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/_read_image.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/_tool_utils.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/base.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/bash.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/edit.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/find.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/grep.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/read.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/web_fetch.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/web_search.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools/write.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/tools_manager.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/clipboard.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/export.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/formatting.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/latex.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/path_complete.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/ui/prompt_history.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/update_check.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/conftest.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/context/test_agents.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/context/test_skills.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/__init__.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_anthropic_provider.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_azure_ai_foundry_provider.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/llm/test_openai_oauth.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_cli_auth_flags.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_cli_provider_resolution.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_binaries.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_error_fallback.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_injection.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_config_migration.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_git_branch.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_handoff_link_interrupt.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_launch_warnings.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_local_auth_config.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_model_provider_resolution.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_notifications_config.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_notify.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_openai_compat.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_permissions.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_persistence.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_session_queries.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_system_prompt.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_system_prompt_git_context.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_themes.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_tools_manager.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_ui_notifications.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_update_check.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/test_update_notice_behavior.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_bash_truncation.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_diff.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_edit.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_edit_display.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read_image.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_read_image_integration.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_subprocess_cancellation.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_web_fetch.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/tools/test_write.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_app_approval_keys.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_floating_list.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_info_bar_clicks.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_handoff.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_paste.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_input_shell_style.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_latex.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_permission_selection_status.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_prompt_history.py +0 -0
- {kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/tests/ui/test_status_line.py +0 -0
- {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.
|
|
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
|
-
<
|
|
22
|
-
|
|
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="
|
|
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
|
-
<
|
|
2
|
-
|
|
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="
|
|
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**.
|
|
Binary file
|
|
@@ -14,7 +14,7 @@ default = true
|
|
|
14
14
|
|
|
15
15
|
[project]
|
|
16
16
|
name = "kon-coding-agent"
|
|
17
|
-
version = "0.3.
|
|
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]
|
|
@@ -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"]
|
{kon_coding_agent-0.3.8 → kon_coding_agent-0.3.9}/src/kon/llm/providers/openai_codex_responses.py
RENAMED
|
@@ -22,11 +22,12 @@ from ...core.types import (
|
|
|
22
22
|
ToolDefinition,
|
|
23
23
|
Usage,
|
|
24
24
|
)
|
|
25
|
-
from ..base import BaseProvider, LLMStream
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
input_details
|
|
332
|
-
|
|
333
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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.
|
|
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:
|