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,663 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic tests for ChatGPT OAuth plugin.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from contextlib import nullcontext
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import MagicMock, patch
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from code_puppy_core_plugins.chatgpt_oauth import (
|
|
14
|
+
config,
|
|
15
|
+
image_generation,
|
|
16
|
+
image_tool,
|
|
17
|
+
utils,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_codex_image_generation_posts_codex_payload(tmp_path):
|
|
22
|
+
encoded = "aGVsbG8="
|
|
23
|
+
response = MagicMock()
|
|
24
|
+
response.json.return_value = {"data": [{"b64_json": encoded}]}
|
|
25
|
+
response.raise_for_status.return_value = None
|
|
26
|
+
|
|
27
|
+
with (
|
|
28
|
+
patch.object(image_generation, "get_valid_access_token", return_value="token"),
|
|
29
|
+
patch.object(
|
|
30
|
+
image_generation,
|
|
31
|
+
"load_stored_tokens",
|
|
32
|
+
return_value={"account_id": "account"},
|
|
33
|
+
),
|
|
34
|
+
patch.object(image_generation.config, "DATA_DIR", str(tmp_path)),
|
|
35
|
+
patch.object(image_generation.requests, "post", return_value=response) as post,
|
|
36
|
+
):
|
|
37
|
+
output_path = image_generation.generate_image("a red fox")
|
|
38
|
+
|
|
39
|
+
assert output_path.read_bytes() == b"hello"
|
|
40
|
+
assert output_path.parent == tmp_path / "generated_images"
|
|
41
|
+
post.assert_called_once()
|
|
42
|
+
call = post.call_args
|
|
43
|
+
assert call.args[0].endswith("/images/generations")
|
|
44
|
+
assert call.kwargs["json"] == {
|
|
45
|
+
"prompt": "a red fox",
|
|
46
|
+
"background": "auto",
|
|
47
|
+
"model": "gpt-image-2",
|
|
48
|
+
"quality": "auto",
|
|
49
|
+
"size": "auto",
|
|
50
|
+
}
|
|
51
|
+
assert call.kwargs["headers"]["ChatGPT-Account-Id"] == "account"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_codex_image_generation_with_references_posts_edits_payload(tmp_path):
|
|
55
|
+
"""Reference images must go to /images/edits as [{"image_url": data-url}].
|
|
56
|
+
|
|
57
|
+
The Codex backend rejects every other shape with HTTP 400 (see the
|
|
58
|
+
image_generation module docstring), so this asserts the exact wire format.
|
|
59
|
+
"""
|
|
60
|
+
reference = tmp_path / "ref.png"
|
|
61
|
+
reference.write_bytes(b"pngbytes")
|
|
62
|
+
expected_b64 = image_generation.base64.b64encode(b"pngbytes").decode()
|
|
63
|
+
|
|
64
|
+
encoded = "aGVsbG8="
|
|
65
|
+
response = MagicMock()
|
|
66
|
+
response.json.return_value = {"data": [{"b64_json": encoded}]}
|
|
67
|
+
response.raise_for_status.return_value = None
|
|
68
|
+
|
|
69
|
+
with (
|
|
70
|
+
patch.object(image_generation, "get_valid_access_token", return_value="token"),
|
|
71
|
+
patch.object(
|
|
72
|
+
image_generation,
|
|
73
|
+
"load_stored_tokens",
|
|
74
|
+
return_value={"account_id": "account"},
|
|
75
|
+
),
|
|
76
|
+
patch.object(image_generation.config, "DATA_DIR", str(tmp_path)),
|
|
77
|
+
patch.object(image_generation.requests, "post", return_value=response) as post,
|
|
78
|
+
):
|
|
79
|
+
image_generation.generate_image("a red fox", [reference])
|
|
80
|
+
|
|
81
|
+
call = post.call_args
|
|
82
|
+
assert call.args[0].endswith("/images/edits")
|
|
83
|
+
payload = call.kwargs["json"]
|
|
84
|
+
assert payload["images"] == [{"image_url": f"data:image/png;base64,{expected_b64}"}]
|
|
85
|
+
assert payload["prompt"] == "a red fox"
|
|
86
|
+
assert payload["model"] == "gpt-image-2"
|
|
87
|
+
# `background` is not accepted by the edits endpoint.
|
|
88
|
+
assert "background" not in payload
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_codex_image_generation_without_references_still_uses_generations(tmp_path):
|
|
92
|
+
"""An empty/omitted reference list must not change existing behavior."""
|
|
93
|
+
response = MagicMock()
|
|
94
|
+
response.json.return_value = {"data": [{"b64_json": "aGVsbG8="}]}
|
|
95
|
+
response.raise_for_status.return_value = None
|
|
96
|
+
|
|
97
|
+
with (
|
|
98
|
+
patch.object(image_generation, "get_valid_access_token", return_value="token"),
|
|
99
|
+
patch.object(
|
|
100
|
+
image_generation,
|
|
101
|
+
"load_stored_tokens",
|
|
102
|
+
return_value={"account_id": "account"},
|
|
103
|
+
),
|
|
104
|
+
patch.object(image_generation.config, "DATA_DIR", str(tmp_path)),
|
|
105
|
+
patch.object(image_generation.requests, "post", return_value=response) as post,
|
|
106
|
+
):
|
|
107
|
+
image_generation.generate_image("a red fox", [])
|
|
108
|
+
|
|
109
|
+
assert post.call_args.args[0].endswith("/images/generations")
|
|
110
|
+
assert "images" not in post.call_args.kwargs["json"]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_codex_image_generation_rejects_missing_reference(tmp_path):
|
|
114
|
+
with (
|
|
115
|
+
patch.object(image_generation, "get_valid_access_token", return_value="token"),
|
|
116
|
+
patch.object(
|
|
117
|
+
image_generation,
|
|
118
|
+
"load_stored_tokens",
|
|
119
|
+
return_value={"account_id": "account"},
|
|
120
|
+
),
|
|
121
|
+
patch.object(image_generation.requests, "post") as post,
|
|
122
|
+
pytest.raises(image_generation.CodexImageGenerationError),
|
|
123
|
+
):
|
|
124
|
+
image_generation.generate_image("a red fox", [tmp_path / "nope.png"])
|
|
125
|
+
|
|
126
|
+
post.assert_not_called()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_codex_image_generation_rejects_oversized_reference(tmp_path):
|
|
130
|
+
reference = tmp_path / "huge.png"
|
|
131
|
+
reference.write_bytes(b"x" * 16)
|
|
132
|
+
|
|
133
|
+
with (
|
|
134
|
+
patch.object(image_generation, "get_valid_access_token", return_value="token"),
|
|
135
|
+
patch.object(
|
|
136
|
+
image_generation,
|
|
137
|
+
"load_stored_tokens",
|
|
138
|
+
return_value={"account_id": "account"},
|
|
139
|
+
),
|
|
140
|
+
patch.object(image_generation, "_MAX_REFERENCE_BYTES", 8),
|
|
141
|
+
patch.object(image_generation.requests, "post") as post,
|
|
142
|
+
pytest.raises(image_generation.CodexImageGenerationError),
|
|
143
|
+
):
|
|
144
|
+
image_generation.generate_image("a red fox", [reference])
|
|
145
|
+
|
|
146
|
+
post.assert_not_called()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_codex_image_generation_requires_auth():
|
|
150
|
+
with (
|
|
151
|
+
patch.object(image_generation, "get_valid_access_token", return_value=None),
|
|
152
|
+
patch.object(image_generation, "load_stored_tokens", return_value={}),
|
|
153
|
+
pytest.raises(image_generation.CodexImageGenerationError, match="codex-auth"),
|
|
154
|
+
):
|
|
155
|
+
image_generation.generate_image("a red fox")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_emit_iterm_image(tmp_path):
|
|
159
|
+
output_path = tmp_path / "puppy.png"
|
|
160
|
+
output_path.write_bytes(b"image-bytes")
|
|
161
|
+
stdout = MagicMock()
|
|
162
|
+
stdout.isatty.return_value = True
|
|
163
|
+
|
|
164
|
+
with (
|
|
165
|
+
patch.dict(
|
|
166
|
+
image_generation.os.environ,
|
|
167
|
+
{"TERM_PROGRAM": "iTerm.app"},
|
|
168
|
+
clear=True,
|
|
169
|
+
),
|
|
170
|
+
patch.object(image_generation.sys, "stdout", stdout),
|
|
171
|
+
patch(
|
|
172
|
+
"code_puppy.messaging.run_ui.suspended_run_ui",
|
|
173
|
+
return_value=nullcontext(),
|
|
174
|
+
),
|
|
175
|
+
):
|
|
176
|
+
assert image_generation.emit_iterm_image(output_path) is True
|
|
177
|
+
|
|
178
|
+
sequence = stdout.write.call_args.args[0]
|
|
179
|
+
assert sequence.startswith("\033]1337;File=name=")
|
|
180
|
+
assert ";inline=1;preserveAspectRatio=1:" in sequence
|
|
181
|
+
assert image_generation.base64.b64encode(b"image-bytes").decode() in sequence
|
|
182
|
+
stdout.flush.assert_called_once()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_emit_iterm_image_ignores_other_terminals(tmp_path):
|
|
186
|
+
stdout = MagicMock()
|
|
187
|
+
stdout.isatty.return_value = True
|
|
188
|
+
with (
|
|
189
|
+
patch.dict(image_generation.os.environ, {}, clear=True),
|
|
190
|
+
patch.object(image_generation.sys, "stdout", stdout),
|
|
191
|
+
):
|
|
192
|
+
assert image_generation.emit_iterm_image(tmp_path / "missing.png") is False
|
|
193
|
+
stdout.write.assert_not_called()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_codex_imagegen_command():
|
|
197
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
198
|
+
|
|
199
|
+
with (
|
|
200
|
+
patch.object(
|
|
201
|
+
image_generation,
|
|
202
|
+
"generate_image",
|
|
203
|
+
return_value=Path("/tmp/generated.png"),
|
|
204
|
+
) as generate,
|
|
205
|
+
patch.object(register_callbacks, "emit_info"),
|
|
206
|
+
patch.object(register_callbacks, "emit_success") as success,
|
|
207
|
+
patch.object(image_generation, "emit_iterm_image") as emit_image,
|
|
208
|
+
):
|
|
209
|
+
assert (
|
|
210
|
+
register_callbacks._handle_custom_command(
|
|
211
|
+
"/codex-imagegen a red fox", "codex-imagegen"
|
|
212
|
+
)
|
|
213
|
+
is True
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
generate.assert_called_once_with("a red fox")
|
|
217
|
+
success.assert_called_once()
|
|
218
|
+
emit_image.assert_called_once_with(Path("/tmp/generated.png"))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_imagegen_skill_and_tool_registration():
|
|
222
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
223
|
+
|
|
224
|
+
with patch.object(
|
|
225
|
+
register_callbacks,
|
|
226
|
+
"load_stored_tokens",
|
|
227
|
+
return_value={"access_token": "token", "account_id": "account"},
|
|
228
|
+
):
|
|
229
|
+
skills = register_callbacks._register_imagegen_skill()
|
|
230
|
+
assert skills[0]["name"] == "codex-imagegen"
|
|
231
|
+
assert Path(skills[0]["skill_md_path"]).is_file()
|
|
232
|
+
assert register_callbacks._advertise_imagegen_tool("code-puppy") == [
|
|
233
|
+
"codex_imagegen"
|
|
234
|
+
]
|
|
235
|
+
tools = register_callbacks._register_imagegen_tools()
|
|
236
|
+
assert tools == [
|
|
237
|
+
{"name": "codex_imagegen", "register_func": image_tool.register_codex_imagegen}
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_imagegen_skill_is_not_registered_when_logged_out():
|
|
242
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
243
|
+
|
|
244
|
+
with patch.object(register_callbacks, "load_stored_tokens", return_value=None):
|
|
245
|
+
assert register_callbacks._register_imagegen_skill() == []
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def test_imagegen_tool_is_not_advertised_when_logged_out():
|
|
249
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
250
|
+
|
|
251
|
+
with patch.object(register_callbacks, "load_stored_tokens", return_value=None):
|
|
252
|
+
assert register_callbacks._advertise_imagegen_tool("code-puppy") == []
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_logout_reloads_agent_to_unbind_imagegen(tmp_path):
|
|
256
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
257
|
+
|
|
258
|
+
token_path = tmp_path / "tokens.json"
|
|
259
|
+
token_path.write_text("{}")
|
|
260
|
+
with (
|
|
261
|
+
patch.object(
|
|
262
|
+
register_callbacks,
|
|
263
|
+
"load_stored_tokens",
|
|
264
|
+
return_value={"access_token": "token", "account_id": "account"},
|
|
265
|
+
),
|
|
266
|
+
patch.object(
|
|
267
|
+
register_callbacks, "get_token_storage_path", return_value=token_path
|
|
268
|
+
),
|
|
269
|
+
patch.object(register_callbacks, "remove_chatgpt_models", return_value=0),
|
|
270
|
+
patch.object(register_callbacks, "emit_info"),
|
|
271
|
+
patch.object(register_callbacks, "emit_success"),
|
|
272
|
+
patch.object(register_callbacks, "_reload_active_agent") as reload_agent,
|
|
273
|
+
patch.object(register_callbacks, "refresh_skill_cache") as refresh_skills,
|
|
274
|
+
):
|
|
275
|
+
register_callbacks._handle_chatgpt_logout()
|
|
276
|
+
|
|
277
|
+
assert not token_path.exists()
|
|
278
|
+
reload_agent.assert_called_once_with()
|
|
279
|
+
refresh_skills.assert_called_once_with()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_logout_does_not_refresh_skills_when_already_logged_out(tmp_path):
|
|
283
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
284
|
+
|
|
285
|
+
token_path = tmp_path / "tokens.json"
|
|
286
|
+
with (
|
|
287
|
+
patch.object(register_callbacks, "load_stored_tokens", return_value=None),
|
|
288
|
+
patch.object(
|
|
289
|
+
register_callbacks, "get_token_storage_path", return_value=token_path
|
|
290
|
+
),
|
|
291
|
+
patch.object(register_callbacks, "remove_chatgpt_models", return_value=0),
|
|
292
|
+
patch.object(register_callbacks, "emit_info"),
|
|
293
|
+
patch.object(register_callbacks, "emit_success"),
|
|
294
|
+
patch.object(register_callbacks, "_reload_active_agent") as reload_agent,
|
|
295
|
+
patch.object(register_callbacks, "refresh_skill_cache") as refresh_skills,
|
|
296
|
+
):
|
|
297
|
+
register_callbacks._handle_chatgpt_logout()
|
|
298
|
+
|
|
299
|
+
reload_agent.assert_not_called()
|
|
300
|
+
refresh_skills.assert_not_called()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_auth_command_refreshes_skill_cache():
|
|
304
|
+
from code_puppy_core_plugins.chatgpt_oauth import register_callbacks
|
|
305
|
+
|
|
306
|
+
with (
|
|
307
|
+
patch.object(register_callbacks, "run_oauth_flow") as oauth_flow,
|
|
308
|
+
patch.object(register_callbacks, "set_model_and_reload_agent") as set_model,
|
|
309
|
+
patch.object(register_callbacks, "refresh_skill_cache") as refresh_skills,
|
|
310
|
+
):
|
|
311
|
+
assert (
|
|
312
|
+
register_callbacks._handle_custom_command("/codex-auth", "codex-auth")
|
|
313
|
+
is True
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
oauth_flow.assert_called_once_with()
|
|
317
|
+
set_model.assert_called_once_with("codex-gpt-5.6-sol")
|
|
318
|
+
refresh_skills.assert_called_once_with()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_codex_imagegen_agent_tool(tmp_path):
|
|
322
|
+
registered = {}
|
|
323
|
+
|
|
324
|
+
class FakeAgent:
|
|
325
|
+
def tool(self, function):
|
|
326
|
+
registered[function.__name__] = function
|
|
327
|
+
return function
|
|
328
|
+
|
|
329
|
+
image_tool.register_codex_imagegen(FakeAgent())
|
|
330
|
+
output_path = tmp_path / "generated.png"
|
|
331
|
+
with (
|
|
332
|
+
patch.object(
|
|
333
|
+
image_tool, "generate_image", return_value=output_path
|
|
334
|
+
) as generate,
|
|
335
|
+
patch.object(image_tool, "emit_iterm_image", return_value=True),
|
|
336
|
+
):
|
|
337
|
+
result = asyncio.run(registered["codex_imagegen"](MagicMock(), "a fox"))
|
|
338
|
+
|
|
339
|
+
assert result == {
|
|
340
|
+
"success": True,
|
|
341
|
+
"path": str(output_path),
|
|
342
|
+
"displayed_inline": True,
|
|
343
|
+
}
|
|
344
|
+
generate.assert_called_once_with("a fox", None)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_codex_imagegen_agent_tool_forwards_reference_images(tmp_path):
|
|
348
|
+
registered = {}
|
|
349
|
+
|
|
350
|
+
class FakeAgent:
|
|
351
|
+
def tool(self, function):
|
|
352
|
+
registered[function.__name__] = function
|
|
353
|
+
return function
|
|
354
|
+
|
|
355
|
+
image_tool.register_codex_imagegen(FakeAgent())
|
|
356
|
+
output_path = tmp_path / "generated.png"
|
|
357
|
+
refs = [str(tmp_path / "ref.png")]
|
|
358
|
+
with (
|
|
359
|
+
patch.object(
|
|
360
|
+
image_tool, "generate_image", return_value=output_path
|
|
361
|
+
) as generate,
|
|
362
|
+
patch.object(image_tool, "emit_iterm_image", return_value=False),
|
|
363
|
+
):
|
|
364
|
+
result = asyncio.run(
|
|
365
|
+
registered["codex_imagegen"](MagicMock(), "same fox, at night", refs)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
assert result["success"] is True
|
|
369
|
+
generate.assert_called_once_with("same fox, at night", refs)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def test_config_paths():
|
|
373
|
+
"""Test configuration path helpers."""
|
|
374
|
+
token_path = config.get_token_storage_path()
|
|
375
|
+
assert token_path.name == "chatgpt_oauth.json"
|
|
376
|
+
# XDG paths use "code_puppy" (without dot) in ~/.local/share or ~/.config
|
|
377
|
+
assert "code_puppy" in str(token_path)
|
|
378
|
+
|
|
379
|
+
config_dir = config.get_config_dir()
|
|
380
|
+
# Default is ~/.code_puppy; XDG paths only used when XDG env vars are set
|
|
381
|
+
assert config_dir.name in ("code_puppy", ".code_puppy")
|
|
382
|
+
|
|
383
|
+
chatgpt_models = config.get_chatgpt_models_path()
|
|
384
|
+
assert chatgpt_models.name == "chatgpt_models.json"
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_oauth_config():
|
|
388
|
+
"""Test OAuth configuration values."""
|
|
389
|
+
assert config.CHATGPT_OAUTH_CONFIG["issuer"] == "https://auth.openai.com"
|
|
390
|
+
assert config.CHATGPT_OAUTH_CONFIG["client_id"] == "app_EMoamEEZ73f0CkXaXp7hrann"
|
|
391
|
+
assert config.CHATGPT_OAUTH_CONFIG["prefix"] == "codex-"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def test_jwt_parsing_with_nested_org():
|
|
395
|
+
"""Test JWT parsing with nested organization structure like the user's payload."""
|
|
396
|
+
# This simulates the user's JWT payload structure
|
|
397
|
+
mock_claims = {
|
|
398
|
+
"aud": ["app_EMoamEEZ73f0CkXaXp7hrann"],
|
|
399
|
+
"auth_provider": "google",
|
|
400
|
+
"email": "mike.pfaf fenberger@gmail.com",
|
|
401
|
+
"https://api.openai.com/auth": {
|
|
402
|
+
"chatgpt_account_id": "d1844a91-9aac-419b-903e-f6a99c76f163",
|
|
403
|
+
"organizations": [
|
|
404
|
+
{
|
|
405
|
+
"id": "org-iydWjnSxSr51VuYhDVMDte5",
|
|
406
|
+
"is_default": True,
|
|
407
|
+
"role": "owner",
|
|
408
|
+
"title": "Personal",
|
|
409
|
+
}
|
|
410
|
+
],
|
|
411
|
+
"groups": ["api-data-sharing-incentives-program", "verified-organization"],
|
|
412
|
+
},
|
|
413
|
+
"sub": "google-oauth2|107692466937587138174",
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
# Test the org extraction logic
|
|
417
|
+
auth_claims = mock_claims.get("https://api.openai.com/auth", {})
|
|
418
|
+
organizations = auth_claims.get("organizations", [])
|
|
419
|
+
|
|
420
|
+
org_id = None
|
|
421
|
+
if organizations:
|
|
422
|
+
default_org = next(
|
|
423
|
+
(org for org in organizations if org.get("is_default")), organizations[0]
|
|
424
|
+
)
|
|
425
|
+
org_id = default_org.get("id")
|
|
426
|
+
|
|
427
|
+
assert org_id == "org-iydWjnSxSr51VuYhDVMDte5"
|
|
428
|
+
|
|
429
|
+
# Test fallback to top-level org_id (should not happen in this case)
|
|
430
|
+
if not org_id:
|
|
431
|
+
org_id = mock_claims.get("organization_id")
|
|
432
|
+
|
|
433
|
+
assert org_id == "org-iydWjnSxSr51VuYhDVMDte5"
|
|
434
|
+
assert config.CHATGPT_OAUTH_CONFIG["required_port"] == 1455
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_code_verifier_generation():
|
|
438
|
+
"""Test PKCE code verifier generation."""
|
|
439
|
+
verifier = utils._generate_code_verifier()
|
|
440
|
+
assert isinstance(verifier, str)
|
|
441
|
+
assert len(verifier) > 50 # Should be long
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def test_code_challenge_computation():
|
|
445
|
+
"""Test PKCE code challenge computation."""
|
|
446
|
+
verifier = "test_verifier_string"
|
|
447
|
+
challenge = utils._compute_code_challenge(verifier)
|
|
448
|
+
assert isinstance(challenge, str)
|
|
449
|
+
assert len(challenge) > 0
|
|
450
|
+
# Should be URL-safe base64
|
|
451
|
+
assert all(
|
|
452
|
+
c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
|
453
|
+
for c in challenge
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def test_prepare_oauth_context():
|
|
458
|
+
"""Test OAuth context preparation."""
|
|
459
|
+
context = utils.prepare_oauth_context()
|
|
460
|
+
assert context.state
|
|
461
|
+
assert context.code_verifier
|
|
462
|
+
assert context.code_challenge
|
|
463
|
+
assert context.created_at > 0
|
|
464
|
+
assert context.redirect_uri is None
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def test_assign_redirect_uri():
|
|
468
|
+
"""Test redirect URI assignment."""
|
|
469
|
+
context = utils.prepare_oauth_context()
|
|
470
|
+
redirect_uri = utils.assign_redirect_uri(context, 1455)
|
|
471
|
+
assert redirect_uri == "http://localhost:1455/auth/callback"
|
|
472
|
+
assert context.redirect_uri == redirect_uri
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def test_build_authorization_url():
|
|
476
|
+
"""Test authorization URL building."""
|
|
477
|
+
context = utils.prepare_oauth_context()
|
|
478
|
+
utils.assign_redirect_uri(context, 1455)
|
|
479
|
+
auth_url = utils.build_authorization_url(context)
|
|
480
|
+
|
|
481
|
+
assert auth_url.startswith("https://auth.openai.com/oauth/authorize?")
|
|
482
|
+
assert "response_type=code" in auth_url
|
|
483
|
+
assert "client_id=" in auth_url
|
|
484
|
+
assert "redirect_uri=" in auth_url
|
|
485
|
+
assert "code_challenge=" in auth_url
|
|
486
|
+
assert "code_challenge_method=S256" in auth_url
|
|
487
|
+
assert f"state={context.state}" in auth_url
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_parse_jwt_claims():
|
|
491
|
+
"""Test JWT claims parsing."""
|
|
492
|
+
# Valid JWT structure (header.payload.signature)
|
|
493
|
+
import base64
|
|
494
|
+
|
|
495
|
+
payload = base64.urlsafe_b64encode(json.dumps({"sub": "user123"}).encode()).decode()
|
|
496
|
+
token = f"header.{payload}.signature"
|
|
497
|
+
|
|
498
|
+
claims = utils.parse_jwt_claims(token)
|
|
499
|
+
assert claims is not None
|
|
500
|
+
assert claims["sub"] == "user123"
|
|
501
|
+
|
|
502
|
+
# Invalid token
|
|
503
|
+
assert utils.parse_jwt_claims("") is None
|
|
504
|
+
assert utils.parse_jwt_claims("invalid") is None
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def test_save_and_load_tokens(tmp_path):
|
|
508
|
+
"""Test token storage and retrieval."""
|
|
509
|
+
with patch.object(
|
|
510
|
+
utils, "get_token_storage_path", return_value=tmp_path / "tokens.json"
|
|
511
|
+
):
|
|
512
|
+
tokens = {
|
|
513
|
+
"access_token": "test_access",
|
|
514
|
+
"refresh_token": "test_refresh",
|
|
515
|
+
"api_key": "sk-test",
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# Save tokens
|
|
519
|
+
assert utils.save_tokens(tokens)
|
|
520
|
+
|
|
521
|
+
# Load tokens
|
|
522
|
+
loaded = utils.load_stored_tokens()
|
|
523
|
+
assert loaded == tokens
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def test_save_and_load_chatgpt_models(tmp_path):
|
|
527
|
+
"""Test ChatGPT models configuration."""
|
|
528
|
+
with patch.object(
|
|
529
|
+
utils, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
530
|
+
):
|
|
531
|
+
models = {
|
|
532
|
+
"codex-gpt-4o": {
|
|
533
|
+
"type": "openai",
|
|
534
|
+
"name": "gpt-4o",
|
|
535
|
+
"oauth_source": "chatgpt-oauth-plugin",
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
# Save models
|
|
540
|
+
assert utils.save_chatgpt_models(models)
|
|
541
|
+
|
|
542
|
+
# Load models
|
|
543
|
+
loaded = utils.load_chatgpt_models()
|
|
544
|
+
assert loaded == models
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def test_remove_chatgpt_models(tmp_path):
|
|
548
|
+
"""Test removal of ChatGPT models from config."""
|
|
549
|
+
with patch.object(
|
|
550
|
+
utils, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
551
|
+
):
|
|
552
|
+
models = {
|
|
553
|
+
"codex-gpt-4o": {
|
|
554
|
+
"type": "openai",
|
|
555
|
+
"oauth_source": "chatgpt-oauth-plugin",
|
|
556
|
+
},
|
|
557
|
+
"claude-3-opus": {
|
|
558
|
+
"type": "anthropic",
|
|
559
|
+
"oauth_source": "other",
|
|
560
|
+
},
|
|
561
|
+
}
|
|
562
|
+
utils.save_chatgpt_models(models)
|
|
563
|
+
|
|
564
|
+
# Remove only ChatGPT models
|
|
565
|
+
removed_count = utils.remove_chatgpt_models()
|
|
566
|
+
assert removed_count == 1
|
|
567
|
+
|
|
568
|
+
# Verify only ChatGPT model was removed
|
|
569
|
+
remaining = utils.load_chatgpt_models()
|
|
570
|
+
assert "codex-gpt-4o" not in remaining
|
|
571
|
+
assert "claude-3-opus" in remaining
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
@patch("code_puppy_core_plugins.chatgpt_oauth.utils.requests.post")
|
|
575
|
+
def test_exchange_code_for_tokens(mock_post):
|
|
576
|
+
"""Test authorization code exchange."""
|
|
577
|
+
mock_response = MagicMock()
|
|
578
|
+
mock_response.status_code = 200
|
|
579
|
+
mock_response.json.return_value = {
|
|
580
|
+
"access_token": "test_access",
|
|
581
|
+
"refresh_token": "test_refresh",
|
|
582
|
+
"id_token": "test_id",
|
|
583
|
+
}
|
|
584
|
+
mock_post.return_value = mock_response
|
|
585
|
+
|
|
586
|
+
context = utils.prepare_oauth_context()
|
|
587
|
+
utils.assign_redirect_uri(context, 1455)
|
|
588
|
+
|
|
589
|
+
tokens = utils.exchange_code_for_tokens("test_code", context)
|
|
590
|
+
assert tokens is not None
|
|
591
|
+
assert tokens["access_token"] == "test_access"
|
|
592
|
+
assert "last_refresh" in tokens
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
@patch("code_puppy_core_plugins.chatgpt_oauth.utils.requests.get")
|
|
596
|
+
def test_fetch_chatgpt_models(mock_get):
|
|
597
|
+
"""Test fetching models from ChatGPT Codex API."""
|
|
598
|
+
mock_response = MagicMock()
|
|
599
|
+
mock_response.status_code = 200
|
|
600
|
+
# New response format uses "models" key with "slug" field
|
|
601
|
+
mock_response.json.return_value = {
|
|
602
|
+
"models": [
|
|
603
|
+
{"slug": "gpt-4o"},
|
|
604
|
+
{"slug": "gpt-3.5-turbo"},
|
|
605
|
+
{"slug": "o1-preview"},
|
|
606
|
+
{"slug": "codex-mini"},
|
|
607
|
+
]
|
|
608
|
+
}
|
|
609
|
+
mock_get.return_value = mock_response
|
|
610
|
+
|
|
611
|
+
models = utils.fetch_chatgpt_models("test_access_token", "test_account_id")
|
|
612
|
+
assert models is not None
|
|
613
|
+
# A successful endpoint response is authoritative; fallback models are
|
|
614
|
+
# used only when discovery fails.
|
|
615
|
+
assert "gpt-5.4" not in models
|
|
616
|
+
assert "gpt-5.4-mini" not in models
|
|
617
|
+
# API-returned models are preserved.
|
|
618
|
+
assert "gpt-4o" in models
|
|
619
|
+
assert "gpt-3.5-turbo" in models
|
|
620
|
+
assert "o1-preview" in models
|
|
621
|
+
assert "codex-mini" in models
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@patch("code_puppy_core_plugins.chatgpt_oauth.utils.requests.get")
|
|
625
|
+
def test_fetch_chatgpt_models_fallback(mock_get):
|
|
626
|
+
"""Test that fetch_chatgpt_models returns default list on API failure."""
|
|
627
|
+
mock_response = MagicMock()
|
|
628
|
+
mock_response.status_code = 404
|
|
629
|
+
mock_response.text = '{"detail":"Not Found"}'
|
|
630
|
+
mock_get.return_value = mock_response
|
|
631
|
+
|
|
632
|
+
models = utils.fetch_chatgpt_models("test_access_token", "test_account_id")
|
|
633
|
+
assert models is not None
|
|
634
|
+
# Keep the fallback aligned with the models currently exposed by Codex.
|
|
635
|
+
assert "gpt-5.4" in models
|
|
636
|
+
assert "gpt-5.4-mini" in models
|
|
637
|
+
assert "gpt-5.3-codex-spark" in models
|
|
638
|
+
assert "codex-auto-review" in models
|
|
639
|
+
assert "gpt-5.3-instant" not in models
|
|
640
|
+
assert "gpt-5.3-codex" not in models
|
|
641
|
+
assert "gpt-5.2-codex" not in models
|
|
642
|
+
assert "gpt-5.2" not in models
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def test_add_models_to_chatgpt_config(tmp_path):
|
|
646
|
+
"""Test adding models to chatgpt_models.json."""
|
|
647
|
+
with patch.object(
|
|
648
|
+
utils, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
649
|
+
):
|
|
650
|
+
models = ["gpt-4o", "gpt-3.5-turbo"]
|
|
651
|
+
|
|
652
|
+
assert utils.add_models_to_extra_config(models)
|
|
653
|
+
|
|
654
|
+
loaded = utils.load_chatgpt_models()
|
|
655
|
+
assert "codex-gpt-4o" in loaded
|
|
656
|
+
assert "codex-gpt-3.5-turbo" in loaded
|
|
657
|
+
assert loaded["codex-gpt-4o"]["type"] == "chatgpt_oauth"
|
|
658
|
+
assert loaded["codex-gpt-4o"]["name"] == "gpt-4o"
|
|
659
|
+
assert loaded["codex-gpt-4o"]["oauth_source"] == "chatgpt-oauth-plugin"
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
if __name__ == "__main__":
|
|
663
|
+
pytest.main([__file__, "-v"])
|