code-puppy 0.0.163__tar.gz → 0.0.165__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.163 → code_puppy-0.0.165}/PKG-INFO +1 -1
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agent.py +8 -7
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/agent_creator_agent.py +7 -5
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/command_handler.py +22 -20
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/prompt_toolkit_completion.py +2 -2
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/config.py +1 -1
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/message_history_processor.py +123 -13
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/model_factory.py +14 -3
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/command_runner.py +0 -1
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/file_modifications.py +2 -3
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/chat_view.py +12 -6
- {code_puppy-0.0.163 → code_puppy-0.0.165}/pyproject.toml +1 -1
- {code_puppy-0.0.163 → code_puppy-0.0.165}/.gitignore +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/LICENSE +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/README.md +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/agent_orchestrator.json +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/agents/runtime_manager.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/meta_command_handler.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/main.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/async_lifecycle.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/blocking_startup.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/captured_stdio_server.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/circuit_breaker.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/config_wizard.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/dashboard.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/error_isolation.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/examples/retry_example.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/health_monitor.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/managed_server.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/manager.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/registry.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/retry_manager.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/server_registry_catalog.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/status_tracker.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/mcp/system_tools.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/models.json +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/state_management.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/app.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/command_history_modal.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/copy_button.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/custom_widgets.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/human_input_modal.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/input_area.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/sidebar.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/components/status_bar.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/messages.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/models/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/models/chat_message.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/models/command_history.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/models/enums.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/screens/__init__.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/screens/help.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/screens/settings.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/tui/screens/tools.py +0 -0
- {code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/version_checker.py +0 -0
|
@@ -6,7 +6,6 @@ from pydantic_ai import Agent
|
|
|
6
6
|
from pydantic_ai.settings import ModelSettings
|
|
7
7
|
from pydantic_ai.usage import UsageLimits
|
|
8
8
|
|
|
9
|
-
from code_puppy.agents import get_current_agent_config
|
|
10
9
|
from code_puppy.message_history_processor import (
|
|
11
10
|
get_model_context_length,
|
|
12
11
|
message_history_accumulator,
|
|
@@ -136,11 +135,12 @@ def reload_code_generation_agent(message_group: str | None):
|
|
|
136
135
|
|
|
137
136
|
# Check if current agent has a pinned model
|
|
138
137
|
from code_puppy.agents import get_current_agent_config
|
|
138
|
+
|
|
139
139
|
agent_config = get_current_agent_config()
|
|
140
140
|
agent_model_name = None
|
|
141
|
-
if hasattr(agent_config,
|
|
141
|
+
if hasattr(agent_config, "get_model_name"):
|
|
142
142
|
agent_model_name = agent_config.get_model_name()
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
# Use agent-specific model if pinned, otherwise use global model
|
|
145
145
|
model_name = agent_model_name if agent_model_name else get_model_name()
|
|
146
146
|
emit_info(
|
|
@@ -203,17 +203,18 @@ def get_code_generation_agent(force_reload=False, message_group: str | None = No
|
|
|
203
203
|
|
|
204
204
|
# Get the global model name
|
|
205
205
|
global_model_name = get_model_name()
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
# Check if current agent has a pinned model
|
|
208
208
|
from code_puppy.agents import get_current_agent_config
|
|
209
|
+
|
|
209
210
|
agent_config = get_current_agent_config()
|
|
210
211
|
agent_model_name = None
|
|
211
|
-
if hasattr(agent_config,
|
|
212
|
+
if hasattr(agent_config, "get_model_name"):
|
|
212
213
|
agent_model_name = agent_config.get_model_name()
|
|
213
|
-
|
|
214
|
+
|
|
214
215
|
# Use agent-specific model if pinned, otherwise use global model
|
|
215
216
|
model_name = agent_model_name if agent_model_name else global_model_name
|
|
216
|
-
|
|
217
|
+
|
|
217
218
|
if _code_generation_agent is None or _LAST_MODEL_NAME != model_name or force_reload:
|
|
218
219
|
return reload_code_generation_agent(message_group)
|
|
219
220
|
return _code_generation_agent
|
|
@@ -28,15 +28,17 @@ class AgentCreatorAgent(BaseAgent):
|
|
|
28
28
|
def get_system_prompt(self) -> str:
|
|
29
29
|
available_tools = get_available_tool_names()
|
|
30
30
|
agents_dir = get_user_agents_directory()
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# Load available models dynamically
|
|
33
33
|
models_config = ModelFactory.load_config()
|
|
34
34
|
model_descriptions = []
|
|
35
35
|
for model_name, model_info in models_config.items():
|
|
36
|
-
model_type = model_info.get(
|
|
37
|
-
context_length = model_info.get(
|
|
38
|
-
model_descriptions.append(
|
|
39
|
-
|
|
36
|
+
model_type = model_info.get("type", "Unknown")
|
|
37
|
+
context_length = model_info.get("context_length", "Unknown")
|
|
38
|
+
model_descriptions.append(
|
|
39
|
+
f"- **{model_name}**: {model_type} model with {context_length} context"
|
|
40
|
+
)
|
|
41
|
+
|
|
40
42
|
available_models_str = "\n".join(model_descriptions)
|
|
41
43
|
|
|
42
44
|
return f"""You are the Agent Creator! 🏗️ Your mission is to help users create awesome JSON agent files through an interactive process.
|
|
@@ -57,7 +57,9 @@ def get_commands_help():
|
|
|
57
57
|
)
|
|
58
58
|
help_lines.append(
|
|
59
59
|
Text("/compact", style="cyan")
|
|
60
|
-
+ Text(
|
|
60
|
+
+ Text(
|
|
61
|
+
" Summarize and compact current chat history (uses compaction_strategy config)"
|
|
62
|
+
)
|
|
61
63
|
)
|
|
62
64
|
help_lines.append(
|
|
63
65
|
Text("/dump_context", style="cyan")
|
|
@@ -371,7 +373,7 @@ def handle_command(command: str):
|
|
|
371
373
|
|
|
372
374
|
# If no model matched, show available models
|
|
373
375
|
from code_puppy.command_line.model_picker_completion import load_model_names
|
|
374
|
-
|
|
376
|
+
|
|
375
377
|
new_input = update_model_in_input(model_command)
|
|
376
378
|
if new_input is not None:
|
|
377
379
|
from code_puppy.agents.runtime_manager import get_runtime_agent_manager
|
|
@@ -406,76 +408,76 @@ def handle_command(command: str):
|
|
|
406
408
|
from code_puppy.agents.json_agent import discover_json_agents
|
|
407
409
|
from code_puppy.command_line.model_picker_completion import load_model_names
|
|
408
410
|
import json
|
|
409
|
-
|
|
411
|
+
|
|
410
412
|
tokens = command.split()
|
|
411
|
-
|
|
413
|
+
|
|
412
414
|
if len(tokens) != 3:
|
|
413
415
|
emit_warning("Usage: /pin_model <agent-name> <model-name>")
|
|
414
|
-
|
|
416
|
+
|
|
415
417
|
# Show available models and JSON agents
|
|
416
418
|
available_models = load_model_names()
|
|
417
419
|
json_agents = discover_json_agents()
|
|
418
|
-
|
|
420
|
+
|
|
419
421
|
emit_info("Available models:")
|
|
420
422
|
for model in available_models:
|
|
421
423
|
emit_info(f" [cyan]{model}[/cyan]")
|
|
422
|
-
|
|
424
|
+
|
|
423
425
|
if json_agents:
|
|
424
426
|
emit_info("\nAvailable JSON agents:")
|
|
425
427
|
for agent_name, agent_path in json_agents.items():
|
|
426
428
|
emit_info(f" [cyan]{agent_name}[/cyan] ({agent_path})")
|
|
427
429
|
return True
|
|
428
|
-
|
|
430
|
+
|
|
429
431
|
agent_name = tokens[1].lower()
|
|
430
432
|
model_name = tokens[2]
|
|
431
|
-
|
|
433
|
+
|
|
432
434
|
# Check if model exists
|
|
433
435
|
available_models = load_model_names()
|
|
434
436
|
if model_name not in available_models:
|
|
435
437
|
emit_error(f"Model '{model_name}' not found")
|
|
436
438
|
emit_warning(f"Available models: {', '.join(available_models)}")
|
|
437
439
|
return True
|
|
438
|
-
|
|
440
|
+
|
|
439
441
|
# Check that we're modifying a JSON agent (not a built-in Python agent)
|
|
440
442
|
json_agents = discover_json_agents()
|
|
441
443
|
if agent_name not in json_agents:
|
|
442
444
|
emit_error(f"JSON agent '{agent_name}' not found")
|
|
443
|
-
|
|
445
|
+
|
|
444
446
|
# Show available JSON agents
|
|
445
447
|
if json_agents:
|
|
446
448
|
emit_info("Available JSON agents:")
|
|
447
449
|
for name, path in json_agents.items():
|
|
448
450
|
emit_info(f" [cyan]{name}[/cyan] ({path})")
|
|
449
451
|
return True
|
|
450
|
-
|
|
452
|
+
|
|
451
453
|
agent_file_path = json_agents[agent_name]
|
|
452
|
-
|
|
454
|
+
|
|
453
455
|
# Load, modify, and save the agent configuration
|
|
454
456
|
try:
|
|
455
457
|
with open(agent_file_path, "r", encoding="utf-8") as f:
|
|
456
458
|
agent_config = json.load(f)
|
|
457
|
-
|
|
459
|
+
|
|
458
460
|
# Set the model
|
|
459
461
|
agent_config["model"] = model_name
|
|
460
|
-
|
|
462
|
+
|
|
461
463
|
# Save the updated configuration
|
|
462
464
|
with open(agent_file_path, "w", encoding="utf-8") as f:
|
|
463
465
|
json.dump(agent_config, f, indent=2, ensure_ascii=False)
|
|
464
|
-
|
|
466
|
+
|
|
465
467
|
emit_success(f"Model '{model_name}' pinned to agent '{agent_name}'")
|
|
466
|
-
|
|
468
|
+
|
|
467
469
|
# If this is the current agent, reload it to use the new model
|
|
468
470
|
from code_puppy.agents import get_current_agent_config
|
|
469
471
|
from code_puppy.agents.runtime_manager import get_runtime_agent_manager
|
|
470
|
-
|
|
472
|
+
|
|
471
473
|
current_agent = get_current_agent_config()
|
|
472
474
|
if current_agent.name == agent_name:
|
|
473
475
|
manager = get_runtime_agent_manager()
|
|
474
476
|
manager.reload_agent()
|
|
475
477
|
emit_info(f"Active agent reloaded with pinned model '{model_name}'")
|
|
476
|
-
|
|
478
|
+
|
|
477
479
|
return True
|
|
478
|
-
|
|
480
|
+
|
|
479
481
|
except Exception as e:
|
|
480
482
|
emit_error(f"Failed to pin model to agent '{agent_name}': {e}")
|
|
481
483
|
return True
|
{code_puppy-0.0.163 → code_puppy-0.0.165}/code_puppy/command_line/prompt_toolkit_completion.py
RENAMED
|
@@ -147,9 +147,9 @@ def get_prompt_with_active_model(base: str = ">>> "):
|
|
|
147
147
|
|
|
148
148
|
# Check if current agent has a pinned model
|
|
149
149
|
agent_model = None
|
|
150
|
-
if current_agent and hasattr(current_agent,
|
|
150
|
+
if current_agent and hasattr(current_agent, "get_model_name"):
|
|
151
151
|
agent_model = current_agent.get_model_name()
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
# Determine which model to display
|
|
154
154
|
if agent_model and agent_model != global_model:
|
|
155
155
|
# Show both models when they differ
|
|
@@ -492,4 +492,4 @@ def save_command_to_history(command: str):
|
|
|
492
492
|
error_msg = (
|
|
493
493
|
f"❌ An unexpected error occurred while saving command history: {str(e)}"
|
|
494
494
|
)
|
|
495
|
-
direct_console.print(f"[bold red]{error_msg}[/bold red]")
|
|
495
|
+
direct_console.print(f"[bold red]{error_msg}[/bold red]")
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import queue
|
|
3
|
-
from typing import Any, List, Set, Tuple
|
|
3
|
+
from typing import Any, Dict, List, Set, Tuple
|
|
4
4
|
|
|
5
5
|
import pydantic
|
|
6
6
|
from pydantic_ai.messages import ModelMessage, ModelRequest, TextPart, ToolCallPart
|
|
7
7
|
|
|
8
8
|
from code_puppy.config import (
|
|
9
|
+
get_compaction_strategy,
|
|
10
|
+
get_compaction_threshold,
|
|
9
11
|
get_model_name,
|
|
10
12
|
get_protected_token_count,
|
|
11
|
-
get_compaction_threshold,
|
|
12
|
-
get_compaction_strategy,
|
|
13
13
|
)
|
|
14
14
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
15
15
|
from code_puppy.model_factory import ModelFactory
|
|
@@ -82,7 +82,9 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
85
|
-
|
|
85
|
+
# First deduplicate tool returns to clean up any duplicates
|
|
86
|
+
deduplicated = deduplicate_tool_returns(messages)
|
|
87
|
+
filtered = [m for m in deduplicated if estimate_tokens_for_message(m) < 50000]
|
|
86
88
|
pruned = prune_interrupted_tool_calls(filtered)
|
|
87
89
|
return pruned
|
|
88
90
|
|
|
@@ -234,21 +236,100 @@ def get_model_context_length() -> int:
|
|
|
234
236
|
return int(context_length)
|
|
235
237
|
|
|
236
238
|
|
|
239
|
+
def deduplicate_tool_returns(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
240
|
+
"""
|
|
241
|
+
Remove duplicate tool returns while preserving the first occurrence for each tool_call_id.
|
|
242
|
+
|
|
243
|
+
This function identifies tool-return parts that share the same tool_call_id and
|
|
244
|
+
removes duplicates, keeping only the first return for each id. This prevents
|
|
245
|
+
conversation corruption from duplicate tool_result blocks.
|
|
246
|
+
"""
|
|
247
|
+
if not messages:
|
|
248
|
+
return messages
|
|
249
|
+
|
|
250
|
+
seen_tool_returns: Set[str] = set()
|
|
251
|
+
deduplicated: List[ModelMessage] = []
|
|
252
|
+
removed_count = 0
|
|
253
|
+
|
|
254
|
+
for msg in messages:
|
|
255
|
+
# Check if this message has any parts we need to filter
|
|
256
|
+
if not hasattr(msg, "parts") or not msg.parts:
|
|
257
|
+
deduplicated.append(msg)
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# Filter parts within this message
|
|
261
|
+
filtered_parts = []
|
|
262
|
+
msg_had_duplicates = False
|
|
263
|
+
|
|
264
|
+
for part in msg.parts:
|
|
265
|
+
tool_call_id = getattr(part, "tool_call_id", None)
|
|
266
|
+
part_kind = getattr(part, "part_kind", None)
|
|
267
|
+
|
|
268
|
+
# Check if this is a tool-return part
|
|
269
|
+
if tool_call_id and part_kind in {
|
|
270
|
+
"tool-return",
|
|
271
|
+
"tool-result",
|
|
272
|
+
"tool_result",
|
|
273
|
+
}:
|
|
274
|
+
if tool_call_id in seen_tool_returns:
|
|
275
|
+
# This is a duplicate return, skip it
|
|
276
|
+
msg_had_duplicates = True
|
|
277
|
+
removed_count += 1
|
|
278
|
+
continue
|
|
279
|
+
else:
|
|
280
|
+
# First occurrence of this return, keep it
|
|
281
|
+
seen_tool_returns.add(tool_call_id)
|
|
282
|
+
filtered_parts.append(part)
|
|
283
|
+
else:
|
|
284
|
+
# Not a tool return, always keep
|
|
285
|
+
filtered_parts.append(part)
|
|
286
|
+
|
|
287
|
+
# If we filtered out parts, create a new message with filtered parts
|
|
288
|
+
if msg_had_duplicates and filtered_parts:
|
|
289
|
+
# Create a new message with the same attributes but filtered parts
|
|
290
|
+
new_msg = type(msg)(parts=filtered_parts)
|
|
291
|
+
# Copy over other attributes if they exist
|
|
292
|
+
for attr_name in dir(msg):
|
|
293
|
+
if (
|
|
294
|
+
not attr_name.startswith("_")
|
|
295
|
+
and attr_name != "parts"
|
|
296
|
+
and hasattr(msg, attr_name)
|
|
297
|
+
):
|
|
298
|
+
try:
|
|
299
|
+
setattr(new_msg, attr_name, getattr(msg, attr_name))
|
|
300
|
+
except (AttributeError, TypeError):
|
|
301
|
+
# Skip attributes that can't be set
|
|
302
|
+
pass
|
|
303
|
+
deduplicated.append(new_msg)
|
|
304
|
+
elif filtered_parts: # No duplicates but has parts
|
|
305
|
+
deduplicated.append(msg)
|
|
306
|
+
# If no parts remain after filtering, drop the entire message
|
|
307
|
+
|
|
308
|
+
if removed_count > 0:
|
|
309
|
+
emit_warning(f"Removed {removed_count} duplicate tool-return part(s)")
|
|
310
|
+
|
|
311
|
+
return deduplicated
|
|
312
|
+
|
|
313
|
+
|
|
237
314
|
def prune_interrupted_tool_calls(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
238
315
|
"""
|
|
239
316
|
Remove any messages that participate in mismatched tool call sequences.
|
|
240
317
|
|
|
241
318
|
A mismatched tool call id is one that appears in a ToolCall (model/tool request)
|
|
242
|
-
without a corresponding tool return, or vice versa. We
|
|
243
|
-
and
|
|
319
|
+
without a corresponding tool return, or vice versa. We enforce a strict 1:1 ratio
|
|
320
|
+
between tool calls and tool returns. We preserve original order and only drop
|
|
321
|
+
messages that contain parts referencing mismatched tool_call_ids.
|
|
244
322
|
"""
|
|
245
323
|
if not messages:
|
|
246
324
|
return messages
|
|
247
325
|
|
|
248
|
-
|
|
249
|
-
|
|
326
|
+
# First deduplicate tool returns to clean up any duplicate returns
|
|
327
|
+
messages = deduplicate_tool_returns(messages)
|
|
250
328
|
|
|
251
|
-
|
|
329
|
+
tool_call_counts: Dict[str, int] = {}
|
|
330
|
+
tool_return_counts: Dict[str, int] = {}
|
|
331
|
+
|
|
332
|
+
# First pass: count occurrences of each tool_call_id for calls vs returns
|
|
252
333
|
for msg in messages:
|
|
253
334
|
for part in getattr(msg, "parts", []) or []:
|
|
254
335
|
tool_call_id = getattr(part, "tool_call_id", None)
|
|
@@ -257,11 +338,25 @@ def prune_interrupted_tool_calls(messages: List[ModelMessage]) -> List[ModelMess
|
|
|
257
338
|
# Heuristic: if it's an explicit ToolCallPart or has a tool_name/args,
|
|
258
339
|
# consider it a call; otherwise it's a return/result.
|
|
259
340
|
if part.part_kind == "tool-call":
|
|
260
|
-
|
|
341
|
+
tool_call_counts[tool_call_id] = (
|
|
342
|
+
tool_call_counts.get(tool_call_id, 0) + 1
|
|
343
|
+
)
|
|
261
344
|
else:
|
|
262
|
-
|
|
345
|
+
tool_return_counts[tool_call_id] = (
|
|
346
|
+
tool_return_counts.get(tool_call_id, 0) + 1
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Find mismatched tool_call_ids (not exactly 1:1 ratio)
|
|
350
|
+
all_tool_ids = set(tool_call_counts.keys()) | set(tool_return_counts.keys())
|
|
351
|
+
mismatched: Set[str] = set()
|
|
352
|
+
|
|
353
|
+
for tool_id in all_tool_ids:
|
|
354
|
+
call_count = tool_call_counts.get(tool_id, 0)
|
|
355
|
+
return_count = tool_return_counts.get(tool_id, 0)
|
|
356
|
+
# Enforce strict 1:1 ratio - both must be exactly 1
|
|
357
|
+
if call_count != 1 or return_count != 1:
|
|
358
|
+
mismatched.add(tool_id)
|
|
263
359
|
|
|
264
|
-
mismatched: Set[str] = tool_call_ids.symmetric_difference(tool_return_ids)
|
|
265
360
|
if not mismatched:
|
|
266
361
|
return messages
|
|
267
362
|
|
|
@@ -287,7 +382,10 @@ def prune_interrupted_tool_calls(messages: List[ModelMessage]) -> List[ModelMess
|
|
|
287
382
|
|
|
288
383
|
|
|
289
384
|
def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
290
|
-
# First,
|
|
385
|
+
# First, deduplicate tool returns to clean up any duplicates
|
|
386
|
+
messages = deduplicate_tool_returns(messages)
|
|
387
|
+
|
|
388
|
+
# Then, prune any interrupted/mismatched tool-call conversations
|
|
291
389
|
total_current_tokens = sum(estimate_tokens_for_message(msg) for msg in messages)
|
|
292
390
|
|
|
293
391
|
model_max = get_model_context_length()
|
|
@@ -379,6 +477,8 @@ def truncation(
|
|
|
379
477
|
messages: List[ModelMessage], protected_tokens: int
|
|
380
478
|
) -> List[ModelMessage]:
|
|
381
479
|
emit_info("Truncating message history to manage token usage")
|
|
480
|
+
# First deduplicate tool returns to clean up any duplicates
|
|
481
|
+
messages = deduplicate_tool_returns(messages)
|
|
382
482
|
result = [messages[0]] # Always keep the first message (system prompt)
|
|
383
483
|
num_tokens = 0
|
|
384
484
|
stack = queue.LifoQueue()
|
|
@@ -401,6 +501,10 @@ def truncation(
|
|
|
401
501
|
|
|
402
502
|
def message_history_accumulator(messages: List[Any]):
|
|
403
503
|
_message_history = get_message_history()
|
|
504
|
+
|
|
505
|
+
# Deduplicate tool returns in current history before processing new messages
|
|
506
|
+
_message_history = deduplicate_tool_returns(_message_history)
|
|
507
|
+
|
|
404
508
|
message_history_hashes = set([hash_message(m) for m in _message_history])
|
|
405
509
|
for msg in messages:
|
|
406
510
|
if (
|
|
@@ -409,6 +513,12 @@ def message_history_accumulator(messages: List[Any]):
|
|
|
409
513
|
):
|
|
410
514
|
_message_history.append(msg)
|
|
411
515
|
|
|
516
|
+
# Deduplicate tool returns again after adding new messages to ensure no duplicates
|
|
517
|
+
_message_history = deduplicate_tool_returns(_message_history)
|
|
518
|
+
|
|
519
|
+
# Update the message history with deduplicated messages
|
|
520
|
+
set_message_history(_message_history)
|
|
521
|
+
|
|
412
522
|
# Apply message history trimming using the main processor
|
|
413
523
|
# This ensures we maintain global state while still managing context limits
|
|
414
524
|
message_history_processor(_message_history)
|
|
@@ -95,9 +95,20 @@ class ModelFactory:
|
|
|
95
95
|
config = json.load(f)
|
|
96
96
|
|
|
97
97
|
if pathlib.Path(EXTRA_MODELS_FILE).exists():
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
try:
|
|
99
|
+
with open(EXTRA_MODELS_FILE, "r") as f:
|
|
100
|
+
extra_config = json.load(f)
|
|
101
|
+
config.update(extra_config)
|
|
102
|
+
except json.JSONDecodeError as e:
|
|
103
|
+
logging.getLogger(__name__).warning(
|
|
104
|
+
f"Failed to load extra models config from {EXTRA_MODELS_FILE}: Invalid JSON - {e}\n"
|
|
105
|
+
f"Please check your extra_models.json file for syntax errors."
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logging.getLogger(__name__).warning(
|
|
109
|
+
f"Failed to load extra models config from {EXTRA_MODELS_FILE}: {e}\n"
|
|
110
|
+
f"The extra models configuration will be ignored."
|
|
111
|
+
)
|
|
101
112
|
return config
|
|
102
113
|
|
|
103
114
|
@staticmethod
|
|
@@ -20,7 +20,6 @@ import json_repair
|
|
|
20
20
|
from pydantic import BaseModel
|
|
21
21
|
from pydantic_ai import RunContext
|
|
22
22
|
|
|
23
|
-
from code_puppy.callbacks import on_delete_file, on_edit_file
|
|
24
23
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
25
24
|
from code_puppy.tools.common import _find_best_window, generate_group_id
|
|
26
25
|
|
|
@@ -567,9 +566,9 @@ def register_edit_file(agent):
|
|
|
567
566
|
except Exception as e:
|
|
568
567
|
return {
|
|
569
568
|
"success": False,
|
|
570
|
-
"path":
|
|
569
|
+
"path": "Not retrievable in Payload",
|
|
571
570
|
"message": f"edit_file call failed: {str(e)} - this means the tool failed to parse your inputs. Refer to the following examples: {parse_error_message}",
|
|
572
|
-
"changed": False
|
|
571
|
+
"changed": False,
|
|
573
572
|
}
|
|
574
573
|
|
|
575
574
|
# Call _edit_file which will extract file_path from payload and handle group_id generation
|
|
@@ -262,29 +262,35 @@ class ChatView(VerticalScroll):
|
|
|
262
262
|
separator = "\n"
|
|
263
263
|
|
|
264
264
|
# Handle content concatenation carefully to preserve Rich objects
|
|
265
|
-
if hasattr(last_message.content, "__rich_console__") or hasattr(
|
|
265
|
+
if hasattr(last_message.content, "__rich_console__") or hasattr(
|
|
266
|
+
message.content, "__rich_console__"
|
|
267
|
+
):
|
|
266
268
|
# If either content is a Rich object, convert both to text and concatenate
|
|
267
269
|
from io import StringIO
|
|
268
270
|
from rich.console import Console
|
|
269
|
-
|
|
271
|
+
|
|
270
272
|
# Convert existing content to string
|
|
271
273
|
if hasattr(last_message.content, "__rich_console__"):
|
|
272
274
|
string_io = StringIO()
|
|
273
|
-
temp_console = Console(
|
|
275
|
+
temp_console = Console(
|
|
276
|
+
file=string_io, width=80, legacy_windows=False, markup=False
|
|
277
|
+
)
|
|
274
278
|
temp_console.print(last_message.content)
|
|
275
279
|
existing_content = string_io.getvalue().rstrip("\n")
|
|
276
280
|
else:
|
|
277
281
|
existing_content = str(last_message.content)
|
|
278
|
-
|
|
282
|
+
|
|
279
283
|
# Convert new content to string
|
|
280
284
|
if hasattr(message.content, "__rich_console__"):
|
|
281
285
|
string_io = StringIO()
|
|
282
|
-
temp_console = Console(
|
|
286
|
+
temp_console = Console(
|
|
287
|
+
file=string_io, width=80, legacy_windows=False, markup=False
|
|
288
|
+
)
|
|
283
289
|
temp_console.print(message.content)
|
|
284
290
|
new_content = string_io.getvalue().rstrip("\n")
|
|
285
291
|
else:
|
|
286
292
|
new_content = str(message.content)
|
|
287
|
-
|
|
293
|
+
|
|
288
294
|
# Combine as plain text
|
|
289
295
|
last_message.content = existing_content + separator + new_content
|
|
290
296
|
else:
|
|
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.163 → code_puppy-0.0.165}/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.163 → code_puppy-0.0.165}/code_puppy/command_line/model_picker_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
|
{code_puppy-0.0.163 → code_puppy-0.0.165}/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
|