kon-coding-agent 0.2.2__tar.gz → 0.2.4__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.2.2 → kon_coding_agent-0.2.4}/PKG-INFO +7 -7
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/README.md +6 -6
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/pyproject.toml +1 -1
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/config.py +1 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/defaults/config.toml +1 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/loop.py +20 -16
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/turn.py +0 -1
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/app.py +56 -30
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/app_protocol.py +2 -4
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/commands.py +8 -12
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/input.py +6 -25
- kon_coding_agent-0.2.4/src/kon/ui/prompt_history.py +98 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/session_ui.py +4 -8
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_agentic_loop.py +3 -2
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/ui/test_input_paste.py +1 -1
- kon_coding_agent-0.2.4/tests/ui/test_prompt_history.py +98 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/uv.lock +1 -1
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.gitignore +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-release-publish/SKILL.md +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.python-version +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/AGENTS.md +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/LICENSE +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/LOCAL.md +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/TODO.md +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/scripts/test_models.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/scripts/test_thinking_blocks.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/context/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/context/agents.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/context/loader.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/context/shared.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/context/skills.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/core/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/core/compaction.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/core/types.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/defaults/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/events.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/base.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/models.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/oauth/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/oauth/copilot.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/oauth/openai.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/anthropic.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/copilot.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/copilot_anthropic.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/github_copilot_headers.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/mock.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/openai_codex_responses.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/openai_completions.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/openai_responses.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/sanitize.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/py.typed +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/session.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/shared.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/_read_image.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/base.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/bash.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/edit.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/find.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/grep.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/read.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools/write.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/tools_manager.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/autocomplete.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/blocks.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/chat.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/clipboard.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/export.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/floating_list.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/formatting.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/path_complete.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/selection_mode.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/styles.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/ui/widgets.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/update_check.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/conftest.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/context/test_agents.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/context/test_skills.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/llm/__init__.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/llm/test_mock_provider.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_cli_provider_resolution.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_compaction.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_config_binaries.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_config_error_fallback.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_config_injection.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_model_provider_resolution.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_session_persistence.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_system_prompt.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_update_check.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/test_update_notice_behavior.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_diff.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_edit.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_read.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_read_image.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_read_image_integration.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_write.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/ui/test_autocomplete.py +0 -0
- {kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/ui/test_floating_list.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kon-coding-agent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Minimal coding agent
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -35,12 +35,9 @@ $ fd . | cut -d/ -f1 | sort | uniq -c | sort -rn
|
|
|
35
35
|
108 kon
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
### Warning
|
|
38
|
+
[Kon](https://bleach.fandom.com/wiki/Kon) is inspired from Bleach, a artificial soul
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
> Platform support: macOS and Linux are supported; Windows is not tested yet.
|
|
40
|
+
## Setup
|
|
44
41
|
|
|
45
42
|
### Prerequisites
|
|
46
43
|
|
|
@@ -57,11 +54,14 @@ This installs `kon` globally as a CLI tool.
|
|
|
57
54
|
### Install from source (advanced)
|
|
58
55
|
|
|
59
56
|
```bash
|
|
60
|
-
git clone
|
|
57
|
+
git clone https://github.com/kuutsav/kon
|
|
61
58
|
cd kon
|
|
62
59
|
uv tool install .
|
|
63
60
|
```
|
|
64
61
|
|
|
62
|
+
> [!WARNING]
|
|
63
|
+
> Platform support: macOS and Linux are supported; Windows is not tested yet.
|
|
64
|
+
|
|
65
65
|
### Run
|
|
66
66
|
|
|
67
67
|
```bash
|
|
@@ -19,12 +19,9 @@ $ fd . | cut -d/ -f1 | sort | uniq -c | sort -rn
|
|
|
19
19
|
108 kon
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### Warning
|
|
22
|
+
[Kon](https://bleach.fandom.com/wiki/Kon) is inspired from Bleach, a artificial soul
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
> Platform support: macOS and Linux are supported; Windows is not tested yet.
|
|
24
|
+
## Setup
|
|
28
25
|
|
|
29
26
|
### Prerequisites
|
|
30
27
|
|
|
@@ -41,11 +38,14 @@ This installs `kon` globally as a CLI tool.
|
|
|
41
38
|
### Install from source (advanced)
|
|
42
39
|
|
|
43
40
|
```bash
|
|
44
|
-
git clone
|
|
41
|
+
git clone https://github.com/kuutsav/kon
|
|
45
42
|
cd kon
|
|
46
43
|
uv tool install .
|
|
47
44
|
```
|
|
48
45
|
|
|
46
|
+
> [!WARNING]
|
|
47
|
+
> Platform support: macOS and Linux are supported; Windows is not tested yet.
|
|
48
|
+
|
|
49
49
|
### Run
|
|
50
50
|
|
|
51
51
|
```bash
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[llm]
|
|
2
2
|
default_provider = "openai-codex" # "zhipu", "github-copilot", "openai-codex"
|
|
3
3
|
default_model = "gpt-5.3-codex"
|
|
4
|
+
default_base_url = ""
|
|
4
5
|
default_thinking_level = "high"
|
|
5
6
|
tool_call_idle_timeout_seconds = 120
|
|
6
7
|
system_prompt = """You are an expert coding assistant called `Kon`.
|
|
@@ -46,14 +46,6 @@ from .turn import run_single_turn
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def build_system_prompt(cwd: str, context: Context | None = None) -> str:
|
|
49
|
-
"""
|
|
50
|
-
Build the system prompt with tools, guidelines, context files, and skills.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
cwd: Working directory
|
|
54
|
-
context: Pre-loaded context (agents files and skills). If None, will load fresh.
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
49
|
now = datetime.now()
|
|
58
50
|
date_time = now.strftime("%A, %B %d, %Y at %I:%M %p %Z").strip()
|
|
59
51
|
|
|
@@ -77,9 +69,6 @@ def build_system_prompt(cwd: str, context: Context | None = None) -> str:
|
|
|
77
69
|
@dataclass
|
|
78
70
|
class AgentConfig:
|
|
79
71
|
max_turns: int | None = None
|
|
80
|
-
system_prompt: str | None = None
|
|
81
|
-
cwd: str | None = None
|
|
82
|
-
context: Context | None = None
|
|
83
72
|
context_window: int | None = None
|
|
84
73
|
max_output_tokens: int | None = None
|
|
85
74
|
|
|
@@ -90,14 +79,32 @@ class Agent:
|
|
|
90
79
|
provider: BaseProvider,
|
|
91
80
|
tools: list[BaseTool],
|
|
92
81
|
session: Session,
|
|
82
|
+
cwd: str | None = None,
|
|
83
|
+
context: Context | None = None,
|
|
84
|
+
system_prompt: str | None = None,
|
|
93
85
|
config: AgentConfig | None = None,
|
|
94
86
|
):
|
|
95
87
|
self.provider = provider
|
|
96
88
|
self.tools = tools
|
|
97
89
|
self.session = session
|
|
98
90
|
self.config = config or AgentConfig()
|
|
91
|
+
self._cwd = cwd or os.getcwd()
|
|
92
|
+
self._context = context or Context.load(self._cwd)
|
|
93
|
+
self._system_prompt = system_prompt or build_system_prompt(self._cwd, self._context)
|
|
99
94
|
self._run_usage = Usage()
|
|
100
95
|
|
|
96
|
+
@property
|
|
97
|
+
def context(self) -> Context:
|
|
98
|
+
return self._context
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def system_prompt(self) -> str:
|
|
102
|
+
return self._system_prompt
|
|
103
|
+
|
|
104
|
+
def reload_context(self) -> None:
|
|
105
|
+
self._context = Context.load(self._cwd)
|
|
106
|
+
self._system_prompt = build_system_prompt(self._cwd, self._context)
|
|
107
|
+
|
|
101
108
|
@property
|
|
102
109
|
def messages(self) -> list[Message]:
|
|
103
110
|
return self.session.messages
|
|
@@ -131,10 +138,7 @@ class Agent:
|
|
|
131
138
|
stop_reason = StopReason.STOP
|
|
132
139
|
was_interrupted = False
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
# If we don't do this date_time might change during execution invalidating cache
|
|
136
|
-
cwd = self.config.cwd or os.getcwd()
|
|
137
|
-
system_prompt = self.config.system_prompt or build_system_prompt(cwd, self.config.context)
|
|
141
|
+
system_prompt = self._system_prompt
|
|
138
142
|
|
|
139
143
|
try:
|
|
140
144
|
max_turns = (
|
|
@@ -225,7 +229,7 @@ class Agent:
|
|
|
225
229
|
return
|
|
226
230
|
|
|
227
231
|
context_window = self.config.context_window or kon_config.agent.default_context_window
|
|
228
|
-
max_output = self.config.max_output_tokens or self.provider.config.max_tokens
|
|
232
|
+
max_output = self.config.max_output_tokens or self.provider.config.max_tokens or 0
|
|
229
233
|
buffer_tokens = kon_config.compaction.buffer_tokens
|
|
230
234
|
|
|
231
235
|
if not is_overflow(last_usage, context_window, max_output, buffer_tokens):
|
|
@@ -11,14 +11,13 @@ from pathlib import Path
|
|
|
11
11
|
from typing import ClassVar
|
|
12
12
|
|
|
13
13
|
from rich.console import Console
|
|
14
|
-
from textual import on
|
|
14
|
+
from textual import events, on
|
|
15
15
|
from textual.app import App, ComposeResult
|
|
16
16
|
from textual.binding import Binding
|
|
17
17
|
|
|
18
18
|
from kon import config, consume_config_warnings, update_available_binaries
|
|
19
19
|
from kon.tools_manager import ensure_tools
|
|
20
20
|
|
|
21
|
-
from ..context import Context
|
|
22
21
|
from ..core.types import StopReason
|
|
23
22
|
from ..events import (
|
|
24
23
|
AgentEndEvent,
|
|
@@ -54,7 +53,7 @@ from ..llm import (
|
|
|
54
53
|
is_openai_logged_in,
|
|
55
54
|
resolve_provider_api_type,
|
|
56
55
|
)
|
|
57
|
-
from ..loop import Agent
|
|
56
|
+
from ..loop import Agent
|
|
58
57
|
from ..session import Session
|
|
59
58
|
from ..tools import DEFAULT_TOOLS, get_tools
|
|
60
59
|
from ..update_check import get_newer_pypi_version
|
|
@@ -85,7 +84,7 @@ _PYPI_PACKAGE_NAME = _get_package_name()
|
|
|
85
84
|
try:
|
|
86
85
|
VERSION = version(_PYPI_PACKAGE_NAME)
|
|
87
86
|
except PackageNotFoundError:
|
|
88
|
-
VERSION = "0.2.
|
|
87
|
+
VERSION = "0.2.4"
|
|
89
88
|
|
|
90
89
|
_COPILOT_API_TYPES: frozenset[ApiType] = frozenset(
|
|
91
90
|
{ApiType.GITHUB_COPILOT, ApiType.GITHUB_COPILOT_RESPONSES, ApiType.ANTHROPIC_COPILOT}
|
|
@@ -137,7 +136,7 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
137
136
|
else (config.llm.default_provider if model is None else None)
|
|
138
137
|
)
|
|
139
138
|
self._api_key = api_key
|
|
140
|
-
self._base_url = base_url
|
|
139
|
+
self._base_url = base_url or config.llm.default_base_url or None
|
|
141
140
|
self._resume_session = resume_session
|
|
142
141
|
self._continue_recent = continue_recent
|
|
143
142
|
self._thinking_level = thinking_level or config.llm.default_thinking_level
|
|
@@ -159,7 +158,6 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
159
158
|
self._provider: BaseProvider | None = None
|
|
160
159
|
self._session: Session | None = None
|
|
161
160
|
self._agent: Agent | None = None
|
|
162
|
-
self._project_context: Context | None = None
|
|
163
161
|
|
|
164
162
|
self._pending_update_notice_version: str | None = None
|
|
165
163
|
self._update_notice_shown = False
|
|
@@ -192,6 +190,12 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
192
190
|
def _get_provider_api_type(self, provider: BaseProvider) -> ApiType:
|
|
193
191
|
return _API_TYPE_BY_PROVIDER.get(type(provider), ApiType.OPENAI_COMPLETIONS)
|
|
194
192
|
|
|
193
|
+
@on(events.TextSelected)
|
|
194
|
+
def _on_text_selected(self) -> None:
|
|
195
|
+
selection = self.screen.get_selected_text()
|
|
196
|
+
if selection:
|
|
197
|
+
self.copy_to_clipboard(selection)
|
|
198
|
+
|
|
195
199
|
def on_mount(self) -> None:
|
|
196
200
|
if config.binaries.fd:
|
|
197
201
|
self._fd_path = shutil.which("fd") or shutil.which("fdfind")
|
|
@@ -254,21 +258,25 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
254
258
|
thinking_level=self._thinking_level,
|
|
255
259
|
)
|
|
256
260
|
|
|
261
|
+
provider_error: str | None = None
|
|
257
262
|
try:
|
|
258
263
|
self._provider = self._create_provider(api_type, provider_config)
|
|
259
264
|
except ValueError as e:
|
|
260
|
-
|
|
261
|
-
chat.add_info_message(str(e), error=True)
|
|
262
|
-
return
|
|
265
|
+
provider_error = str(e)
|
|
263
266
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
self._thinking_level
|
|
267
|
-
|
|
267
|
+
if self._provider:
|
|
268
|
+
valid_levels = self._provider.thinking_levels
|
|
269
|
+
if self._thinking_level not in valid_levels:
|
|
270
|
+
self._thinking_level = valid_levels[0] if valid_levels else "medium"
|
|
271
|
+
self._provider.set_thinking_level(self._thinking_level)
|
|
268
272
|
|
|
269
273
|
if not self._continue_recent and not self._resume_session:
|
|
270
274
|
selected_model = get_model(self._model, self._model_provider)
|
|
271
|
-
model_provider =
|
|
275
|
+
model_provider = (
|
|
276
|
+
selected_model.provider
|
|
277
|
+
if selected_model
|
|
278
|
+
else (self._provider.name if self._provider else self._model_provider)
|
|
279
|
+
)
|
|
272
280
|
self._model_provider = model_provider
|
|
273
281
|
self._session = Session.create(
|
|
274
282
|
self._cwd,
|
|
@@ -276,19 +284,31 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
276
284
|
model_id=self._model,
|
|
277
285
|
thinking_level=self._thinking_level,
|
|
278
286
|
)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
#
|
|
283
|
-
#
|
|
287
|
+
if model_provider:
|
|
288
|
+
self._session.append_model_change(model_provider, self._model, base_url)
|
|
289
|
+
|
|
290
|
+
# Create Agent once — it owns context + system prompt (stable across queries
|
|
291
|
+
# for prompt-prefix caching on llama-server and similar engines).
|
|
292
|
+
if self._provider is not None and self._session is not None:
|
|
293
|
+
self._agent = Agent(
|
|
294
|
+
provider=self._provider,
|
|
295
|
+
tools=get_tools(DEFAULT_TOOLS),
|
|
296
|
+
session=self._session,
|
|
297
|
+
cwd=self._cwd,
|
|
298
|
+
)
|
|
284
299
|
|
|
285
300
|
chat = self.query_one("#chat-log", ChatLog)
|
|
286
301
|
chat.add_session_info(VERSION)
|
|
287
302
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
303
|
+
if self._agent:
|
|
304
|
+
# TODO: Surface self._agent.context.skill_warnings in UI
|
|
305
|
+
chat.add_loaded_resources(
|
|
306
|
+
context_paths=[format_path(f.path) for f in self._agent.context.agents_files],
|
|
307
|
+
skill_paths=[format_path(s.file_path) for s in self._agent.context.skills],
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if provider_error:
|
|
311
|
+
chat.add_info_message(provider_error, error=True)
|
|
292
312
|
|
|
293
313
|
for warning in consume_config_warnings():
|
|
294
314
|
chat.add_info_message(warning, warning=True)
|
|
@@ -572,6 +592,14 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
572
592
|
self._is_running = False
|
|
573
593
|
return
|
|
574
594
|
|
|
595
|
+
if self._agent is None:
|
|
596
|
+
self._agent = Agent(
|
|
597
|
+
provider=self._provider,
|
|
598
|
+
tools=get_tools(DEFAULT_TOOLS),
|
|
599
|
+
session=self._session,
|
|
600
|
+
cwd=self._cwd,
|
|
601
|
+
)
|
|
602
|
+
|
|
575
603
|
current_prompt = prompt
|
|
576
604
|
|
|
577
605
|
while True:
|
|
@@ -583,14 +611,12 @@ class Kon(CommandsMixin, SessionUIMixin, App[None]):
|
|
|
583
611
|
if self._interrupt_requested:
|
|
584
612
|
self._cancel_event.set()
|
|
585
613
|
|
|
586
|
-
tools = get_tools(DEFAULT_TOOLS)
|
|
587
614
|
model_info = get_model(self._model, self._model_provider)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
self._agent = Agent(self._provider, tools, self._session, agent_config)
|
|
615
|
+
self._agent.provider = self._provider
|
|
616
|
+
self._agent.session = self._session
|
|
617
|
+
self._agent.tools = get_tools(DEFAULT_TOOLS)
|
|
618
|
+
self._agent.config.context_window = model_info.context_window if model_info else None
|
|
619
|
+
self._agent.config.max_output_tokens = model_info.max_tokens if model_info else None
|
|
594
620
|
|
|
595
621
|
status.set_status("working")
|
|
596
622
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
from typing import Protocol
|
|
1
|
+
from typing import Any, Protocol
|
|
2
2
|
|
|
3
|
-
from ..context import Context
|
|
4
3
|
from ..llm import ApiType, BaseProvider, ProviderConfig
|
|
5
4
|
from ..session import Session
|
|
6
5
|
from .selection_mode import SelectionMode
|
|
@@ -20,7 +19,7 @@ class Kon(Protocol):
|
|
|
20
19
|
_selection_mode: SelectionMode | None
|
|
21
20
|
_provider: BaseProvider | None
|
|
22
21
|
_session: Session | None
|
|
23
|
-
|
|
22
|
+
_agent: Any
|
|
24
23
|
|
|
25
24
|
# Methods expected by mixins
|
|
26
25
|
def exit(self) -> None: ...
|
|
@@ -32,4 +31,3 @@ class Kon(Protocol):
|
|
|
32
31
|
# Methods expected by SessionUIMixin
|
|
33
32
|
def _get_provider_api_type(self, provider: BaseProvider) -> ApiType: ...
|
|
34
33
|
def _create_provider(self, api_type: ApiType, config: ProviderConfig) -> BaseProvider: ...
|
|
35
|
-
def _get_system_prompt(self) -> str: ...
|
|
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
|
|
7
7
|
from kon import config
|
|
8
8
|
|
|
9
|
-
from ..context import Context
|
|
10
9
|
from ..core.compaction import generate_summary
|
|
11
10
|
from ..core.types import AssistantMessage, ToolCall, ToolResultMessage
|
|
12
11
|
from ..llm import (
|
|
@@ -43,7 +42,7 @@ class CommandsMixin:
|
|
|
43
42
|
_api_key: str | None
|
|
44
43
|
_provider: BaseProvider | None
|
|
45
44
|
_session: Session | None
|
|
46
|
-
|
|
45
|
+
_agent: Any
|
|
47
46
|
_is_running: bool
|
|
48
47
|
|
|
49
48
|
# Methods from App - declared for type checking
|
|
@@ -59,7 +58,6 @@ class CommandsMixin:
|
|
|
59
58
|
|
|
60
59
|
def _get_provider_api_type(self, provider: BaseProvider) -> ApiType: ...
|
|
61
60
|
def _create_provider(self, api_type: ApiType, config: ProviderConfig) -> BaseProvider: ...
|
|
62
|
-
def _get_system_prompt(self) -> str: ...
|
|
63
61
|
|
|
64
62
|
def _handle_command(self, text: str) -> bool:
|
|
65
63
|
parts = text[1:].split(maxsplit=1)
|
|
@@ -242,8 +240,6 @@ Keybindings:
|
|
|
242
240
|
self.run_worker(self._do_new_conversation(chat, info_bar, status), exclusive=False)
|
|
243
241
|
|
|
244
242
|
async def _do_new_conversation(self, chat: ChatLog, info_bar, status) -> None:
|
|
245
|
-
from kon.context import Context
|
|
246
|
-
|
|
247
243
|
await chat.remove_all_children()
|
|
248
244
|
|
|
249
245
|
status.reset()
|
|
@@ -255,12 +251,12 @@ Keybindings:
|
|
|
255
251
|
|
|
256
252
|
chat.add_session_info(getattr(self, "VERSION", ""))
|
|
257
253
|
|
|
258
|
-
self.
|
|
259
|
-
|
|
260
|
-
#
|
|
254
|
+
self._agent.reload_context()
|
|
255
|
+
self._agent.session = self._session
|
|
256
|
+
# TODO: Surface self._agent.context.skill_warnings in UI
|
|
261
257
|
chat.add_loaded_resources(
|
|
262
|
-
context_paths=[format_path(f.path) for f in self.
|
|
263
|
-
skill_paths=[format_path(s.file_path) for s in self.
|
|
258
|
+
context_paths=[format_path(f.path) for f in self._agent.context.agents_files],
|
|
259
|
+
skill_paths=[format_path(s.file_path) for s in self._agent.context.skills],
|
|
264
260
|
)
|
|
265
261
|
|
|
266
262
|
chat.add_info_message("Started new conversation")
|
|
@@ -510,7 +506,7 @@ Keybindings:
|
|
|
510
506
|
chat.add_info_message("Session has no messages to export")
|
|
511
507
|
return
|
|
512
508
|
|
|
513
|
-
system_prompt = self.
|
|
509
|
+
system_prompt = self._agent.system_prompt
|
|
514
510
|
tools = get_tools(DEFAULT_TOOLS)
|
|
515
511
|
|
|
516
512
|
provider_name = self._provider.name if self._provider else "unknown"
|
|
@@ -585,7 +581,7 @@ Keybindings:
|
|
|
585
581
|
|
|
586
582
|
try:
|
|
587
583
|
summary = await generate_summary(
|
|
588
|
-
self._session.all_messages, self._provider, system_prompt=self.
|
|
584
|
+
self._session.all_messages, self._provider, system_prompt=self._agent.system_prompt
|
|
589
585
|
)
|
|
590
586
|
self._session.append_compaction(
|
|
591
587
|
summary=summary,
|
|
@@ -21,6 +21,7 @@ from .autocomplete import (
|
|
|
21
21
|
)
|
|
22
22
|
from .floating_list import ListItem
|
|
23
23
|
from .path_complete import PathComplete
|
|
24
|
+
from .prompt_history import PromptHistory
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
26
27
|
pass
|
|
@@ -93,9 +94,7 @@ class InputBox(Vertical):
|
|
|
93
94
|
) -> None:
|
|
94
95
|
super().__init__(id=id, classes=classes)
|
|
95
96
|
self._cwd = cwd or os.getcwd()
|
|
96
|
-
self._history
|
|
97
|
-
self._history_index: int = -1
|
|
98
|
-
self._history_temp: str = ""
|
|
97
|
+
self._history = PromptHistory()
|
|
99
98
|
|
|
100
99
|
# Autocomplete providers
|
|
101
100
|
self._slash_provider = SlashCommandProvider(DEFAULT_COMMANDS.copy())
|
|
@@ -460,33 +459,15 @@ class InputBox(Vertical):
|
|
|
460
459
|
# -------------------------------------------------------------------------
|
|
461
460
|
|
|
462
461
|
def _add_to_history(self, text: str) -> None:
|
|
463
|
-
|
|
464
|
-
self._history.append(text)
|
|
465
|
-
self._history_index = -1
|
|
466
|
-
self._history_temp = ""
|
|
462
|
+
self._history.append(text)
|
|
467
463
|
|
|
468
464
|
def _history_navigate(self, direction: int) -> None:
|
|
469
|
-
if not self._history:
|
|
470
|
-
return
|
|
471
|
-
|
|
472
465
|
textarea = self.query_one("#input-textarea", TextArea)
|
|
473
|
-
|
|
474
|
-
if
|
|
475
|
-
self._history_temp = textarea.text
|
|
476
|
-
|
|
477
|
-
new_index = self._history_index + direction
|
|
478
|
-
|
|
479
|
-
if new_index < -1 or new_index >= len(self._history):
|
|
466
|
+
result = self._history.navigate(direction, textarea.text)
|
|
467
|
+
if result is None:
|
|
480
468
|
return
|
|
481
|
-
|
|
482
|
-
self._history_index = new_index
|
|
483
|
-
|
|
484
469
|
textarea.clear()
|
|
485
|
-
|
|
486
|
-
textarea.insert(self._history_temp)
|
|
487
|
-
else:
|
|
488
|
-
history_item = self._history[-(self._history_index + 1)]
|
|
489
|
-
textarea.insert(history_item)
|
|
470
|
+
textarea.insert(result)
|
|
490
471
|
|
|
491
472
|
# -------------------------------------------------------------------------
|
|
492
473
|
# Messages
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from kon import CONFIG_DIR_NAME
|
|
7
|
+
|
|
8
|
+
MAX_HISTORY_ENTRIES = 50
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _history_path() -> Path:
|
|
12
|
+
return Path.home() / CONFIG_DIR_NAME / "prompt-history.jsonl"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PromptHistory:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self._entries: list[str] = []
|
|
18
|
+
self._index: int = 0
|
|
19
|
+
self._draft: str = ""
|
|
20
|
+
self._load()
|
|
21
|
+
|
|
22
|
+
def _load(self) -> None:
|
|
23
|
+
path = _history_path()
|
|
24
|
+
if not path.exists():
|
|
25
|
+
return
|
|
26
|
+
try:
|
|
27
|
+
text = path.read_text(encoding="utf-8")
|
|
28
|
+
except OSError:
|
|
29
|
+
return
|
|
30
|
+
lines: list[str] = []
|
|
31
|
+
for line in text.strip().split("\n"):
|
|
32
|
+
if not line:
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
entry = json.loads(line)
|
|
36
|
+
except (json.JSONDecodeError, ValueError):
|
|
37
|
+
continue
|
|
38
|
+
if isinstance(entry, str) and entry:
|
|
39
|
+
lines.append(entry)
|
|
40
|
+
self._entries = lines[-MAX_HISTORY_ENTRIES:]
|
|
41
|
+
if len(lines) > MAX_HISTORY_ENTRIES:
|
|
42
|
+
self._rewrite()
|
|
43
|
+
|
|
44
|
+
def _rewrite(self) -> None:
|
|
45
|
+
path = _history_path()
|
|
46
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
try:
|
|
48
|
+
content = "\n".join(json.dumps(e) for e in self._entries) + "\n"
|
|
49
|
+
path.write_text(content, encoding="utf-8")
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def _append_to_file(self, entry: str) -> None:
|
|
54
|
+
path = _history_path()
|
|
55
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
try:
|
|
57
|
+
with path.open("a", encoding="utf-8") as f:
|
|
58
|
+
f.write(json.dumps(entry) + "\n")
|
|
59
|
+
except OSError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def append(self, text: str) -> None:
|
|
63
|
+
if not text:
|
|
64
|
+
return
|
|
65
|
+
if self._entries and self._entries[-1] == text:
|
|
66
|
+
self._reset_index()
|
|
67
|
+
return
|
|
68
|
+
self._entries.append(text)
|
|
69
|
+
trimmed = len(self._entries) > MAX_HISTORY_ENTRIES
|
|
70
|
+
if trimmed:
|
|
71
|
+
self._entries = self._entries[-MAX_HISTORY_ENTRIES:]
|
|
72
|
+
self._rewrite()
|
|
73
|
+
else:
|
|
74
|
+
self._append_to_file(text)
|
|
75
|
+
self._reset_index()
|
|
76
|
+
|
|
77
|
+
def _reset_index(self) -> None:
|
|
78
|
+
self._index = 0
|
|
79
|
+
self._draft = ""
|
|
80
|
+
|
|
81
|
+
def navigate(self, direction: int, current_text: str) -> str | None:
|
|
82
|
+
if not self._entries:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
if self._index == 0:
|
|
86
|
+
self._draft = current_text
|
|
87
|
+
|
|
88
|
+
new_index = self._index + direction
|
|
89
|
+
|
|
90
|
+
if new_index > 0 or abs(new_index) > len(self._entries):
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
self._index = new_index
|
|
94
|
+
|
|
95
|
+
if self._index == 0:
|
|
96
|
+
return self._draft
|
|
97
|
+
|
|
98
|
+
return self._entries[self._index]
|
|
@@ -4,7 +4,6 @@ import json
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
-
from ..context import Context
|
|
8
7
|
from ..core.types import (
|
|
9
8
|
AssistantMessage,
|
|
10
9
|
ImageContent,
|
|
@@ -15,7 +14,6 @@ from ..core.types import (
|
|
|
15
14
|
UserMessage,
|
|
16
15
|
)
|
|
17
16
|
from ..llm import ApiType, BaseProvider, ProviderConfig, get_max_tokens, get_model
|
|
18
|
-
from ..loop import build_system_prompt
|
|
19
17
|
from ..session import CompactionEntry, CustomMessageEntry, MessageEntry, Session
|
|
20
18
|
from ..tools import tools_by_name
|
|
21
19
|
from .chat import ChatLog
|
|
@@ -26,7 +24,7 @@ from .widgets import InfoBar, StatusLine, format_path
|
|
|
26
24
|
class SessionUIMixin:
|
|
27
25
|
# Attributes provided by the App subclass
|
|
28
26
|
_cwd: str
|
|
29
|
-
|
|
27
|
+
_agent: Any
|
|
30
28
|
_hide_thinking: bool
|
|
31
29
|
_session: Session | None
|
|
32
30
|
_current_block_type: str | None
|
|
@@ -43,8 +41,6 @@ class SessionUIMixin:
|
|
|
43
41
|
# Methods from other mixins/main class
|
|
44
42
|
def _get_provider_api_type(self, provider: BaseProvider) -> ApiType: ...
|
|
45
43
|
def _create_provider(self, api_type: ApiType, config: ProviderConfig) -> BaseProvider: ...
|
|
46
|
-
def _get_system_prompt(self) -> str:
|
|
47
|
-
return build_system_prompt(self._cwd, self._project_context)
|
|
48
44
|
|
|
49
45
|
def _extract_text_content(self, content: str | list[TextContent | ImageContent]) -> str:
|
|
50
46
|
if isinstance(content, str):
|
|
@@ -220,10 +216,10 @@ class SessionUIMixin:
|
|
|
220
216
|
|
|
221
217
|
chat.add_session_info(getattr(self, "VERSION", ""))
|
|
222
218
|
|
|
223
|
-
if self.
|
|
219
|
+
if self._agent:
|
|
224
220
|
chat.add_loaded_resources(
|
|
225
|
-
context_paths=[format_path(f.path) for f in self.
|
|
226
|
-
skill_paths=[format_path(s.file_path) for s in self.
|
|
221
|
+
context_paths=[format_path(f.path) for f in self._agent.context.agents_files],
|
|
222
|
+
skill_paths=[format_path(s.file_path) for s in self._agent.context.skills],
|
|
227
223
|
)
|
|
228
224
|
|
|
229
225
|
self._render_session_entries(session)
|
|
@@ -146,7 +146,8 @@ async def test_agent_system_prompt(tools, in_memory_session):
|
|
|
146
146
|
provider,
|
|
147
147
|
tools,
|
|
148
148
|
in_memory_session,
|
|
149
|
-
|
|
149
|
+
system_prompt="Custom system prompt",
|
|
150
|
+
config=AgentConfig(max_turns=1),
|
|
150
151
|
)
|
|
151
152
|
|
|
152
153
|
events = []
|
|
@@ -209,7 +210,7 @@ async def test_agent_custom_cwd(tools):
|
|
|
209
210
|
provider = MockProvider(scenario="simple_text")
|
|
210
211
|
session = Session.in_memory(cwd="/custom/path")
|
|
211
212
|
|
|
212
|
-
agent = Agent(provider, tools, session,
|
|
213
|
+
agent = Agent(provider, tools, session, cwd="/custom/path")
|
|
213
214
|
|
|
214
215
|
events = []
|
|
215
216
|
async for event in agent.run("Where am I?"):
|
|
@@ -70,4 +70,4 @@ def test_submit_keeps_display_text_but_expands_query_text() -> None:
|
|
|
70
70
|
assert input_box._fake_textarea.cleared is True
|
|
71
71
|
assert input_box._pastes == {}
|
|
72
72
|
assert input_box._paste_counter == 0
|
|
73
|
-
assert input_box._history[-1] == f"prefix {pasted} suffix"
|
|
73
|
+
assert input_box._history._entries[-1] == f"prefix {pasted} suffix"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from kon.ui import prompt_history as ph
|
|
8
|
+
from kon.ui.prompt_history import MAX_HISTORY_ENTRIES, PromptHistory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def _isolate_history(tmp_path, monkeypatch):
|
|
13
|
+
history_file = tmp_path / "prompt-history.jsonl"
|
|
14
|
+
monkeypatch.setattr(ph, "_history_path", lambda: history_file)
|
|
15
|
+
return history_file
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_append_and_navigate():
|
|
19
|
+
h = PromptHistory()
|
|
20
|
+
h.append("hello")
|
|
21
|
+
h.append("world")
|
|
22
|
+
|
|
23
|
+
assert h.navigate(-1, "") == "world"
|
|
24
|
+
assert h.navigate(-1, "world") == "hello"
|
|
25
|
+
assert h.navigate(1, "hello") == "world"
|
|
26
|
+
assert h.navigate(1, "world") == ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_navigate_empty():
|
|
30
|
+
h = PromptHistory()
|
|
31
|
+
assert h.navigate(-1, "") is None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_navigate_preserves_draft():
|
|
35
|
+
h = PromptHistory()
|
|
36
|
+
h.append("old")
|
|
37
|
+
|
|
38
|
+
result = h.navigate(-1, "my draft")
|
|
39
|
+
assert result == "old"
|
|
40
|
+
result = h.navigate(1, "old")
|
|
41
|
+
assert result == "my draft"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_navigate_bounds():
|
|
45
|
+
h = PromptHistory()
|
|
46
|
+
h.append("only")
|
|
47
|
+
|
|
48
|
+
assert h.navigate(-1, "") == "only"
|
|
49
|
+
assert h.navigate(-1, "only") is None
|
|
50
|
+
h._reset_index()
|
|
51
|
+
assert h.navigate(1, "") is None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_dedup_consecutive():
|
|
55
|
+
h = PromptHistory()
|
|
56
|
+
h.append("same")
|
|
57
|
+
h.append("same")
|
|
58
|
+
assert len(h._entries) == 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_persistence(tmp_path):
|
|
62
|
+
h1 = PromptHistory()
|
|
63
|
+
h1.append("first")
|
|
64
|
+
h1.append("second")
|
|
65
|
+
|
|
66
|
+
h2 = PromptHistory()
|
|
67
|
+
assert h2._entries == ["first", "second"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_max_entries_trim(tmp_path):
|
|
71
|
+
h = PromptHistory()
|
|
72
|
+
for i in range(MAX_HISTORY_ENTRIES + 10):
|
|
73
|
+
h.append(f"entry-{i}")
|
|
74
|
+
|
|
75
|
+
assert len(h._entries) == MAX_HISTORY_ENTRIES
|
|
76
|
+
assert h._entries[0] == "entry-10"
|
|
77
|
+
assert h._entries[-1] == f"entry-{MAX_HISTORY_ENTRIES + 9}"
|
|
78
|
+
|
|
79
|
+
h2 = PromptHistory()
|
|
80
|
+
assert len(h2._entries) == MAX_HISTORY_ENTRIES
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_corrupt_lines_ignored(tmp_path):
|
|
84
|
+
path = ph._history_path()
|
|
85
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
path.write_text(
|
|
87
|
+
json.dumps("good")
|
|
88
|
+
+ "\n"
|
|
89
|
+
+ "not valid json\n"
|
|
90
|
+
+ json.dumps(42)
|
|
91
|
+
+ "\n"
|
|
92
|
+
+ json.dumps("also good")
|
|
93
|
+
+ "\n",
|
|
94
|
+
encoding="utf-8",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
h = PromptHistory()
|
|
98
|
+
assert h._entries == ["good", "also good"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-tmux-test/run-e2e-tests.sh
RENAMED
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/.kon/skills/kon-tmux-test/setup-test-project.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/copilot_anthropic.py
RENAMED
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/github_copilot_headers.py
RENAMED
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/openai_codex_responses.py
RENAMED
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/src/kon/llm/providers/openai_completions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.2.2 → kon_coding_agent-0.2.4}/tests/tools/test_read_image_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|