code-puppy 0.0.910__tar.gz → 0.0.912__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_puppy-0.0.910 → code_puppy-0.0.912}/PKG-INFO +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_builder.py +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_compaction.py +19 -1
- code_puppy-0.0.912/code_puppy/agents/_model_message_transform.py +130 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_runtime.py +23 -41
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/retry_checkpoint.py +2 -2
- code_puppy-0.0.912/code_puppy/agents/run_invocation.py +72 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/callbacks.py +7 -3
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/compaction.py +37 -5
- code_puppy-0.0.912/code_puppy/context_accounting.py +154 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_switching.py +5 -2
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/__init__.py +9 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/token_usage.py +67 -48
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_invocation.py +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.912}/pyproject.toml +1 -1
- code_puppy-0.0.910/code_puppy/agents/_model_message_transform.py +0 -31
- {code_puppy-0.0.910 → code_puppy-0.0.912}/.gitignore +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/LICENSE +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/README.md +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_plugin_i18n_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_pydantic_tool_helpers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_pydantic_video_patch.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agent_completion_inbox.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agent_execution_context.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_code_mode.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_code_mode_guidance.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_diagnostics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_foreign_thinking.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_history.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_key_listeners.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_non_streaming_render.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_output_limits.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_run_signals.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_steer_processor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_subagent_recursion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_windows_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_wire_tool_names.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_helios.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_model_judge.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_web_retriever.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/event_stream_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/retry_profiles.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/run_stats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/smooth_stream.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/stream_status.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/subagent_stream_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/thinking_output.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/asyncio_cleanup.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/atomic_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/atomic_json.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/ca_bundle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/eager_timing.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_oauth_transport.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_thinking_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_details.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_menus.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_vllm.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/agent_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/autosave_search.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/completers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/completion_cache.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/config_apply.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/file_index.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/help_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/help_overlay.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/image_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/trust_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp_binding_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/menu_session.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_settings_defs.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/pagination.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/refresh_models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_browser.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_browser_data.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_schema.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_settings.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_shims.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_values.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/shell_passthrough.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/skills_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/tui_style.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/uc_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/config_file.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/diagnostics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/events/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/events/bridge.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/file_completion_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/file_completion_tokens.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_common.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_model.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/README.md +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/aliases.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/engine.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/executor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/matcher.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/validator.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/http_retry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/httpx2_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/audit.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/formats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/interpolation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locale.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/en-US.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/es.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/fr-CA.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/plugin_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/plurals.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/pseudo.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/translate.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/kennel_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/list_filtering.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/main.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/agent_bindings.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/http_auth.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/manager_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/project_config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/toolset_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_prompts/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_prompts/hook_creator.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bar_painters.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bar_rendering.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bottom_bar.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/chords.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_actions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_history.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_keys.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_paste.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_queue.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_submission.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/external_editor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/identity_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/idle_status.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/inline_bar.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/line_editor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/menu_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/pause_controller.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/prompt_prefix_style.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/run_ui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/run_ui_wiring.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_stats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_toggle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/status_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/subagent_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/theme_accent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/tool_output.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/transcript_guard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_descriptions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/observability.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/platform_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/oauth_pasteback.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/trust.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/trust_notice.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/private_inference.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/provider_credentials.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/provider_identity.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/secret_store.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/secret_store_backends.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_format_migration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_migration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_surrogate_unpickler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/shared_credentials.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/skill_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/splash.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/steer_metadata.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/_browser_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/_lazy.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/approval_feedback.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/constants.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/registration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/renderers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/theme.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/tool_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_permission_state.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/fs_access.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/image_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/io_backends.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/model_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ripgrep.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/shell_backgrounding.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/skills_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_context.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_usage_metrics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/universal_constructor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/win_shell_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/undo_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/universal_constructor_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/version_checker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/zai_model.py +0 -0
|
@@ -726,7 +726,7 @@ def build_pydantic_agent(
|
|
|
726
726
|
history_compaction,
|
|
727
727
|
ProcessHistory(steer_processor),
|
|
728
728
|
build_response_clamp(),
|
|
729
|
-
build_model_message_transform(logical_agent_name),
|
|
729
|
+
build_model_message_transform(logical_agent_name, agent),
|
|
730
730
|
# Sub-agent recursion guards on the wrap_tool_execute seam
|
|
731
731
|
# (denies invoke_agent calls past the depth caps before the
|
|
732
732
|
# tool body runs). Sole wrap_tool_execute implementer, so
|
|
@@ -64,6 +64,7 @@ from code_puppy.config import (
|
|
|
64
64
|
get_protected_token_count,
|
|
65
65
|
get_summarization_model_name,
|
|
66
66
|
)
|
|
67
|
+
from code_puppy.context_accounting import context_tokens, model_names
|
|
67
68
|
from code_puppy.messaging import emit_warning
|
|
68
69
|
|
|
69
70
|
# Kept importable at module level for the context-indicator plugin, which
|
|
@@ -202,7 +203,12 @@ class CodePuppyCompactionStore:
|
|
|
202
203
|
return self.agent._get_model_context_length()
|
|
203
204
|
|
|
204
205
|
def context_overhead(self) -> int:
|
|
205
|
-
|
|
206
|
+
overhead = self.agent._estimate_context_overhead()
|
|
207
|
+
# Cached so the outbound-request stamp (``_model_message_transform``)
|
|
208
|
+
# can report the exact overhead this measurement used, instead of
|
|
209
|
+
# re-deriving a possibly-different value moments later.
|
|
210
|
+
self.agent._context_overhead = overhead
|
|
211
|
+
return overhead
|
|
206
212
|
|
|
207
213
|
def model_name(self) -> Optional[str]:
|
|
208
214
|
if self.agent is None:
|
|
@@ -225,6 +231,17 @@ def _take_forced_compaction_request() -> bool:
|
|
|
225
231
|
return get_pause_controller().take_compaction_request()
|
|
226
232
|
|
|
227
233
|
|
|
234
|
+
def _total_tokens(messages, model, overhead) -> int:
|
|
235
|
+
"""Anchor the whole-history total to the live request model's identity.
|
|
236
|
+
|
|
237
|
+
Wired as the capability's ``total_estimator`` -- see
|
|
238
|
+
``code_puppy.context_accounting.context_tokens`` for the anchor/fallback
|
|
239
|
+
logic. Kept as a module-level function (not a lambda) so it shows up by
|
|
240
|
+
name in tracebacks and can be monkeypatched directly in tests.
|
|
241
|
+
"""
|
|
242
|
+
return context_tokens(messages, model_names(model), overhead)
|
|
243
|
+
|
|
244
|
+
|
|
228
245
|
def build_history_compaction(agent: Any) -> PureHistoryCompaction:
|
|
229
246
|
"""Build the pure ``HistoryCompaction`` capability wired to Code Puppy.
|
|
230
247
|
|
|
@@ -243,6 +260,7 @@ def build_history_compaction(agent: Any) -> PureHistoryCompaction:
|
|
|
243
260
|
strategy_name=lambda: get_compaction_strategy(),
|
|
244
261
|
compaction_threshold=lambda: get_compaction_threshold(),
|
|
245
262
|
token_estimator=estimate_tokens_for_message,
|
|
263
|
+
total_estimator=_total_tokens,
|
|
246
264
|
message_hasher=hash_message,
|
|
247
265
|
history_sanitizer=sanitize_tool_call_ids,
|
|
248
266
|
force_poll=_take_forced_compaction_request,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Plugin transforms for final outbound model messages."""
|
|
2
|
+
|
|
3
|
+
from copy import copy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic_ai import RunContext
|
|
7
|
+
from pydantic_ai.capabilities import Hooks, WrapModelRequestHandler
|
|
8
|
+
from pydantic_ai.messages import ModelResponse
|
|
9
|
+
from pydantic_ai.models import ModelRequestContext
|
|
10
|
+
from pydantic_ai.models.wrapper import WrapperModel
|
|
11
|
+
|
|
12
|
+
from code_puppy.agents._foreign_thinking import strip_foreign_thinking
|
|
13
|
+
from code_puppy.callbacks import on_transform_model_messages
|
|
14
|
+
|
|
15
|
+
# Per-request prefix fingerprint, handed from the ``wrap_model_request`` leg
|
|
16
|
+
# to the ``after_model_request`` leg via this attribute on the (mutable,
|
|
17
|
+
# per-request) ``ModelRequestContext``. ``None`` means "this request's
|
|
18
|
+
# outbound messages diverged from the durable history it was built from, so
|
|
19
|
+
# don't stamp an anchor" -- see ``build_model_message_transform``.
|
|
20
|
+
_PREFIX_ATTR = "_context_accounting_prefix"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _ContinuationObserver(WrapperModel):
|
|
24
|
+
"""Observe a suspended->continued chain *before* pydantic-ai merges it.
|
|
25
|
+
|
|
26
|
+
``continuation_delay`` runs on intermediate suspended responses before
|
|
27
|
+
their usage can merge. This request-local wrapper preserves the real
|
|
28
|
+
model's delay behavior while marking the final response ineligible for
|
|
29
|
+
a receipt. The framework's durable-execution capabilities use the same
|
|
30
|
+
request-model swap seam. Provider details and billing limitations live
|
|
31
|
+
in ``docs/API_CONTEXT_ACCOUNTING.md``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, wrapped):
|
|
35
|
+
super().__init__(wrapped)
|
|
36
|
+
self.continued = False
|
|
37
|
+
|
|
38
|
+
def continuation_delay(self, response: ModelResponse) -> float | None:
|
|
39
|
+
self.continued = True
|
|
40
|
+
return super().continuation_delay(response)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_model_message_transform(agent_name: str | None, owner: Any = None) -> Hooks:
|
|
44
|
+
"""Build the request-only plugin transform for an agent.
|
|
45
|
+
|
|
46
|
+
``owner`` is the Code Puppy agent (or agent-shaped object) whose
|
|
47
|
+
``_context_overhead`` compaction just computed for this same request --
|
|
48
|
+
see ``CodePuppyCompactionStore.context_overhead``. Passing it lets the
|
|
49
|
+
stamped receipt record the *exact* overhead the compaction trigger used,
|
|
50
|
+
so the next request's ``context_tokens`` call reconstructs the same
|
|
51
|
+
total instead of silently drifting by whatever overhead changed between
|
|
52
|
+
the two reads. ``None`` (sub-agents/tests that don't wire one) just
|
|
53
|
+
stamps ``overhead=0``, which only costs an extra estimate on that one
|
|
54
|
+
anchor -- never a wrong total, because ``context_tokens`` always folds
|
|
55
|
+
the caller's *current* overhead back in.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
async def transform(
|
|
59
|
+
_ctx: RunContext[Any],
|
|
60
|
+
*,
|
|
61
|
+
request_context: ModelRequestContext,
|
|
62
|
+
handler: WrapModelRequestHandler,
|
|
63
|
+
) -> ModelResponse:
|
|
64
|
+
from code_puppy.context_accounting import fingerprint, invalidate_anchors
|
|
65
|
+
|
|
66
|
+
durable_messages = request_context.messages
|
|
67
|
+
# Snapshot *before* anything can touch the messages. strip_foreign_thinking
|
|
68
|
+
# is a no-op pass-through for non-Anthropic models (same list AND same
|
|
69
|
+
# message objects by reference, not a copy) -- so a plugin transform
|
|
70
|
+
# that mutates in place would otherwise mutate ``durable_messages`` too,
|
|
71
|
+
# and a fingerprint taken afterwards would never see the divergence.
|
|
72
|
+
original_fingerprint = fingerprint(durable_messages)
|
|
73
|
+
|
|
74
|
+
transformed_context = copy(request_context)
|
|
75
|
+
transformed_context.messages = strip_foreign_thinking(
|
|
76
|
+
list(durable_messages), request_context.model
|
|
77
|
+
)
|
|
78
|
+
await on_transform_model_messages(agent_name, transformed_context.messages)
|
|
79
|
+
|
|
80
|
+
# The anchor must describe what the *durable history* looked like,
|
|
81
|
+
# because that's what next turn's ``context_tokens`` re-fingerprints
|
|
82
|
+
# against. If anything between the snapshot above and the handler
|
|
83
|
+
# call -- the built-in foreign-thinking strip, or a plugin's rewrite,
|
|
84
|
+
# in place or not -- changed what's actually sent, the response's
|
|
85
|
+
# usage no longer describes ``durable_messages`` and must not anchor it.
|
|
86
|
+
prefix = original_fingerprint
|
|
87
|
+
if fingerprint(transformed_context.messages) != prefix:
|
|
88
|
+
prefix = None
|
|
89
|
+
|
|
90
|
+
# Wrap *after* the fingerprint check above (which needs the real
|
|
91
|
+
# model for foreign-thinking detection) and *before* the handler
|
|
92
|
+
# call, so the continuation loop inside `handler` actually drives
|
|
93
|
+
# this wrapper's `continuation_delay` -- see `_ContinuationObserver`.
|
|
94
|
+
observer = _ContinuationObserver(transformed_context.model)
|
|
95
|
+
transformed_context.model = observer
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = await handler(transformed_context)
|
|
99
|
+
except BaseException:
|
|
100
|
+
invalidate_anchors(durable_messages)
|
|
101
|
+
raise
|
|
102
|
+
|
|
103
|
+
if observer.continued:
|
|
104
|
+
# Usage on `response` is a cumulative sum across segments, not
|
|
105
|
+
# one measured prompt -- decline to anchor it. Billing/usage
|
|
106
|
+
# itself is untouched; only our own receipt is suppressed.
|
|
107
|
+
prefix = None
|
|
108
|
+
setattr(request_context, _PREFIX_ATTR, prefix)
|
|
109
|
+
return response
|
|
110
|
+
|
|
111
|
+
async def stamp(
|
|
112
|
+
_ctx: RunContext[Any],
|
|
113
|
+
*,
|
|
114
|
+
request_context: ModelRequestContext,
|
|
115
|
+
response: ModelResponse,
|
|
116
|
+
) -> ModelResponse:
|
|
117
|
+
from code_puppy.context_accounting import model_names, record_anchor
|
|
118
|
+
|
|
119
|
+
prefix = getattr(request_context, _PREFIX_ATTR, None)
|
|
120
|
+
if prefix is not None:
|
|
121
|
+
record_anchor(
|
|
122
|
+
request_context.messages,
|
|
123
|
+
response,
|
|
124
|
+
context_overhead=getattr(owner, "_context_overhead", 0),
|
|
125
|
+
prefix=prefix,
|
|
126
|
+
request_model=model_names(request_context.model),
|
|
127
|
+
)
|
|
128
|
+
return response
|
|
129
|
+
|
|
130
|
+
return Hooks(model_request=transform, after_model_request=stamp)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Replaces the monolithic ``BaseAgent.run_with_mcp`` coroutine. Everything here
|
|
4
4
|
is a free function; the agent is passed in explicitly. Integration points
|
|
5
|
-
|
|
5
|
+
include:
|
|
6
6
|
|
|
7
|
-
- Plugin-supplied async context managers wrap
|
|
7
|
+
- Plugin-supplied async context managers wrap each physical model attempt (see
|
|
8
8
|
``on_agent_run_context``); used e.g. by the DBOS plugin to set a workflow
|
|
9
9
|
ID and swap MCP toolsets in/out.
|
|
10
10
|
- SIGINT fallback-handler choice driven by ``sigint_fallback_cancels()``
|
|
@@ -25,7 +25,6 @@ import ssl
|
|
|
25
25
|
import sys
|
|
26
26
|
import threading
|
|
27
27
|
import uuid
|
|
28
|
-
from contextlib import AsyncExitStack
|
|
29
28
|
from typing import Any, Callable, Iterator, List, Optional, Sequence, Type, Union
|
|
30
29
|
|
|
31
30
|
import httpcore
|
|
@@ -82,8 +81,8 @@ from code_puppy.agents._run_signals import (
|
|
|
82
81
|
sigint_should_cancel,
|
|
83
82
|
)
|
|
84
83
|
from code_puppy.agents.event_stream_handler import event_stream_handler
|
|
84
|
+
from code_puppy.agents.run_invocation import ModelCall, run_with_exception_retry
|
|
85
85
|
from code_puppy.callbacks import (
|
|
86
|
-
on_agent_exception,
|
|
87
86
|
on_agent_run_cancel,
|
|
88
87
|
on_agent_run_context,
|
|
89
88
|
on_agent_run_end,
|
|
@@ -784,10 +783,23 @@ async def _run_with_mcp_impl(
|
|
|
784
783
|
|
|
785
784
|
if agent._code_generation_agent is None:
|
|
786
785
|
build_pydantic_agent(agent)
|
|
787
|
-
pydantic_agent = agent._code_generation_agent
|
|
788
786
|
|
|
789
787
|
if output_type is not None:
|
|
790
|
-
|
|
788
|
+
build_pydantic_agent(agent, output_type=output_type)
|
|
789
|
+
|
|
790
|
+
# MCP lifecycle tasks can invalidate the cache during this run. Keep its
|
|
791
|
+
# built client as a fallback, while still resolving live replacements lazily.
|
|
792
|
+
built_client = agent._code_generation_agent
|
|
793
|
+
invocation = ModelCall(
|
|
794
|
+
lambda: (
|
|
795
|
+
current
|
|
796
|
+
if (current := agent._code_generation_agent) is not None
|
|
797
|
+
else built_client
|
|
798
|
+
),
|
|
799
|
+
lambda current: on_agent_run_context(
|
|
800
|
+
agent, current, group_id, getattr(agent, "_mcp_servers", None) or []
|
|
801
|
+
),
|
|
802
|
+
)
|
|
791
803
|
|
|
792
804
|
prompt = _should_prepend_system_prompt(agent, prompt)
|
|
793
805
|
prompt_payload = _build_prompt_payload(prompt, attachments, link_attachments)
|
|
@@ -830,42 +842,19 @@ async def _run_with_mcp_impl(
|
|
|
830
842
|
usage_limits=usage_limits, event_stream_handler=stream_handler, **kwargs
|
|
831
843
|
)
|
|
832
844
|
_call = _main_retry(
|
|
833
|
-
resumable_call(agent,
|
|
845
|
+
resumable_call(agent, invocation, prompt_to_use, **run_options)
|
|
834
846
|
)
|
|
835
847
|
|
|
836
|
-
|
|
837
|
-
"""Run ``_call`` and let plugins request one exception retry."""
|
|
838
|
-
try:
|
|
839
|
-
return await _call()
|
|
840
|
-
except Exception as exc:
|
|
841
|
-
hook_results = await on_agent_exception(
|
|
842
|
-
exc,
|
|
843
|
-
agent=agent,
|
|
844
|
-
agent_name=agent.name,
|
|
845
|
-
model_name=agent.get_model_name(),
|
|
846
|
-
)
|
|
847
|
-
retry_req = next(
|
|
848
|
-
(r for r in hook_results if isinstance(r, dict) and r.get("retry")),
|
|
849
|
-
None,
|
|
850
|
-
)
|
|
851
|
-
if not retry_req:
|
|
852
|
-
raise
|
|
853
|
-
|
|
854
|
-
retry_delay = retry_req.get("delay", 0.0)
|
|
855
|
-
if retry_delay:
|
|
856
|
-
await asyncio.sleep(retry_delay)
|
|
857
|
-
return await _call()
|
|
858
|
-
|
|
859
|
-
result = await _call_with_exception_recovery()
|
|
848
|
+
result = await run_with_exception_retry(_call, agent=agent)
|
|
860
849
|
|
|
861
850
|
# ``now``-mode steers are injected by make_steer_history_processor
|
|
862
851
|
# (before every model call); ``queue``-mode ones drain between runs
|
|
863
852
|
# below — additive, won't interrupt in-progress work.
|
|
864
853
|
async def _follow_up_run(follow_up_prompt: Any) -> Any:
|
|
865
854
|
call = _main_retry(
|
|
866
|
-
resumable_call(agent,
|
|
855
|
+
resumable_call(agent, invocation, follow_up_prompt, **run_options)
|
|
867
856
|
)
|
|
868
|
-
return await call
|
|
857
|
+
return await run_with_exception_retry(call, agent=agent)
|
|
869
858
|
|
|
870
859
|
hook_retries_used = 0
|
|
871
860
|
queued_steers_used = 0
|
|
@@ -935,14 +924,7 @@ async def _run_with_mcp_impl(
|
|
|
935
924
|
agent._message_history
|
|
936
925
|
)
|
|
937
926
|
|
|
938
|
-
|
|
939
|
-
run_ctxs = on_agent_run_context(
|
|
940
|
-
agent, pydantic_agent, group_id, mcp_servers
|
|
941
|
-
)
|
|
942
|
-
async with AsyncExitStack() as stack:
|
|
943
|
-
for cm in run_ctxs:
|
|
944
|
-
await stack.enter_async_context(cm)
|
|
945
|
-
return await _do_run(prompt_payload)
|
|
927
|
+
return await _do_run(prompt_payload)
|
|
946
928
|
except* UsageLimitExceeded as ule:
|
|
947
929
|
emit_info(f"Usage limit exceeded: {ule}", group_id=group_id)
|
|
948
930
|
emit_info(
|
|
@@ -32,7 +32,7 @@ class RetryCheckpoint:
|
|
|
32
32
|
return self.completed
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def resumable_call(agent,
|
|
35
|
+
def resumable_call(agent, client, prompt, **kwargs):
|
|
36
36
|
"""One logical prompt: resend only if it never reached persistent history."""
|
|
37
37
|
checkpointed = False
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ def resumable_call(agent, pydantic_agent, prompt, **kwargs):
|
|
|
46
46
|
if isinstance(part, UserPromptPart)
|
|
47
47
|
}
|
|
48
48
|
try:
|
|
49
|
-
return await
|
|
49
|
+
return await client.run(
|
|
50
50
|
None if checkpointed else prompt,
|
|
51
51
|
message_history=agent._message_history,
|
|
52
52
|
**kwargs,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Separate logical-prompt retries from physical model-call resources."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
5
|
+
from contextlib import AsyncExitStack
|
|
6
|
+
from typing import Any, AsyncContextManager
|
|
7
|
+
|
|
8
|
+
from pydantic_ai.exceptions import RunCancelled
|
|
9
|
+
|
|
10
|
+
from code_puppy.callbacks import on_agent_exception
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ModelCall:
|
|
14
|
+
"""Run one physical attempt against a freshly resolved client.
|
|
15
|
+
|
|
16
|
+
``resolve`` returns the current client, which may have been replaced by
|
|
17
|
+
recovery. ``contexts(client)`` creates fresh async context managers for
|
|
18
|
+
that same client. Checkpoint state belongs outside this adapter: pass it
|
|
19
|
+
to ``resumable_call`` once for each logical prompt, before applying retry.
|
|
20
|
+
Neither the client nor its context managers are cached between attempts.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
resolve: Callable[[], Any],
|
|
26
|
+
contexts: Callable[[Any], Sequence[AsyncContextManager[Any]]],
|
|
27
|
+
) -> None:
|
|
28
|
+
self.resolve = resolve
|
|
29
|
+
self.contexts = contexts
|
|
30
|
+
|
|
31
|
+
async def run(self, prompt: Any, **kwargs: Any) -> Any:
|
|
32
|
+
current = self.resolve()
|
|
33
|
+
async with AsyncExitStack() as stack:
|
|
34
|
+
for context in self.contexts(current):
|
|
35
|
+
await stack.enter_async_context(context)
|
|
36
|
+
return await current.run(prompt, **kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def run_with_exception_retry(
|
|
40
|
+
call: Callable[[], Awaitable[Any]], *, agent: Any
|
|
41
|
+
) -> Any:
|
|
42
|
+
"""Let exception callbacks request one retry of the same logical call.
|
|
43
|
+
|
|
44
|
+
The callback may replace the model client; ``ModelCall`` resolves that
|
|
45
|
+
replacement on the next attempt. Retry the existing callable, rather than
|
|
46
|
+
constructing another checkpoint. Exceptions from the retry propagate and
|
|
47
|
+
cancellation never enters exception recovery.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
return await call()
|
|
51
|
+
except RunCancelled:
|
|
52
|
+
raise
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
if isinstance(exc, ExceptionGroup) and exc.subgroup(RunCancelled) is not None:
|
|
55
|
+
raise # Preserve the whole original group, including sibling errors.
|
|
56
|
+
hook_results = await on_agent_exception(
|
|
57
|
+
exc,
|
|
58
|
+
agent=agent,
|
|
59
|
+
agent_name=agent.name,
|
|
60
|
+
model_name=agent.get_model_name(),
|
|
61
|
+
)
|
|
62
|
+
retry_req = next(
|
|
63
|
+
(r for r in hook_results if isinstance(r, dict) and r.get("retry")),
|
|
64
|
+
None,
|
|
65
|
+
)
|
|
66
|
+
if not retry_req:
|
|
67
|
+
raise
|
|
68
|
+
|
|
69
|
+
retry_delay = retry_req.get("delay", 0.0)
|
|
70
|
+
if retry_delay:
|
|
71
|
+
await asyncio.sleep(retry_delay)
|
|
72
|
+
return await call()
|
|
@@ -1806,9 +1806,13 @@ def on_wrap_pydantic_agent(
|
|
|
1806
1806
|
def on_agent_run_context(agent, pydantic_agent, group_id, mcp_servers) -> List[Any]:
|
|
1807
1807
|
"""Collect async context managers that should wrap the ``pydantic_agent.run()`` call.
|
|
1808
1808
|
|
|
1809
|
-
Each callback returns
|
|
1810
|
-
``None``.
|
|
1811
|
-
|
|
1809
|
+
Each callback returns a fresh async CM (with ``__aenter__``/``__aexit__``)
|
|
1810
|
+
or ``None``. Main-agent contexts are created and exited per physical model
|
|
1811
|
+
attempt, including retries and follow-ups, around the currently resolved
|
|
1812
|
+
client. They are not a scope for the whole prompt or retry loop. The caller
|
|
1813
|
+
composes all non-``None`` results via ``contextlib.AsyncExitStack``.
|
|
1814
|
+
In ``tools/subagent_invocation.py``, this same hook retains whole-run
|
|
1815
|
+
scope around the subagent's run and retry loop, not per-attempt scope.
|
|
1812
1816
|
|
|
1813
1817
|
Returns a list of async context managers (may be empty).
|
|
1814
1818
|
"""
|
|
@@ -24,7 +24,7 @@ from typing import Any, Callable, List, Optional, Protocol, Set, Tuple
|
|
|
24
24
|
from pydantic_ai import CapabilityEvent
|
|
25
25
|
from pydantic_ai.capabilities import AbstractCapability
|
|
26
26
|
from pydantic_ai.messages import ModelMessage, ModelResponse, ThinkingPart
|
|
27
|
-
from pydantic_ai.models import ModelRequestContext
|
|
27
|
+
from pydantic_ai.models import Model, ModelRequestContext
|
|
28
28
|
from pydantic_ai.tools import RunContext
|
|
29
29
|
from pydantic_ai.usage import RunUsage
|
|
30
30
|
|
|
@@ -255,6 +255,17 @@ class HistoryCompaction(AbstractCapability[Any]):
|
|
|
255
255
|
message_hasher: Callable[[ModelMessage], str]
|
|
256
256
|
"""Stable content hash used for merge dedup + dropped tracking."""
|
|
257
257
|
|
|
258
|
+
total_estimator: Optional[Callable[[List[ModelMessage], Model, int], int]] = None
|
|
259
|
+
"""Optional whole-history total, given the resolved request model.
|
|
260
|
+
|
|
261
|
+
When set, this replaces ``sum(token_estimator(...)) + context_overhead``
|
|
262
|
+
for both the pre- and post-compaction measurement. The seam exists so an
|
|
263
|
+
application can anchor the total to a provider-reported usage figure
|
|
264
|
+
instead of a per-message heuristic sum, without this module knowing
|
|
265
|
+
anything about how that anchor is produced or stored. ``None`` keeps the
|
|
266
|
+
historical sum-based behavior.
|
|
267
|
+
"""
|
|
268
|
+
|
|
258
269
|
history_sanitizer: Optional[Callable[[List[ModelMessage]], List[ModelMessage]]] = (
|
|
259
270
|
None
|
|
260
271
|
)
|
|
@@ -350,6 +361,24 @@ class HistoryCompaction(AbstractCapability[Any]):
|
|
|
350
361
|
)
|
|
351
362
|
return cleaned
|
|
352
363
|
|
|
364
|
+
def _total(
|
|
365
|
+
self,
|
|
366
|
+
ctx: RunContext[Any],
|
|
367
|
+
messages: List[ModelMessage],
|
|
368
|
+
model_name: Optional[str],
|
|
369
|
+
context_overhead: int,
|
|
370
|
+
) -> int:
|
|
371
|
+
"""Resolve the whole-history total via ``total_estimator`` if wired.
|
|
372
|
+
|
|
373
|
+
Falls back to the historical ``sum(token_estimator) + overhead`` when
|
|
374
|
+
no ``total_estimator`` was injected, so callers that never wire one
|
|
375
|
+
see unchanged behavior.
|
|
376
|
+
"""
|
|
377
|
+
if self.total_estimator is not None:
|
|
378
|
+
return self.total_estimator(messages, ctx.model, context_overhead)
|
|
379
|
+
message_tokens = sum(self.token_estimator(m, model_name) for m in messages)
|
|
380
|
+
return message_tokens + context_overhead
|
|
381
|
+
|
|
353
382
|
async def measure_and_compact(
|
|
354
383
|
self,
|
|
355
384
|
ctx: RunContext[Any],
|
|
@@ -366,8 +395,7 @@ class HistoryCompaction(AbstractCapability[Any]):
|
|
|
366
395
|
always survive a failed compaction.
|
|
367
396
|
"""
|
|
368
397
|
model_name = self.store.model_name()
|
|
369
|
-
|
|
370
|
-
total_tokens = message_tokens + context_overhead
|
|
398
|
+
total_tokens = self._total(ctx, messages, model_name, context_overhead)
|
|
371
399
|
proportion_used = total_tokens / model_max if model_max else 0.0
|
|
372
400
|
|
|
373
401
|
await _safe_emit(
|
|
@@ -425,8 +453,12 @@ class HistoryCompaction(AbstractCapability[Any]):
|
|
|
425
453
|
dropped = [m for m in messages if self.message_hasher(m) not in result_hashes]
|
|
426
454
|
|
|
427
455
|
# Parity with the historical implementation: the post-compaction
|
|
428
|
-
# measurement reports message tokens only (no overhead term).
|
|
429
|
-
|
|
456
|
+
# measurement reports message tokens only (no overhead term). A
|
|
457
|
+
# ``total_estimator`` naturally falls back to the same "message
|
|
458
|
+
# tokens only" shape here too -- compaction just removed the
|
|
459
|
+
# earlier prefix an anchor would need, so the anchor fingerprint
|
|
460
|
+
# can never match and the estimator sums the (now-shorter) result.
|
|
461
|
+
final_token_count = self._total(ctx, result, model_name, 0)
|
|
430
462
|
await _safe_emit(
|
|
431
463
|
ctx,
|
|
432
464
|
ContextUsageMeasuredEvent(
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Context totals anchored to completed API requests, with a heuristic delta.
|
|
2
|
+
|
|
3
|
+
A receipt identifies the unchanged measured prefix, response and request
|
|
4
|
+
model; new content and changed overhead remain estimates. Invalid receipts
|
|
5
|
+
fall back rather than reuse stale usage. Continuation-merged responses do
|
|
6
|
+
not anchor because cumulative billing is not a single measured prompt.
|
|
7
|
+
|
|
8
|
+
Design, provider limitations and rollout semantics are documented in
|
|
9
|
+
``docs/API_CONTEXT_ACCOUNTING.md``. The request hook owns continuation
|
|
10
|
+
observation; this module never alters provider usage or billing.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
from pydantic_ai.messages import ModelMessagesTypeAdapter, ModelResponse, TextPart
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def estimate_tokens_for_message(message, model_name):
|
|
20
|
+
"""Import lazily so the shared entry point has no agent-package cycle."""
|
|
21
|
+
from code_puppy.agents._history import estimate_tokens_for_message as estimate
|
|
22
|
+
|
|
23
|
+
return estimate(message, model_name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_ANCHOR = "context_anchor"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def fingerprint(messages) -> str:
|
|
30
|
+
"""Include arguments, signatures and binary payloads, excluding receipts.
|
|
31
|
+
|
|
32
|
+
Receipts live under ``metadata`` so stamping one does not change the
|
|
33
|
+
fingerprint of the message it rides on -- otherwise a receipt would
|
|
34
|
+
invalidate itself the instant it was written.
|
|
35
|
+
"""
|
|
36
|
+
clean = ModelMessagesTypeAdapter.dump_python(messages, mode="json")
|
|
37
|
+
payload = [
|
|
38
|
+
{
|
|
39
|
+
key: value
|
|
40
|
+
for key, value in message.items()
|
|
41
|
+
if key in {"parts", "instructions", "kind"}
|
|
42
|
+
}
|
|
43
|
+
for message in clean
|
|
44
|
+
]
|
|
45
|
+
return hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def record_anchor(
|
|
49
|
+
messages, response, *, context_overhead=0, prefix=None, request_model=None
|
|
50
|
+
):
|
|
51
|
+
"""Stamp only a successfully completed response; usage is never fabricated.
|
|
52
|
+
|
|
53
|
+
Trusts ``prefix`` and ``response.usage`` as given -- it has no way to
|
|
54
|
+
tell a continuation-merged (cumulative) usage from an ordinary one on
|
|
55
|
+
its own. The caller must not call this helper when a continuation was observed
|
|
56
|
+
(see ``_ContinuationObserver`` in ``agents/_model_message_transform.py``).
|
|
57
|
+
Here, ``prefix=None`` computes a fingerprint; it does not suppress stamping.
|
|
58
|
+
"""
|
|
59
|
+
if (
|
|
60
|
+
response.usage.input_tokens <= 0
|
|
61
|
+
or not response.model_name
|
|
62
|
+
or response.state != "complete"
|
|
63
|
+
):
|
|
64
|
+
return
|
|
65
|
+
response.metadata = dict(response.metadata or {})
|
|
66
|
+
response.metadata[_ANCHOR] = {
|
|
67
|
+
"prefix": prefix if prefix is not None else fingerprint(messages),
|
|
68
|
+
"response": fingerprint([response]),
|
|
69
|
+
"overhead": context_overhead,
|
|
70
|
+
"request_models": sorted(
|
|
71
|
+
request_model
|
|
72
|
+
if isinstance(request_model, frozenset)
|
|
73
|
+
else {request_model if request_model is not None else response.model_name}
|
|
74
|
+
),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def invalidate_anchors(messages):
|
|
79
|
+
"""Conservatively decline anchors after an unsuccessful request.
|
|
80
|
+
|
|
81
|
+
This is a deliberate failed-request policy, not proof the old measurement
|
|
82
|
+
became invalid. An unchanged prefix could retain its previous receipt,
|
|
83
|
+
and a retained partial response already forces fallback. We nevertheless
|
|
84
|
+
choose full estimation until a fresh completion; changing that contract
|
|
85
|
+
requires reconsidering the failure/cancellation regressions together.
|
|
86
|
+
"""
|
|
87
|
+
for message in messages:
|
|
88
|
+
if isinstance(message, ModelResponse) and message.metadata:
|
|
89
|
+
message.metadata.pop(_ANCHOR, None)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def context_tokens(messages, model_name, context_overhead=0) -> int:
|
|
93
|
+
"""One total for compaction, the status bar, and context metadata.
|
|
94
|
+
|
|
95
|
+
API input includes system/tool/cache tokens. Plain text output usage is
|
|
96
|
+
an incremental approximation; it may include hidden reasoning when no
|
|
97
|
+
ThinkingPart is exposed, conservatively overestimating replay. Visible
|
|
98
|
+
reasoning/tool replay is estimated instead.
|
|
99
|
+
Only the latest response may anchor: partial/zero usage must not cause a
|
|
100
|
+
silent rewind to an older observation.
|
|
101
|
+
"""
|
|
102
|
+
names = model_name if isinstance(model_name, frozenset) else {model_name}
|
|
103
|
+
estimate_model = next(iter(names)) if len(names) == 1 else None
|
|
104
|
+
for index in range(len(messages) - 1, -1, -1):
|
|
105
|
+
response = messages[index]
|
|
106
|
+
if not isinstance(response, ModelResponse):
|
|
107
|
+
continue
|
|
108
|
+
receipt = (response.metadata or {}).get(_ANCHOR)
|
|
109
|
+
if (
|
|
110
|
+
isinstance(receipt, dict)
|
|
111
|
+
and response.usage.input_tokens > 0
|
|
112
|
+
and response.state == "complete"
|
|
113
|
+
and receipt.get("request_models") == sorted(names)
|
|
114
|
+
and receipt.get("prefix") == fingerprint(messages[:index])
|
|
115
|
+
and receipt.get("response") == fingerprint([response])
|
|
116
|
+
and isinstance(receipt.get("overhead"), (int, float))
|
|
117
|
+
):
|
|
118
|
+
replay = (
|
|
119
|
+
response.usage.output_tokens
|
|
120
|
+
if response.parts
|
|
121
|
+
and all(isinstance(p, TextPart) for p in response.parts)
|
|
122
|
+
and response.usage.output_tokens > 0
|
|
123
|
+
else estimate_tokens_for_message(response, response.model_name)
|
|
124
|
+
)
|
|
125
|
+
return (
|
|
126
|
+
response.usage.input_tokens
|
|
127
|
+
+ replay
|
|
128
|
+
+ context_overhead
|
|
129
|
+
- receipt["overhead"]
|
|
130
|
+
+ sum(
|
|
131
|
+
estimate_tokens_for_message(m, estimate_model)
|
|
132
|
+
for m in messages[index + 1 :]
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
break
|
|
136
|
+
return context_overhead + sum(
|
|
137
|
+
estimate_tokens_for_message(m, estimate_model) for m in messages
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def active_model_name(agent):
|
|
142
|
+
"""Use the request model identity, not an arbitrary config alias."""
|
|
143
|
+
model = getattr(agent, "cur_model", None)
|
|
144
|
+
if model is None:
|
|
145
|
+
model = getattr(getattr(agent, "pydantic_agent", None), "model", None)
|
|
146
|
+
return model_names(model) if model is not None else agent.get_model_name()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def model_names(model):
|
|
150
|
+
"""Identify a request model together with its routing candidates."""
|
|
151
|
+
candidates = getattr(model, "models", ())
|
|
152
|
+
if candidates:
|
|
153
|
+
return frozenset({model.model_name, *(m.model_name for m in candidates)})
|
|
154
|
+
return model.model_name
|
|
@@ -85,8 +85,11 @@ def _refresh_context_status(agent) -> None:
|
|
|
85
85
|
try:
|
|
86
86
|
capacity = agent._get_model_context_length()
|
|
87
87
|
history = agent.get_message_history() or []
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
from code_puppy.context_accounting import active_model_name, context_tokens
|
|
89
|
+
|
|
90
|
+
total_tokens = context_tokens(
|
|
91
|
+
history, active_model_name(agent), agent._estimate_context_overhead()
|
|
92
|
+
)
|
|
90
93
|
proportion = total_tokens / capacity if capacity else 0.0
|
|
91
94
|
update_spinner_context(format_context_info(total_tokens, capacity, proportion))
|
|
92
95
|
except Exception:
|