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.
Files changed (329) hide show
  1. {code_puppy-0.0.910 → code_puppy-0.0.912}/PKG-INFO +1 -1
  2. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_builder.py +1 -1
  3. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_compaction.py +19 -1
  4. code_puppy-0.0.912/code_puppy/agents/_model_message_transform.py +130 -0
  5. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_runtime.py +23 -41
  6. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/retry_checkpoint.py +2 -2
  7. code_puppy-0.0.912/code_puppy/agents/run_invocation.py +72 -0
  8. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/callbacks.py +7 -3
  9. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/compaction.py +37 -5
  10. code_puppy-0.0.912/code_puppy/context_accounting.py +154 -0
  11. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_switching.py +5 -2
  12. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/__init__.py +9 -0
  13. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/token_usage.py +67 -48
  14. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_invocation.py +1 -1
  15. {code_puppy-0.0.910 → code_puppy-0.0.912}/pyproject.toml +1 -1
  16. code_puppy-0.0.910/code_puppy/agents/_model_message_transform.py +0 -31
  17. {code_puppy-0.0.910 → code_puppy-0.0.912}/.gitignore +0 -0
  18. {code_puppy-0.0.910 → code_puppy-0.0.912}/LICENSE +0 -0
  19. {code_puppy-0.0.910 → code_puppy-0.0.912}/README.md +0 -0
  20. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/__init__.py +0 -0
  21. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/__main__.py +0 -0
  22. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_plugin_i18n_lifecycle.py +0 -0
  23. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_pydantic_tool_helpers.py +0 -0
  24. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/_pydantic_video_patch.py +0 -0
  25. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agent_completion_inbox.py +0 -0
  26. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agent_execution_context.py +0 -0
  27. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/__init__.py +0 -0
  28. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_code_mode.py +0 -0
  29. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_code_mode_guidance.py +0 -0
  30. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_diagnostics.py +0 -0
  31. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_foreign_thinking.py +0 -0
  32. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_history.py +0 -0
  33. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_key_listeners.py +0 -0
  34. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_non_streaming_render.py +0 -0
  35. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_output_limits.py +0 -0
  36. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_run_signals.py +0 -0
  37. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_steer_processor.py +0 -0
  38. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_subagent_recursion.py +0 -0
  39. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_windows_console.py +0 -0
  40. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/_wire_tool_names.py +0 -0
  41. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_code_puppy.py +0 -0
  42. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_creator_agent.py +0 -0
  43. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_helios.py +0 -0
  44. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_manager.py +0 -0
  45. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_model_judge.py +0 -0
  46. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_planning.py +0 -0
  47. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_qa_kitten.py +0 -0
  48. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/agent_web_retriever.py +0 -0
  49. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/base_agent.py +0 -0
  50. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/event_stream_handler.py +0 -0
  51. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/json_agent.py +0 -0
  52. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/retry_profiles.py +0 -0
  53. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/run_stats.py +0 -0
  54. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/smooth_stream.py +0 -0
  55. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/stream_status.py +0 -0
  56. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/subagent_stream_handler.py +0 -0
  57. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/agents/thinking_output.py +0 -0
  58. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/asyncio_cleanup.py +0 -0
  59. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/atomic_io.py +0 -0
  60. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/atomic_json.py +0 -0
  61. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/ca_bundle.py +0 -0
  62. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/__init__.py +0 -0
  63. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/capabilities/eager_timing.py +0 -0
  64. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/chatgpt_codex_client.py +0 -0
  65. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_cache_client.py +0 -0
  66. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_oauth_transport.py +0 -0
  67. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/claude_thinking_display.py +0 -0
  68. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/cli_runner.py +0 -0
  69. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/__init__.py +0 -0
  70. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_details.py +0 -0
  71. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_menu.py +0 -0
  72. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_menus.py +0 -0
  73. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/add_model_vllm.py +0 -0
  74. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/agent_menu.py +0 -0
  75. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/attachments.py +0 -0
  76. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/autosave_menu.py +0 -0
  77. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/autosave_search.py +0 -0
  78. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/clipboard.py +0 -0
  79. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/colors_menu.py +0 -0
  80. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/command_handler.py +0 -0
  81. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/command_registry.py +0 -0
  82. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/completers.py +0 -0
  83. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/completion_cache.py +0 -0
  84. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/config_apply.py +0 -0
  85. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/config_commands.py +0 -0
  86. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/core_commands.py +0 -0
  87. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/file_index.py +0 -0
  88. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/file_path_completion.py +0 -0
  89. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/help_catalog.py +0 -0
  90. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/help_overlay.py +0 -0
  91. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/image_utils.py +0 -0
  92. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/load_context_completion.py +0 -0
  93. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/__init__.py +0 -0
  94. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/base.py +0 -0
  95. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  96. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  97. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  98. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/edit_command.py +0 -0
  99. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/handler.py +0 -0
  100. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/help_command.py +0 -0
  101. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/install_command.py +0 -0
  102. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/install_menu.py +0 -0
  103. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/list_command.py +0 -0
  104. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/logs_command.py +0 -0
  105. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
  106. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/remove_command.py +0 -0
  107. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/restart_command.py +0 -0
  108. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/search_command.py +0 -0
  109. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
  110. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  111. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/start_command.py +0 -0
  112. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/status_command.py +0 -0
  113. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  114. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/stop_command.py +0 -0
  115. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/trust_command.py +0 -0
  116. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/utils.py +0 -0
  117. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  118. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp_binding_menu.py +0 -0
  119. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/mcp_completion.py +0 -0
  120. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/menu_session.py +0 -0
  121. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_picker_completion.py +0 -0
  122. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_settings_defs.py +0 -0
  123. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/model_settings_menu.py +0 -0
  124. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/onboarding_slides.py +0 -0
  125. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/onboarding_wizard.py +0 -0
  126. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/pagination.py +0 -0
  127. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/pin_command_completion.py +0 -0
  128. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/refresh_models.py +0 -0
  129. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_browser.py +0 -0
  130. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_browser_data.py +0 -0
  131. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/session_commands.py +0 -0
  132. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu.py +0 -0
  133. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_catalog.py +0 -0
  134. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_schema.py +0 -0
  135. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_settings.py +0 -0
  136. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_shims.py +0 -0
  137. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/set_menu_values.py +0 -0
  138. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/shell_passthrough.py +0 -0
  139. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/skills_completion.py +0 -0
  140. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/tui_style.py +0 -0
  141. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/uc_menu.py +0 -0
  142. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/command_line/utils.py +0 -0
  143. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/config.py +0 -0
  144. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/config_file.py +0 -0
  145. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/diagnostics.py +0 -0
  146. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/error_logging.py +0 -0
  147. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/events/__init__.py +0 -0
  148. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/events/bridge.py +0 -0
  149. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/file_completion_io.py +0 -0
  150. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/file_completion_tokens.py +0 -0
  151. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_code_assist.py +0 -0
  152. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_common.py +0 -0
  153. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/gemini_model.py +0 -0
  154. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/README.md +0 -0
  155. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/__init__.py +0 -0
  156. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/aliases.py +0 -0
  157. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/engine.py +0 -0
  158. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/executor.py +0 -0
  159. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/matcher.py +0 -0
  160. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/models.py +0 -0
  161. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/registry.py +0 -0
  162. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/hook_engine/validator.py +0 -0
  163. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/http_retry.py +0 -0
  164. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/http_utils.py +0 -0
  165. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/httpx2_utils.py +0 -0
  166. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/__init__.py +0 -0
  167. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/audit.py +0 -0
  168. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/catalog.py +0 -0
  169. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/formats.py +0 -0
  170. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/interpolation.py +0 -0
  171. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locale.py +0 -0
  172. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/en-US.json +0 -0
  173. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/es.json +0 -0
  174. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/locales/fr-CA.json +0 -0
  175. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/plugin_catalog.py +0 -0
  176. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/plurals.py +0 -0
  177. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/pseudo.py +0 -0
  178. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/i18n/translate.py +0 -0
  179. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/kennel_provider.py +0 -0
  180. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/keymap.py +0 -0
  181. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/list_filtering.py +0 -0
  182. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/main.py +0 -0
  183. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/__init__.py +0 -0
  184. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/agent_bindings.py +0 -0
  185. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/async_lifecycle.py +0 -0
  186. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/blocking_startup.py +0 -0
  187. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/circuit_breaker.py +0 -0
  188. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/config_wizard.py +0 -0
  189. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/dashboard.py +0 -0
  190. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/error_isolation.py +0 -0
  191. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/examples/retry_example.py +0 -0
  192. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/health_monitor.py +0 -0
  193. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/http_auth.py +0 -0
  194. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/managed_server.py +0 -0
  195. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/manager.py +0 -0
  196. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/manager_lifecycle.py +0 -0
  197. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/mcp_logs.py +0 -0
  198. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/project_config.py +0 -0
  199. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/registry.py +0 -0
  200. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/retry_manager.py +0 -0
  201. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  202. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/status_tracker.py +0 -0
  203. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/system_tools.py +0 -0
  204. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
  205. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_/toolset_utils.py +0 -0
  206. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_prompts/__init__.py +0 -0
  207. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/mcp_prompts/hook_creator.py +0 -0
  208. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/__init__.py +0 -0
  209. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bar_painters.py +0 -0
  210. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bar_rendering.py +0 -0
  211. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bottom_bar.py +0 -0
  212. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/bus.py +0 -0
  213. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/chords.py +0 -0
  214. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/commands.py +0 -0
  215. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_actions.py +0 -0
  216. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_completion.py +0 -0
  217. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_display.py +0 -0
  218. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_history.py +0 -0
  219. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_keys.py +0 -0
  220. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_paste.py +0 -0
  221. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_queue.py +0 -0
  222. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/editor_submission.py +0 -0
  223. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/external_editor.py +0 -0
  224. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/identity_line.py +0 -0
  225. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/idle_status.py +0 -0
  226. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/inline_bar.py +0 -0
  227. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/line_editor.py +0 -0
  228. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/markdown_patches.py +0 -0
  229. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/menu_lifecycle.py +0 -0
  230. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/message_queue.py +0 -0
  231. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/messages.py +0 -0
  232. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/pause_controller.py +0 -0
  233. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/prompt_prefix_style.py +0 -0
  234. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/queue_console.py +0 -0
  235. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/renderers.py +0 -0
  236. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/rich_renderer.py +0 -0
  237. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/run_ui.py +0 -0
  238. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/run_ui_wiring.py +0 -0
  239. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_line.py +0 -0
  240. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_stats.py +0 -0
  241. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/speculation_toggle.py +0 -0
  242. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/spinner/__init__.py +0 -0
  243. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/status_line.py +0 -0
  244. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/subagent_console.py +0 -0
  245. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/theme_accent.py +0 -0
  246. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/tool_output.py +0 -0
  247. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/messaging/transcript_guard.py +0 -0
  248. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_descriptions.py +0 -0
  249. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_factory.py +0 -0
  250. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/model_utils.py +0 -0
  251. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models.json +0 -0
  252. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models_dev_api.json +0 -0
  253. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/models_dev_parser.py +0 -0
  254. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/observability.py +0 -0
  255. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/platform_utils.py +0 -0
  256. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/config.py +0 -0
  257. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/oauth_pasteback.py +0 -0
  258. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  259. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/trust.py +0 -0
  260. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/plugins/trust_notice.py +0 -0
  261. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/private_inference.py +0 -0
  262. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/provider_credentials.py +0 -0
  263. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/provider_identity.py +0 -0
  264. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/pydantic_patches.py +0 -0
  265. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/reopenable_async_client.py +0 -0
  266. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/round_robin_model.py +0 -0
  267. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/secret_store.py +0 -0
  268. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/secret_store_backends.py +0 -0
  269. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_format_migration.py +0 -0
  270. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_lifecycle.py +0 -0
  271. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_migration.py +0 -0
  272. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_storage.py +0 -0
  273. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/session_surrogate_unpickler.py +0 -0
  274. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/shared_credentials.py +0 -0
  275. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/skill_provider.py +0 -0
  276. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/splash.py +0 -0
  277. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/status_display.py +0 -0
  278. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/steer_metadata.py +0 -0
  279. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/terminal_utils.py +0 -0
  280. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/__init__.py +0 -0
  281. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/_browser_registry.py +0 -0
  282. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/_lazy.py +0 -0
  283. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/agent_tools.py +0 -0
  284. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/approval_feedback.py +0 -0
  285. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/__init__.py +0 -0
  286. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/constants.py +0 -0
  287. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
  288. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/handler.py +0 -0
  289. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/models.py +0 -0
  290. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/registration.py +0 -0
  291. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/renderers.py +0 -0
  292. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
  293. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/theme.py +0 -0
  294. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
  295. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/__init__.py +0 -0
  296. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_control.py +0 -0
  297. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_interactions.py +0 -0
  298. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
  299. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_locators.py +0 -0
  300. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_manager.py +0 -0
  301. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_navigation.py +0 -0
  302. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
  303. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  304. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_scripts.py +0 -0
  305. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
  306. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/browser_workflows.py +0 -0
  307. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/browser/tool_registry.py +0 -0
  308. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/command_runner.py +0 -0
  309. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/common.py +0 -0
  310. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/display.py +0 -0
  311. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_modifications.py +0 -0
  312. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_operations.py +0 -0
  313. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/file_permission_state.py +0 -0
  314. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/fs_access.py +0 -0
  315. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/image_tools.py +0 -0
  316. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/io_backends.py +0 -0
  317. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/model_tools.py +0 -0
  318. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/ripgrep.py +0 -0
  319. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/shell_backgrounding.py +0 -0
  320. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/skills_tools.py +0 -0
  321. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_context.py +0 -0
  322. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/subagent_usage_metrics.py +0 -0
  323. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/tools_content.py +0 -0
  324. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/universal_constructor.py +0 -0
  325. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/tools/win_shell_io.py +0 -0
  326. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/undo_manager.py +0 -0
  327. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/universal_constructor_provider.py +0 -0
  328. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/version_checker.py +0 -0
  329. {code_puppy-0.0.910 → code_puppy-0.0.912}/code_puppy/zai_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code-puppy
3
- Version: 0.0.910
3
+ Version: 0.0.912
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -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
- return self.agent._estimate_context_overhead()
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
- preserved verbatim:
5
+ include:
6
6
 
7
- - Plugin-supplied async context managers wrap the run (see
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
- pydantic_agent = build_pydantic_agent(agent, output_type=output_type)
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, pydantic_agent, prompt_to_use, **run_options)
845
+ resumable_call(agent, invocation, prompt_to_use, **run_options)
834
846
  )
835
847
 
836
- async def _call_with_exception_recovery() -> Any:
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, pydantic_agent, follow_up_prompt, **run_options)
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
- mcp_servers = getattr(agent, "_mcp_servers", None) or []
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, pydantic_agent, prompt, **kwargs):
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 pydantic_agent.run(
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 an async CM (with ``__aenter__``/``__aexit__``) or
1810
- ``None``. The caller composes all non-``None`` results via
1811
- ``contextlib.AsyncExitStack``.
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
- message_tokens = sum(self.token_estimator(m, model_name) for m in messages)
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
- final_token_count = sum(self.token_estimator(m, model_name) for m in result)
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
- message_tokens = sum(agent.estimate_tokens_for_message(msg) for msg in history)
89
- total_tokens = message_tokens + agent._estimate_context_overhead()
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: