code-puppy 0.0.910__tar.gz → 0.0.911__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.911}/PKG-INFO +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_builder.py +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_compaction.py +19 -1
- code_puppy-0.0.911/code_puppy/agents/_model_message_transform.py +130 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/capabilities/compaction.py +37 -5
- code_puppy-0.0.911/code_puppy/context_accounting.py +154 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/model_switching.py +5 -2
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/__init__.py +9 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/token_usage.py +67 -48
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/subagent_invocation.py +1 -1
- {code_puppy-0.0.910 → code_puppy-0.0.911}/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.911}/.gitignore +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/LICENSE +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/README.md +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/_plugin_i18n_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/_pydantic_tool_helpers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/_pydantic_video_patch.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agent_completion_inbox.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agent_execution_context.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_code_mode.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_code_mode_guidance.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_diagnostics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_foreign_thinking.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_history.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_key_listeners.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_non_streaming_render.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_output_limits.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_run_signals.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_runtime.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_steer_processor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_subagent_recursion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_windows_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/_wire_tool_names.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_helios.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_model_judge.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/agent_web_retriever.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/event_stream_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/retry_checkpoint.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/retry_profiles.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/run_stats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/smooth_stream.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/stream_status.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/subagent_stream_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/agents/thinking_output.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/asyncio_cleanup.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/atomic_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/atomic_json.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/ca_bundle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/capabilities/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/capabilities/eager_timing.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/claude_oauth_transport.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/claude_thinking_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/add_model_details.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/add_model_menus.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/add_model_vllm.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/agent_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/autosave_search.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/completers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/completion_cache.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/config_apply.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/file_index.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/help_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/help_overlay.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/image_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/trust_command.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp_binding_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/menu_session.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/model_settings_defs.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/pagination.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/refresh_models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/session_browser.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/session_browser_data.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu_schema.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu_settings.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu_shims.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/set_menu_values.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/shell_passthrough.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/skills_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/tui_style.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/uc_menu.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/config_file.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/diagnostics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/events/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/events/bridge.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/file_completion_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/file_completion_tokens.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/gemini_common.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/gemini_model.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/README.md +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/aliases.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/engine.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/executor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/matcher.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/hook_engine/validator.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/http_retry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/httpx2_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/audit.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/formats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/interpolation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/locale.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/locales/en-US.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/locales/es.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/locales/fr-CA.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/plugin_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/plurals.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/pseudo.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/i18n/translate.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/kennel_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/list_filtering.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/main.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/agent_bindings.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/http_auth.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/manager_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/project_config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_/toolset_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_prompts/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/mcp_prompts/hook_creator.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/bar_painters.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/bar_rendering.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/bottom_bar.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/chords.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_actions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_completion.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_history.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_keys.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_paste.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_queue.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/editor_submission.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/external_editor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/identity_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/idle_status.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/inline_bar.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/line_editor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/menu_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/pause_controller.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/prompt_prefix_style.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/run_ui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/run_ui_wiring.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/speculation_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/speculation_stats.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/speculation_toggle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/status_line.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/subagent_console.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/theme_accent.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/tool_output.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/messaging/transcript_guard.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/model_descriptions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/models.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/observability.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/platform_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/config.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/oauth_pasteback.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/trust.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/plugins/trust_notice.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/private_inference.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/provider_credentials.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/provider_identity.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/secret_store.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/secret_store_backends.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/session_format_migration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/session_lifecycle.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/session_migration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/session_surrogate_unpickler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/shared_credentials.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/skill_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/splash.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/steer_metadata.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/_browser_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/_lazy.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/approval_feedback.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/constants.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/handler.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/models.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/registration.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/renderers.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/theme.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/browser/tool_registry.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/display.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/file_permission_state.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/fs_access.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/image_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/io_backends.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/model_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/ripgrep.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/shell_backgrounding.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/skills_tools.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/subagent_context.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/subagent_usage_metrics.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/universal_constructor.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/tools/win_shell_io.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/undo_manager.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/universal_constructor_provider.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/code_puppy/version_checker.py +0 -0
- {code_puppy-0.0.910 → code_puppy-0.0.911}/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)
|
|
@@ -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:
|
|
@@ -190,6 +190,15 @@ def _load_installed_plugins() -> list[str]:
|
|
|
190
190
|
)
|
|
191
191
|
for entry_point in discovered:
|
|
192
192
|
plugin_name = entry_point.name
|
|
193
|
+
if plugin_name == "token_ratio_learner":
|
|
194
|
+
# Retired: context accounting now anchors to the API's own
|
|
195
|
+
# reported prompt-token usage (see code_puppy.context_accounting)
|
|
196
|
+
# instead of a learned chars-per-token ratio. Older installed
|
|
197
|
+
# core-plugins bundles may still advertise this entry point for
|
|
198
|
+
# a while; skip it unconditionally rather than let a dead
|
|
199
|
+
# learner monkeypatch the shared fallback estimator.
|
|
200
|
+
logger.debug("Skipping retired plugin %s", plugin_name)
|
|
201
|
+
continue
|
|
193
202
|
try:
|
|
194
203
|
with _plugin_loading_context(plugin_name):
|
|
195
204
|
entry_point.load()
|
|
@@ -3,36 +3,37 @@
|
|
|
3
3
|
Kept separate from ``register_callbacks.py`` so this stays unit-testable in
|
|
4
4
|
isolation and so callbacks file remains thin.
|
|
5
5
|
|
|
6
|
-
Design note —
|
|
7
|
-
|
|
8
|
-
``/context``
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
6
|
+
Design note — why the aggregate total is anchored, not independently estimated:
|
|
7
|
+
|
|
8
|
+
``/context`` used to compute its own ``max(1, floor(len(text) / 2.5))``
|
|
9
|
+
estimate over the whole history, deliberately kept separate from
|
|
10
|
+
compaction's calibrated estimator so the two heuristics couldn't disagree
|
|
11
|
+
in confusing ways. But two heuristics measuring the same conversation can
|
|
12
|
+
still drift from each other *and* from what the provider actually billed
|
|
13
|
+
-- especially once caching is involved (Anthropic cache read/write, OpenAI
|
|
14
|
+
cached tokens) -- and neither was the ground truth.
|
|
15
|
+
|
|
16
|
+
``code_puppy.context_accounting.context_tokens`` anchors the total to the
|
|
17
|
+
latest completed response's own API-reported prompt-token count and only
|
|
18
|
+
estimates the delta since then, falling back to a plain per-message
|
|
19
|
+
estimate when there's no valid anchor (first request, a rewritten prefix,
|
|
20
|
+
a model switch, or a failed/cancelled request). Compaction, the status bar,
|
|
21
|
+
and this module's aggregate total now share that one function, so they
|
|
22
|
+
can't disagree with each other -- and the number agrees with the account
|
|
23
|
+
you're actually billed on.
|
|
24
|
+
|
|
25
|
+
The per-bucket overhead breakdown below (system prompt, AGENTS.md, tool
|
|
26
|
+
schemas, MCP, kennel memory) stays a separate, deliberately raw heuristic:
|
|
27
|
+
it has no corresponding per-bucket figure in the API response to anchor
|
|
28
|
+
to, so it remains an estimate, not a billed-token partition.
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
31
|
from __future__ import annotations
|
|
31
32
|
|
|
32
33
|
import json
|
|
33
34
|
import math
|
|
34
|
-
from dataclasses import dataclass
|
|
35
|
-
from typing import Any, List, Optional
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from typing import Any, List, Optional, cast
|
|
36
37
|
|
|
37
38
|
__all__ = [
|
|
38
39
|
"ContextUsage",
|
|
@@ -56,8 +57,8 @@ GREEN_CIRCLE = "🟢"
|
|
|
56
57
|
YELLOW_CIRCLE = "🟡"
|
|
57
58
|
RED_CIRCLE = "🔴"
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
60
|
+
# Model-agnostic heuristic for approximate /context overhead buckets.
|
|
61
|
+
# Aggregate totals use shared accounting rather than these raw estimates.
|
|
61
62
|
_CHARS_PER_TOKEN = 2.5
|
|
62
63
|
|
|
63
64
|
|
|
@@ -68,6 +69,18 @@ class ContextUsage:
|
|
|
68
69
|
``overhead_tokens`` is the *sum* of the per-bucket breakdown fields. The
|
|
69
70
|
breakdown fields are optional (default 0) so legacy call sites that only
|
|
70
71
|
care about the aggregate keep working without changes.
|
|
72
|
+
|
|
73
|
+
``total_tokens`` is the authoritative aggregate -- the same number
|
|
74
|
+
compaction used to decide whether to trim. It is intentionally *not*
|
|
75
|
+
derived from ``used_tokens + overhead_tokens``: the overhead breakdown
|
|
76
|
+
is an independent, approximate re-estimate (e.g. it re-queries live MCP
|
|
77
|
+
tool schemas rather than the agent's cached server list), so it can be
|
|
78
|
+
larger *or* smaller than the shared total. Deriving the displayed total
|
|
79
|
+
from the breakdown would let an approximate bucket sum silently
|
|
80
|
+
override the number compaction actually decided against. Pass the real
|
|
81
|
+
total explicitly via the private ``_total_tokens`` constructor field;
|
|
82
|
+
when omitted (legacy/test call sites), it falls back to
|
|
83
|
+
``used_tokens + overhead_tokens`` so existing behavior is unchanged.
|
|
71
84
|
"""
|
|
72
85
|
|
|
73
86
|
used_tokens: int
|
|
@@ -79,10 +92,17 @@ class ContextUsage:
|
|
|
79
92
|
pydantic_tools_tokens: int = 0
|
|
80
93
|
mcp_tokens: int = 0
|
|
81
94
|
kennel_memory_tokens: int = 0
|
|
95
|
+
_total_tokens: Optional[int] = field(default=None, repr=False)
|
|
96
|
+
|
|
97
|
+
def __post_init__(self) -> None:
|
|
98
|
+
if self._total_tokens is None:
|
|
99
|
+
object.__setattr__(
|
|
100
|
+
self, "_total_tokens", self.used_tokens + self.overhead_tokens
|
|
101
|
+
)
|
|
82
102
|
|
|
83
103
|
@property
|
|
84
104
|
def total_tokens(self) -> int:
|
|
85
|
-
return self.
|
|
105
|
+
return cast(int, self._total_tokens) # always set in __post_init__
|
|
86
106
|
|
|
87
107
|
@property
|
|
88
108
|
def proportion(self) -> float:
|
|
@@ -114,28 +134,15 @@ def pick_indicator(proportion: float) -> str:
|
|
|
114
134
|
def _raw_estimate_tokens(text: str) -> int:
|
|
115
135
|
"""Pure char/2.5 heuristic. Identical for every model, every time.
|
|
116
136
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
Keeps detailed overhead buckets model-agnostic and independent of
|
|
138
|
+
calibrated message estimates. The authoritative aggregate is computed
|
|
139
|
+
separately through shared context accounting.
|
|
120
140
|
"""
|
|
121
141
|
if not text:
|
|
122
142
|
return 0
|
|
123
143
|
return max(1, math.floor(len(text) / _CHARS_PER_TOKEN))
|
|
124
144
|
|
|
125
145
|
|
|
126
|
-
def _raw_tokens_for_message(message: Any) -> int:
|
|
127
|
-
"""Sum raw tokens across a message's parts via the canonical stringifier."""
|
|
128
|
-
# ``stringify_part`` is a pure formatter — safe to import directly.
|
|
129
|
-
from code_puppy.agents._history import stringify_part
|
|
130
|
-
|
|
131
|
-
total = 0
|
|
132
|
-
for part in getattr(message, "parts", []) or []:
|
|
133
|
-
part_str = stringify_part(part)
|
|
134
|
-
if part_str:
|
|
135
|
-
total += _raw_estimate_tokens(part_str)
|
|
136
|
-
return total
|
|
137
|
-
|
|
138
|
-
|
|
139
146
|
def _raw_tokens_for_pydantic_tools(tools: Optional[dict]) -> int:
|
|
140
147
|
"""Estimate tokens contributed by pydantic-ai registered tools.
|
|
141
148
|
|
|
@@ -370,11 +377,12 @@ def get_current_usage() -> Optional[ContextUsage]:
|
|
|
370
377
|
Returns ``None`` whenever any required piece of data is unavailable —
|
|
371
378
|
missing agent, missing model config, or *any* exception while estimating
|
|
372
379
|
history/overhead/capacity. We deliberately do **not** fall back to
|
|
373
|
-
zero on partial failures: a misleading
|
|
380
|
+
zero on partial failures: a misleading green indicator is worse than no
|
|
374
381
|
indicator at all (the prompt simply hides the badge).
|
|
375
382
|
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
Aggregate counting shares compaction's API anchor and fallback estimator
|
|
384
|
+
(see the module docstring). Detailed overhead buckets below are
|
|
385
|
+
estimates, not a billed-token partition.
|
|
378
386
|
"""
|
|
379
387
|
try:
|
|
380
388
|
from code_puppy.agents.agent_manager import get_current_agent
|
|
@@ -390,7 +398,6 @@ def get_current_usage() -> Optional[ContextUsage]:
|
|
|
390
398
|
|
|
391
399
|
try:
|
|
392
400
|
history = agent.get_message_history() or []
|
|
393
|
-
used = sum(_raw_tokens_for_message(m) for m in history)
|
|
394
401
|
capacity = agent._get_model_context_length()
|
|
395
402
|
except Exception:
|
|
396
403
|
return None
|
|
@@ -403,8 +410,16 @@ def get_current_usage() -> Optional[ContextUsage]:
|
|
|
403
410
|
except Exception:
|
|
404
411
|
return None
|
|
405
412
|
|
|
413
|
+
try:
|
|
414
|
+
from code_puppy.context_accounting import active_model_name, context_tokens
|
|
415
|
+
|
|
416
|
+
overhead = agent._estimate_context_overhead()
|
|
417
|
+
total = context_tokens(history, active_model_name(agent), overhead)
|
|
418
|
+
except Exception:
|
|
419
|
+
return None
|
|
420
|
+
|
|
406
421
|
return ContextUsage(
|
|
407
|
-
used_tokens=int(
|
|
422
|
+
used_tokens=max(0, int(total) - breakdown.total),
|
|
408
423
|
overhead_tokens=breakdown.total,
|
|
409
424
|
capacity=int(capacity),
|
|
410
425
|
system_prompt_tokens=breakdown.system_prompt_tokens,
|
|
@@ -412,4 +427,8 @@ def get_current_usage() -> Optional[ContextUsage]:
|
|
|
412
427
|
pydantic_tools_tokens=breakdown.pydantic_tools_tokens,
|
|
413
428
|
mcp_tokens=breakdown.mcp_tokens,
|
|
414
429
|
kennel_memory_tokens=breakdown.kennel_memory_tokens,
|
|
430
|
+
# Authoritative: must equal compaction's decision total exactly,
|
|
431
|
+
# even when the (approximate, independently-estimated) overhead
|
|
432
|
+
# breakdown is larger than it -- see ContextUsage's docstring.
|
|
433
|
+
_total_tokens=int(total),
|
|
415
434
|
)
|
|
@@ -488,7 +488,7 @@ async def _invoke_agent_impl(
|
|
|
488
488
|
# pydantic-ai v2).
|
|
489
489
|
capabilities=[
|
|
490
490
|
HistoryCompaction(agent_config),
|
|
491
|
-
build_model_message_transform(agent_name),
|
|
491
|
+
build_model_message_transform(agent_name, agent_config),
|
|
492
492
|
# Recursion guards ride the wrap_tool_execute seam so a
|
|
493
493
|
# sub-agent's own invoke_agent calls are denied before
|
|
494
494
|
# the tool body runs. Sole wrap_tool_execute implementer,
|