kon-coding-agent 0.2.0__tar.gz → 0.2.2__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 (104) hide show
  1. kon_coding_agent-0.2.2/.kon/skills/kon-release-publish/SKILL.md +85 -0
  2. kon_coding_agent-0.2.2/LOCAL.md +25 -0
  3. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/PKG-INFO +19 -20
  4. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/README.md +18 -19
  5. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/TODO.md +0 -2
  6. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/pyproject.toml +1 -1
  7. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/config.py +10 -1
  8. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/defaults/config.toml +4 -1
  9. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/events.py +9 -0
  10. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/mock.py +40 -0
  11. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/loop.py +9 -4
  12. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/session.py +16 -7
  13. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/turn.py +31 -1
  14. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/app.py +29 -6
  15. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/commands.py +13 -3
  16. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/session_ui.py +8 -3
  17. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/widgets.py +10 -1
  18. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_agentic_loop.py +49 -0
  19. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_compaction.py +2 -2
  20. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_session_persistence.py +20 -6
  21. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/uv.lock +1 -1
  22. kon_coding_agent-0.2.0/LOCAL.md +0 -34
  23. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/.gitignore +0 -0
  24. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
  25. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
  26. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
  27. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/.python-version +0 -0
  28. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/AGENTS.md +0 -0
  29. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/LICENSE +0 -0
  30. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/scripts/test_models.py +0 -0
  31. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/scripts/test_thinking_blocks.py +0 -0
  32. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/__init__.py +0 -0
  33. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/context/__init__.py +0 -0
  34. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/context/agents.py +0 -0
  35. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/context/loader.py +0 -0
  36. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/context/shared.py +0 -0
  37. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/context/skills.py +0 -0
  38. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/core/__init__.py +0 -0
  39. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/core/compaction.py +0 -0
  40. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/core/types.py +0 -0
  41. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/defaults/__init__.py +0 -0
  42. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/__init__.py +0 -0
  43. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/base.py +0 -0
  44. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/models.py +0 -0
  45. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/oauth/__init__.py +0 -0
  46. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/oauth/copilot.py +0 -0
  47. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/oauth/openai.py +0 -0
  48. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/__init__.py +0 -0
  49. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/anthropic.py +0 -0
  50. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/copilot.py +0 -0
  51. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/copilot_anthropic.py +0 -0
  52. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/github_copilot_headers.py +0 -0
  53. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/openai_codex_responses.py +0 -0
  54. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/openai_completions.py +0 -0
  55. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/openai_responses.py +0 -0
  56. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/llm/providers/sanitize.py +0 -0
  57. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/py.typed +0 -0
  58. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/shared.py +0 -0
  59. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/__init__.py +0 -0
  60. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/_read_image.py +0 -0
  61. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/base.py +0 -0
  62. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/bash.py +0 -0
  63. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/edit.py +0 -0
  64. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/find.py +0 -0
  65. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/grep.py +0 -0
  66. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/read.py +0 -0
  67. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools/write.py +0 -0
  68. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/tools_manager.py +0 -0
  69. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/__init__.py +0 -0
  70. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/app_protocol.py +0 -0
  71. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/autocomplete.py +0 -0
  72. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/blocks.py +0 -0
  73. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/chat.py +0 -0
  74. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/clipboard.py +0 -0
  75. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/export.py +0 -0
  76. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/floating_list.py +0 -0
  77. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/formatting.py +0 -0
  78. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/input.py +0 -0
  79. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/path_complete.py +0 -0
  80. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/selection_mode.py +0 -0
  81. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/ui/styles.py +0 -0
  82. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/src/kon/update_check.py +0 -0
  83. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/conftest.py +0 -0
  84. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/context/test_agents.py +0 -0
  85. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/context/test_skills.py +0 -0
  86. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/llm/__init__.py +0 -0
  87. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/llm/test_mock_provider.py +0 -0
  88. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_cli_provider_resolution.py +0 -0
  89. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_config_binaries.py +0 -0
  90. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_config_error_fallback.py +0 -0
  91. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_config_injection.py +0 -0
  92. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_model_provider_resolution.py +0 -0
  93. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_system_prompt.py +0 -0
  94. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_update_check.py +0 -0
  95. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/test_update_notice_behavior.py +0 -0
  96. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_diff.py +0 -0
  97. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_edit.py +0 -0
  98. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_read.py +0 -0
  99. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_read_image.py +0 -0
  100. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_read_image_integration.py +0 -0
  101. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/tools/test_write.py +0 -0
  102. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/ui/test_autocomplete.py +0 -0
  103. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/ui/test_floating_list.py +0 -0
  104. {kon_coding_agent-0.2.0 → kon_coding_agent-0.2.2}/tests/ui/test_input_paste.py +0 -0
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: kon-release-publish
3
+ description: Tag, publish to PyPI, and create GitHub release for Kon with validation and rollback-safe steps
4
+ ---
5
+
6
+ # Kon Release + PyPI Publish
7
+
8
+ Use this skill when the user asks to cut a new Kon version, tag it, publish to PyPI, and/or create a GitHub release.
9
+
10
+ ## Inputs to confirm
11
+
12
+ - Target version (example: `0.2.1`)
13
+ - Base range for notes (usually previous tag, example: `v0.2.0..HEAD`)
14
+ - Whether to push `main`
15
+ - Whether to publish to PyPI now
16
+ - Whether to create GitHub release now
17
+
18
+ ## Files to bump
19
+
20
+ - `pyproject.toml` → `[project].version`
21
+ - `src/kon/ui/app.py` → fallback `VERSION = "..."`
22
+ - `uv.lock` → local package version block
23
+
24
+ ## Release workflow
25
+
26
+ 1. **Preflight**
27
+ - `git status --short --branch` must be clean (or confirm with user)
28
+ - `git tag --list` and `git log --oneline <prev_tag>..HEAD` to summarize changes
29
+
30
+ 2. **Version bump**
31
+ - Update version in all 3 files above
32
+
33
+ 3. **Quality gates**
34
+ - `uv run ruff format .`
35
+ - `uv run ruff check .`
36
+ - `uv run pyright .`
37
+ - `uv run pytest`
38
+
39
+ 4. **Commit**
40
+ - Commit message: `build: bump version to <version>`
41
+
42
+ 5. **Tag**
43
+ - Annotated tag: `git tag -a v<version> -m "v<version> ..."`
44
+ - Include concise “changes since previous tag” bullets
45
+
46
+ 6. **Push**
47
+ - `git push origin main`
48
+ - `git push origin v<version>`
49
+
50
+ 7. **Build + verify artifacts**
51
+ - `rm -rf dist && uv build`
52
+ - `uv run python -m twine check dist/*`
53
+
54
+ 8. **Publish to PyPI**
55
+ - Prefer token file if present (example `~/.pypi-token`):
56
+ - `TWINE_USERNAME=__token__ TWINE_PASSWORD="$(< ~/.pypi-token)" uv run python -m twine upload dist/*`
57
+ - Verify:
58
+ - `https://pypi.org/project/kon-coding-agent/<version>/`
59
+ - `https://pypi.org/pypi/kon-coding-agent/json` reports latest version
60
+
61
+ 9. **Create GitHub release**
62
+ - If token exists at `~/.github-token`, call Releases API:
63
+ - `POST /repos/<owner>/<repo>/releases` with:
64
+ - `tag_name: v<version>`
65
+ - `target_commitish: main`
66
+ - `name: v<version>`
67
+ - `generate_release_notes: true`
68
+ - If 403 occurs, report missing token scopes/permissions (`contents:write` required)
69
+
70
+ ## Important notes
71
+
72
+ - **Tagging and GitHub release are separate**:
73
+ - Tag = git ref in repository
74
+ - Release = GitHub object attached to a tag (notes/assets)
75
+ - You can do either independently, but most projects do both together for user-facing releases.
76
+ - If PyPI publish succeeds but GitHub release fails, do **not** retag/re-publish. Just fix auth and create the release for the existing tag.
77
+
78
+ ## Output checklist to report
79
+
80
+ - Version bumped in all files
81
+ - Checks passed
82
+ - Commit hash
83
+ - Tag created and pushed
84
+ - PyPI upload URL
85
+ - GitHub release URL (or exact error + remediation)
@@ -0,0 +1,25 @@
1
+ # Local Models
2
+
3
+ This document provides detailed information about running and configuring local models with Kon.
4
+
5
+ ## Tested Models
6
+
7
+ | Model | Quantization | Context Length | TPS | System Specs |
8
+ | ----- | -------------- | -------------- | --- | ------------ |
9
+ | `qwen/qwen3-coder-next` | Q4_K_M | 64,000 | N/A | i7-14700F × 28, 64GB RAM, 24GB VRAM (RTX 3090) |
10
+ | `zai-org/glm-4.7-flash` | Q4_K_M | 64,000 | ~80-90 | i7-14700F × 28, 64GB RAM, 24GB VRAM (RTX 3090) |
11
+
12
+ Run a local model using llama-server with the following command:
13
+
14
+ ```bash
15
+ ./llama-server -m <models-dir>/GLM-4.7-Flash-GGUF/GLM-4.7-Flash-Q4_K_M.gguf -n 8192 -c 64000
16
+ ```
17
+
18
+ Then start kon:
19
+
20
+ ```bash
21
+ kon --model zai-org/glm-4.7-flash --provider openai --base-url http://localhost:8080/v1 --api-key ""
22
+ ```
23
+
24
+ > [!NOTE]
25
+ > I was not able to run qwen-coder-next reliably on my system. Either the provider config had some issues or it's too big for my system (i'm not sure)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kon-coding-agent
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Minimal coding agent
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -215,34 +215,32 @@ UI (app.py)
215
215
 
216
216
  ## Supported Models
217
217
 
218
- Kon works well with local models exposed through an OpenAI-compatible `/v1` API (for example LM Studio).
218
+ Kon works well with local models exposed through an OpenAI-compatible `/v1` API.
219
219
 
220
- ### Example on LM Studio
220
+ ### Example using llama-server
221
221
 
222
- To run a local model from LM Studio:
222
+ To run a local model using llama-server:
223
223
 
224
224
  ```bash
225
- # GLM-4.7-flash
226
- kon --provider openai-responses \
227
- --base-url http://127.0.0.1:1234/v1 \
228
- --model zai-org/glm-4.7-flash \
229
- --api-key ""
230
-
231
- # Qwen3-coder-next
232
- kon --provider openai-responses \
233
- --base-url http://127.0.0.1:1234/v1 \
234
- --model qwen/qwen3-coder-next \
235
- --api-key ""
225
+ ./llama-server -m <models-dir>/GLM-4.7-Flash-GGUF/GLM-4.7-Flash-Q4_K_M.gguf \
226
+ -n 8192 \
227
+ -c 64000
228
+
229
+ # Then use Kon with:
230
+ kon --model zai-org/glm-4.7-flash \
231
+ --provider openai \
232
+ --base-url http://localhost:8080/v1 \
233
+ --api-key ""
236
234
  ```
237
235
 
238
- For detailed configuration and performance benchmarks, see [LOCAL.md](LOCAL.md).
236
+ `GLM-4.7-Flash-Q4` ran at 80-90 tps on my i7-14700F × 28, 64GB RAM, 24GB VRAM (RTX 3090)
239
237
 
240
238
  ### All Supported Providers
241
239
 
242
240
  | Model (local=*) | Provider | Thinking | Vision |
243
241
  | ----- | -------- | -------- | ------ |
244
- | `*zai-org/glm-4.7-flash` | OpenAI Responses | Yes | No |
245
- | `*qwen/qwen3-coder-next` | OpenAI Responses | Yes | No |
242
+ | `*zai-org/glm-4.7-flash` | OpenAI Completions | Yes | No |
243
+ | `*qwen/qwen3-coder-next` | OpenAI Completions | Yes | No |
246
244
  | `glm-4.7` | ZhiPu (OpenAI Completions) | Yes | No |
247
245
  | `glm-5` | ZhiPu (OpenAI Completions) | Yes | No |
248
246
  | `claude-sonnet-4.5` | GitHub Copilot | Yes | Yes |
@@ -264,7 +262,8 @@ Most important knobs:
264
262
  - `llm.default_thinking_level`
265
263
  - `llm.system_prompt` (**you can fully override Kon’s system prompt here**)
266
264
  - `llm.tool_call_idle_timeout_seconds` (fallback timeout for stalled tool-call streaming)
267
- - `compaction.on_overflow`, `compaction.buffer_tokens`, `compaction.default_context_window`
265
+ - `compaction.on_overflow`, `compaction.buffer_tokens`
266
+ - `agent.max_turns`, `agent.default_context_window`
268
267
 
269
268
  You can also theme the UI via `[ui.colors]` values.
270
269
 
@@ -275,7 +274,7 @@ Example:
275
274
  default_provider = "openai-codex"
276
275
  default_model = "gpt-5.3-codex"
277
276
  default_thinking_level = "high"
278
- tool_call_idle_timeout_seconds = 10
277
+ tool_call_idle_timeout_seconds = 60
279
278
  system_prompt = """Your custom system prompt here"""
280
279
 
281
280
  [compaction]
@@ -199,34 +199,32 @@ UI (app.py)
199
199
 
200
200
  ## Supported Models
201
201
 
202
- Kon works well with local models exposed through an OpenAI-compatible `/v1` API (for example LM Studio).
202
+ Kon works well with local models exposed through an OpenAI-compatible `/v1` API.
203
203
 
204
- ### Example on LM Studio
204
+ ### Example using llama-server
205
205
 
206
- To run a local model from LM Studio:
206
+ To run a local model using llama-server:
207
207
 
208
208
  ```bash
209
- # GLM-4.7-flash
210
- kon --provider openai-responses \
211
- --base-url http://127.0.0.1:1234/v1 \
212
- --model zai-org/glm-4.7-flash \
213
- --api-key ""
214
-
215
- # Qwen3-coder-next
216
- kon --provider openai-responses \
217
- --base-url http://127.0.0.1:1234/v1 \
218
- --model qwen/qwen3-coder-next \
219
- --api-key ""
209
+ ./llama-server -m <models-dir>/GLM-4.7-Flash-GGUF/GLM-4.7-Flash-Q4_K_M.gguf \
210
+ -n 8192 \
211
+ -c 64000
212
+
213
+ # Then use Kon with:
214
+ kon --model zai-org/glm-4.7-flash \
215
+ --provider openai \
216
+ --base-url http://localhost:8080/v1 \
217
+ --api-key ""
220
218
  ```
221
219
 
222
- For detailed configuration and performance benchmarks, see [LOCAL.md](LOCAL.md).
220
+ `GLM-4.7-Flash-Q4` ran at 80-90 tps on my i7-14700F × 28, 64GB RAM, 24GB VRAM (RTX 3090)
223
221
 
224
222
  ### All Supported Providers
225
223
 
226
224
  | Model (local=*) | Provider | Thinking | Vision |
227
225
  | ----- | -------- | -------- | ------ |
228
- | `*zai-org/glm-4.7-flash` | OpenAI Responses | Yes | No |
229
- | `*qwen/qwen3-coder-next` | OpenAI Responses | Yes | No |
226
+ | `*zai-org/glm-4.7-flash` | OpenAI Completions | Yes | No |
227
+ | `*qwen/qwen3-coder-next` | OpenAI Completions | Yes | No |
230
228
  | `glm-4.7` | ZhiPu (OpenAI Completions) | Yes | No |
231
229
  | `glm-5` | ZhiPu (OpenAI Completions) | Yes | No |
232
230
  | `claude-sonnet-4.5` | GitHub Copilot | Yes | Yes |
@@ -248,7 +246,8 @@ Most important knobs:
248
246
  - `llm.default_thinking_level`
249
247
  - `llm.system_prompt` (**you can fully override Kon’s system prompt here**)
250
248
  - `llm.tool_call_idle_timeout_seconds` (fallback timeout for stalled tool-call streaming)
251
- - `compaction.on_overflow`, `compaction.buffer_tokens`, `compaction.default_context_window`
249
+ - `compaction.on_overflow`, `compaction.buffer_tokens`
250
+ - `agent.max_turns`, `agent.default_context_window`
252
251
 
253
252
  You can also theme the UI via `[ui.colors]` values.
254
253
 
@@ -259,7 +258,7 @@ Example:
259
258
  default_provider = "openai-codex"
260
259
  default_model = "gpt-5.3-codex"
261
260
  default_thinking_level = "high"
262
- tool_call_idle_timeout_seconds = 10
261
+ tool_call_idle_timeout_seconds = 60
263
262
  system_prompt = """Your custom system prompt here"""
264
263
 
265
264
  [compaction]
@@ -1,5 +1,3 @@
1
- - show new release update in ui to prompt the user to upgrade
2
- - show tokens streamed in for bash, edit and write tools (which can large at times)
3
1
  - if @ or / menu is open pressing esc closes it but interrupts stream as well
4
2
  - bug in how we report context size, tokens in and out and cached tokens for codex?
5
3
  - steer (immediate) and normal queues
@@ -14,7 +14,7 @@ default = true
14
14
 
15
15
  [project]
16
16
  name = "kon-coding-agent"
17
- version = "0.2.0"
17
+ version = "0.2.2"
18
18
  description = "Minimal coding agent"
19
19
  readme = "README.md"
20
20
  requires-python = ">=3.12"
@@ -59,12 +59,16 @@ class LLMConfig(BaseModel):
59
59
  default_model: str
60
60
  default_thinking_level: str
61
61
  system_prompt: str
62
- tool_call_idle_timeout_seconds: float = 10
62
+ tool_call_idle_timeout_seconds: float = 60
63
63
 
64
64
 
65
65
  class CompactionConfig(BaseModel):
66
66
  on_overflow: OnOverflowMode = "continue"
67
67
  buffer_tokens: int = 20000
68
+
69
+
70
+ class AgentConfig(BaseModel):
71
+ max_turns: int = 500
68
72
  default_context_window: int = 200000
69
73
 
70
74
 
@@ -72,6 +76,7 @@ class ConfigSchema(BaseModel):
72
76
  llm: LLMConfig
73
77
  ui: UIConfig
74
78
  compaction: CompactionConfig
79
+ agent: AgentConfig
75
80
 
76
81
 
77
82
  class _BinariesConfig:
@@ -126,6 +131,10 @@ class Config:
126
131
  def compaction(self) -> CompactionConfig:
127
132
  return self._parsed.compaction
128
133
 
134
+ @property
135
+ def agent(self) -> AgentConfig:
136
+ return self._parsed.agent
137
+
129
138
  @property
130
139
  def binaries(self) -> _BinariesConfig:
131
140
  return _BinariesConfig(AVAILABLE_BINARIES)
@@ -2,7 +2,7 @@
2
2
  default_provider = "openai-codex" # "zhipu", "github-copilot", "openai-codex"
3
3
  default_model = "gpt-5.3-codex"
4
4
  default_thinking_level = "high"
5
- tool_call_idle_timeout_seconds = 10
5
+ tool_call_idle_timeout_seconds = 120
6
6
  system_prompt = """You are an expert coding assistant called `Kon`.
7
7
  You help users by reading, searching, executing commands, editing code, and writing new files.
8
8
 
@@ -21,6 +21,9 @@ You help users by reading, searching, executing commands, editing code, and writ
21
21
  [compaction]
22
22
  on_overflow = "continue" # "continue" or "pause"
23
23
  buffer_tokens = 20000
24
+
25
+ [agent]
26
+ max_turns = 500
24
27
  default_context_window = 200000
25
28
 
26
29
  [ui.colors]
@@ -100,6 +100,14 @@ class ToolArgsDeltaEvent:
100
100
  delta: str = ""
101
101
 
102
102
 
103
+ @dataclass
104
+ class ToolArgsTokenUpdateEvent:
105
+ type: Literal["tool_args_token_update"] = "tool_args_token_update"
106
+ tool_call_id: str = ""
107
+ tool_name: str = ""
108
+ token_count: int = 0
109
+
110
+
103
111
  @dataclass
104
112
  class ToolEndEvent:
105
113
  type: Literal["tool_end"] = "tool_end"
@@ -180,6 +188,7 @@ StreamEvent = (
180
188
  | TextEndEvent
181
189
  | ToolStartEvent
182
190
  | ToolArgsDeltaEvent
191
+ | ToolArgsTokenUpdateEvent
183
192
  | ToolEndEvent
184
193
  | ToolResultEvent
185
194
  | RetryEvent
@@ -15,6 +15,7 @@ Scenarios (set via scenario parameter):
15
15
  - "unknown_tool": call unknown tool
16
16
  - "long_text": multiple text chunks
17
17
  - "tool_hang": emits a tool call and then never sends StreamDone
18
+ - "tool_with_many_chunks": tool call with many argument chunks for token counting tests
18
19
  """
19
20
 
20
21
  import asyncio
@@ -138,6 +139,45 @@ class MockProvider(BaseProvider):
138
139
 
139
140
  return tool_hang_iter()
140
141
 
142
+ case "tool_with_many_chunks":
143
+
144
+ async def tool_with_many_chunks_iter():
145
+ # Tool call with many chunks to test token counting
146
+ # 24 chunks of 8 chars each = 192 chars = 48 tokens
147
+ # Should trigger token update events at chunks 12, 16, 20, 24
148
+ yield ToolCallStart(id="call-1", name="bash", index=0)
149
+ chunks = [
150
+ "aaaaaaa",
151
+ "bbbbbbb",
152
+ "ccccccc",
153
+ "ddddddd",
154
+ "eeeeeee",
155
+ "fffffff",
156
+ "ggggggg",
157
+ "hhhhhhh",
158
+ "iiiiiii",
159
+ "jjjjjjj",
160
+ "kkkkkkk",
161
+ "lllllll",
162
+ "mmmmmmm",
163
+ "nnnnnnn",
164
+ "ooooooo",
165
+ "ppppppp",
166
+ "qqqqqqq",
167
+ "rrrrrrr",
168
+ "sssssss",
169
+ "ttttttt",
170
+ "uuuuuuu",
171
+ "vvvvvvv",
172
+ "wwwwwww",
173
+ "xxxxxxxx",
174
+ ]
175
+ for chunk in chunks:
176
+ yield ToolCallDelta(index=0, arguments_delta=chunk)
177
+ yield StreamDone(stop_reason=StopReason.TOOL_USE)
178
+
179
+ return tool_with_many_chunks_iter()
180
+
141
181
  case _:
142
182
  # Fallback to default
143
183
  async def default_iter():
@@ -76,7 +76,7 @@ def build_system_prompt(cwd: str, context: Context | None = None) -> str:
76
76
 
77
77
  @dataclass
78
78
  class AgentConfig:
79
- max_turns: int = 200
79
+ max_turns: int | None = None
80
80
  system_prompt: str | None = None
81
81
  cwd: str | None = None
82
82
  context: Context | None = None
@@ -137,7 +137,12 @@ class Agent:
137
137
  system_prompt = self.config.system_prompt or build_system_prompt(cwd, self.config.context)
138
138
 
139
139
  try:
140
- while turn < self.config.max_turns:
140
+ max_turns = (
141
+ self.config.max_turns
142
+ if self.config.max_turns is not None
143
+ else kon_config.agent.max_turns
144
+ )
145
+ while turn < max_turns:
141
146
  if cancel_event and cancel_event.is_set():
142
147
  was_interrupted = True
143
148
  stop_reason = StopReason.INTERRUPTED
@@ -194,7 +199,7 @@ class Agent:
194
199
  if stop_reason != StopReason.TOOL_USE:
195
200
  break
196
201
 
197
- if turn >= self.config.max_turns and not was_interrupted:
202
+ if turn >= max_turns and not was_interrupted:
198
203
  stop_reason = StopReason.LENGTH
199
204
 
200
205
  except Exception as e: # intentionally broad — top-level boundary; crash = broken TUI
@@ -219,7 +224,7 @@ class Agent:
219
224
  if last_usage is None:
220
225
  return
221
226
 
222
- context_window = self.config.context_window or kon_config.compaction.default_context_window
227
+ context_window = self.config.context_window or kon_config.agent.default_context_window
223
228
  max_output = self.config.max_output_tokens or self.provider.config.max_tokens
224
229
  buffer_tokens = kon_config.compaction.buffer_tokens
225
230
 
@@ -58,6 +58,7 @@ class ModelChangeEntry(EntryBase):
58
58
  type: Literal["model_change"] = "model_change"
59
59
  provider: str
60
60
  model_id: str
61
+ base_url: str | None = None
61
62
 
62
63
 
63
64
  class CompactionEntry(EntryBase):
@@ -241,13 +242,16 @@ class Session:
241
242
  self._append_entry(entry)
242
243
  return entry.id
243
244
 
244
- def append_model_change(self, provider: str, model_id: str) -> str:
245
+ def append_model_change(
246
+ self, provider: str, model_id: str, base_url: str | None = None
247
+ ) -> str:
245
248
  entry = ModelChangeEntry(
246
249
  id=self._generate_entry_id(),
247
250
  parent_id=self._leaf_id,
248
251
  timestamp=_now_iso(),
249
252
  provider=provider,
250
253
  model_id=model_id,
254
+ base_url=base_url,
251
255
  )
252
256
  self._append_entry(entry)
253
257
  return entry.id
@@ -372,20 +376,25 @@ class Session:
372
376
  return self._initial_thinking_level
373
377
 
374
378
  @property
375
- def model(self) -> tuple[str, str] | None:
379
+ def model(self) -> tuple[str, str, str | None] | None:
376
380
  for entry in reversed(self._entries):
377
381
  if isinstance(entry, ModelChangeEntry):
378
- return (entry.provider, entry.model_id)
382
+ return (entry.provider, entry.model_id, entry.base_url)
379
383
 
380
384
  if self._initial_provider and self._initial_model_id:
381
- return (self._initial_provider, self._initial_model_id)
385
+ return (self._initial_provider, self._initial_model_id, None)
382
386
  return None
383
387
 
384
- def set_model(self, provider: str, model_id: str) -> None:
388
+ def set_model(self, provider: str, model_id: str, base_url: str | None = None) -> None:
385
389
  current = self.model
386
- if current and current[0] == provider and current[1] == model_id:
390
+ if (
391
+ current
392
+ and current[0] == provider
393
+ and current[1] == model_id
394
+ and current[2] == base_url
395
+ ):
387
396
  return
388
- self.append_model_change(provider, model_id)
397
+ self.append_model_change(provider, model_id, base_url)
389
398
 
390
399
  def set_thinking_level(self, thinking_level: str) -> None:
391
400
  if self.thinking_level == thinking_level:
@@ -59,6 +59,7 @@ from .events import (
59
59
  ThinkingEndEvent,
60
60
  ThinkingStartEvent,
61
61
  ToolArgsDeltaEvent,
62
+ ToolArgsTokenUpdateEvent,
62
63
  ToolEndEvent,
63
64
  ToolResultEvent,
64
65
  ToolStartEvent,
@@ -70,7 +71,14 @@ from .llm.base import LLMStream
70
71
  from .tools import BaseTool, get_tool, get_tool_definitions
71
72
 
72
73
  _STREAM_EXHAUSTED = object()
73
- _DEFAULT_TOOL_CALL_IDLE_TIMEOUT_SECONDS = 10.0
74
+ _DEFAULT_TOOL_CALL_IDLE_TIMEOUT_SECONDS = 60.0
75
+ _TOOL_ARGS_TOKEN_DISPLAY_THRESHOLD = 20
76
+ _TOOL_ARGS_TOKEN_CHUNK_UPDATE_INTERVAL = 4
77
+
78
+
79
+ def _count_tokens(text: str) -> int:
80
+ """Estimate token count from text (approx 4 chars per token)."""
81
+ return len(text) // 4
74
82
 
75
83
 
76
84
  class StreamState(StrEnum):
@@ -228,6 +236,10 @@ async def run_single_turn(
228
236
  pending_tool_calls: list[dict] = []
229
237
  current_tool_call: dict | None = None
230
238
 
239
+ # Token counting for tool argument streaming
240
+ _tool_arg_chunk_counter = 0
241
+ _tool_arg_token_count = 0
242
+
231
243
  current_state: StreamState | None = None
232
244
  stop_reason: StopReason = StopReason.STOP
233
245
  interrupted = False
@@ -381,6 +393,10 @@ async def run_single_turn(
381
393
  pending_tool_calls.append(current_tool_call)
382
394
  current_tool_call = None
383
395
 
396
+ # Reset token counters when starting a new tool call
397
+ _tool_arg_chunk_counter = 0
398
+ _tool_arg_token_count = 0
399
+
384
400
  current_state = StreamState.TOOL_CALL
385
401
  current_tool_call = {"id": id, "name": name, "arguments": ""}
386
402
 
@@ -391,6 +407,20 @@ async def run_single_turn(
391
407
  current_tool_call["arguments"] += delta
392
408
  yield ToolArgsDeltaEvent(tool_call_id=current_tool_call["id"], delta=delta)
393
409
 
410
+ # Count tokens and fire update event every Nth chunk after threshold tokens
411
+ _tool_arg_chunk_counter += 1
412
+ _tool_arg_token_count += _count_tokens(delta)
413
+
414
+ if (
415
+ _tool_arg_token_count > _TOOL_ARGS_TOKEN_DISPLAY_THRESHOLD
416
+ and _tool_arg_chunk_counter % _TOOL_ARGS_TOKEN_CHUNK_UPDATE_INTERVAL == 0
417
+ ):
418
+ yield ToolArgsTokenUpdateEvent(
419
+ tool_call_id=current_tool_call["id"],
420
+ tool_name=current_tool_call["name"],
421
+ token_count=_tool_arg_token_count,
422
+ )
423
+
394
424
  case StreamDone(stop_reason=reason):
395
425
  stop_reason = reason
396
426
 
@@ -4,8 +4,10 @@ import os
4
4
  import shutil
5
5
  import sys
6
6
  import time
7
+ import tomllib
7
8
  from collections import deque
8
9
  from importlib.metadata import PackageNotFoundError, version
10
+ from pathlib import Path
9
11
  from typing import ClassVar
10
12
 
11
13
  from rich.console import Console
@@ -32,6 +34,7 @@ from ..events import (
32
34
  ThinkingDeltaEvent,
33
35
  ThinkingEndEvent,
34
36
  ThinkingStartEvent,
37
+ ToolArgsTokenUpdateEvent,
35
38
  ToolEndEvent,
36
39
  ToolResultEvent,
37
40
  ToolStartEvent,
@@ -65,12 +68,24 @@ from .session_ui import SessionUIMixin
65
68
  from .styles import STYLES
66
69
  from .widgets import InfoBar, QueueDisplay, StatusLine, format_path
67
70
 
68
- _PYPI_PACKAGE_NAME = "kon-coding-agent"
71
+
72
+ def _get_package_name() -> str:
73
+ pyproject_path = Path(__file__).parent.parent.parent.parent / "pyproject.toml"
74
+ if pyproject_path.exists():
75
+ try:
76
+ data = tomllib.loads(pyproject_path.read_text())
77
+ return data["project"]["name"]
78
+ except Exception:
79
+ pass
80
+ return "kon-coding-agent"
81
+
82
+
83
+ _PYPI_PACKAGE_NAME = _get_package_name()
69
84
 
70
85
  try:
71
86
  VERSION = version(_PYPI_PACKAGE_NAME)
72
87
  except PackageNotFoundError:
73
- VERSION = "0.2.0"
88
+ VERSION = "0.2.2"
74
89
 
75
90
  _COPILOT_API_TYPES: frozenset[ApiType] = frozenset(
76
91
  {ApiType.GITHUB_COPILOT, ApiType.GITHUB_COPILOT_RESPONSES, ApiType.ANTHROPIC_COPILOT}
@@ -202,16 +217,20 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
202
217
  if self._session.entries:
203
218
  model_info = self._session.model
204
219
  if model_info:
205
- provider, self._model = model_info
220
+ provider, self._model, session_base_url = model_info
206
221
  self._model_provider = provider
222
+ if self._base_url is None and session_base_url:
223
+ self._base_url = session_base_url
207
224
  self._thinking_level = self._session.thinking_level
208
225
  elif self._continue_recent:
209
226
  self._session = Session.continue_recent(self._cwd)
210
227
  if self._session.entries:
211
228
  model_info = self._session.model
212
229
  if model_info:
213
- provider, self._model = model_info
230
+ provider, self._model, session_base_url = model_info
214
231
  self._model_provider = provider
232
+ if self._base_url is None and session_base_url:
233
+ self._base_url = session_base_url
215
234
  self._thinking_level = self._session.thinking_level
216
235
 
217
236
  model_info = get_model(self._model, self._model_provider)
@@ -257,7 +276,7 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
257
276
  model_id=self._model,
258
277
  thinking_level=self._thinking_level,
259
278
  )
260
- self._session.append_model_change(model_provider, self._model)
279
+ self._session.append_model_change(model_provider, self._model, base_url)
261
280
 
262
281
  self._project_context = Context.load(self._cwd)
263
282
  # TODO: Surface self._project_context.skill_warnings in UI (e.g. chat info/error messages)
@@ -567,7 +586,6 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
567
586
  tools = get_tools(DEFAULT_TOOLS)
568
587
  model_info = get_model(self._model, self._model_provider)
569
588
  agent_config = AgentConfig(
570
- max_turns=50,
571
589
  system_prompt=self._get_system_prompt(),
572
590
  context_window=model_info.context_window if model_info else None,
573
591
  max_output_tokens=model_info.max_tokens if model_info else None,
@@ -621,6 +639,10 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
621
639
  chat.start_tool(name, id, "")
622
640
  self._current_block_type = "tool_call"
623
641
  status.increment_tool_calls()
642
+ status.set_streaming_tokens(0) # Reset token count for new tool
643
+
644
+ case ToolArgsTokenUpdateEvent(token_count=tc):
645
+ status.set_streaming_tokens(tc)
624
646
 
625
647
  case ToolEndEvent(tool_call_id=id, display=display):
626
648
  chat.update_tool_call_msg(id, display)
@@ -743,6 +765,7 @@ def main():
743
765
  dest="resume_session",
744
766
  help="Resume a specific session by ID (full or unique prefix)",
745
767
  )
768
+ parser.add_argument("--version", action="version", version=f"kon {VERSION}")
746
769
  args = parser.parse_args()
747
770
 
748
771
  app = Kon(