code-puppy-core-plugins 0.0.2__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_core_plugins/__init__.py +5 -0
- code_puppy_core_plugins/acp/README.md +266 -0
- code_puppy_core_plugins/acp/__init__.py +8 -0
- code_puppy_core_plugins/acp/agent.py +494 -0
- code_puppy_core_plugins/acp/bridge.py +309 -0
- code_puppy_core_plugins/acp/capabilities.py +73 -0
- code_puppy_core_plugins/acp/commands.py +116 -0
- code_puppy_core_plugins/acp/content.py +115 -0
- code_puppy_core_plugins/acp/io_delegation.py +231 -0
- code_puppy_core_plugins/acp/mcp_config.py +66 -0
- code_puppy_core_plugins/acp/permissions.py +159 -0
- code_puppy_core_plugins/acp/persistence.py +197 -0
- code_puppy_core_plugins/acp/register_callbacks.py +110 -0
- code_puppy_core_plugins/acp/replay.py +114 -0
- code_puppy_core_plugins/acp/session.py +329 -0
- code_puppy_core_plugins/acp/session_config.py +179 -0
- code_puppy_core_plugins/acp/state.py +135 -0
- code_puppy_core_plugins/agent_skills/__init__.py +22 -0
- code_puppy_core_plugins/agent_skills/config.py +208 -0
- code_puppy_core_plugins/agent_skills/discovery.py +337 -0
- code_puppy_core_plugins/agent_skills/downloader.py +392 -0
- code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
- code_puppy_core_plugins/agent_skills/installer.py +22 -0
- code_puppy_core_plugins/agent_skills/metadata.py +219 -0
- code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
- code_puppy_core_plugins/agent_skills/provider.py +52 -0
- code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
- code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
- code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
- code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
- code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
- code_puppy_core_plugins/aws_bedrock/config.py +101 -0
- code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
- code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
- code_puppy_core_plugins/azure_foundry/README.md +238 -0
- code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
- code_puppy_core_plugins/azure_foundry/config.py +126 -0
- code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
- code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
- code_puppy_core_plugins/azure_foundry/token.py +182 -0
- code_puppy_core_plugins/azure_foundry/utils.py +375 -0
- code_puppy_core_plugins/btw/__init__.py +1 -0
- code_puppy_core_plugins/btw/inline_view.py +135 -0
- code_puppy_core_plugins/btw/register_callbacks.py +106 -0
- code_puppy_core_plugins/btw/side_query.py +91 -0
- code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
- code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
- code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
- code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
- code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
- code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
- code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
- code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
- code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
- code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
- code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
- code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
- code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
- code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
- code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
- code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
- code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
- code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
- code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
- code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
- code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
- code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
- code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
- code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
- code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
- code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
- code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
- code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
- code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
- code_puppy_core_plugins/computer_use/README.md +109 -0
- code_puppy_core_plugins/computer_use/__init__.py +1 -0
- code_puppy_core_plugins/computer_use/accessibility.py +163 -0
- code_puppy_core_plugins/computer_use/activation.py +75 -0
- code_puppy_core_plugins/computer_use/backend.py +584 -0
- code_puppy_core_plugins/computer_use/backend_types.py +5 -0
- code_puppy_core_plugins/computer_use/capture.py +110 -0
- code_puppy_core_plugins/computer_use/commands.py +62 -0
- code_puppy_core_plugins/computer_use/geometry.py +81 -0
- code_puppy_core_plugins/computer_use/inline_image.py +56 -0
- code_puppy_core_plugins/computer_use/keycodes.py +47 -0
- code_puppy_core_plugins/computer_use/policy.py +144 -0
- code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
- code_puppy_core_plugins/computer_use/safety.py +18 -0
- code_puppy_core_plugins/computer_use/settle.py +64 -0
- code_puppy_core_plugins/computer_use/state.py +86 -0
- code_puppy_core_plugins/computer_use/tools.py +469 -0
- code_puppy_core_plugins/context_indicator/__init__.py +14 -0
- code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
- code_puppy_core_plugins/context_indicator/usage.py +40 -0
- code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
- code_puppy_core_plugins/copilot_auth/config.py +93 -0
- code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
- code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
- code_puppy_core_plugins/copilot_auth/utils.py +587 -0
- code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
- code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
- code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
- code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
- code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
- code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
- code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
- code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
- code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
- code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
- code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
- code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
- code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
- code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
- code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
- code_puppy_core_plugins/emoji_filter/config.py +26 -0
- code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
- code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
- code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
- code_puppy_core_plugins/example_custom_command/README.md +328 -0
- code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
- code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
- code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
- code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
- code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
- code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
- code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
- code_puppy_core_plugins/force_push_guard/detector.py +96 -0
- code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
- code_puppy_core_plugins/fork/__init__.py +7 -0
- code_puppy_core_plugins/fork/register_callbacks.py +445 -0
- code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
- code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
- code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
- code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
- code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
- code_puppy_core_plugins/grok_oauth/config.py +49 -0
- code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
- code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
- code_puppy_core_plugins/grok_oauth/utils.py +195 -0
- code_puppy_core_plugins/herdr/README.md +168 -0
- code_puppy_core_plugins/herdr/__init__.py +9 -0
- code_puppy_core_plugins/herdr/client.py +339 -0
- code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
- code_puppy_core_plugins/herdr/reporter.py +212 -0
- code_puppy_core_plugins/herdr/smoke.py +143 -0
- code_puppy_core_plugins/herdr/sources.py +133 -0
- code_puppy_core_plugins/hook_creator/__init__.py +1 -0
- code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy_core_plugins/hook_manager/__init__.py +1 -0
- code_puppy_core_plugins/hook_manager/config.py +290 -0
- code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
- code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
- code_puppy_core_plugins/meta_oauth/config.py +55 -0
- code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
- code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
- code_puppy_core_plugins/meta_oauth/utils.py +252 -0
- code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
- code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
- code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
- code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
- code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
- code_puppy_core_plugins/no_tools/__init__.py +0 -0
- code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
- code_puppy_core_plugins/oauth_pasteback.py +110 -0
- code_puppy_core_plugins/oauth_puppy_html.py +194 -0
- code_puppy_core_plugins/obsidian_agent/README.md +52 -0
- code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
- code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
- code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
- code_puppy_core_plugins/ollama/__init__.py +1 -0
- code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
- code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
- code_puppy_core_plugins/ollama_setup/completer.py +38 -0
- code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
- code_puppy_core_plugins/plugin_list/__init__.py +0 -0
- code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
- code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
- code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
- code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
- code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
- code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
- code_puppy_core_plugins/pop_command/__init__.py +1 -0
- code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
- code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
- code_puppy_core_plugins/prompt_newline/config.py +21 -0
- code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
- code_puppy_core_plugins/prune/__init__.py +1 -0
- code_puppy_core_plugins/prune/prune_menu.py +308 -0
- code_puppy_core_plugins/prune/prune_model.py +517 -0
- code_puppy_core_plugins/prune/prune_render.py +354 -0
- code_puppy_core_plugins/prune/register_callbacks.py +388 -0
- code_puppy_core_plugins/puppy_kennel/README.md +203 -0
- code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
- code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
- code_puppy_core_plugins/puppy_kennel/config.py +36 -0
- code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
- code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
- code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
- code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
- code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
- code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
- code_puppy_core_plugins/puppy_kennel/state.py +68 -0
- code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
- code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
- code_puppy_core_plugins/puppy_spinner/README.md +93 -0
- code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
- code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
- code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
- code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
- code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
- code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
- code_puppy_core_plugins/quick_resume/__init__.py +0 -0
- code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
- code_puppy_core_plugins/review_pr/__init__.py +1 -0
- code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
- code_puppy_core_plugins/shell_safety/__init__.py +6 -0
- code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
- code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
- code_puppy_core_plugins/stack_dump/__init__.py +1 -0
- code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
- code_puppy_core_plugins/statusline/__init__.py +14 -0
- code_puppy_core_plugins/statusline/config.py +68 -0
- code_puppy_core_plugins/statusline/payload.py +138 -0
- code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
- code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
- code_puppy_core_plugins/statusline/runner.py +122 -0
- code_puppy_core_plugins/statusline/statusline_command.py +197 -0
- code_puppy_core_plugins/steer_queue/__init__.py +10 -0
- code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
- code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
- code_puppy_core_plugins/subagent_panel/README.md +102 -0
- code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
- code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
- code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
- code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
- code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
- code_puppy_core_plugins/subagent_panel/state.py +231 -0
- code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
- code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
- code_puppy_core_plugins/theme/README.md +86 -0
- code_puppy_core_plugins/theme/__init__.py +14 -0
- code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
- code_puppy_core_plugins/theme/content_styles.py +146 -0
- code_puppy_core_plugins/theme/osc_palette.py +178 -0
- code_puppy_core_plugins/theme/picker.py +358 -0
- code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
- code_puppy_core_plugins/theme/register_callbacks.py +348 -0
- code_puppy_core_plugins/theme/rich_themes.py +202 -0
- code_puppy_core_plugins/theme/themes.py +720 -0
- code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
- code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
- code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
- code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
- code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
- code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
- code_puppy_core_plugins/universal_constructor/models.py +138 -0
- code_puppy_core_plugins/universal_constructor/provider.py +62 -0
- code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
- code_puppy_core_plugins/universal_constructor/registry.py +302 -0
- code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
- code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
- code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
- code_puppy_core_plugins/wiggum/__init__.py +1 -0
- code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
- code_puppy_core_plugins/wiggum/judge.py +299 -0
- code_puppy_core_plugins/wiggum/judge_config.py +249 -0
- code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
- code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
- code_puppy_core_plugins/wiggum/state.py +89 -0
- code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
- code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
- code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
- code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
- code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
- code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
- code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Timestamp heartbeat plugin — see register_callbacks for details."""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Plugin: timestamp heartbeat — stamp the current datetime into tool results.
|
|
2
|
+
|
|
3
|
+
Models are time-blind: nothing in a long agent run tells them how much
|
|
4
|
+
wall-clock time has passed. Every N tool calls this plugin injects a
|
|
5
|
+
``__SYS_TIMESTAMP__`` key into the tool's result dict so the model gets a
|
|
6
|
+
periodic clock tick without any extra transcript noise or history messages.
|
|
7
|
+
|
|
8
|
+
How the injection works (and why it's safe):
|
|
9
|
+
``pydantic_patches._patched_call_tool`` does ``return result`` inside a
|
|
10
|
+
``try`` block and awaits ``on_post_tool_call(..., result, ...)`` in the
|
|
11
|
+
``finally``. Python evaluates the return value (a *reference* to the
|
|
12
|
+
dict) before running ``finally``, so an **in-place** mutation of that
|
|
13
|
+
dict here is visible to the caller — and therefore to the model when
|
|
14
|
+
the result is serialized into the ToolReturnPart.
|
|
15
|
+
|
|
16
|
+
Corollary: only plain ``dict`` results can be stamped. When the k-th
|
|
17
|
+
call returns something else (a string, a pydantic model), the stamp is
|
|
18
|
+
marked *due* and lands on the next dict-shaped result instead of being
|
|
19
|
+
silently dropped.
|
|
20
|
+
|
|
21
|
+
Config (puppy.cfg):
|
|
22
|
+
timestamp_heartbeat_interval = 10 # stamp every N tool calls; 0 disables
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from code_puppy.callbacks import register_callback
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
TIMESTAMP_KEY = "__SYS_TIMESTAMP__"
|
|
36
|
+
CONFIG_KEY = "timestamp_heartbeat_interval"
|
|
37
|
+
DEFAULT_INTERVAL = 10
|
|
38
|
+
|
|
39
|
+
# Module-level state. Tool calls are dispatched on the event loop, so plain
|
|
40
|
+
# ints are fine — no locking theater needed.
|
|
41
|
+
_calls_since_stamp = 0
|
|
42
|
+
_stamp_due = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_interval() -> int:
|
|
46
|
+
"""Read the heartbeat interval from puppy.cfg (0 or negative disables)."""
|
|
47
|
+
try:
|
|
48
|
+
from code_puppy.config import get_value
|
|
49
|
+
|
|
50
|
+
raw = get_value(CONFIG_KEY)
|
|
51
|
+
if raw is None or str(raw).strip() == "":
|
|
52
|
+
return DEFAULT_INTERVAL
|
|
53
|
+
return int(str(raw).strip())
|
|
54
|
+
except (ValueError, TypeError):
|
|
55
|
+
logger.warning(
|
|
56
|
+
"Invalid %s value; falling back to %d", CONFIG_KEY, DEFAULT_INTERVAL
|
|
57
|
+
)
|
|
58
|
+
return DEFAULT_INTERVAL
|
|
59
|
+
except Exception:
|
|
60
|
+
return DEFAULT_INTERVAL
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _now_stamp() -> str:
|
|
64
|
+
"""Local time with UTC offset, second precision — readable and unambiguous."""
|
|
65
|
+
return datetime.now().astimezone().isoformat(timespec="seconds")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _reset_state() -> None:
|
|
69
|
+
"""Reset the counter/due flag (used by tests and defensive re-init)."""
|
|
70
|
+
global _calls_since_stamp, _stamp_due
|
|
71
|
+
_calls_since_stamp = 0
|
|
72
|
+
_stamp_due = False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _on_post_tool_call(
|
|
76
|
+
tool_name: str,
|
|
77
|
+
tool_args: dict,
|
|
78
|
+
result: Any,
|
|
79
|
+
duration_ms: float,
|
|
80
|
+
context: Any = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Count tool calls; every ``interval`` calls, stamp the result dict."""
|
|
83
|
+
global _calls_since_stamp, _stamp_due
|
|
84
|
+
_ = tool_name, tool_args, duration_ms, context # signature conformance
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
interval = _get_interval()
|
|
88
|
+
if interval <= 0:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
_calls_since_stamp += 1
|
|
92
|
+
if _calls_since_stamp >= interval:
|
|
93
|
+
_stamp_due = True
|
|
94
|
+
_calls_since_stamp = 0
|
|
95
|
+
|
|
96
|
+
if _stamp_due and isinstance(result, dict):
|
|
97
|
+
result[TIMESTAMP_KEY] = _now_stamp()
|
|
98
|
+
_stamp_due = False
|
|
99
|
+
except Exception:
|
|
100
|
+
# A clock tick is never worth breaking a tool call.
|
|
101
|
+
logger.debug("timestamp heartbeat failed", exc_info=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
register_callback("post_tool_call", _on_post_tool_call)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
__all__ = [
|
|
108
|
+
"CONFIG_KEY",
|
|
109
|
+
"DEFAULT_INTERVAL",
|
|
110
|
+
"TIMESTAMP_KEY",
|
|
111
|
+
"_get_interval",
|
|
112
|
+
"_on_post_tool_call",
|
|
113
|
+
"_reset_state",
|
|
114
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Learned token-per-character ratios for accurate token estimation.
|
|
2
|
+
|
|
3
|
+
When the API reports real prompt_tokens we can compute the actual
|
|
4
|
+
chars-per-token ratio for that model and store it keyed by the bare
|
|
5
|
+
model name (the part after ``:``). Future ``count_tokens`` calls use the
|
|
6
|
+
stored ratio for that model, falling back to a hardcoded default.
|
|
7
|
+
|
|
8
|
+
The default (2.5) matches the classic char/token heuristic used across
|
|
9
|
+
most models. Real tokenizers vary (GPT-4 ~3.5, Claude ~2.8), but 2.5
|
|
10
|
+
provides a reasonable middle ground. The max clamp (3.5) prevents
|
|
11
|
+
absurdly high ratios from inflating token estimates.
|
|
12
|
+
|
|
13
|
+
Storage lives at ``~/.code_puppy/token_ratios.json``, overridable via
|
|
14
|
+
the env var ``CODE_PUPPY_TOKEN_RATIOS_PATH``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import tempfile
|
|
21
|
+
import threading
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
# Constants
|
|
27
|
+
|
|
28
|
+
# Default 2.5 chars/token slightly overestimates for safer compaction.
|
|
29
|
+
_DEFAULT_RATIO = 2.5
|
|
30
|
+
|
|
31
|
+
# Clamp learned ratios to [1.5, 3.5]; extremes imply implausible tokenization and
|
|
32
|
+
# can collapse estimates or risk context overflow.
|
|
33
|
+
_MIN_RATIO = 1.5
|
|
34
|
+
_MAX_RATIO = 3.5
|
|
35
|
+
|
|
36
|
+
# Lazy in-memory cache, populated by ``_ensure_ratios_loaded()``.
|
|
37
|
+
|
|
38
|
+
_LEARNED_RATIOS: dict[str, float] = {}
|
|
39
|
+
_ratios_loaded: bool = False
|
|
40
|
+
_ratios_lock = threading.Lock()
|
|
41
|
+
|
|
42
|
+
# Learned-ratio storage path.
|
|
43
|
+
|
|
44
|
+
_TOKEN_RATIOS_PATH: Path = Path(
|
|
45
|
+
os.path.expanduser(
|
|
46
|
+
os.environ.get(
|
|
47
|
+
"CODE_PUPPY_TOKEN_RATIOS_PATH",
|
|
48
|
+
str(Path.home() / ".code_puppy" / "token_ratios.json"),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Internal helpers
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _ensure_ratios_loaded() -> None:
|
|
58
|
+
"""Populate ``_LEARNED_RATIOS`` from disk on first call (idempotent)."""
|
|
59
|
+
global _ratios_loaded
|
|
60
|
+
with _ratios_lock:
|
|
61
|
+
if _ratios_loaded:
|
|
62
|
+
return
|
|
63
|
+
_LEARNED_RATIOS.clear()
|
|
64
|
+
_LEARNED_RATIOS.update(_load_learned_ratios())
|
|
65
|
+
_ratios_loaded = True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load_learned_ratios() -> dict[str, float]:
|
|
69
|
+
"""Load learned token ratios from the on-disk JSON file.
|
|
70
|
+
|
|
71
|
+
Returns an empty dict when the file doesn't exist or is corrupt —
|
|
72
|
+
callers fall back to ``_DEFAULT_RATIO``.
|
|
73
|
+
|
|
74
|
+
Each loaded value is clamped to ``[_MIN_RATIO, _MAX_RATIO]`` so that
|
|
75
|
+
poisoned or corrupted values cannot cause token-count collapse.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
if _TOKEN_RATIOS_PATH.is_file():
|
|
79
|
+
data = json.loads(_TOKEN_RATIOS_PATH.read_text(encoding="utf-8"))
|
|
80
|
+
if isinstance(data, dict):
|
|
81
|
+
clamped: dict[str, float] = {}
|
|
82
|
+
for k, v in data.items():
|
|
83
|
+
if isinstance(v, (int, float)) and v > 0:
|
|
84
|
+
# Lowercase keys for case-insensitive matching.
|
|
85
|
+
clamped[k.lower()] = max(_MIN_RATIO, min(_MAX_RATIO, float(v)))
|
|
86
|
+
return clamped
|
|
87
|
+
except (OSError, json.JSONDecodeError, ValueError):
|
|
88
|
+
pass
|
|
89
|
+
return {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _save_learned_ratios(ratios: dict[str, float]) -> None:
|
|
93
|
+
"""Persist learned token ratios to disk atomically (best-effort, never raises)."""
|
|
94
|
+
try:
|
|
95
|
+
parent = _TOKEN_RATIOS_PATH.parent
|
|
96
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
fd, tmp_name = tempfile.mkstemp(dir=parent, suffix=".tmp")
|
|
98
|
+
try:
|
|
99
|
+
with os.fdopen(fd, "w", encoding="utf-8") as tmp:
|
|
100
|
+
json.dump(ratios, tmp, indent=2)
|
|
101
|
+
os.replace(tmp_name, str(_TOKEN_RATIOS_PATH))
|
|
102
|
+
except OSError:
|
|
103
|
+
try:
|
|
104
|
+
os.unlink(tmp_name)
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
raise
|
|
108
|
+
except OSError:
|
|
109
|
+
pass # Silently ignore write failures (permissions, disk full, etc.)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _record_token_ratio(model: str, char_count: int, token_count: int) -> None:
|
|
113
|
+
"""Record an observed chars-per-token ratio for *model*.
|
|
114
|
+
|
|
115
|
+
Called after a successful API response where we know both the input
|
|
116
|
+
character count and the actual prompt token count. Stores a running
|
|
117
|
+
average keyed by the bare model name (the part after ``:``) so that
|
|
118
|
+
the same model accessed through different providers shares the estimate.
|
|
119
|
+
|
|
120
|
+
The running average uses a 70/30 blend: 70% old, 30% new. This lets
|
|
121
|
+
the estimate adapt to the real tokenizer without swinging wildly on
|
|
122
|
+
every call.
|
|
123
|
+
"""
|
|
124
|
+
if char_count <= 0 or token_count <= 0:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
# Strip the provider prefix and lowercase the model name.
|
|
128
|
+
model_name = model.split(":", 1)[1] if ":" in model else model
|
|
129
|
+
model_name = model_name.lower()
|
|
130
|
+
|
|
131
|
+
_ensure_ratios_loaded()
|
|
132
|
+
|
|
133
|
+
new_ratio = char_count / token_count
|
|
134
|
+
new_ratio = max(_MIN_RATIO, min(_MAX_RATIO, new_ratio))
|
|
135
|
+
|
|
136
|
+
with _ratios_lock:
|
|
137
|
+
old = _LEARNED_RATIOS.get(model_name)
|
|
138
|
+
if old is not None:
|
|
139
|
+
blended = 0.7 * old + 0.3 * new_ratio
|
|
140
|
+
else:
|
|
141
|
+
blended = new_ratio
|
|
142
|
+
_LEARNED_RATIOS[model_name] = round(blended, 4)
|
|
143
|
+
_save_learned_ratios(_LEARNED_RATIOS)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Public API
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def count_tokens(text: str, model: str | None = None) -> int:
|
|
150
|
+
"""Estimate token count using a character-based heuristic.
|
|
151
|
+
|
|
152
|
+
If we have a *learned* chars-per-token ratio for *model*, use it.
|
|
153
|
+
Otherwise fall back to ``_DEFAULT_RATIO`` (2.5).
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
text: The text content whose token count we need.
|
|
157
|
+
model: Optional model identifier string (e.g.
|
|
158
|
+
``"anthropic:claude-sonnet"``). The provider prefix is
|
|
159
|
+
stripped before lookup, so ``"wafer:glm5.1"`` and bare
|
|
160
|
+
``"glm5.1"`` resolve to the same key.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Estimated token count (always >= 1 for non-empty input).
|
|
164
|
+
"""
|
|
165
|
+
_ensure_ratios_loaded()
|
|
166
|
+
|
|
167
|
+
if not text:
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
# Look up by bare model name (strip provider prefix, lowercase).
|
|
171
|
+
model_name = None
|
|
172
|
+
if model:
|
|
173
|
+
model_name = model.split(":", 1)[1] if ":" in model else model
|
|
174
|
+
model_name = model_name.lower()
|
|
175
|
+
|
|
176
|
+
raw_ratio = _LEARNED_RATIOS.get(model_name or "", _DEFAULT_RATIO)
|
|
177
|
+
ratio = max(_MIN_RATIO, min(_MAX_RATIO, raw_ratio))
|
|
178
|
+
|
|
179
|
+
result = round(len(text) / ratio)
|
|
180
|
+
return result if result > 0 else 1
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_ratio_for_model(model: str) -> float:
|
|
184
|
+
"""Return the current chars/token ratio for *model* (or default)."""
|
|
185
|
+
_ensure_ratios_loaded()
|
|
186
|
+
model_name = model.split(":", 1)[1] if ":" in model else model
|
|
187
|
+
model_name = model_name.lower()
|
|
188
|
+
raw = _LEARNED_RATIOS.get(model_name, _DEFAULT_RATIO)
|
|
189
|
+
return max(_MIN_RATIO, min(_MAX_RATIO, raw))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def list_known_ratios() -> dict[str, float]:
|
|
193
|
+
"""Return a copy of all learned ratios (for debugging/stats)."""
|
|
194
|
+
_ensure_ratios_loaded()
|
|
195
|
+
with _ratios_lock:
|
|
196
|
+
return dict(_LEARNED_RATIOS)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Storage path override (for tests)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def set_ratios_path(path: str | Path) -> None:
|
|
203
|
+
"""Override the token ratios file path (mostly for testing)."""
|
|
204
|
+
global _TOKEN_RATIOS_PATH, _ratios_loaded, _LEARNED_RATIOS
|
|
205
|
+
_TOKEN_RATIOS_PATH = Path(path)
|
|
206
|
+
with _ratios_lock:
|
|
207
|
+
_LEARNED_RATIOS.clear()
|
|
208
|
+
_ratios_loaded = False
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Register callbacks for the token-ratio-learner plugin.
|
|
2
|
+
|
|
3
|
+
On startup we monkeypatch four things:
|
|
4
|
+
|
|
5
|
+
1. ``_history.estimate_tokens`` — replaces the hardcoded
|
|
6
|
+
``max(1, floor(len(text)/2.5))`` with a learned-ratio lookup, falling
|
|
7
|
+
back to ``_DEFAULT_RATIO`` (2.5) which matches the classic heuristic.
|
|
8
|
+
|
|
9
|
+
2. ``_history.estimate_tokens_for_message`` — replaces per-part char/2.5
|
|
10
|
+
summing + ``model_token_multiplier`` with direct
|
|
11
|
+
``ratios.count_tokens(part_str, model=model_name)`` per part. The
|
|
12
|
+
learned ratio is already model-calibrated, so the multiplier becomes
|
|
13
|
+
redundant.
|
|
14
|
+
|
|
15
|
+
3. ``_runtime.run_with_mcp`` — wraps the orchestration so that after every
|
|
16
|
+
successful agent run we extract ``result.usage().input_tokens`` and
|
|
17
|
+
the character count of the input, then call
|
|
18
|
+
``ratios._record_token_ratio()`` to update the running average.
|
|
19
|
+
|
|
20
|
+
4. ``subagent_stream_handler._estimate_tokens`` — same replacement as
|
|
21
|
+
(1) so that subagent streaming token counts use learned ratios.
|
|
22
|
+
|
|
23
|
+
We do **not** touch ``event_stream_handler`` inlining — that code is for
|
|
24
|
+
progress display only, not for compaction decisions.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from typing import Any, Optional, Union
|
|
29
|
+
|
|
30
|
+
from code_puppy.callbacks import register_callback
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# Original references, populated during startup.
|
|
35
|
+
|
|
36
|
+
_original_estimate_tokens = None
|
|
37
|
+
_original_estimate_tokens_for_message = None
|
|
38
|
+
_original_run_with_mcp = None
|
|
39
|
+
_original_subagent_estimate_tokens = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Patched ``estimate_tokens`` (root text-level)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _patched_estimate_tokens(text: str) -> int:
|
|
46
|
+
"""Drop-in replacement that uses learned char/token ratios.
|
|
47
|
+
|
|
48
|
+
No model argument — uses the default ratio. Callers that need
|
|
49
|
+
model-specific calibration go through ``estimate_tokens_for_message``.
|
|
50
|
+
"""
|
|
51
|
+
from code_puppy_core_plugins.token_ratio_learner.ratios import count_tokens
|
|
52
|
+
|
|
53
|
+
return count_tokens(text, model=None)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Patched ``estimate_tokens_for_message``
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _patched_estimate_tokens_for_message(
|
|
60
|
+
message: Any,
|
|
61
|
+
model_name: Optional[str] = None,
|
|
62
|
+
) -> int:
|
|
63
|
+
"""Per-message token estimation using learned model-specific ratios.
|
|
64
|
+
|
|
65
|
+
Iterates over ``message.parts``, stringifying each with
|
|
66
|
+
``stringify_part``, then calls ``ratios.count_tokens(part_str, model)``.
|
|
67
|
+
Bypasses the old ``estimate_tokens`` + ``model_token_multiplier`` chain
|
|
68
|
+
entirely — the learned ratio *is* the calibration.
|
|
69
|
+
"""
|
|
70
|
+
# Import stringify_part from the same module the original lives in
|
|
71
|
+
from code_puppy.agents._history import stringify_part
|
|
72
|
+
from code_puppy_core_plugins.token_ratio_learner.ratios import count_tokens
|
|
73
|
+
|
|
74
|
+
total = 0
|
|
75
|
+
for part in getattr(message, "parts", []) or []:
|
|
76
|
+
part_str = stringify_part(part)
|
|
77
|
+
if part_str:
|
|
78
|
+
total += count_tokens(part_str, model=model_name)
|
|
79
|
+
return max(1, total)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Patched ``run_with_mcp`` records ratios after each API call.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _compute_input_char_count(
|
|
86
|
+
agent: Any, prompt: Union[str, list], attachments: Any, link_attachments: Any
|
|
87
|
+
) -> int:
|
|
88
|
+
"""Compute a reasonable character count for the input to the model.
|
|
89
|
+
|
|
90
|
+
Counts the prompt text (or list items) plus the stringified message
|
|
91
|
+
history. Binary/URL attachments aren't tokenized as text so they're
|
|
92
|
+
skipped.
|
|
93
|
+
"""
|
|
94
|
+
char_count = 0
|
|
95
|
+
|
|
96
|
+
if isinstance(prompt, str):
|
|
97
|
+
char_count += len(prompt)
|
|
98
|
+
elif isinstance(prompt, list):
|
|
99
|
+
for item in prompt:
|
|
100
|
+
if isinstance(item, str):
|
|
101
|
+
char_count += len(item)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
history = agent._message_history
|
|
105
|
+
from code_puppy.agents._history import stringify_part
|
|
106
|
+
|
|
107
|
+
for msg in history:
|
|
108
|
+
for part in getattr(msg, "parts", []) or []:
|
|
109
|
+
part_str = stringify_part(part)
|
|
110
|
+
if part_str:
|
|
111
|
+
char_count += len(part_str)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
return char_count
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def _patched_run_with_mcp(
|
|
119
|
+
agent: Any,
|
|
120
|
+
prompt: str,
|
|
121
|
+
*,
|
|
122
|
+
attachments: Optional[Any] = None,
|
|
123
|
+
link_attachments: Optional[Any] = None,
|
|
124
|
+
output_type: Optional[Any] = None,
|
|
125
|
+
**kwargs: Any,
|
|
126
|
+
) -> Any:
|
|
127
|
+
"""Wrap ``run_with_mcp`` to learn token ratios from real API responses."""
|
|
128
|
+
global _original_run_with_mcp
|
|
129
|
+
|
|
130
|
+
input_char_count = _compute_input_char_count(
|
|
131
|
+
agent, prompt, attachments, link_attachments
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
result = await _original_run_with_mcp(
|
|
135
|
+
agent,
|
|
136
|
+
prompt,
|
|
137
|
+
attachments=attachments,
|
|
138
|
+
link_attachments=link_attachments,
|
|
139
|
+
output_type=output_type,
|
|
140
|
+
**kwargs,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Try to extract token usage from the result
|
|
144
|
+
try:
|
|
145
|
+
usage = result.usage() # AgentRunResult.usage() → RunUsage
|
|
146
|
+
input_tokens = getattr(usage, "input_tokens", 0) or getattr(
|
|
147
|
+
usage, "request_tokens", 0
|
|
148
|
+
)
|
|
149
|
+
if input_tokens > 0 and input_char_count > 0:
|
|
150
|
+
from code_puppy_core_plugins.token_ratio_learner.ratios import (
|
|
151
|
+
_record_token_ratio,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
model = agent.get_model_name()
|
|
155
|
+
if model:
|
|
156
|
+
_record_token_ratio(model, input_char_count, input_tokens)
|
|
157
|
+
except Exception:
|
|
158
|
+
pass # Never let ratio recording break the agent run
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Patched subagent ``_estimate_tokens``
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _patched_subagent_estimate_tokens(content: str) -> int:
|
|
167
|
+
"""Drop-in replacement for subagent token estimation."""
|
|
168
|
+
from code_puppy_core_plugins.token_ratio_learner.ratios import count_tokens
|
|
169
|
+
|
|
170
|
+
return count_tokens(content, model=None)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Startup hook
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _on_startup() -> None:
|
|
177
|
+
"""Apply all monkeypatches on startup."""
|
|
178
|
+
global _original_estimate_tokens, _original_estimate_tokens_for_message
|
|
179
|
+
global _original_run_with_mcp, _original_subagent_estimate_tokens
|
|
180
|
+
|
|
181
|
+
# 1. Patch _history.estimate_tokens (root text-level)
|
|
182
|
+
from code_puppy.agents import _history
|
|
183
|
+
|
|
184
|
+
_original_estimate_tokens = _history.estimate_tokens
|
|
185
|
+
_history.estimate_tokens = _patched_estimate_tokens
|
|
186
|
+
logger.info("token_ratio_learner: patched _history.estimate_tokens")
|
|
187
|
+
|
|
188
|
+
# 2. Patch _history.estimate_tokens_for_message
|
|
189
|
+
_original_estimate_tokens_for_message = _history.estimate_tokens_for_message
|
|
190
|
+
_history.estimate_tokens_for_message = _patched_estimate_tokens_for_message
|
|
191
|
+
logger.info("token_ratio_learner: patched _history.estimate_tokens_for_message")
|
|
192
|
+
|
|
193
|
+
# 3. Patch _runtime.run_with_mcp
|
|
194
|
+
from code_puppy.agents import _runtime
|
|
195
|
+
|
|
196
|
+
_original_run_with_mcp = _runtime.run_with_mcp
|
|
197
|
+
_runtime.run_with_mcp = _patched_run_with_mcp
|
|
198
|
+
logger.info("token_ratio_learner: patched _runtime.run_with_mcp")
|
|
199
|
+
|
|
200
|
+
# 4. Patch subagent_stream_handler._estimate_tokens
|
|
201
|
+
# NOTE: ``subagent_stream_handler`` re-exports a function, not its module;
|
|
202
|
+
# import the actual module via ``importlib`` before patching.
|
|
203
|
+
import importlib
|
|
204
|
+
|
|
205
|
+
ssh_mod = importlib.import_module("code_puppy.agents.subagent_stream_handler")
|
|
206
|
+
|
|
207
|
+
_original_subagent_estimate_tokens = ssh_mod._estimate_tokens
|
|
208
|
+
ssh_mod._estimate_tokens = _patched_subagent_estimate_tokens
|
|
209
|
+
logger.info("token_ratio_learner: patched subagent_stream_handler._estimate_tokens")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Register the startup callback
|
|
213
|
+
register_callback("startup", _on_startup)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Universal Constructor - Dynamic tool creation and management plugin.
|
|
2
|
+
|
|
3
|
+
This plugin enables users to create, manage, and deploy custom tools
|
|
4
|
+
that extend Code Puppy's capabilities. Tools are stored in the user's
|
|
5
|
+
config directory and can be organized into namespaces via subdirectories.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# User tools directory - where user-created UC tools live
|
|
11
|
+
USER_UC_DIR = Path.home() / ".code_puppy" / "plugins" / "universal_constructor"
|
|
12
|
+
|
|
13
|
+
__all__ = ["USER_UC_DIR"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Pydantic models for Universal Constructor tools and responses.
|
|
2
|
+
|
|
3
|
+
This module defines the data structures used throughout the UC plugin
|
|
4
|
+
for representing tool metadata, tool information, and operation responses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolMeta(BaseModel):
|
|
13
|
+
"""Metadata for a UC tool.
|
|
14
|
+
|
|
15
|
+
This is the structure expected in the TOOL_META dictionary
|
|
16
|
+
at the top of each tool file.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = Field(..., description="Human-readable tool name")
|
|
20
|
+
namespace: str = Field(
|
|
21
|
+
default="", description="Namespace for the tool (from subdirectory path)"
|
|
22
|
+
)
|
|
23
|
+
description: str = Field(..., description="What the tool does")
|
|
24
|
+
enabled: bool = Field(default=True, description="Whether the tool is active")
|
|
25
|
+
version: str = Field(default="1.0.0", description="Semantic version of the tool")
|
|
26
|
+
author: str = Field(default="", description="Tool author or creator")
|
|
27
|
+
created_at: Optional[str] = Field(
|
|
28
|
+
default=None, description="When the tool was created (ISO format string)"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
model_config = {"extra": "allow"} # Allow additional metadata fields
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UCToolInfo(BaseModel):
|
|
35
|
+
"""Full information about a UC tool.
|
|
36
|
+
|
|
37
|
+
Combines metadata with runtime information like function signature
|
|
38
|
+
and source file location.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
meta: ToolMeta = Field(..., description="Tool metadata")
|
|
42
|
+
signature: str = Field(..., description="Function signature string")
|
|
43
|
+
source_path: str = Field(..., description="Path to the tool source file")
|
|
44
|
+
function_name: str = Field(default="", description="Name of the callable function")
|
|
45
|
+
docstring: Optional[str] = Field(default=None, description="Function docstring")
|
|
46
|
+
|
|
47
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def full_name(self) -> str:
|
|
51
|
+
"""Get the fully qualified tool name including namespace."""
|
|
52
|
+
if self.meta.namespace:
|
|
53
|
+
return f"{self.meta.namespace}.{self.meta.name}"
|
|
54
|
+
return self.meta.name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Response models for UC operations
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class UCListOutput(BaseModel):
|
|
61
|
+
"""Response model for listing UC tools."""
|
|
62
|
+
|
|
63
|
+
tools: List[UCToolInfo] = Field(
|
|
64
|
+
default_factory=list, description="List of available tools"
|
|
65
|
+
)
|
|
66
|
+
total_count: int = Field(default=0, description="Total number of tools")
|
|
67
|
+
enabled_count: int = Field(default=0, description="Number of enabled tools")
|
|
68
|
+
error: Optional[str] = Field(default=None, description="Error message if any")
|
|
69
|
+
|
|
70
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class UCCallOutput(BaseModel):
|
|
74
|
+
"""Response model for calling a UC tool."""
|
|
75
|
+
|
|
76
|
+
success: bool = Field(..., description="Whether the call succeeded")
|
|
77
|
+
tool_name: str = Field(..., description="Name of the tool that was called")
|
|
78
|
+
result: Any = Field(default=None, description="Return value from the tool")
|
|
79
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
80
|
+
execution_time: Optional[float] = Field(
|
|
81
|
+
default=None, description="Execution time in seconds"
|
|
82
|
+
)
|
|
83
|
+
source_preview: Optional[str] = Field(
|
|
84
|
+
default=None, description="Preview of the tool's source code that was executed"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class UCCreateOutput(BaseModel):
|
|
89
|
+
"""Response model for creating a UC tool."""
|
|
90
|
+
|
|
91
|
+
success: bool = Field(..., description="Whether creation succeeded")
|
|
92
|
+
tool_name: str = Field(default="", description="Name of the created tool")
|
|
93
|
+
source_path: Optional[str] = Field(
|
|
94
|
+
default=None, description="Path where tool was saved"
|
|
95
|
+
)
|
|
96
|
+
preview: Optional[str] = Field(
|
|
97
|
+
default=None, description="Preview of the first 10 lines of source code"
|
|
98
|
+
)
|
|
99
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
100
|
+
validation_warnings: List[str] = Field(
|
|
101
|
+
default_factory=list, description="Non-fatal validation warnings"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class UCUpdateOutput(BaseModel):
|
|
108
|
+
"""Response model for updating a UC tool."""
|
|
109
|
+
|
|
110
|
+
success: bool = Field(..., description="Whether update succeeded")
|
|
111
|
+
tool_name: str = Field(default="", description="Name of the updated tool")
|
|
112
|
+
source_path: Optional[str] = Field(
|
|
113
|
+
default=None, description="Path to the updated tool"
|
|
114
|
+
)
|
|
115
|
+
preview: Optional[str] = Field(
|
|
116
|
+
default=None, description="Preview of the first 10 lines of updated source code"
|
|
117
|
+
)
|
|
118
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
119
|
+
changes_applied: List[str] = Field(
|
|
120
|
+
default_factory=list, description="List of changes that were applied"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class UCInfoOutput(BaseModel):
|
|
127
|
+
"""Response model for getting info about a specific UC tool."""
|
|
128
|
+
|
|
129
|
+
success: bool = Field(..., description="Whether lookup succeeded")
|
|
130
|
+
tool: Optional[UCToolInfo] = Field(
|
|
131
|
+
default=None, description="Tool information if found"
|
|
132
|
+
)
|
|
133
|
+
source_code: Optional[str] = Field(
|
|
134
|
+
default=None, description="Source code of the tool"
|
|
135
|
+
)
|
|
136
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
137
|
+
|
|
138
|
+
model_config = {"arbitrary_types_allowed": True}
|