code-puppy 0.0.170__tar.gz → 0.0.171__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.
- {code_puppy-0.0.170 → code_puppy-0.0.171}/PKG-INFO +2 -2
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agent.py +8 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/message_history_processor.py +131 -75
- code_puppy-0.0.171/code_puppy/state_management.py +157 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/app.py +4 -2
- {code_puppy-0.0.170 → code_puppy-0.0.171}/pyproject.toml +2 -2
- code_puppy-0.0.170/code_puppy/state_management.py +0 -200
- {code_puppy-0.0.170 → code_puppy-0.0.171}/.gitignore +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/LICENSE +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/README.md +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/agent_orchestrator.json +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/agents/runtime_manager.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/meta_command_handler.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/config.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/main.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/async_lifecycle.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/blocking_startup.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/captured_stdio_server.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/circuit_breaker.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/config_wizard.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/dashboard.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/error_isolation.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/examples/retry_example.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/health_monitor.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/managed_server.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/manager.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/registry.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/retry_manager.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/server_registry_catalog.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/status_tracker.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/mcp/system_tools.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/models.json +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/chat_view.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/input_area.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/status_bar.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/screens/__init__.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/screens/settings.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/version_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.171
|
|
4
4
|
Summary: Code generation agent
|
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
|
@@ -24,7 +24,7 @@ Requires-Dist: logfire>=0.7.1
|
|
|
24
24
|
Requires-Dist: openai>=1.99.1
|
|
25
25
|
Requires-Dist: pathspec>=0.11.0
|
|
26
26
|
Requires-Dist: prompt-toolkit>=3.0.38
|
|
27
|
-
Requires-Dist: pydantic-ai>=1.0.
|
|
27
|
+
Requires-Dist: pydantic-ai>=1.0.10
|
|
28
28
|
Requires-Dist: pydantic>=2.4.0
|
|
29
29
|
Requires-Dist: pyjwt>=2.8.0
|
|
30
30
|
Requires-Dist: pytest-cov>=6.1.1
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from pydantic_ai.models.openai import OpenAIModelSettings, OpenAIResponsesModelSettings
|
|
3
4
|
from typing import Dict, Optional
|
|
4
5
|
|
|
5
6
|
from pydantic_ai import Agent
|
|
@@ -170,7 +171,14 @@ def reload_code_generation_agent(message_group: str | None):
|
|
|
170
171
|
console.print(f"Max output tokens per message: {output_tokens}")
|
|
171
172
|
model_settings_dict["max_tokens"] = output_tokens
|
|
172
173
|
|
|
174
|
+
|
|
173
175
|
model_settings = ModelSettings(**model_settings_dict)
|
|
176
|
+
if "gpt-5" in model_name:
|
|
177
|
+
model_settings_dict["openai_reasoning_effort"] = "high"
|
|
178
|
+
model_settings_dict["extra_body"] = {
|
|
179
|
+
"verbosity": "low"
|
|
180
|
+
}
|
|
181
|
+
model_settings = OpenAIModelSettings(**model_settings_dict)
|
|
174
182
|
agent = Agent(
|
|
175
183
|
model=model,
|
|
176
184
|
instructions=instructions,
|
|
@@ -3,7 +3,15 @@ import queue
|
|
|
3
3
|
from typing import Any, List, Set, Tuple
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
|
-
from pydantic_ai.messages import
|
|
6
|
+
from pydantic_ai.messages import (
|
|
7
|
+
ModelMessage,
|
|
8
|
+
ModelRequest,
|
|
9
|
+
TextPart,
|
|
10
|
+
ToolCallPart,
|
|
11
|
+
ToolCallPartDelta,
|
|
12
|
+
ToolReturn,
|
|
13
|
+
ToolReturnPart,
|
|
14
|
+
)
|
|
7
15
|
|
|
8
16
|
from code_puppy.config import (
|
|
9
17
|
get_model_name,
|
|
@@ -82,9 +90,46 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
|
82
90
|
|
|
83
91
|
|
|
84
92
|
def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
if not messages:
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
# Never drop the system prompt, even if it is extremely large.
|
|
97
|
+
system_message, *rest = messages
|
|
98
|
+
filtered_rest = [
|
|
99
|
+
m for m in rest if estimate_tokens_for_message(m) < 50000
|
|
100
|
+
]
|
|
101
|
+
return [system_message] + filtered_rest
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _is_tool_call_part(part: Any) -> bool:
|
|
105
|
+
if isinstance(part, (ToolCallPart, ToolCallPartDelta)):
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
part_kind = (getattr(part, "part_kind", "") or "").replace("_", "-")
|
|
109
|
+
if part_kind == "tool-call":
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
has_tool_name = getattr(part, "tool_name", None) is not None
|
|
113
|
+
has_args = getattr(part, "args", None) is not None
|
|
114
|
+
has_args_delta = getattr(part, "args_delta", None) is not None
|
|
115
|
+
|
|
116
|
+
return bool(has_tool_name and (has_args or has_args_delta))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _is_tool_return_part(part: Any) -> bool:
|
|
120
|
+
if isinstance(part, (ToolReturnPart, ToolReturn)):
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
part_kind = (getattr(part, "part_kind", "") or "").replace("_", "-")
|
|
124
|
+
if part_kind in {"tool-return", "tool-result"}:
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
if getattr(part, "tool_call_id", None) is None:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
has_content = getattr(part, "content", None) is not None
|
|
131
|
+
has_content_delta = getattr(part, "content_delta", None) is not None
|
|
132
|
+
return bool(has_content or has_content_delta)
|
|
88
133
|
|
|
89
134
|
|
|
90
135
|
def split_messages_for_protected_summarization(
|
|
@@ -126,19 +171,18 @@ def split_messages_for_protected_summarization(
|
|
|
126
171
|
if protected_token_count + message_tokens > protected_tokens_limit:
|
|
127
172
|
break
|
|
128
173
|
|
|
129
|
-
protected_messages.
|
|
174
|
+
protected_messages.append(message)
|
|
130
175
|
protected_token_count += message_tokens
|
|
131
176
|
|
|
132
|
-
#
|
|
177
|
+
# Messages that were added while scanning backwards are currently in reverse order.
|
|
178
|
+
# Reverse them to restore chronological ordering, then prepend the system prompt.
|
|
179
|
+
protected_messages.reverse()
|
|
133
180
|
protected_messages.insert(0, system_message)
|
|
134
181
|
|
|
135
|
-
# Messages to summarize are everything between system message and
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
messages_to_summarize = messages[
|
|
140
|
-
1:protected_start_idx
|
|
141
|
-
] # Start from 1 to skip system message
|
|
182
|
+
# Messages to summarize are everything between the system message and the
|
|
183
|
+
# protected tail zone we just constructed.
|
|
184
|
+
protected_start_idx = max(1, len(messages) - (len(protected_messages) - 1))
|
|
185
|
+
messages_to_summarize = messages[1:protected_start_idx]
|
|
142
186
|
|
|
143
187
|
emit_info(
|
|
144
188
|
f"🔒 Protecting {len(protected_messages)} recent messages ({protected_token_count} tokens, limit: {protected_tokens_limit})"
|
|
@@ -164,43 +208,28 @@ def deduplicate_tool_returns(messages: List[ModelMessage]) -> List[ModelMessage]
|
|
|
164
208
|
removed_count = 0
|
|
165
209
|
|
|
166
210
|
for msg in messages:
|
|
167
|
-
# Check if this message has any parts we need to filter
|
|
168
211
|
if not hasattr(msg, "parts") or not msg.parts:
|
|
169
212
|
deduplicated.append(msg)
|
|
170
213
|
continue
|
|
171
214
|
|
|
172
|
-
# Filter parts within this message
|
|
173
215
|
filtered_parts = []
|
|
174
216
|
msg_had_duplicates = False
|
|
175
217
|
|
|
176
218
|
for part in msg.parts:
|
|
177
219
|
tool_call_id = getattr(part, "tool_call_id", None)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
# Check if this is a tool-return part
|
|
181
|
-
if tool_call_id and part_kind in {
|
|
182
|
-
"tool-return",
|
|
183
|
-
"tool-result",
|
|
184
|
-
"tool_result",
|
|
185
|
-
}:
|
|
220
|
+
if tool_call_id and _is_tool_return_part(part):
|
|
186
221
|
if tool_call_id in seen_tool_returns:
|
|
187
|
-
# This is a duplicate return, skip it
|
|
188
222
|
msg_had_duplicates = True
|
|
189
223
|
removed_count += 1
|
|
190
224
|
continue
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
# Not a tool return, always keep
|
|
197
|
-
filtered_parts.append(part)
|
|
225
|
+
seen_tool_returns.add(tool_call_id)
|
|
226
|
+
filtered_parts.append(part)
|
|
227
|
+
|
|
228
|
+
if not filtered_parts:
|
|
229
|
+
continue
|
|
198
230
|
|
|
199
|
-
|
|
200
|
-
if msg_had_duplicates and filtered_parts:
|
|
201
|
-
# Create a new message with the same attributes but filtered parts
|
|
231
|
+
if msg_had_duplicates:
|
|
202
232
|
new_msg = type(msg)(parts=filtered_parts)
|
|
203
|
-
# Copy over other attributes if they exist
|
|
204
233
|
for attr_name in dir(msg):
|
|
205
234
|
if (
|
|
206
235
|
not attr_name.startswith("_")
|
|
@@ -210,12 +239,10 @@ def deduplicate_tool_returns(messages: List[ModelMessage]) -> List[ModelMessage]
|
|
|
210
239
|
try:
|
|
211
240
|
setattr(new_msg, attr_name, getattr(msg, attr_name))
|
|
212
241
|
except (AttributeError, TypeError):
|
|
213
|
-
# Skip attributes that can't be set
|
|
214
242
|
pass
|
|
215
243
|
deduplicated.append(new_msg)
|
|
216
|
-
|
|
244
|
+
else:
|
|
217
245
|
deduplicated.append(msg)
|
|
218
|
-
# If no parts remain after filtering, drop the entire message
|
|
219
246
|
|
|
220
247
|
if removed_count > 0:
|
|
221
248
|
emit_warning(f"Removed {removed_count} duplicate tool-return part(s)")
|
|
@@ -224,23 +251,35 @@ def deduplicate_tool_returns(messages: List[ModelMessage]) -> List[ModelMessage]
|
|
|
224
251
|
|
|
225
252
|
|
|
226
253
|
def summarize_messages(
|
|
227
|
-
messages: List[ModelMessage], with_protection=True
|
|
254
|
+
messages: List[ModelMessage], with_protection: bool = True
|
|
228
255
|
) -> Tuple[List[ModelMessage], List[ModelMessage]]:
|
|
229
256
|
"""
|
|
230
257
|
Summarize messages while protecting recent messages up to PROTECTED_TOKENS.
|
|
231
258
|
|
|
232
259
|
Returns:
|
|
233
|
-
|
|
260
|
+
Tuple of (compacted_messages, summarized_source_messages)
|
|
261
|
+
where compacted_messages always preserves the original system message
|
|
262
|
+
as the first entry.
|
|
234
263
|
"""
|
|
235
|
-
messages_to_summarize
|
|
264
|
+
messages_to_summarize: List[ModelMessage]
|
|
265
|
+
protected_messages: List[ModelMessage]
|
|
266
|
+
|
|
236
267
|
if with_protection:
|
|
237
268
|
messages_to_summarize, protected_messages = (
|
|
238
269
|
split_messages_for_protected_summarization(messages)
|
|
239
270
|
)
|
|
271
|
+
else:
|
|
272
|
+
messages_to_summarize = messages[1:] if messages else []
|
|
273
|
+
protected_messages = messages[:1]
|
|
274
|
+
|
|
275
|
+
if not messages:
|
|
276
|
+
return [], []
|
|
277
|
+
|
|
278
|
+
system_message = messages[0]
|
|
240
279
|
|
|
241
280
|
if not messages_to_summarize:
|
|
242
|
-
# Nothing to summarize, return
|
|
243
|
-
return
|
|
281
|
+
# Nothing to summarize, so just return the original sequence
|
|
282
|
+
return prune_interrupted_tool_calls(messages), []
|
|
244
283
|
|
|
245
284
|
instructions = (
|
|
246
285
|
"The input will be a log of Agentic AI steps that have been taken"
|
|
@@ -257,12 +296,24 @@ def summarize_messages(
|
|
|
257
296
|
new_messages = run_summarization_sync(
|
|
258
297
|
instructions, message_history=messages_to_summarize
|
|
259
298
|
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
299
|
+
|
|
300
|
+
if not isinstance(new_messages, list):
|
|
301
|
+
emit_warning(
|
|
302
|
+
"Summarization agent returned non-list output; wrapping into message request"
|
|
303
|
+
)
|
|
304
|
+
new_messages = [ModelRequest([TextPart(str(new_messages))])]
|
|
305
|
+
|
|
306
|
+
compacted: List[ModelMessage] = [system_message] + list(new_messages)
|
|
307
|
+
|
|
308
|
+
# Drop the system message from protected_messages because we already included it
|
|
309
|
+
protected_tail = [msg for msg in protected_messages if msg is not system_message]
|
|
310
|
+
|
|
311
|
+
compacted.extend(protected_tail)
|
|
312
|
+
|
|
313
|
+
return prune_interrupted_tool_calls(compacted), messages_to_summarize
|
|
263
314
|
except Exception as e:
|
|
264
315
|
emit_error(f"Summarization failed during compaction: {e}")
|
|
265
|
-
return messages,
|
|
316
|
+
return messages, [] # Return original messages on failure
|
|
266
317
|
|
|
267
318
|
|
|
268
319
|
def summarize_message(message: ModelMessage) -> ModelMessage:
|
|
@@ -329,11 +380,10 @@ def prune_interrupted_tool_calls(messages: List[ModelMessage]) -> List[ModelMess
|
|
|
329
380
|
tool_call_id = getattr(part, "tool_call_id", None)
|
|
330
381
|
if not tool_call_id:
|
|
331
382
|
continue
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if part.part_kind == "tool-call":
|
|
383
|
+
|
|
384
|
+
if _is_tool_call_part(part) and not _is_tool_return_part(part):
|
|
335
385
|
tool_call_ids.add(tool_call_id)
|
|
336
|
-
|
|
386
|
+
elif _is_tool_return_part(part):
|
|
337
387
|
tool_return_ids.add(tool_call_id)
|
|
338
388
|
|
|
339
389
|
mismatched: Set[str] = tool_call_ids.symmetric_difference(tool_return_ids)
|
|
@@ -362,12 +412,17 @@ def prune_interrupted_tool_calls(messages: List[ModelMessage]) -> List[ModelMess
|
|
|
362
412
|
|
|
363
413
|
|
|
364
414
|
def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
365
|
-
|
|
366
|
-
|
|
415
|
+
cleaned_history = prune_interrupted_tool_calls(
|
|
416
|
+
deduplicate_tool_returns(messages)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
total_current_tokens = sum(
|
|
420
|
+
estimate_tokens_for_message(msg) for msg in cleaned_history
|
|
421
|
+
)
|
|
367
422
|
|
|
368
423
|
model_max = get_model_context_length()
|
|
369
424
|
|
|
370
|
-
proportion_used = total_current_tokens / model_max
|
|
425
|
+
proportion_used = total_current_tokens / model_max if model_max else 0
|
|
371
426
|
|
|
372
427
|
# Check if we're in TUI mode and can update the status bar
|
|
373
428
|
from code_puppy.state_management import get_tui_app_instance, is_tui_mode
|
|
@@ -406,17 +461,15 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
406
461
|
compaction_strategy = get_compaction_strategy()
|
|
407
462
|
|
|
408
463
|
if proportion_used > compaction_threshold:
|
|
464
|
+
filtered_history = filter_huge_messages(cleaned_history)
|
|
465
|
+
|
|
409
466
|
if compaction_strategy == "truncation":
|
|
410
|
-
# Use truncation instead of summarization
|
|
411
467
|
protected_tokens = get_protected_token_count()
|
|
412
|
-
result_messages = truncation(
|
|
413
|
-
|
|
414
|
-
)
|
|
415
|
-
summarized_messages = [] # No summarization in truncation mode
|
|
468
|
+
result_messages = truncation(filtered_history, protected_tokens)
|
|
469
|
+
summarized_messages: List[ModelMessage] = []
|
|
416
470
|
else:
|
|
417
|
-
# Default to summarization
|
|
418
471
|
result_messages, summarized_messages = summarize_messages(
|
|
419
|
-
|
|
472
|
+
filtered_history
|
|
420
473
|
)
|
|
421
474
|
|
|
422
475
|
final_token_count = sum(
|
|
@@ -447,7 +500,9 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
447
500
|
for m in summarized_messages:
|
|
448
501
|
add_compacted_message_hash(hash_message(m))
|
|
449
502
|
return result_messages
|
|
450
|
-
|
|
503
|
+
|
|
504
|
+
set_message_history(cleaned_history)
|
|
505
|
+
return cleaned_history
|
|
451
506
|
|
|
452
507
|
|
|
453
508
|
def truncation(
|
|
@@ -475,16 +530,17 @@ def truncation(
|
|
|
475
530
|
|
|
476
531
|
|
|
477
532
|
def message_history_accumulator(messages: List[Any]):
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
message_history_processor(
|
|
490
|
-
|
|
533
|
+
existing_history = list(get_message_history())
|
|
534
|
+
seen_hashes = {hash_message(message) for message in existing_history}
|
|
535
|
+
compacted_hashes = get_compacted_message_hashes()
|
|
536
|
+
|
|
537
|
+
for message in messages:
|
|
538
|
+
message_hash = hash_message(message)
|
|
539
|
+
if message_hash in seen_hashes or message_hash in compacted_hashes:
|
|
540
|
+
continue
|
|
541
|
+
existing_history.append(message)
|
|
542
|
+
seen_hashes.add(message_hash)
|
|
543
|
+
|
|
544
|
+
updated_history = message_history_processor(existing_history)
|
|
545
|
+
set_message_history(updated_history)
|
|
546
|
+
return updated_history
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
from typing import Any, List, Set
|
|
4
|
+
|
|
5
|
+
import pydantic
|
|
6
|
+
|
|
7
|
+
_tui_mode: bool = False
|
|
8
|
+
_tui_app_instance: Any = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _require_agent_manager() -> ModuleType:
|
|
12
|
+
"""Import the agent manager module, raising if it is unavailable."""
|
|
13
|
+
try:
|
|
14
|
+
from code_puppy.agents import agent_manager
|
|
15
|
+
except Exception as error: # pragma: no cover - import errors surface immediately
|
|
16
|
+
raise RuntimeError("Agent manager module unavailable") from error
|
|
17
|
+
return agent_manager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def add_compacted_message_hash(message_hash: str) -> None:
|
|
21
|
+
"""Add a message hash to the set of compacted message hashes."""
|
|
22
|
+
manager = _require_agent_manager()
|
|
23
|
+
manager.add_current_agent_compacted_message_hash(message_hash)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_compacted_message_hashes() -> Set[str]:
|
|
27
|
+
"""Get the set of compacted message hashes."""
|
|
28
|
+
manager = _require_agent_manager()
|
|
29
|
+
return manager.get_current_agent_compacted_message_hashes()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def set_tui_mode(enabled: bool) -> None:
|
|
33
|
+
"""Set the global TUI mode state.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
enabled: True if running in TUI mode, False otherwise
|
|
37
|
+
"""
|
|
38
|
+
global _tui_mode
|
|
39
|
+
_tui_mode = enabled
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_tui_mode() -> bool:
|
|
43
|
+
"""Check if the application is running in TUI mode.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
True if running in TUI mode, False otherwise
|
|
47
|
+
"""
|
|
48
|
+
return _tui_mode
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_tui_app_instance(app_instance: Any) -> None:
|
|
52
|
+
"""Set the global TUI app instance reference.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
app_instance: The TUI app instance
|
|
56
|
+
"""
|
|
57
|
+
global _tui_app_instance
|
|
58
|
+
_tui_app_instance = app_instance
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_tui_app_instance() -> Any:
|
|
62
|
+
"""Get the current TUI app instance.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The TUI app instance if available, None otherwise
|
|
66
|
+
"""
|
|
67
|
+
return _tui_app_instance
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_tui_mode() -> bool:
|
|
71
|
+
"""Get the current TUI mode state.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if running in TUI mode, False otherwise
|
|
75
|
+
"""
|
|
76
|
+
return _tui_mode
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_message_history() -> List[Any]:
|
|
80
|
+
"""Get message history for the active agent."""
|
|
81
|
+
manager = _require_agent_manager()
|
|
82
|
+
return manager.get_current_agent_message_history()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def set_message_history(history: List[Any]) -> None:
|
|
86
|
+
"""Replace the message history for the active agent."""
|
|
87
|
+
manager = _require_agent_manager()
|
|
88
|
+
manager.set_current_agent_message_history(history)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def clear_message_history() -> None:
|
|
92
|
+
"""Clear message history for the active agent."""
|
|
93
|
+
manager = _require_agent_manager()
|
|
94
|
+
manager.clear_current_agent_message_history()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def append_to_message_history(message: Any) -> None:
|
|
98
|
+
"""Append a message to the active agent's history."""
|
|
99
|
+
manager = _require_agent_manager()
|
|
100
|
+
manager.append_to_current_agent_message_history(message)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def extend_message_history(history: List[Any]) -> None:
|
|
104
|
+
"""Extend the active agent's message history."""
|
|
105
|
+
manager = _require_agent_manager()
|
|
106
|
+
manager.extend_current_agent_message_history(history)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _stringify_part(part: Any) -> str:
|
|
110
|
+
"""Create a stable string representation for a message part.
|
|
111
|
+
|
|
112
|
+
We deliberately ignore timestamps so identical content hashes the same even when
|
|
113
|
+
emitted at different times. This prevents status updates from blowing up the
|
|
114
|
+
history when they are repeated with new timestamps."""
|
|
115
|
+
|
|
116
|
+
attributes: List[str] = [part.__class__.__name__]
|
|
117
|
+
|
|
118
|
+
# Role/instructions help disambiguate parts that otherwise share content
|
|
119
|
+
if hasattr(part, "role") and part.role:
|
|
120
|
+
attributes.append(f"role={part.role}")
|
|
121
|
+
if hasattr(part, "instructions") and part.instructions:
|
|
122
|
+
attributes.append(f"instructions={part.instructions}")
|
|
123
|
+
|
|
124
|
+
if hasattr(part, "tool_call_id") and part.tool_call_id:
|
|
125
|
+
attributes.append(f"tool_call_id={part.tool_call_id}")
|
|
126
|
+
|
|
127
|
+
if hasattr(part, "tool_name") and part.tool_name:
|
|
128
|
+
attributes.append(f"tool_name={part.tool_name}")
|
|
129
|
+
|
|
130
|
+
content = getattr(part, "content", None)
|
|
131
|
+
if content is None:
|
|
132
|
+
attributes.append("content=None")
|
|
133
|
+
elif isinstance(content, str):
|
|
134
|
+
attributes.append(f"content={content}")
|
|
135
|
+
elif isinstance(content, pydantic.BaseModel):
|
|
136
|
+
attributes.append(f"content={json.dumps(content.model_dump(), sort_keys=True)}")
|
|
137
|
+
elif isinstance(content, dict):
|
|
138
|
+
attributes.append(f"content={json.dumps(content, sort_keys=True)}")
|
|
139
|
+
else:
|
|
140
|
+
attributes.append(f"content={repr(content)}")
|
|
141
|
+
|
|
142
|
+
return "|".join(attributes)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def hash_message(message: Any) -> int:
|
|
146
|
+
"""Create a stable hash for a model message that ignores timestamps."""
|
|
147
|
+
role = getattr(message, "role", None)
|
|
148
|
+
instructions = getattr(message, "instructions", None)
|
|
149
|
+
header_bits: List[str] = []
|
|
150
|
+
if role:
|
|
151
|
+
header_bits.append(f"role={role}")
|
|
152
|
+
if instructions:
|
|
153
|
+
header_bits.append(f"instructions={instructions}")
|
|
154
|
+
|
|
155
|
+
part_strings = [_stringify_part(part) for part in getattr(message, "parts", [])]
|
|
156
|
+
canonical = "||".join(header_bits + part_strings)
|
|
157
|
+
return hash(canonical)
|
|
@@ -430,8 +430,10 @@ class CodePuppyTUI(App):
|
|
|
430
430
|
else:
|
|
431
431
|
# Only cancel the agent task if NO processes were killed
|
|
432
432
|
self._current_worker.cancel()
|
|
433
|
-
state_management.
|
|
434
|
-
|
|
433
|
+
state_management.set_message_history(
|
|
434
|
+
prune_interrupted_tool_calls(
|
|
435
|
+
state_management.get_message_history()
|
|
436
|
+
)
|
|
435
437
|
)
|
|
436
438
|
self.add_system_message("⚠️ Processing cancelled by user")
|
|
437
439
|
# Stop spinner and clear state only when agent is actually cancelled
|
|
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-puppy"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.171"
|
|
8
8
|
description = "Code generation agent"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"pydantic-ai>=1.0.
|
|
12
|
+
"pydantic-ai>=1.0.10",
|
|
13
13
|
"httpx>=0.24.1",
|
|
14
14
|
"rich>=13.4.2",
|
|
15
15
|
"logfire>=0.7.1",
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
from typing import Any, List
|
|
2
|
-
|
|
3
|
-
# Legacy global state - maintained for backward compatibility
|
|
4
|
-
_message_history: List[Any] = []
|
|
5
|
-
_compacted_message_hashes = set()
|
|
6
|
-
|
|
7
|
-
# Flag to control whether to use agent-specific history (True) or global history (False)
|
|
8
|
-
_use_agent_specific_history = True
|
|
9
|
-
_tui_mode: bool = False
|
|
10
|
-
_tui_app_instance: Any = None
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def add_compacted_message_hash(message_hash: str) -> None:
|
|
14
|
-
"""Add a message hash to the set of compacted message hashes."""
|
|
15
|
-
if _use_agent_specific_history:
|
|
16
|
-
try:
|
|
17
|
-
from code_puppy.agents.agent_manager import (
|
|
18
|
-
add_current_agent_compacted_message_hash,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
add_current_agent_compacted_message_hash(message_hash)
|
|
22
|
-
return
|
|
23
|
-
except Exception:
|
|
24
|
-
# Fallback to global if agent system fails
|
|
25
|
-
pass
|
|
26
|
-
_compacted_message_hashes.add(message_hash)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_compacted_message_hashes():
|
|
30
|
-
"""Get the set of compacted message hashes."""
|
|
31
|
-
if _use_agent_specific_history:
|
|
32
|
-
try:
|
|
33
|
-
from code_puppy.agents.agent_manager import (
|
|
34
|
-
get_current_agent_compacted_message_hashes,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
return get_current_agent_compacted_message_hashes()
|
|
38
|
-
except Exception:
|
|
39
|
-
# Fallback to global if agent system fails
|
|
40
|
-
pass
|
|
41
|
-
return _compacted_message_hashes
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def set_tui_mode(enabled: bool) -> None:
|
|
45
|
-
"""Set the global TUI mode state.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
enabled: True if running in TUI mode, False otherwise
|
|
49
|
-
"""
|
|
50
|
-
global _tui_mode
|
|
51
|
-
_tui_mode = enabled
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def is_tui_mode() -> bool:
|
|
55
|
-
"""Check if the application is running in TUI mode.
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
True if running in TUI mode, False otherwise
|
|
59
|
-
"""
|
|
60
|
-
return _tui_mode
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def set_tui_app_instance(app_instance: Any) -> None:
|
|
64
|
-
"""Set the global TUI app instance reference.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
app_instance: The TUI app instance
|
|
68
|
-
"""
|
|
69
|
-
global _tui_app_instance
|
|
70
|
-
_tui_app_instance = app_instance
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def get_tui_app_instance() -> Any:
|
|
74
|
-
"""Get the current TUI app instance.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
The TUI app instance if available, None otherwise
|
|
78
|
-
"""
|
|
79
|
-
return _tui_app_instance
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def get_tui_mode() -> bool:
|
|
83
|
-
"""Get the current TUI mode state.
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
True if running in TUI mode, False otherwise
|
|
87
|
-
"""
|
|
88
|
-
return _tui_mode
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def get_message_history() -> List[Any]:
|
|
92
|
-
"""Get message history - uses agent-specific history if enabled, otherwise global."""
|
|
93
|
-
if _use_agent_specific_history:
|
|
94
|
-
try:
|
|
95
|
-
from code_puppy.agents.agent_manager import (
|
|
96
|
-
get_current_agent_message_history,
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
return get_current_agent_message_history()
|
|
100
|
-
except Exception:
|
|
101
|
-
# Fallback to global if agent system fails
|
|
102
|
-
return _message_history
|
|
103
|
-
return _message_history
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def set_message_history(history: List[Any]) -> None:
|
|
107
|
-
"""Set message history - uses agent-specific history if enabled, otherwise global."""
|
|
108
|
-
if _use_agent_specific_history:
|
|
109
|
-
try:
|
|
110
|
-
from code_puppy.agents.agent_manager import (
|
|
111
|
-
set_current_agent_message_history,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
set_current_agent_message_history(history)
|
|
115
|
-
return
|
|
116
|
-
except Exception:
|
|
117
|
-
# Fallback to global if agent system fails
|
|
118
|
-
pass
|
|
119
|
-
global _message_history
|
|
120
|
-
_message_history = history
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def clear_message_history() -> None:
|
|
124
|
-
"""Clear message history - uses agent-specific history if enabled, otherwise global."""
|
|
125
|
-
if _use_agent_specific_history:
|
|
126
|
-
try:
|
|
127
|
-
from code_puppy.agents.agent_manager import (
|
|
128
|
-
clear_current_agent_message_history,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
clear_current_agent_message_history()
|
|
132
|
-
return
|
|
133
|
-
except Exception:
|
|
134
|
-
# Fallback to global if agent system fails
|
|
135
|
-
pass
|
|
136
|
-
global _message_history
|
|
137
|
-
_message_history = []
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def append_to_message_history(message: Any) -> None:
|
|
141
|
-
"""Append to message history - uses agent-specific history if enabled, otherwise global."""
|
|
142
|
-
if _use_agent_specific_history:
|
|
143
|
-
try:
|
|
144
|
-
from code_puppy.agents.agent_manager import (
|
|
145
|
-
append_to_current_agent_message_history,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
append_to_current_agent_message_history(message)
|
|
149
|
-
return
|
|
150
|
-
except Exception:
|
|
151
|
-
# Fallback to global if agent system fails
|
|
152
|
-
pass
|
|
153
|
-
_message_history.append(message)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def extend_message_history(history: List[Any]) -> None:
|
|
157
|
-
"""Extend message history - uses agent-specific history if enabled, otherwise global."""
|
|
158
|
-
if _use_agent_specific_history:
|
|
159
|
-
try:
|
|
160
|
-
from code_puppy.agents.agent_manager import (
|
|
161
|
-
extend_current_agent_message_history,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
extend_current_agent_message_history(history)
|
|
165
|
-
return
|
|
166
|
-
except Exception:
|
|
167
|
-
# Fallback to global if agent system fails
|
|
168
|
-
pass
|
|
169
|
-
_message_history.extend(history)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def set_use_agent_specific_history(enabled: bool) -> None:
|
|
173
|
-
"""Enable or disable agent-specific message history.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
enabled: True to use per-agent history, False to use global history.
|
|
177
|
-
"""
|
|
178
|
-
global _use_agent_specific_history
|
|
179
|
-
_use_agent_specific_history = enabled
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def is_using_agent_specific_history() -> bool:
|
|
183
|
-
"""Check if agent-specific message history is enabled.
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
True if using per-agent history, False if using global history.
|
|
187
|
-
"""
|
|
188
|
-
return _use_agent_specific_history
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def hash_message(message):
|
|
192
|
-
hashable_entities = []
|
|
193
|
-
for part in message.parts:
|
|
194
|
-
if hasattr(part, "timestamp"):
|
|
195
|
-
hashable_entities.append(part.timestamp.isoformat())
|
|
196
|
-
elif hasattr(part, "tool_call_id"):
|
|
197
|
-
hashable_entities.append(part.tool_call_id)
|
|
198
|
-
else:
|
|
199
|
-
hashable_entities.append(part.content)
|
|
200
|
-
return hash(",".join(hashable_entities))
|
|
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
|
{code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/load_context_completion.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
|
{code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/model_picker_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/command_line/prompt_toolkit_completion.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
|
{code_puppy-0.0.170 → code_puppy-0.0.171}/code_puppy/tui/components/command_history_modal.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
|