kader 0.1.5__tar.gz → 1.0.0__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.
- {kader-0.1.5 → kader-1.0.0}/PKG-INFO +38 -1
- {kader-0.1.5 → kader-1.0.0}/README.md +36 -0
- {kader-0.1.5 → kader-1.0.0}/cli/app.py +98 -61
- kader-1.0.0/cli/app.tcss +309 -0
- {kader-0.1.5 → kader-1.0.0}/cli/utils.py +1 -6
- kader-1.0.0/cli/widgets/conversation.py +101 -0
- kader-1.0.0/examples/planner_executor_example.py +74 -0
- {kader-0.1.5 → kader-1.0.0}/examples/tools_example.py +63 -0
- {kader-0.1.5 → kader-1.0.0}/kader/__init__.py +2 -0
- {kader-0.1.5 → kader-1.0.0}/kader/agent/agents.py +8 -0
- {kader-0.1.5 → kader-1.0.0}/kader/agent/base.py +68 -5
- {kader-0.1.5 → kader-1.0.0}/kader/memory/types.py +60 -0
- kader-1.0.0/kader/prompts/__init__.py +17 -0
- kader-1.0.0/kader/prompts/agent_prompts.py +55 -0
- kader-1.0.0/kader/prompts/templates/executor_agent.j2 +70 -0
- kader-1.0.0/kader/prompts/templates/kader_planner.j2 +71 -0
- {kader-0.1.5 → kader-1.0.0}/kader/providers/ollama.py +2 -2
- {kader-0.1.5 → kader-1.0.0}/kader/tools/__init__.py +26 -0
- kader-1.0.0/kader/tools/agent.py +452 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/filesys.py +1 -1
- {kader-0.1.5 → kader-1.0.0}/kader/tools/todo.py +43 -2
- kader-1.0.0/kader/utils/__init__.py +10 -0
- kader-1.0.0/kader/utils/checkpointer.py +371 -0
- kader-1.0.0/kader/utils/context_aggregator.py +347 -0
- kader-1.0.0/kader/workflows/__init__.py +13 -0
- kader-1.0.0/kader/workflows/base.py +71 -0
- kader-1.0.0/kader/workflows/planner_executor.py +251 -0
- {kader-0.1.5 → kader-1.0.0}/pyproject.toml +2 -1
- kader-1.0.0/tests/conftest.py +50 -0
- {kader-0.1.5 → kader-1.0.0}/tests/test_agent_logger_integration.py +21 -11
- {kader-0.1.5 → kader-1.0.0}/tests/test_todo_tool.py +58 -5
- kader-1.0.0/tests/tools/test_agent_tool.py +275 -0
- kader-1.0.0/tests/tools/test_agent_tool_persistence.py +213 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_filesystem_tools.py +3 -3
- {kader-0.1.5 → kader-1.0.0}/uv.lock +12 -1
- kader-0.1.5/cli/app.tcss +0 -664
- kader-0.1.5/cli/widgets/conversation.py +0 -55
- kader-0.1.5/kader/prompts/__init__.py +0 -9
- kader-0.1.5/kader/prompts/agent_prompts.py +0 -27
- kader-0.1.5/tests/conftest.py +0 -14
- {kader-0.1.5 → kader-1.0.0}/.github/workflows/ci.yml +0 -0
- {kader-0.1.5 → kader-1.0.0}/.github/workflows/release.yml +0 -0
- {kader-0.1.5 → kader-1.0.0}/.gitignore +0 -0
- {kader-0.1.5 → kader-1.0.0}/.python-version +0 -0
- {kader-0.1.5 → kader-1.0.0}/.qwen/QWEN.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/.qwen/agents/technical-writer.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/.qwen/agents/test-automation-specialist.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/README.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/__init__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/__main__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/widgets/__init__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/widgets/confirmation.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/cli/widgets/loading.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/.gitignore +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/README.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/memory_example.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/ollama_example.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/planning_agent_example.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/python_developer/main.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/python_developer/template.yaml +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/react_agent_example.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/simple_agent.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/examples/todo_agent/main.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/agent/__init__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/agent/logger.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/config.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/memory/__init__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/memory/conversation.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/memory/session.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/memory/state.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/prompts/base.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/prompts/templates/planning_agent.j2 +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/prompts/templates/react_agent.j2 +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/providers/__init__.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/providers/base.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/providers/mock.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/README.md +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/base.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/exec_commands.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/filesystem.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/protocol.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/rag.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/utils.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/kader/tools/web.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/providers/test_mock.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/providers/test_ollama.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/providers/test_providers_base.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/test_agent_logger.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/test_base_agent.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/test_file_memory.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_exec_commands.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_filesys_tools.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_rag.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_tools_base.py +0 -0
- {kader-0.1.5 → kader-1.0.0}/tests/tools/test_web.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: kader coding agent
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: aiofiles>=25.1.0
|
|
6
7
|
Requires-Dist: faiss-cpu>=1.9.0
|
|
7
8
|
Requires-Dist: jinja2>=3.1.6
|
|
8
9
|
Requires-Dist: loguru>=0.7.3
|
|
@@ -32,6 +33,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
|
|
|
32
33
|
- 🔄 **ReAct Agent Framework** - Reasoning and Acting agent architecture
|
|
33
34
|
- 🗂️ **File System Tools** - Read, write, search, and edit files
|
|
34
35
|
- 🔍 **Planning Agent** - Task planning and execution capabilities
|
|
36
|
+
- 🤝 **Agent-As-Tool** - Spawn sub-agents for specific tasks with isolated memory
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
@@ -177,6 +179,40 @@ Kader provides several agent types:
|
|
|
177
179
|
- **PlanningAgent**: Agent that plans multi-step tasks
|
|
178
180
|
- **BaseAgent**: Base agent class for creating custom agents
|
|
179
181
|
|
|
182
|
+
### Agent-As-Tool (AgentTool)
|
|
183
|
+
|
|
184
|
+
The `AgentTool` allows you to wrap a `ReActAgent` as a callable tool, enabling agents to spawn sub-agents for specific tasks with isolated memory contexts.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from kader.tools import AgentTool
|
|
188
|
+
|
|
189
|
+
# Autonomous execution (runs without pausing for confirmation)
|
|
190
|
+
autonomous_agent = AgentTool(
|
|
191
|
+
name="research_agent",
|
|
192
|
+
description="Research topics autonomously",
|
|
193
|
+
interrupt_before_tool=False,
|
|
194
|
+
)
|
|
195
|
+
result = autonomous_agent.execute(task="Find info about topic X")
|
|
196
|
+
|
|
197
|
+
# Interactive execution (pauses for user confirmation before each tool)
|
|
198
|
+
def my_callback(tool_call_dict, llm_content=None):
|
|
199
|
+
user_input = input("Execute? [y/n]: ")
|
|
200
|
+
return (user_input.lower() == 'y', None)
|
|
201
|
+
|
|
202
|
+
interactive_agent = AgentTool(
|
|
203
|
+
name="interactive_agent",
|
|
204
|
+
interrupt_before_tool=True,
|
|
205
|
+
tool_confirmation_callback=my_callback,
|
|
206
|
+
)
|
|
207
|
+
result = interactive_agent.execute(task="Analyze data and generate report")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Key Features:**
|
|
211
|
+
- Each sub-agent has isolated memory (separate `SlidingWindowConversationManager`)
|
|
212
|
+
- Default tools included: filesystem, web search, command executor
|
|
213
|
+
- Optional `interrupt_before_tool` for user confirmation before tool execution
|
|
214
|
+
- Task completes when the agent returns its final response
|
|
215
|
+
|
|
180
216
|
### Memory Management
|
|
181
217
|
|
|
182
218
|
Kader's memory system includes:
|
|
@@ -194,6 +230,7 @@ Kader includes a rich set of tools:
|
|
|
194
230
|
- **Command Executor**: Execute shell commands safely
|
|
195
231
|
- **Web Tools**: Search and fetch web content
|
|
196
232
|
- **RAG Tools**: Retrieval Augmented Generation capabilities
|
|
233
|
+
- **AgentTool**: Spawn sub-agents for specific tasks
|
|
197
234
|
|
|
198
235
|
## Examples
|
|
199
236
|
|
|
@@ -15,6 +15,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
|
|
|
15
15
|
- 🔄 **ReAct Agent Framework** - Reasoning and Acting agent architecture
|
|
16
16
|
- 🗂️ **File System Tools** - Read, write, search, and edit files
|
|
17
17
|
- 🔍 **Planning Agent** - Task planning and execution capabilities
|
|
18
|
+
- 🤝 **Agent-As-Tool** - Spawn sub-agents for specific tasks with isolated memory
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -160,6 +161,40 @@ Kader provides several agent types:
|
|
|
160
161
|
- **PlanningAgent**: Agent that plans multi-step tasks
|
|
161
162
|
- **BaseAgent**: Base agent class for creating custom agents
|
|
162
163
|
|
|
164
|
+
### Agent-As-Tool (AgentTool)
|
|
165
|
+
|
|
166
|
+
The `AgentTool` allows you to wrap a `ReActAgent` as a callable tool, enabling agents to spawn sub-agents for specific tasks with isolated memory contexts.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from kader.tools import AgentTool
|
|
170
|
+
|
|
171
|
+
# Autonomous execution (runs without pausing for confirmation)
|
|
172
|
+
autonomous_agent = AgentTool(
|
|
173
|
+
name="research_agent",
|
|
174
|
+
description="Research topics autonomously",
|
|
175
|
+
interrupt_before_tool=False,
|
|
176
|
+
)
|
|
177
|
+
result = autonomous_agent.execute(task="Find info about topic X")
|
|
178
|
+
|
|
179
|
+
# Interactive execution (pauses for user confirmation before each tool)
|
|
180
|
+
def my_callback(tool_call_dict, llm_content=None):
|
|
181
|
+
user_input = input("Execute? [y/n]: ")
|
|
182
|
+
return (user_input.lower() == 'y', None)
|
|
183
|
+
|
|
184
|
+
interactive_agent = AgentTool(
|
|
185
|
+
name="interactive_agent",
|
|
186
|
+
interrupt_before_tool=True,
|
|
187
|
+
tool_confirmation_callback=my_callback,
|
|
188
|
+
)
|
|
189
|
+
result = interactive_agent.execute(task="Analyze data and generate report")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Key Features:**
|
|
193
|
+
- Each sub-agent has isolated memory (separate `SlidingWindowConversationManager`)
|
|
194
|
+
- Default tools included: filesystem, web search, command executor
|
|
195
|
+
- Optional `interrupt_before_tool` for user confirmation before tool execution
|
|
196
|
+
- Task completes when the agent returns its final response
|
|
197
|
+
|
|
163
198
|
### Memory Management
|
|
164
199
|
|
|
165
200
|
Kader's memory system includes:
|
|
@@ -177,6 +212,7 @@ Kader includes a rich set of tools:
|
|
|
177
212
|
- **Command Executor**: Execute shell commands safely
|
|
178
213
|
- **Web Tools**: Search and fetch web content
|
|
179
214
|
- **RAG Tools**: Retrieval Augmented Generation capabilities
|
|
215
|
+
- **AgentTool**: Spawn sub-agents for specific tasks
|
|
180
216
|
|
|
181
217
|
## Examples
|
|
182
218
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Kader CLI - Modern Vibe Coding CLI with Textual."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import atexit
|
|
4
5
|
import threading
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
7
|
from importlib.metadata import version as get_version
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Optional
|
|
@@ -18,18 +20,15 @@ from textual.widgets import (
|
|
|
18
20
|
Tree,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
|
-
from kader.agent.agents import ReActAgent
|
|
22
23
|
from kader.memory import (
|
|
23
24
|
FileSessionManager,
|
|
24
25
|
MemoryConfig,
|
|
25
|
-
SlidingWindowConversationManager,
|
|
26
26
|
)
|
|
27
|
-
from kader.
|
|
27
|
+
from kader.workflows import PlannerExecutorWorkflow
|
|
28
28
|
|
|
29
29
|
from .utils import (
|
|
30
30
|
DEFAULT_MODEL,
|
|
31
31
|
HELP_TEXT,
|
|
32
|
-
THEME_NAMES,
|
|
33
32
|
)
|
|
34
33
|
from .widgets import ConversationView, InlineSelector, LoadingSpinner, ModelSelector
|
|
35
34
|
|
|
@@ -51,7 +50,6 @@ Type a message below to start chatting, or use one of the commands:
|
|
|
51
50
|
|
|
52
51
|
- `/help` - Show available commands
|
|
53
52
|
- `/models` - View available LLM models
|
|
54
|
-
- `/theme` - Change the color theme
|
|
55
53
|
- `/clear` - Clear the conversation
|
|
56
54
|
- `/save` - Save current session
|
|
57
55
|
- `/load` - Load a saved session
|
|
@@ -83,7 +81,6 @@ class KaderApp(App):
|
|
|
83
81
|
BINDINGS = [
|
|
84
82
|
Binding("ctrl+q", "quit", "Quit"),
|
|
85
83
|
Binding("ctrl+l", "clear", "Clear"),
|
|
86
|
-
Binding("ctrl+t", "cycle_theme", "Theme"),
|
|
87
84
|
Binding("ctrl+s", "save_session", "Save"),
|
|
88
85
|
Binding("ctrl+r", "refresh_tree", "Refresh"),
|
|
89
86
|
Binding("tab", "focus_next", "Next", show=False),
|
|
@@ -92,7 +89,6 @@ class KaderApp(App):
|
|
|
92
89
|
|
|
93
90
|
def __init__(self) -> None:
|
|
94
91
|
super().__init__()
|
|
95
|
-
self._current_theme_index = 0
|
|
96
92
|
self._is_processing = False
|
|
97
93
|
self._current_model = DEFAULT_MODEL
|
|
98
94
|
self._current_session_id: str | None = None
|
|
@@ -107,22 +103,79 @@ class KaderApp(App):
|
|
|
107
103
|
self._model_selector: Optional[ModelSelector] = None
|
|
108
104
|
self._update_info: Optional[str] = None # Latest version if update available
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
# Dedicated thread pool for agent invocation (isolated from default pool)
|
|
107
|
+
self._agent_executor = ThreadPoolExecutor(
|
|
108
|
+
max_workers=2, thread_name_prefix="kader_agent"
|
|
109
|
+
)
|
|
110
|
+
# Ensure executor is properly shut down on exit
|
|
111
|
+
atexit.register(self._agent_executor.shutdown, wait=False)
|
|
112
|
+
|
|
113
|
+
self._workflow = self._create_workflow(self._current_model)
|
|
111
114
|
|
|
112
|
-
def
|
|
113
|
-
"""Create a new
|
|
114
|
-
|
|
115
|
-
memory = SlidingWindowConversationManager(window_size=10)
|
|
116
|
-
return ReActAgent(
|
|
115
|
+
def _create_workflow(self, model_name: str) -> PlannerExecutorWorkflow:
|
|
116
|
+
"""Create a new PlannerExecutorWorkflow with the specified model."""
|
|
117
|
+
return PlannerExecutorWorkflow(
|
|
117
118
|
name="kader_cli",
|
|
118
|
-
tools=registry,
|
|
119
|
-
memory=memory,
|
|
120
119
|
model_name=model_name,
|
|
121
|
-
use_persistence=True,
|
|
122
120
|
interrupt_before_tool=True,
|
|
123
121
|
tool_confirmation_callback=self._tool_confirmation_callback,
|
|
122
|
+
direct_execution_callback=self._direct_execution_callback,
|
|
123
|
+
tool_execution_result_callback=self._tool_execution_result_callback,
|
|
124
|
+
use_persistence=True,
|
|
125
|
+
executor_names=["executor"],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _direct_execution_callback(self, message: str, tool_name: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Callback for direct execution tools - called from agent thread.
|
|
131
|
+
|
|
132
|
+
Shows a message in the conversation view without blocking for confirmation.
|
|
133
|
+
"""
|
|
134
|
+
# Schedule message display on main thread
|
|
135
|
+
self.call_from_thread(self._show_direct_execution_message, message, tool_name)
|
|
136
|
+
|
|
137
|
+
def _show_direct_execution_message(self, message: str, tool_name: str) -> None:
|
|
138
|
+
"""Show a direct execution message in the conversation view."""
|
|
139
|
+
try:
|
|
140
|
+
conversation = self.query_one("#conversation-view", ConversationView)
|
|
141
|
+
# User-friendly message showing the tool is executing
|
|
142
|
+
friendly_message = f"[>] Executing {tool_name}..."
|
|
143
|
+
conversation.add_message(friendly_message, "assistant")
|
|
144
|
+
conversation.scroll_end()
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def _tool_execution_result_callback(
|
|
149
|
+
self, tool_name: str, success: bool, result: str
|
|
150
|
+
) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Callback for tool execution results - called from agent thread.
|
|
153
|
+
|
|
154
|
+
Updates the conversation view with the execution result.
|
|
155
|
+
"""
|
|
156
|
+
# Schedule result display on main thread
|
|
157
|
+
self.call_from_thread(
|
|
158
|
+
self._show_tool_execution_result, tool_name, success, result
|
|
124
159
|
)
|
|
125
160
|
|
|
161
|
+
def _show_tool_execution_result(
|
|
162
|
+
self, tool_name: str, success: bool, result: str
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Show the tool execution result in the conversation view."""
|
|
165
|
+
try:
|
|
166
|
+
conversation = self.query_one("#conversation-view", ConversationView)
|
|
167
|
+
if success:
|
|
168
|
+
# User-friendly success message
|
|
169
|
+
friendly_message = f"(+) {tool_name} completed successfully"
|
|
170
|
+
else:
|
|
171
|
+
# User-friendly error message with truncated result
|
|
172
|
+
error_preview = result[:100] + "..." if len(result) > 100 else result
|
|
173
|
+
friendly_message = f"(-) {tool_name} failed: {error_preview}"
|
|
174
|
+
conversation.add_message(friendly_message, "assistant")
|
|
175
|
+
conversation.scroll_end()
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
|
|
126
179
|
def _tool_confirmation_callback(self, message: str) -> tuple[bool, Optional[str]]:
|
|
127
180
|
"""
|
|
128
181
|
Callback for tool confirmation - called from agent thread.
|
|
@@ -139,7 +192,10 @@ class KaderApp(App):
|
|
|
139
192
|
|
|
140
193
|
# Wait for user response (blocking in agent thread)
|
|
141
194
|
# This is safe because we're in a background thread
|
|
142
|
-
|
|
195
|
+
# Timeout after 5 minutes to prevent indefinite blocking
|
|
196
|
+
if not self._confirmation_event.wait(timeout=300):
|
|
197
|
+
# Timeout occurred - decline tool execution gracefully
|
|
198
|
+
return (False, "Tool confirmation timed out after 5 minutes")
|
|
143
199
|
|
|
144
200
|
# Return the result
|
|
145
201
|
return self._confirmation_result
|
|
@@ -187,7 +243,8 @@ class KaderApp(App):
|
|
|
187
243
|
if event.confirmed:
|
|
188
244
|
if tool_message:
|
|
189
245
|
conversation.add_message(tool_message, "assistant")
|
|
190
|
-
|
|
246
|
+
# Show executing message - will be updated by result callback
|
|
247
|
+
conversation.add_message("[>] Executing tool...", "assistant")
|
|
191
248
|
# Restart spinner
|
|
192
249
|
try:
|
|
193
250
|
spinner = self.query_one(LoadingSpinner)
|
|
@@ -253,7 +310,7 @@ class KaderApp(App):
|
|
|
253
310
|
# Update model and recreate agent
|
|
254
311
|
old_model = self._current_model
|
|
255
312
|
self._current_model = event.model
|
|
256
|
-
self.
|
|
313
|
+
self._workflow = self._create_workflow(self._current_model)
|
|
257
314
|
|
|
258
315
|
conversation.add_message(
|
|
259
316
|
f"(+) Model changed from `{old_model}` to `{self._current_model}`",
|
|
@@ -433,16 +490,10 @@ Please resize your terminal."""
|
|
|
433
490
|
conversation.add_message(HELP_TEXT, "assistant")
|
|
434
491
|
elif cmd == "/models":
|
|
435
492
|
await self._show_model_selector(conversation)
|
|
436
|
-
elif cmd == "/theme":
|
|
437
|
-
self._cycle_theme()
|
|
438
|
-
theme_name = THEME_NAMES[self._current_theme_index]
|
|
439
|
-
conversation.add_message(
|
|
440
|
-
f"{{~}} Theme changed to **{theme_name}**!", "assistant"
|
|
441
|
-
)
|
|
442
493
|
elif cmd == "/clear":
|
|
443
494
|
conversation.clear_messages()
|
|
444
|
-
self.
|
|
445
|
-
self.
|
|
495
|
+
self._workflow.planner.memory.clear()
|
|
496
|
+
self._workflow.planner.provider.reset_tracking() # Reset usage/cost tracking
|
|
446
497
|
self._current_session_id = None
|
|
447
498
|
self.notify("Conversation cleared!", severity="information")
|
|
448
499
|
elif cmd == "/save":
|
|
@@ -472,7 +523,7 @@ Please resize your terminal."""
|
|
|
472
523
|
)
|
|
473
524
|
|
|
474
525
|
async def _handle_chat(self, message: str) -> None:
|
|
475
|
-
"""Handle regular chat messages with
|
|
526
|
+
"""Handle regular chat messages with PlannerExecutorWorkflow."""
|
|
476
527
|
if self._is_processing:
|
|
477
528
|
self.notify("Please wait for the current response...", severity="warning")
|
|
478
529
|
return
|
|
@@ -500,16 +551,21 @@ Please resize your terminal."""
|
|
|
500
551
|
spinner = self.query_one(LoadingSpinner)
|
|
501
552
|
|
|
502
553
|
try:
|
|
503
|
-
# Run the
|
|
554
|
+
# Run the workflow in a dedicated thread pool
|
|
504
555
|
loop = asyncio.get_event_loop()
|
|
505
556
|
response = await loop.run_in_executor(
|
|
506
|
-
|
|
557
|
+
self._agent_executor, lambda: self._workflow.run(message)
|
|
507
558
|
)
|
|
508
559
|
|
|
509
560
|
# Hide spinner and show response (this runs on main thread via await)
|
|
510
561
|
spinner.stop()
|
|
511
|
-
if response
|
|
512
|
-
conversation.add_message(
|
|
562
|
+
if response:
|
|
563
|
+
conversation.add_message(
|
|
564
|
+
response,
|
|
565
|
+
"assistant",
|
|
566
|
+
model_name=self._workflow.planner.provider.model,
|
|
567
|
+
usage_cost=self._workflow.planner.provider.total_cost.total_cost,
|
|
568
|
+
)
|
|
513
569
|
|
|
514
570
|
except Exception as e:
|
|
515
571
|
spinner.stop()
|
|
@@ -522,34 +578,13 @@ Please resize your terminal."""
|
|
|
522
578
|
# Auto-refresh directory tree in case agent created/modified files
|
|
523
579
|
self._refresh_directory_tree()
|
|
524
580
|
|
|
525
|
-
def _cycle_theme(self) -> None:
|
|
526
|
-
"""Cycle through available themes."""
|
|
527
|
-
# Remove current theme class if it's not dark
|
|
528
|
-
current_theme = THEME_NAMES[self._current_theme_index]
|
|
529
|
-
if current_theme != "dark":
|
|
530
|
-
self.remove_class(f"theme-{current_theme}")
|
|
531
|
-
|
|
532
|
-
# Move to next theme
|
|
533
|
-
self._current_theme_index = (self._current_theme_index + 1) % len(THEME_NAMES)
|
|
534
|
-
new_theme = THEME_NAMES[self._current_theme_index]
|
|
535
|
-
|
|
536
|
-
# Apply new theme class (dark is default, no class needed)
|
|
537
|
-
if new_theme != "dark":
|
|
538
|
-
self.add_class(f"theme-{new_theme}")
|
|
539
|
-
|
|
540
581
|
def action_clear(self) -> None:
|
|
541
582
|
"""Clear the conversation (Ctrl+L)."""
|
|
542
583
|
conversation = self.query_one("#conversation-view", ConversationView)
|
|
543
584
|
conversation.clear_messages()
|
|
544
|
-
self.
|
|
585
|
+
self._workflow.planner.memory.clear()
|
|
545
586
|
self.notify("Conversation cleared!", severity="information")
|
|
546
587
|
|
|
547
|
-
def action_cycle_theme(self) -> None:
|
|
548
|
-
"""Cycle theme (Ctrl+T)."""
|
|
549
|
-
self._cycle_theme()
|
|
550
|
-
theme_name = THEME_NAMES[self._current_theme_index]
|
|
551
|
-
self.notify(f"Theme: {theme_name}", severity="information")
|
|
552
|
-
|
|
553
588
|
def action_save_session(self) -> None:
|
|
554
589
|
"""Save session (Ctrl+S)."""
|
|
555
590
|
conversation = self.query_one("#conversation-view", ConversationView)
|
|
@@ -579,8 +614,10 @@ Please resize your terminal."""
|
|
|
579
614
|
session = self._session_manager.create_session("kader_cli")
|
|
580
615
|
self._current_session_id = session.session_id
|
|
581
616
|
|
|
582
|
-
# Get messages from
|
|
583
|
-
messages = [
|
|
617
|
+
# Get messages from planner memory and save
|
|
618
|
+
messages = [
|
|
619
|
+
msg.message for msg in self._workflow.planner.memory.get_messages()
|
|
620
|
+
]
|
|
584
621
|
self._session_manager.save_conversation(self._current_session_id, messages)
|
|
585
622
|
|
|
586
623
|
conversation.add_message(
|
|
@@ -611,11 +648,11 @@ Please resize your terminal."""
|
|
|
611
648
|
|
|
612
649
|
# Clear current state
|
|
613
650
|
conversation.clear_messages()
|
|
614
|
-
self.
|
|
651
|
+
self._workflow.planner.memory.clear()
|
|
615
652
|
|
|
616
653
|
# Add loaded messages to memory and UI
|
|
617
654
|
for msg in messages:
|
|
618
|
-
self.
|
|
655
|
+
self._workflow.planner.memory.add_message(msg)
|
|
619
656
|
role = msg.get("role", "user")
|
|
620
657
|
content = msg.get("content", "")
|
|
621
658
|
if role in ["user", "assistant"] and content:
|
|
@@ -664,9 +701,9 @@ Please resize your terminal."""
|
|
|
664
701
|
"""Display LLM usage costs."""
|
|
665
702
|
try:
|
|
666
703
|
# Get cost and usage from the provider
|
|
667
|
-
cost = self.
|
|
668
|
-
usage = self.
|
|
669
|
-
model = self.
|
|
704
|
+
cost = self._workflow.planner.provider.total_cost
|
|
705
|
+
usage = self._workflow.planner.provider.total_usage
|
|
706
|
+
model = self._workflow.planner.provider.model
|
|
670
707
|
|
|
671
708
|
lines = [
|
|
672
709
|
"## Usage Costs ($)\n",
|