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,375 @@
|
|
|
1
|
+
"""Utility functions for the Azure AI Foundry plugin.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions for configuration management,
|
|
4
|
+
environment variable resolution, and model configuration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .config import (
|
|
16
|
+
DEFAULT_CONTEXT_LENGTHS,
|
|
17
|
+
ENV_FOUNDRY_RESOURCE,
|
|
18
|
+
get_extra_models_path,
|
|
19
|
+
get_openai_context_length,
|
|
20
|
+
)
|
|
21
|
+
from code_puppy import atomic_io, atomic_json
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Context window suffix pattern: [<number><unit>] where unit is k or m (case-insensitive)
|
|
26
|
+
# Examples: [200k], [500k], [1m], [2m]
|
|
27
|
+
_CONTEXT_SUFFIX_PATTERN = re.compile(r"\[(\d+)([km])\]", re.IGNORECASE)
|
|
28
|
+
|
|
29
|
+
# Multipliers for context window units
|
|
30
|
+
_CONTEXT_MULTIPLIERS = {
|
|
31
|
+
"k": 1_000,
|
|
32
|
+
"m": 1_000_000,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_context_window_suffix(name: str) -> tuple[str, int | None]:
|
|
37
|
+
"""Parse and extract context window suffix from a model/deployment name.
|
|
38
|
+
|
|
39
|
+
Supports Claude Code format: [<number><unit>] where unit is 'k' (thousands)
|
|
40
|
+
or 'm' (millions). Examples: [200k], [500k], [1m], [2m]
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: The model or deployment name, possibly with context suffix.
|
|
44
|
+
e.g., "it-entra-claude-opus-4-6[1m]"
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A tuple of (stripped_name, context_length):
|
|
48
|
+
- stripped_name: The name with the suffix removed
|
|
49
|
+
- context_length: The parsed context length in tokens, or None if no suffix
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
>>> parse_context_window_suffix("claude-opus-4-6[1m]")
|
|
53
|
+
('claude-opus-4-6', 1000000)
|
|
54
|
+
>>> parse_context_window_suffix("claude-sonnet[200k]")
|
|
55
|
+
('claude-sonnet', 200000)
|
|
56
|
+
>>> parse_context_window_suffix("claude-haiku-4-5")
|
|
57
|
+
('claude-haiku-4-5', None)
|
|
58
|
+
"""
|
|
59
|
+
match = _CONTEXT_SUFFIX_PATTERN.search(name)
|
|
60
|
+
if not match:
|
|
61
|
+
return name, None
|
|
62
|
+
|
|
63
|
+
number = int(match.group(1))
|
|
64
|
+
unit = match.group(2).lower()
|
|
65
|
+
multiplier = _CONTEXT_MULTIPLIERS[unit]
|
|
66
|
+
context_length = number * multiplier
|
|
67
|
+
|
|
68
|
+
# Remove the suffix from the name
|
|
69
|
+
stripped_name = _CONTEXT_SUFFIX_PATTERN.sub("", name).strip()
|
|
70
|
+
|
|
71
|
+
return stripped_name, context_length
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolve_env_var(value: str) -> str:
|
|
75
|
+
"""Resolve a value that may contain an environment variable reference.
|
|
76
|
+
|
|
77
|
+
If the value starts with '$', it's treated as an environment variable
|
|
78
|
+
reference and resolved. Otherwise, the value is returned as-is.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
value: The value to resolve, possibly starting with '$'.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The resolved value.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
>>> os.environ["MY_VAR"] = "test"
|
|
88
|
+
>>> resolve_env_var("$MY_VAR")
|
|
89
|
+
'test'
|
|
90
|
+
>>> resolve_env_var("literal")
|
|
91
|
+
'literal'
|
|
92
|
+
"""
|
|
93
|
+
if value and value.startswith("$"):
|
|
94
|
+
env_var = value[1:]
|
|
95
|
+
resolved = os.environ.get(env_var)
|
|
96
|
+
if resolved is None:
|
|
97
|
+
logger.warning(f"Environment variable '{env_var}' not set")
|
|
98
|
+
return ""
|
|
99
|
+
return resolved
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def load_extra_models() -> dict[str, Any]:
|
|
104
|
+
"""Load the extra_models.json configuration file.
|
|
105
|
+
|
|
106
|
+
Bounded and lock-aware via :mod:`code_puppy.atomic_json` -- a large or
|
|
107
|
+
concurrently-being-written file can no longer balloon memory or read a
|
|
108
|
+
torn write.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary containing model configurations, or empty dict if file
|
|
112
|
+
doesn't exist or is invalid.
|
|
113
|
+
"""
|
|
114
|
+
extra_models_path = str(get_extra_models_path())
|
|
115
|
+
try:
|
|
116
|
+
return atomic_json.load_json(extra_models_path, default={})
|
|
117
|
+
except atomic_json.JsonFileCorrupt as e:
|
|
118
|
+
logger.error(f"Invalid JSON in extra_models.json: {e}")
|
|
119
|
+
return {}
|
|
120
|
+
except OSError as e:
|
|
121
|
+
logger.error(f"Error loading extra_models.json: {e}")
|
|
122
|
+
return {}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def save_extra_models(models: dict[str, Any]) -> bool:
|
|
126
|
+
"""Save model configurations to extra_models.json.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
models: Dictionary of model configurations to save.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if save succeeded, False otherwise.
|
|
133
|
+
"""
|
|
134
|
+
extra_models_path = str(get_extra_models_path())
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
with atomic_io.path_lock(extra_models_path):
|
|
138
|
+
atomic_io.atomic_write_bytes(
|
|
139
|
+
extra_models_path,
|
|
140
|
+
json.dumps(models, indent=2, ensure_ascii=False).encode("utf-8"),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
logger.info(f"Saved {len(models)} models to extra_models.json")
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Error saving extra_models.json: {e}")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_foundry_model_config(
|
|
152
|
+
deployment_name: str,
|
|
153
|
+
model_tier: str,
|
|
154
|
+
foundry_resource: str | None = None,
|
|
155
|
+
context_length: int | None = None,
|
|
156
|
+
) -> dict[str, Any]:
|
|
157
|
+
"""Build a Code Puppy model configuration for an Azure Foundry deployment.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
deployment_name: The deployment name in Azure Foundry.
|
|
161
|
+
model_tier: One of "opus", "sonnet", or "haiku".
|
|
162
|
+
foundry_resource: The Foundry resource name. If None, uses env var reference.
|
|
163
|
+
context_length: Override for context length. If None, uses default for tier.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dictionary containing the model configuration.
|
|
167
|
+
"""
|
|
168
|
+
if context_length is None:
|
|
169
|
+
context_length = DEFAULT_CONTEXT_LENGTHS.get(model_tier.lower(), 200000)
|
|
170
|
+
|
|
171
|
+
resource_value = (
|
|
172
|
+
foundry_resource if foundry_resource else f"${ENV_FOUNDRY_RESOURCE}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# All Anthropic models need extended_thinking + budget_tokens in the request body,
|
|
176
|
+
# plus interleaved_thinking for the beta header; Opus additionally adds effort.
|
|
177
|
+
supported_settings = [
|
|
178
|
+
"temperature",
|
|
179
|
+
"extended_thinking",
|
|
180
|
+
"budget_tokens",
|
|
181
|
+
"interleaved_thinking",
|
|
182
|
+
]
|
|
183
|
+
if model_tier.lower() == "opus":
|
|
184
|
+
supported_settings.append("effort")
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"type": "azure_foundry",
|
|
188
|
+
"provider": "azure_foundry",
|
|
189
|
+
"name": deployment_name,
|
|
190
|
+
"foundry_resource": resource_value,
|
|
191
|
+
"context_length": context_length,
|
|
192
|
+
"supported_settings": supported_settings,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def add_foundry_models_to_config(
|
|
197
|
+
resource_name: str,
|
|
198
|
+
opus_deployment: str | None = None,
|
|
199
|
+
sonnet_deployment: str | None = None,
|
|
200
|
+
haiku_deployment: str | None = None,
|
|
201
|
+
) -> list[str]:
|
|
202
|
+
"""Add Azure Foundry model configurations to extra_models.json.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
resource_name: The Azure Foundry resource name.
|
|
206
|
+
opus_deployment: Deployment name for Opus model (optional).
|
|
207
|
+
sonnet_deployment: Deployment name for Sonnet model (optional).
|
|
208
|
+
haiku_deployment: Deployment name for Haiku model (optional).
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List of model keys that were added.
|
|
212
|
+
|
|
213
|
+
Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
|
|
214
|
+
happen inside one locked transaction -- extra_models.json is also
|
|
215
|
+
touched by add_model_menu.py and the ollama_setup/aws_bedrock plugins,
|
|
216
|
+
so a read-then-write split across two unlocked calls could lose one of
|
|
217
|
+
those concurrent updates.
|
|
218
|
+
"""
|
|
219
|
+
deployments = [
|
|
220
|
+
("opus", opus_deployment, "foundry-claude-opus"),
|
|
221
|
+
("sonnet", sonnet_deployment, "foundry-claude-sonnet"),
|
|
222
|
+
("haiku", haiku_deployment, "foundry-claude-haiku"),
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
entries: dict[str, dict[str, Any]] = {}
|
|
226
|
+
for tier, deployment, model_key in deployments:
|
|
227
|
+
if deployment:
|
|
228
|
+
# Parse context window suffix (Claude Code format: [200k], [1m], etc.)
|
|
229
|
+
actual_deployment, context_length = parse_context_window_suffix(deployment)
|
|
230
|
+
entries[model_key] = build_foundry_model_config(
|
|
231
|
+
deployment_name=actual_deployment,
|
|
232
|
+
model_tier=tier,
|
|
233
|
+
foundry_resource=resource_name,
|
|
234
|
+
context_length=context_length,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if not entries:
|
|
238
|
+
return []
|
|
239
|
+
|
|
240
|
+
def _mutate(models: dict[str, Any]) -> dict[str, Any]:
|
|
241
|
+
models.update(entries)
|
|
242
|
+
return models
|
|
243
|
+
|
|
244
|
+
extra_models_path = str(get_extra_models_path())
|
|
245
|
+
try:
|
|
246
|
+
atomic_json.mutate_json(extra_models_path, _mutate, default={})
|
|
247
|
+
except (atomic_json.JsonFileCorrupt, OSError) as e:
|
|
248
|
+
logger.error(f"Error saving extra_models.json: {e}")
|
|
249
|
+
return []
|
|
250
|
+
|
|
251
|
+
return list(entries.keys())
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
FOUNDRY_TYPES = {"azure_foundry", "azure_foundry_openai"}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
_GPT5_SUPPORTED_SETTINGS = [
|
|
258
|
+
"reasoning_effort",
|
|
259
|
+
"summary",
|
|
260
|
+
"verbosity",
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_foundry_openai_supported_settings(model_name: str) -> list[str]:
|
|
265
|
+
"""Return supported settings for an Azure Foundry OpenAI model.
|
|
266
|
+
|
|
267
|
+
Later GPT-5-family models support Code Puppy's reasoning/summary/verbosity
|
|
268
|
+
controls in addition to the baseline temperature setting.
|
|
269
|
+
"""
|
|
270
|
+
supported_settings = ["temperature"]
|
|
271
|
+
if model_name.startswith("gpt-5"):
|
|
272
|
+
supported_settings.extend(_GPT5_SUPPORTED_SETTINGS)
|
|
273
|
+
return supported_settings
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def add_discovered_models_to_config(
|
|
277
|
+
resource_name: str,
|
|
278
|
+
deployments: list,
|
|
279
|
+
) -> list[str]:
|
|
280
|
+
"""Add auto-discovered deployments to extra_models.json.
|
|
281
|
+
|
|
282
|
+
Classifies each deployment by model format (Anthropic vs OpenAI)
|
|
283
|
+
and creates the appropriate model config.
|
|
284
|
+
|
|
285
|
+
Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
|
|
286
|
+
happen inside one locked transaction (see :func:`add_foundry_models_to_config`).
|
|
287
|
+
"""
|
|
288
|
+
from .discovery import AzureDeployment
|
|
289
|
+
|
|
290
|
+
entries: dict[str, dict[str, Any]] = {}
|
|
291
|
+
|
|
292
|
+
for d in deployments:
|
|
293
|
+
if not isinstance(d, AzureDeployment):
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
key = f"foundry-{d.name}"
|
|
297
|
+
|
|
298
|
+
if d.model_format == "Anthropic":
|
|
299
|
+
tier = "haiku"
|
|
300
|
+
for t in ("opus", "sonnet"):
|
|
301
|
+
if t in d.model_name.lower():
|
|
302
|
+
tier = t
|
|
303
|
+
break
|
|
304
|
+
entries[key] = build_foundry_model_config(
|
|
305
|
+
deployment_name=d.name,
|
|
306
|
+
model_tier=tier,
|
|
307
|
+
foundry_resource=resource_name,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
elif d.model_format == "OpenAI":
|
|
311
|
+
entries[key] = {
|
|
312
|
+
"type": "azure_foundry_openai",
|
|
313
|
+
"provider": "azure_foundry_openai",
|
|
314
|
+
"name": d.name,
|
|
315
|
+
"foundry_resource": resource_name,
|
|
316
|
+
"context_length": get_openai_context_length(d.model_name),
|
|
317
|
+
"supported_settings": get_foundry_openai_supported_settings(
|
|
318
|
+
d.model_name
|
|
319
|
+
),
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if not entries:
|
|
323
|
+
return []
|
|
324
|
+
|
|
325
|
+
def _mutate(models: dict[str, Any]) -> dict[str, Any]:
|
|
326
|
+
models.update(entries)
|
|
327
|
+
return models
|
|
328
|
+
|
|
329
|
+
extra_models_path = str(get_extra_models_path())
|
|
330
|
+
try:
|
|
331
|
+
atomic_json.mutate_json(extra_models_path, _mutate, default={})
|
|
332
|
+
except (atomic_json.JsonFileCorrupt, OSError) as e:
|
|
333
|
+
logger.error(f"Error saving extra_models.json: {e}")
|
|
334
|
+
return []
|
|
335
|
+
|
|
336
|
+
return list(entries.keys())
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def remove_foundry_models_from_config() -> list[str]:
|
|
340
|
+
"""Remove all Azure Foundry model configurations from extra_models.json.
|
|
341
|
+
|
|
342
|
+
Uses :func:`code_puppy.atomic_json.mutate_json` so the read and write
|
|
343
|
+
happen inside one locked transaction (see :func:`add_foundry_models_to_config`).
|
|
344
|
+
"""
|
|
345
|
+
extra_models_path = str(get_extra_models_path())
|
|
346
|
+
removed_models: list[str] = []
|
|
347
|
+
|
|
348
|
+
def _mutate(models: dict[str, Any]) -> dict[str, Any]:
|
|
349
|
+
keys_to_remove = [
|
|
350
|
+
key
|
|
351
|
+
for key, config in models.items()
|
|
352
|
+
if isinstance(config, dict) and config.get("type") in FOUNDRY_TYPES
|
|
353
|
+
]
|
|
354
|
+
for key in keys_to_remove:
|
|
355
|
+
del models[key]
|
|
356
|
+
removed_models.append(key)
|
|
357
|
+
return models
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
atomic_json.mutate_json(extra_models_path, _mutate, default={})
|
|
361
|
+
except (atomic_json.JsonFileCorrupt, OSError) as e:
|
|
362
|
+
logger.error(f"Failed to persist model removal: {e}")
|
|
363
|
+
return []
|
|
364
|
+
|
|
365
|
+
return removed_models
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_foundry_models_from_config() -> dict[str, Any]:
|
|
369
|
+
"""Get all Azure Foundry model configurations from extra_models.json."""
|
|
370
|
+
models = load_extra_models()
|
|
371
|
+
return {
|
|
372
|
+
key: config
|
|
373
|
+
for key, config in models.items()
|
|
374
|
+
if isinstance(config, dict) and config.get("type") in FOUNDRY_TYPES
|
|
375
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""/btw — quick side questions without polluting the main conversation."""
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Inline rendering for `/btw` answers (Claude Code style).
|
|
2
|
+
|
|
3
|
+
Why direct console writes instead of ``emit_info``: the `/btw` handler
|
|
4
|
+
blocks the main event loop while the side query runs, which starves the
|
|
5
|
+
async transcript renderer — queued messages would only paint *after*
|
|
6
|
+
the handler returns. Every slash command already executes under
|
|
7
|
+
``suspended_run_ui`` (idle and mid-run), so the terminal is ours for
|
|
8
|
+
the duration; printing straight to it is the same trick the TUI menus
|
|
9
|
+
use.
|
|
10
|
+
|
|
11
|
+
The dismiss-wait is load-bearing mid-run: the agent stays parked at its
|
|
12
|
+
pause boundary until the user dismisses (or the timeout fires), so the
|
|
13
|
+
resuming stream can't immediately scroll the answer away.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
DISMISS_TIMEOUT_S = 120.0
|
|
26
|
+
_DISMISS_KEYS = (b" ", b"\r", b"\n", b"\x1b")
|
|
27
|
+
_HINT = "Press Space, Enter, or Escape to dismiss"
|
|
28
|
+
_INDENT = (0, 0, 0, 2) # answer block: two-cell left inset
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_tty() -> bool:
|
|
32
|
+
if os.getenv("CODE_PUPPY_NO_TUI") == "1":
|
|
33
|
+
return False
|
|
34
|
+
try:
|
|
35
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
36
|
+
except Exception:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _console():
|
|
41
|
+
from rich.console import Console
|
|
42
|
+
|
|
43
|
+
return Console()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def show_asking(question: str, model_name: str) -> None:
|
|
47
|
+
"""Echo the question and a progress line before the query blocks."""
|
|
48
|
+
from rich.markup import escape
|
|
49
|
+
|
|
50
|
+
console = _console()
|
|
51
|
+
console.print()
|
|
52
|
+
console.print(f"[bold yellow]/btw[/bold yellow] [dim]{escape(question)}[/dim]")
|
|
53
|
+
console.print(f" [dim]thinking ({escape(model_name)})...[/dim]")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def show_answer(answer: str) -> None:
|
|
57
|
+
"""Render the answer inline as Markdown, plus the dismiss hint."""
|
|
58
|
+
from rich.markdown import Markdown
|
|
59
|
+
from rich.padding import Padding
|
|
60
|
+
|
|
61
|
+
console = _console()
|
|
62
|
+
console.print()
|
|
63
|
+
console.print(Padding(Markdown(answer), _INDENT))
|
|
64
|
+
console.print()
|
|
65
|
+
console.print(f" [dim]{_HINT}[/dim]")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def wait_for_dismiss(timeout_s: float = DISMISS_TIMEOUT_S) -> None:
|
|
69
|
+
"""Block until Space / Enter / Esc, or auto-dismiss on timeout.
|
|
70
|
+
|
|
71
|
+
Never raises. Skips waiting entirely when a raw key can't be read
|
|
72
|
+
(no termios/msvcrt, weird stdin) — better to resume than to hang.
|
|
73
|
+
"""
|
|
74
|
+
if not is_tty():
|
|
75
|
+
return
|
|
76
|
+
try:
|
|
77
|
+
_wait_key_posix(timeout_s)
|
|
78
|
+
return
|
|
79
|
+
except ImportError:
|
|
80
|
+
pass
|
|
81
|
+
except Exception:
|
|
82
|
+
logger.debug("btw: posix key wait failed", exc_info=True)
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
_wait_key_windows(timeout_s)
|
|
86
|
+
except Exception:
|
|
87
|
+
logger.debug("btw: windows key wait failed", exc_info=True)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _wait_key_posix(timeout_s: float) -> None:
|
|
91
|
+
import select
|
|
92
|
+
import termios
|
|
93
|
+
import tty
|
|
94
|
+
|
|
95
|
+
fd = sys.stdin.fileno()
|
|
96
|
+
old = termios.tcgetattr(fd)
|
|
97
|
+
try:
|
|
98
|
+
tty.setcbreak(fd)
|
|
99
|
+
deadline = time.monotonic() + timeout_s
|
|
100
|
+
while True:
|
|
101
|
+
remaining = deadline - time.monotonic()
|
|
102
|
+
if remaining <= 0:
|
|
103
|
+
return
|
|
104
|
+
ready, _, _ = select.select([fd], [], [], min(remaining, 0.5))
|
|
105
|
+
if ready and os.read(fd, 1) in _DISMISS_KEYS:
|
|
106
|
+
return
|
|
107
|
+
finally:
|
|
108
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _wait_key_windows(timeout_s: float) -> None:
|
|
112
|
+
import msvcrt
|
|
113
|
+
|
|
114
|
+
deadline = time.monotonic() + timeout_s
|
|
115
|
+
while time.monotonic() < deadline:
|
|
116
|
+
if msvcrt.kbhit() and msvcrt.getch() in _DISMISS_KEYS:
|
|
117
|
+
return
|
|
118
|
+
time.sleep(0.05)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def emit_fallback(question: str, answer: str) -> None:
|
|
122
|
+
"""Non-TTY path: plain transcript message via the message queue."""
|
|
123
|
+
from code_puppy.messaging import emit_info
|
|
124
|
+
|
|
125
|
+
emit_info(f"/btw {question}\n\n{answer}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
__all__ = [
|
|
129
|
+
"DISMISS_TIMEOUT_S",
|
|
130
|
+
"emit_fallback",
|
|
131
|
+
"is_tty",
|
|
132
|
+
"show_answer",
|
|
133
|
+
"show_asking",
|
|
134
|
+
"wait_for_dismiss",
|
|
135
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Plugin: `/btw` — ask a quick side question without derailing the task.
|
|
2
|
+
|
|
3
|
+
Runs an independent, single-turn model query (fresh throwaway agent, no
|
|
4
|
+
tools, no shared history) and shows the answer inline, Claude Code
|
|
5
|
+
style. The main conversation's context window never sees the exchange,
|
|
6
|
+
so minor "by the way..." curiosities stop costing you expensive tokens.
|
|
7
|
+
|
|
8
|
+
Mid-run behaviour comes free from ``run_ui._run_paused_commands``:
|
|
9
|
+
slash commands typed while the agent works are queued, the in-flight
|
|
10
|
+
stream finishes, the agent parks at its pause boundary, the command
|
|
11
|
+
runs with the terminal released, then the run resumes. `/btw` just has
|
|
12
|
+
to behave like any other blocking command inside that window — and the
|
|
13
|
+
dismiss-wait keeps the window open until the user has read the answer.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
/btw <question> One-shot side query, answer shown inline
|
|
17
|
+
/btw --help Show usage
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from typing import List, Optional, Tuple
|
|
24
|
+
|
|
25
|
+
from code_puppy.callbacks import register_callback
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
COMMAND_NAME = "btw"
|
|
30
|
+
|
|
31
|
+
_USAGE = (
|
|
32
|
+
"Usage: /btw <question>\n"
|
|
33
|
+
" Runs an independent single-turn query and shows the answer in a\n"
|
|
34
|
+
" temporary overlay. Your main conversation history is untouched."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _custom_help() -> List[Tuple[str, str]]:
|
|
39
|
+
return [
|
|
40
|
+
(
|
|
41
|
+
COMMAND_NAME,
|
|
42
|
+
"Quick side question, answered inline (single turn, history untouched)",
|
|
43
|
+
)
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_question(command: str) -> Optional[str]:
|
|
48
|
+
"""Return the question text, or None if usage was shown instead."""
|
|
49
|
+
parts = command.split(maxsplit=1)
|
|
50
|
+
question = parts[1].strip() if len(parts) == 2 else ""
|
|
51
|
+
if not question or question in {"--help", "-h", "help"}:
|
|
52
|
+
from code_puppy.messaging import emit_info
|
|
53
|
+
|
|
54
|
+
emit_info(_USAGE)
|
|
55
|
+
return None
|
|
56
|
+
return question
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
60
|
+
if name != COMMAND_NAME:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
question = _parse_question(command)
|
|
64
|
+
if question is None:
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
from code_puppy.messaging import emit_error, emit_info
|
|
68
|
+
|
|
69
|
+
from . import inline_view
|
|
70
|
+
from .side_query import ask_blocking, resolve_model_name
|
|
71
|
+
|
|
72
|
+
model_name = resolve_model_name()
|
|
73
|
+
if not model_name:
|
|
74
|
+
emit_error("/btw: could not resolve a model to ask. Check your model config.")
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
tty = inline_view.is_tty()
|
|
78
|
+
if tty:
|
|
79
|
+
inline_view.show_asking(question, model_name)
|
|
80
|
+
else:
|
|
81
|
+
emit_info(f"btw: asking {model_name} (side query, history untouched)...")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
answer = ask_blocking(model_name, question)
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
emit_error(f"/btw failed ({type(exc).__name__}): {exc}")
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
if tty:
|
|
90
|
+
inline_view.show_answer(answer)
|
|
91
|
+
inline_view.wait_for_dismiss()
|
|
92
|
+
else:
|
|
93
|
+
inline_view.emit_fallback(question, answer)
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
register_callback("custom_command", _handle_custom_command)
|
|
98
|
+
register_callback("custom_command_help", _custom_help)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = [
|
|
102
|
+
"COMMAND_NAME",
|
|
103
|
+
"_custom_help",
|
|
104
|
+
"_handle_custom_command",
|
|
105
|
+
"_parse_question",
|
|
106
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""One-shot, history-free model query backing the `/btw` command.
|
|
2
|
+
|
|
3
|
+
Deliberately minimal (YAGNI): a throwaway pydantic_ai Agent with no
|
|
4
|
+
tools, no message history, and a tiny system prompt. The main agent's
|
|
5
|
+
context window never sees any of this — that's the whole point.
|
|
6
|
+
|
|
7
|
+
Threading note: ``handle_command`` executes synchronously *inside* the
|
|
8
|
+
already-running main event loop, so we can't ``asyncio.run`` on the
|
|
9
|
+
main thread. Same escape hatch as the ``steer_queue`` menu: hop to a
|
|
10
|
+
worker thread and give it its own loop.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import concurrent.futures
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
QUERY_TIMEOUT_S = 180.0
|
|
22
|
+
|
|
23
|
+
_INSTRUCTIONS = (
|
|
24
|
+
"You are answering a quick side question ('/btw') a developer asked "
|
|
25
|
+
"mid-coding-session. Answer directly and concisely — a few sentences "
|
|
26
|
+
"or a short snippet is ideal. No preamble, no follow-up questions."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_model_name() -> str | None:
|
|
31
|
+
"""Current agent's pinned model if any, else the global model."""
|
|
32
|
+
try:
|
|
33
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
|
34
|
+
|
|
35
|
+
pinned = get_current_agent().get_model_name()
|
|
36
|
+
if pinned:
|
|
37
|
+
return pinned
|
|
38
|
+
except Exception:
|
|
39
|
+
logger.debug("btw: agent model lookup failed", exc_info=True)
|
|
40
|
+
try:
|
|
41
|
+
from code_puppy.config import get_global_model_name
|
|
42
|
+
|
|
43
|
+
return get_global_model_name()
|
|
44
|
+
except Exception:
|
|
45
|
+
logger.debug("btw: global model lookup failed", exc_info=True)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _ask(model_name: str, question: str) -> str:
|
|
50
|
+
"""Single-turn query against a fresh Agent. Raises on failure."""
|
|
51
|
+
from pydantic_ai import Agent, UsageLimits
|
|
52
|
+
|
|
53
|
+
from code_puppy.model_factory import ModelFactory, make_model_settings
|
|
54
|
+
from code_puppy.model_utils import prepare_prompt_for_model
|
|
55
|
+
|
|
56
|
+
models_config = ModelFactory.load_config()
|
|
57
|
+
if model_name not in models_config:
|
|
58
|
+
raise ValueError(f"model {model_name!r} not present in the model config")
|
|
59
|
+
|
|
60
|
+
model = ModelFactory.get_model(model_name, models_config)
|
|
61
|
+
prepared = prepare_prompt_for_model(
|
|
62
|
+
model_name,
|
|
63
|
+
_INSTRUCTIONS,
|
|
64
|
+
question,
|
|
65
|
+
prepend_system_to_user=True,
|
|
66
|
+
)
|
|
67
|
+
agent = Agent(
|
|
68
|
+
model=model,
|
|
69
|
+
instructions=prepared.instructions,
|
|
70
|
+
retries=1,
|
|
71
|
+
model_settings=make_model_settings(model_name),
|
|
72
|
+
)
|
|
73
|
+
result = await agent.run(
|
|
74
|
+
prepared.user_prompt,
|
|
75
|
+
usage_limits=UsageLimits(request_limit=3),
|
|
76
|
+
)
|
|
77
|
+
return str(result.output)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def ask_blocking(
|
|
81
|
+
model_name: str,
|
|
82
|
+
question: str,
|
|
83
|
+
timeout_s: float = QUERY_TIMEOUT_S,
|
|
84
|
+
) -> str:
|
|
85
|
+
"""Run the one-shot query on a worker thread; block until answered."""
|
|
86
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
|
87
|
+
future = executor.submit(lambda: asyncio.run(_ask(model_name, question)))
|
|
88
|
+
return future.result(timeout=timeout_s)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = ["QUERY_TIMEOUT_S", "ask_blocking", "resolve_model_name"]
|