newcode 0.1.1__py3-none-any.whl
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/__init__.py +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +147 -0
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +630 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +122 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +380 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +167 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_scheduler.py +121 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +2145 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +296 -0
- code_puppy/agents/pack/husky.py +307 -0
- code_puppy/agents/pack/retriever.py +380 -0
- code_puppy/agents/pack/shepherd.py +327 -0
- code_puppy/agents/pack/terrier.py +281 -0
- code_puppy/agents/pack/watchdog.py +357 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +453 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +674 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +664 -0
- code_puppy/cli_runner.py +1038 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +526 -0
- code_puppy/command_line/command_handler.py +283 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +853 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +91 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1787 -0
- code_puppy/error_logging.py +133 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +15 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +195 -0
- code_puppy/hook_engine/executor.py +293 -0
- code_puppy/hook_engine/matcher.py +145 -0
- code_puppy/hook_engine/models.py +222 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +141 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1153 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +96 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +130 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +100 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +295 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +499 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +131 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +163 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +601 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +48 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +528 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +277 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +551 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +205 -0
- code_puppy/plugins/oauth_puppy_html.py +224 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +317 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +470 -0
- code_puppy/tools/agent_tools.py +616 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +36 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +378 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +739 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +251 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- newcode-0.1.1.data/data/code_puppy/models.json +130 -0
- newcode-0.1.1.data/data/code_puppy/models_dev_api.json +1 -0
- newcode-0.1.1.dist-info/METADATA +154 -0
- newcode-0.1.1.dist-info/RECORD +289 -0
- newcode-0.1.1.dist-info/WHEEL +4 -0
- newcode-0.1.1.dist-info/entry_points.txt +3 -0
- newcode-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.live import Live
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.spinner import Spinner
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
# Global variable to track current token per second rate
|
|
12
|
+
CURRENT_TOKEN_RATE = 0.0
|
|
13
|
+
_TOKEN_RATE_LOCK = threading.Lock()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StatusDisplay:
|
|
17
|
+
"""
|
|
18
|
+
Displays real-time status information during model execution,
|
|
19
|
+
including token per second rate and rotating loading messages.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, console: Console):
|
|
23
|
+
self.console = console
|
|
24
|
+
self.token_count = 0
|
|
25
|
+
self.start_time = None
|
|
26
|
+
self.last_update_time = None
|
|
27
|
+
self.last_token_count = 0
|
|
28
|
+
self.current_rate = 0
|
|
29
|
+
self.is_active = False
|
|
30
|
+
self.task = None
|
|
31
|
+
self.live = None
|
|
32
|
+
self.loading_messages = [
|
|
33
|
+
"Fetching...",
|
|
34
|
+
"Sniffing around...",
|
|
35
|
+
"Wagging tail...",
|
|
36
|
+
"Pawsing for a moment...",
|
|
37
|
+
"Chasing tail...",
|
|
38
|
+
"Digging up results...",
|
|
39
|
+
"Barking at the data...",
|
|
40
|
+
"Rolling over...",
|
|
41
|
+
"Panting with excitement...",
|
|
42
|
+
"Chewing on it...",
|
|
43
|
+
"Prancing along...",
|
|
44
|
+
"Howling at the code...",
|
|
45
|
+
"Snuggling up to the task...",
|
|
46
|
+
"Bounding through data...",
|
|
47
|
+
"Puppy pondering...",
|
|
48
|
+
]
|
|
49
|
+
self.current_message_index = 0
|
|
50
|
+
self.spinner = Spinner("dots", text="")
|
|
51
|
+
|
|
52
|
+
def _calculate_rate(self) -> float:
|
|
53
|
+
"""Calculate the current token rate"""
|
|
54
|
+
current_time = time.time()
|
|
55
|
+
if self.last_update_time:
|
|
56
|
+
time_diff = current_time - self.last_update_time
|
|
57
|
+
token_diff = self.token_count - self.last_token_count
|
|
58
|
+
if time_diff > 0:
|
|
59
|
+
rate = token_diff / time_diff
|
|
60
|
+
# Smooth the rate calculation with the current rate
|
|
61
|
+
if self.current_rate > 0:
|
|
62
|
+
self.current_rate = (self.current_rate * 0.7) + (rate * 0.3)
|
|
63
|
+
else:
|
|
64
|
+
self.current_rate = rate
|
|
65
|
+
|
|
66
|
+
# Only ensure rate is not negative
|
|
67
|
+
self.current_rate = max(0, self.current_rate)
|
|
68
|
+
|
|
69
|
+
# Update the global rate for other components to access
|
|
70
|
+
global CURRENT_TOKEN_RATE
|
|
71
|
+
with _TOKEN_RATE_LOCK:
|
|
72
|
+
CURRENT_TOKEN_RATE = self.current_rate
|
|
73
|
+
|
|
74
|
+
self.last_update_time = current_time
|
|
75
|
+
self.last_token_count = self.token_count
|
|
76
|
+
return self.current_rate
|
|
77
|
+
|
|
78
|
+
def update_rate_from_sse(
|
|
79
|
+
self, completion_tokens: int, completion_time: float
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Update the token rate directly using SSE time_info data
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
completion_tokens: Number of tokens in the completion (from SSE stream)
|
|
85
|
+
completion_time: Time taken for completion in seconds (from SSE stream)
|
|
86
|
+
"""
|
|
87
|
+
if completion_time > 0:
|
|
88
|
+
# Using the direct t/s formula: tokens / time
|
|
89
|
+
rate = completion_tokens / completion_time
|
|
90
|
+
|
|
91
|
+
# Use a lighter smoothing for this more accurate data
|
|
92
|
+
if self.current_rate > 0:
|
|
93
|
+
self.current_rate = (self.current_rate * 0.3) + (
|
|
94
|
+
rate * 0.7
|
|
95
|
+
) # Weight SSE data more heavily
|
|
96
|
+
else:
|
|
97
|
+
self.current_rate = rate
|
|
98
|
+
|
|
99
|
+
# Update the global rate
|
|
100
|
+
global CURRENT_TOKEN_RATE
|
|
101
|
+
with _TOKEN_RATE_LOCK:
|
|
102
|
+
CURRENT_TOKEN_RATE = self.current_rate
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def get_current_rate() -> float:
|
|
106
|
+
"""Get the current token rate for use in other components"""
|
|
107
|
+
global CURRENT_TOKEN_RATE
|
|
108
|
+
with _TOKEN_RATE_LOCK:
|
|
109
|
+
return CURRENT_TOKEN_RATE
|
|
110
|
+
|
|
111
|
+
def update_token_count(self, tokens: int) -> None:
|
|
112
|
+
"""Update the token count and recalculate the rate"""
|
|
113
|
+
# Reset timing if this is the first update of a new task
|
|
114
|
+
if self.start_time is None:
|
|
115
|
+
self.start_time = time.time()
|
|
116
|
+
self.last_update_time = self.start_time
|
|
117
|
+
# Reset token counters for new task
|
|
118
|
+
self.last_token_count = 0
|
|
119
|
+
self.current_rate = 0.0
|
|
120
|
+
# Set initial token count
|
|
121
|
+
self.token_count = tokens if tokens >= 0 else 0
|
|
122
|
+
return # Don't calculate rate on first initialization
|
|
123
|
+
|
|
124
|
+
# Allow for incremental updates (common for streaming) or absolute updates
|
|
125
|
+
if tokens > self.token_count or tokens < 0:
|
|
126
|
+
# Incremental update or reset
|
|
127
|
+
self.token_count = tokens if tokens >= 0 else 0
|
|
128
|
+
else:
|
|
129
|
+
# If tokens <= current count but > 0, treat as incremental
|
|
130
|
+
# This handles simulated token streaming
|
|
131
|
+
self.token_count += tokens
|
|
132
|
+
|
|
133
|
+
self._calculate_rate()
|
|
134
|
+
|
|
135
|
+
def _get_status_panel(self) -> Panel:
|
|
136
|
+
"""Generate a status panel with current rate and animated message"""
|
|
137
|
+
rate_text = (
|
|
138
|
+
f"{self.current_rate:.1f} t/s" if self.current_rate > 0 else "Warming up..."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Update spinner
|
|
142
|
+
self.spinner.update()
|
|
143
|
+
|
|
144
|
+
# Rotate through loading messages every few updates
|
|
145
|
+
if int(time.time() * 2) % 4 == 0:
|
|
146
|
+
self.current_message_index = (self.current_message_index + 1) % len(
|
|
147
|
+
self.loading_messages
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Create a highly visible status message
|
|
151
|
+
status_text = Text.assemble(
|
|
152
|
+
Text(f"⏳ {rate_text} ", style="bold cyan"),
|
|
153
|
+
str(self.spinner),
|
|
154
|
+
Text(
|
|
155
|
+
f" {self.loading_messages[self.current_message_index]} ⏳",
|
|
156
|
+
style="bold yellow",
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Use expanded panel with more visible formatting
|
|
161
|
+
return Panel(
|
|
162
|
+
status_text,
|
|
163
|
+
title="[bold blue]Agent Status[/bold blue]",
|
|
164
|
+
border_style="bright_blue",
|
|
165
|
+
expand=False,
|
|
166
|
+
padding=(1, 2),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _get_status_text(self) -> Text:
|
|
170
|
+
"""Generate a status text with current rate and animated message"""
|
|
171
|
+
rate_text = (
|
|
172
|
+
f"{self.current_rate:.1f} t/s" if self.current_rate > 0 else "Warming up..."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Update spinner
|
|
176
|
+
self.spinner.update()
|
|
177
|
+
|
|
178
|
+
# Rotate through loading messages
|
|
179
|
+
self.current_message_index = (self.current_message_index + 1) % len(
|
|
180
|
+
self.loading_messages
|
|
181
|
+
)
|
|
182
|
+
message = self.loading_messages[self.current_message_index]
|
|
183
|
+
|
|
184
|
+
# Create a highly visible status text
|
|
185
|
+
return Text.assemble(
|
|
186
|
+
Text(f"⏳ {rate_text}", style="bold cyan"),
|
|
187
|
+
Text(f" {message}", style="yellow"),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def _update_display(self) -> None:
|
|
191
|
+
"""Update the display continuously while active using Rich Live display"""
|
|
192
|
+
# Lazy import to avoid circular dependency during module initialization
|
|
193
|
+
from code_puppy.messaging import emit_info
|
|
194
|
+
|
|
195
|
+
# Add a newline to ensure we're below the blue bar
|
|
196
|
+
emit_info("")
|
|
197
|
+
|
|
198
|
+
# Create a Live display that will update in-place
|
|
199
|
+
with Live(
|
|
200
|
+
self._get_status_text(),
|
|
201
|
+
console=self.console,
|
|
202
|
+
refresh_per_second=2, # Update twice per second
|
|
203
|
+
transient=False, # Keep the final state visible
|
|
204
|
+
) as live:
|
|
205
|
+
# Keep updating the live display while active
|
|
206
|
+
while self.is_active:
|
|
207
|
+
live.update(self._get_status_text())
|
|
208
|
+
await asyncio.sleep(0.5)
|
|
209
|
+
|
|
210
|
+
def start(self) -> None:
|
|
211
|
+
"""Start the status display"""
|
|
212
|
+
if not self.is_active:
|
|
213
|
+
self.is_active = True
|
|
214
|
+
self.start_time = time.time()
|
|
215
|
+
self.last_update_time = self.start_time
|
|
216
|
+
self.token_count = 0
|
|
217
|
+
self.last_token_count = 0
|
|
218
|
+
self.current_rate = 0
|
|
219
|
+
self.task = asyncio.create_task(self._update_display())
|
|
220
|
+
|
|
221
|
+
def stop(self) -> None:
|
|
222
|
+
"""Stop the status display"""
|
|
223
|
+
# Lazy import to avoid circular dependency during module initialization
|
|
224
|
+
from code_puppy.messaging import emit_info
|
|
225
|
+
|
|
226
|
+
if self.is_active:
|
|
227
|
+
self.is_active = False
|
|
228
|
+
if self.task:
|
|
229
|
+
self.task.cancel()
|
|
230
|
+
self.task = None
|
|
231
|
+
|
|
232
|
+
# Print final stats
|
|
233
|
+
elapsed = time.time() - self.start_time if self.start_time else 0
|
|
234
|
+
avg_rate = self.token_count / elapsed if elapsed > 0 else 0
|
|
235
|
+
emit_info(
|
|
236
|
+
f"Completed: {self.token_count} tokens in {elapsed:.1f}s ({avg_rate:.1f} t/s avg)"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Reset state
|
|
240
|
+
self.start_time = None
|
|
241
|
+
self.token_count = 0
|
|
242
|
+
self.last_update_time = None
|
|
243
|
+
self.last_token_count = 0
|
|
244
|
+
self.current_rate = 0
|
|
245
|
+
|
|
246
|
+
# Reset global rate to 0 to avoid affecting subsequent tasks
|
|
247
|
+
global CURRENT_TOKEN_RATE
|
|
248
|
+
with _TOKEN_RATE_LOCK:
|
|
249
|
+
CURRENT_TOKEN_RATE = 0.0
|
|
250
|
+
else:
|
|
251
|
+
# Even if not active, ensure we print stats when stop is called
|
|
252
|
+
# This is for testing purposes
|
|
253
|
+
elapsed = time.time() - self.start_time if self.start_time else 0
|
|
254
|
+
avg_rate = self.token_count / elapsed if elapsed > 0 else 0
|
|
255
|
+
emit_info(
|
|
256
|
+
f"Completed: {self.token_count} tokens in {elapsed:.1f}s ({avg_rate:.1f} t/s avg)"
|
|
257
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import atexit
|
|
3
|
+
import threading
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from pydantic_ai import Agent
|
|
8
|
+
|
|
9
|
+
from code_puppy.config import (
|
|
10
|
+
get_global_model_name,
|
|
11
|
+
)
|
|
12
|
+
from code_puppy.model_factory import ModelFactory, make_model_settings
|
|
13
|
+
|
|
14
|
+
# Keep a module-level agent reference to avoid rebuilding per call
|
|
15
|
+
_summarization_agent = None
|
|
16
|
+
_agent_lock = threading.Lock()
|
|
17
|
+
|
|
18
|
+
# Safe sync runner for async agent.run calls
|
|
19
|
+
# Avoids "event loop is already running" by offloading to a separate thread loop when needed
|
|
20
|
+
_thread_pool: ThreadPoolExecutor | None = None
|
|
21
|
+
|
|
22
|
+
# Reload counter
|
|
23
|
+
_reload_count = 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ensure_thread_pool():
|
|
27
|
+
global _thread_pool
|
|
28
|
+
# Check if pool is None OR if it's been shutdown
|
|
29
|
+
if _thread_pool is None or _thread_pool._shutdown:
|
|
30
|
+
_thread_pool = ThreadPoolExecutor(
|
|
31
|
+
max_workers=1, thread_name_prefix="summarizer-loop"
|
|
32
|
+
)
|
|
33
|
+
return _thread_pool
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _shutdown_thread_pool():
|
|
37
|
+
global _thread_pool
|
|
38
|
+
if _thread_pool is not None:
|
|
39
|
+
_thread_pool.shutdown(wait=False)
|
|
40
|
+
_thread_pool = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
atexit.register(_shutdown_thread_pool)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _run_agent_async(agent: Agent, prompt: str, message_history: List):
|
|
47
|
+
return await agent.run(prompt, message_history=message_history)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SummarizationError(Exception):
|
|
51
|
+
"""Raised when summarization fails with details about the failure."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
54
|
+
self.original_error = original_error
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run_summarization_sync(prompt: str, message_history: List) -> List:
|
|
59
|
+
"""Run the summarization agent synchronously.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
SummarizationError: If summarization fails for any reason.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
agent = get_summarization_agent()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise SummarizationError(
|
|
68
|
+
f"Failed to initialize summarization agent: {type(e).__name__}: {e}",
|
|
69
|
+
original_error=e,
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
# Handle claude-code models: prepend system prompt to user prompt
|
|
73
|
+
from code_puppy.model_utils import prepare_prompt_for_model
|
|
74
|
+
|
|
75
|
+
model_name = get_global_model_name()
|
|
76
|
+
prepared = prepare_prompt_for_model(
|
|
77
|
+
model_name, _get_summarization_instructions(), prompt
|
|
78
|
+
)
|
|
79
|
+
prompt = prepared.user_prompt
|
|
80
|
+
|
|
81
|
+
def _run_in_thread():
|
|
82
|
+
"""
|
|
83
|
+
Run the async agent in a dedicated thread with its own event loop.
|
|
84
|
+
Uses run_until_complete instead of asyncio.run to avoid shutting down
|
|
85
|
+
the default executor (which breaks DBOS in the main thread).
|
|
86
|
+
Does NOT touch global event loop state.
|
|
87
|
+
"""
|
|
88
|
+
loop = asyncio.new_event_loop()
|
|
89
|
+
try:
|
|
90
|
+
coro = agent.run(prompt, message_history=message_history)
|
|
91
|
+
return loop.run_until_complete(coro)
|
|
92
|
+
finally:
|
|
93
|
+
# Clean up without shutting down the default executor
|
|
94
|
+
try:
|
|
95
|
+
# Cancel pending tasks
|
|
96
|
+
pending = asyncio.all_tasks(loop)
|
|
97
|
+
for task in pending:
|
|
98
|
+
task.cancel()
|
|
99
|
+
if pending:
|
|
100
|
+
loop.run_until_complete(
|
|
101
|
+
asyncio.gather(*pending, return_exceptions=True)
|
|
102
|
+
)
|
|
103
|
+
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
104
|
+
finally:
|
|
105
|
+
loop.close()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
# Always use thread pool since we're likely in an existing event loop
|
|
109
|
+
pool = _ensure_thread_pool()
|
|
110
|
+
result = pool.submit(_run_in_thread).result()
|
|
111
|
+
return result.new_messages()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
error_type = type(e).__name__
|
|
114
|
+
error_msg = str(e) if str(e) else "(no details available)"
|
|
115
|
+
raise SummarizationError(
|
|
116
|
+
f"LLM call failed during summarization: [{error_type}] {error_msg}",
|
|
117
|
+
original_error=e,
|
|
118
|
+
) from e
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_summarization_instructions() -> str:
|
|
122
|
+
"""Get the system instructions for the summarization agent."""
|
|
123
|
+
return """You are a message summarization expert. Your task is to summarize conversation messages
|
|
124
|
+
while preserving important context and information. The summaries should be concise but capture the essential content
|
|
125
|
+
and intent of the original messages. This is to help manage token usage in a conversation history
|
|
126
|
+
while maintaining context for the AI to continue the conversation effectively.
|
|
127
|
+
|
|
128
|
+
When summarizing:
|
|
129
|
+
1. Keep summary concise but informative
|
|
130
|
+
2. Preserve important context and key information and decisions
|
|
131
|
+
3. Keep any important technical details
|
|
132
|
+
4. Don't summarize the system message
|
|
133
|
+
5. Make sure all tool calls and responses are summarized, as they are vital
|
|
134
|
+
6. Focus on token usage efficiency and system message preservation"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def reload_summarization_agent():
|
|
138
|
+
"""Create a specialized agent for summarizing messages when context limit is reached."""
|
|
139
|
+
from code_puppy.model_utils import prepare_prompt_for_model
|
|
140
|
+
|
|
141
|
+
models_config = ModelFactory.load_config()
|
|
142
|
+
model_name = get_global_model_name()
|
|
143
|
+
model = ModelFactory.get_model(model_name, models_config)
|
|
144
|
+
|
|
145
|
+
# Handle claude-code models: swap instructions (prompt prepending happens in run_summarization_sync)
|
|
146
|
+
instructions = _get_summarization_instructions()
|
|
147
|
+
prepared = prepare_prompt_for_model(
|
|
148
|
+
model_name, instructions, "", prepend_system_to_user=False
|
|
149
|
+
)
|
|
150
|
+
instructions = prepared.instructions
|
|
151
|
+
|
|
152
|
+
model_settings = make_model_settings(model_name)
|
|
153
|
+
|
|
154
|
+
agent = Agent(
|
|
155
|
+
model=model,
|
|
156
|
+
instructions=instructions,
|
|
157
|
+
output_type=str,
|
|
158
|
+
retries=1, # Fewer retries for summarization
|
|
159
|
+
model_settings=model_settings,
|
|
160
|
+
)
|
|
161
|
+
# NOTE: We intentionally DON'T wrap in DBOSAgent here.
|
|
162
|
+
# Summarization is a simple one-shot call that doesn't need durable execution,
|
|
163
|
+
# and DBOSAgent causes async event loop conflicts with run_sync().
|
|
164
|
+
return agent
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_summarization_agent(force_reload=True):
|
|
168
|
+
"""
|
|
169
|
+
Retrieve the summarization agent with the currently set MODEL_NAME.
|
|
170
|
+
Forces a reload if the model has changed, or if force_reload is passed.
|
|
171
|
+
"""
|
|
172
|
+
global _summarization_agent
|
|
173
|
+
with _agent_lock:
|
|
174
|
+
if force_reload or _summarization_agent is None:
|
|
175
|
+
_summarization_agent = reload_summarization_agent()
|
|
176
|
+
return _summarization_agent
|