shotgun-sh 0.10.2.dev2__tar.gz → 0.10.3.dev2__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 (325) hide show
  1. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/PKG-INFO +2 -2
  2. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/pyproject.toml +2 -2
  3. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/agent_manager.py +44 -0
  4. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/provider.py +84 -102
  5. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/context_analyzer/analyzer.py +2 -6
  6. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/compaction.py +15 -2
  7. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/constants.py +3 -0
  8. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/history_processors.py +2 -1
  9. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_estimation.py +68 -1
  10. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/models.py +38 -3
  11. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/openai.py +38 -37
  12. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/openai_compatible.py +36 -37
  13. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/context.py +2 -2
  14. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/manager.py +118 -15
  15. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/llm_proxy/client.py +16 -6
  16. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/main.py +13 -1
  17. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/posthog_telemetry.py +14 -0
  18. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/app.py +14 -0
  19. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/spinner.py +6 -0
  20. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/vertical_tail.py +8 -1
  21. shotgun_sh-0.10.3.dev2/src/shotgun/tui/markdown.py +51 -0
  22. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/chat_screen.py +47 -87
  23. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/codebase_index_prompt_screen.py +2 -1
  24. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/hint_message.py +2 -1
  25. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/agent_response.py +2 -1
  26. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/user_question.py +1 -1
  27. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/welcome_message.py +2 -1
  28. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/github_issue.py +3 -1
  29. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/pipx_migration.py +2 -1
  30. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/provider_config.py +49 -11
  31. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shotgun_auth.py +2 -1
  32. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/tier1_warning_modal.py +3 -1
  33. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/welcome.py +2 -1
  34. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/.gitignore +0 -0
  35. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/LICENSE +0 -0
  36. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/README.md +0 -0
  37. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/hatch_build.py +0 -0
  38. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/__init__.py +0 -0
  39. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/__init__.py +0 -0
  40. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/__init__.py +0 -0
  41. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/autopilot_orchestrator.py +0 -0
  42. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/claude_cli_subprocess.py +0 -0
  43. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/claude_subprocess.py +0 -0
  44. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/dependency_graph.py +0 -0
  45. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/lightweight_parser.py +0 -0
  46. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/llm_parser.py +0 -0
  47. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/models.py +0 -0
  48. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/__init__.py +0 -0
  49. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/context_preamble.j2 +0 -0
  50. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/create_pr.j2 +0 -0
  51. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/execute_parallel_stages.j2 +0 -0
  52. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/execute_stage.j2 +0 -0
  53. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/qa_testing.j2 +0 -0
  54. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/prompts/review_code.j2 +0 -0
  55. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/stage_monitor.py +0 -0
  56. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/autopilot/tasks_parser.py +0 -0
  57. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/cancellation.py +0 -0
  58. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/common.py +0 -0
  59. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/README.md +0 -0
  60. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/__init__.py +0 -0
  61. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/constants.py +0 -0
  62. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/manager.py +0 -0
  63. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/models.py +0 -0
  64. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/streaming_test.py +0 -0
  65. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/config/tier_detection.py +0 -0
  66. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/constants/__init__.py +0 -0
  67. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/constants/file_types.py +0 -0
  68. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/context_analyzer/__init__.py +0 -0
  69. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/context_analyzer/constants.py +0 -0
  70. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/context_analyzer/formatter.py +0 -0
  71. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/context_analyzer/models.py +0 -0
  72. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/__init__.py +0 -0
  73. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/filters.py +0 -0
  74. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/__init__.py +0 -0
  75. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/chunking.py +0 -0
  76. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/context_extraction.py +0 -0
  77. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/file_content_deduplication.py +0 -0
  78. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/history_building.py +0 -0
  79. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/message_utils.py +0 -0
  80. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/__init__.py +0 -0
  81. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/anthropic.py +0 -0
  82. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/base.py +0 -0
  83. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/openai.py +0 -0
  84. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/sentencepiece_counter.py +0 -0
  85. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/tokenizer_cache.py +0 -0
  86. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/history/token_counting/utils.py +0 -0
  87. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/conversation/manager.py +0 -0
  88. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/error/__init__.py +0 -0
  89. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/error/models.py +0 -0
  90. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/export.py +0 -0
  91. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/file_read.py +0 -0
  92. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/gemini3_patch.py +0 -0
  93. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/llm.py +0 -0
  94. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/messages.py +0 -0
  95. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/models.py +0 -0
  96. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/plan.py +0 -0
  97. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/research.py +0 -0
  98. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/__init__.py +0 -0
  99. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/models.py +0 -0
  100. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/router.py +0 -0
  101. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/tools/__init__.py +0 -0
  102. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/tools/delegation_tools.py +0 -0
  103. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/router/tools/plan_tools.py +0 -0
  104. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/runner.py +0 -0
  105. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/specify.py +0 -0
  106. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tasks.py +0 -0
  107. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/__init__.py +0 -0
  108. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  109. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  110. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  111. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  112. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/models.py +0 -0
  113. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  114. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  115. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/context7.py +0 -0
  116. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/file_management.py +0 -0
  117. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/file_read_tools/__init__.py +0 -0
  118. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/file_read_tools/multimodal_file_read.py +0 -0
  119. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/__init__.py +0 -0
  120. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/insert_section.py +0 -0
  121. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/models.py +0 -0
  122. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/remove_section.py +0 -0
  123. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/replace_section.py +0 -0
  124. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/markdown_tools/utils.py +0 -0
  125. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/mermaid_validation.py +0 -0
  126. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/registry.py +0 -0
  127. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  128. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  129. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  130. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  131. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/agents/usage_manager.py +0 -0
  132. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/api_endpoints.py +0 -0
  133. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/attachments/__init__.py +0 -0
  134. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/attachments/errors.py +0 -0
  135. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/attachments/models.py +0 -0
  136. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/attachments/parser.py +0 -0
  137. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/attachments/processor.py +0 -0
  138. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/build_constants.py +0 -0
  139. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/__init__.py +0 -0
  140. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/clear.py +0 -0
  141. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/codebase/__init__.py +0 -0
  142. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/codebase/commands.py +0 -0
  143. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/codebase/models.py +0 -0
  144. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/compact.py +0 -0
  145. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/config.py +0 -0
  146. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/error_handler.py +0 -0
  147. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/feedback.py +0 -0
  148. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/models.py +0 -0
  149. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/run.py +0 -0
  150. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/spec/__init__.py +0 -0
  151. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/spec/backup.py +0 -0
  152. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/spec/commands.py +0 -0
  153. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/spec/models.py +0 -0
  154. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/spec/pull_service.py +0 -0
  155. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/update.py +0 -0
  156. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/cli/utils.py +0 -0
  157. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/__init__.py +0 -0
  158. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/__init__.py +0 -0
  159. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/benchmark_runner.py +0 -0
  160. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/exporters.py +0 -0
  161. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/formatters/__init__.py +0 -0
  162. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/formatters/base.py +0 -0
  163. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/formatters/json_formatter.py +0 -0
  164. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/formatters/markdown.py +0 -0
  165. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/benchmarks/models.py +0 -0
  166. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/__init__.py +0 -0
  167. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/call_resolution.py +0 -0
  168. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/change_detector.py +0 -0
  169. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  170. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/cypher_models.py +0 -0
  171. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/errors.py +0 -0
  172. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/__init__.py +0 -0
  173. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/base.py +0 -0
  174. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/factory.py +0 -0
  175. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/go/__init__.py +0 -0
  176. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/go/extractor.py +0 -0
  177. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/javascript/__init__.py +0 -0
  178. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/javascript/extractor.py +0 -0
  179. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/protocol.py +0 -0
  180. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/python/__init__.py +0 -0
  181. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/python/extractor.py +0 -0
  182. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/rust/__init__.py +0 -0
  183. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/rust/extractor.py +0 -0
  184. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/types.py +0 -0
  185. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/typescript/__init__.py +0 -0
  186. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/extractors/typescript/extractor.py +0 -0
  187. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/gitignore.py +0 -0
  188. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/ingestor.py +0 -0
  189. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/kuzu_compat.py +0 -0
  190. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/language_config.py +0 -0
  191. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/metrics_collector.py +0 -0
  192. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/metrics_types.py +0 -0
  193. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/nl_query.py +0 -0
  194. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/parallel_executor.py +0 -0
  195. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/parser_loader.py +0 -0
  196. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/work_distributor.py +0 -0
  197. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/core/worker.py +0 -0
  198. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/indexing_state.py +0 -0
  199. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/models.py +0 -0
  200. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/codebase/service.py +0 -0
  201. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/exceptions.py +0 -0
  202. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/llm_proxy/__init__.py +0 -0
  203. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/llm_proxy/clients.py +0 -0
  204. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/llm_proxy/constants.py +0 -0
  205. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/llm_proxy/models.py +0 -0
  206. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/logging_config.py +0 -0
  207. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/__init__.py +0 -0
  208. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/__init__.py +0 -0
  209. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/export.j2 +0 -0
  210. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/file_read.j2 +0 -0
  211. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  212. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  213. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  214. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  215. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/partials/router_delegation_mode.j2 +0 -0
  216. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/plan.j2 +0 -0
  217. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/research.j2 +0 -0
  218. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/router.j2 +0 -0
  219. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/specify.j2 +0 -0
  220. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  221. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  222. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  223. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/__init__.py +0 -0
  224. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  225. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  226. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  227. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  228. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  229. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  230. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/history/__init__.py +0 -0
  231. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/history/chunk_summarization.j2 +0 -0
  232. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/history/combine_summaries.j2 +0 -0
  233. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  234. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/history/summarization.j2 +0 -0
  235. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/loader.py +0 -0
  236. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/prompts/tools/web_search.j2 +0 -0
  237. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/py.typed +0 -0
  238. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/sdk/__init__.py +0 -0
  239. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/sdk/codebase.py +0 -0
  240. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/sdk/exceptions.py +0 -0
  241. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/sdk/models.py +0 -0
  242. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/sdk/services.py +0 -0
  243. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/settings.py +0 -0
  244. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/__init__.py +0 -0
  245. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/client.py +0 -0
  246. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/constants.py +0 -0
  247. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/exceptions.py +0 -0
  248. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/models.py +0 -0
  249. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/__init__.py +0 -0
  250. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/file_scanner.py +0 -0
  251. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/hasher.py +0 -0
  252. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/models.py +0 -0
  253. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/upload_pipeline.py +0 -0
  254. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/shared_specs/utils.py +0 -0
  255. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/specs_client.py +0 -0
  256. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/shotgun_web/supabase_client.py +0 -0
  257. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/telemetry.py +0 -0
  258. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/__init__.py +0 -0
  259. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/commands/__init__.py +0 -0
  260. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/attachment_bar.py +0 -0
  261. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/context_indicator.py +0 -0
  262. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/mode_indicator.py +0 -0
  263. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/prompt_input.py +0 -0
  264. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/splash.py +0 -0
  265. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/status_bar.py +0 -0
  266. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/components/update_indicator.py +0 -0
  267. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/containers.py +0 -0
  268. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/dependencies.py +0 -0
  269. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  270. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/layout.py +0 -0
  271. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/protocols.py +0 -0
  272. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/autopilot_startup.py +0 -0
  273. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/__init__.py +0 -0
  274. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/chat.tcss +0 -0
  275. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/codebase_index_selection.py +0 -0
  276. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/help_text.py +0 -0
  277. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat/prompt_history.py +0 -0
  278. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat.tcss +0 -0
  279. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  280. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/attachment_hint.py +0 -0
  281. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  282. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/__init__.py +0 -0
  283. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/chat_history.py +0 -0
  284. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/formatters.py +0 -0
  285. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/history/partial_response.py +0 -0
  286. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/chat_screen/messages.py +0 -0
  287. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/confirmation_dialog.py +0 -0
  288. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/database_locked_dialog.py +0 -0
  289. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/database_timeout_dialog.py +0 -0
  290. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/directory_setup.py +0 -0
  291. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/feedback.py +0 -0
  292. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/kuzu_error_dialog.py +0 -0
  293. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/model_picker.py +0 -0
  294. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/models.py +0 -0
  295. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shared_specs/__init__.py +0 -0
  296. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shared_specs/create_spec_dialog.py +0 -0
  297. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shared_specs/models.py +0 -0
  298. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shared_specs/share_specs_dialog.py +0 -0
  299. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/shared_specs/upload_progress_screen.py +0 -0
  300. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/spec_pull.py +0 -0
  301. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/screens/splash.py +0 -0
  302. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/services/__init__.py +0 -0
  303. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/services/conversation_service.py +0 -0
  304. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/services/ollama.py +0 -0
  305. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/state/__init__.py +0 -0
  306. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/state/processing_state.py +0 -0
  307. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/styles.tcss +0 -0
  308. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/templates/app_index.html +0 -0
  309. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/utils/__init__.py +0 -0
  310. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/utils/mode_progress.py +0 -0
  311. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/__init__.py +0 -0
  312. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/approval_widget.py +0 -0
  313. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/cascade_confirmation_widget.py +0 -0
  314. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/plan_panel.py +0 -0
  315. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/stage_approval_widget.py +0 -0
  316. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/step_checkpoint_widget.py +0 -0
  317. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/tui/widgets/widget_coordinator.py +0 -0
  318. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/__init__.py +0 -0
  319. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/datetime_utils.py +0 -0
  320. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/env_utils.py +0 -0
  321. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/file_system_utils.py +0 -0
  322. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/format_utils.py +0 -0
  323. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/marketing.py +0 -0
  324. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/source_detection.py +0 -0
  325. {shotgun_sh-0.10.2.dev2 → shotgun_sh-0.10.3.dev2}/src/shotgun/utils/update_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.10.2.dev2
3
+ Version: 0.10.3.dev2
4
4
  Summary: AI-powered research, planning, and task management CLI tool
5
5
  Project-URL: Homepage, https://shotgun.sh/
6
6
  Project-URL: Repository, https://github.com/shotgun-sh/shotgun
@@ -34,7 +34,7 @@ Requires-Dist: packaging>=23.0
34
34
  Requires-Dist: pathspec>=0.12.0
35
35
  Requires-Dist: posthog>=3.0.0
36
36
  Requires-Dist: psutil>=5.9.0
37
- Requires-Dist: pydantic-ai==1.58.0
37
+ Requires-Dist: pydantic-ai==1.59.0
38
38
  Requires-Dist: pydantic-settings>=2.0.0
39
39
  Requires-Dist: pyperclip>=1.10.0
40
40
  Requires-Dist: real-ladybug==0.14.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.10.2.dev2"
3
+ version = "0.10.3.dev2"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -24,7 +24,7 @@ classifiers = [
24
24
  dependencies = [
25
25
  "typer>=0.12.0",
26
26
  "rich>=13.0.0",
27
- "pydantic-ai==1.58.0", # Pinned due to bug workaround in gemini3_patch.py
27
+ "pydantic-ai==1.59.0", # Pinned due to bug workaround in gemini3_patch.py
28
28
  "pydantic-settings>=2.0.0",
29
29
  "httpx>=0.27.0",
30
30
  "jinja2>=3.1.0",
@@ -86,12 +86,14 @@ from shotgun.agents.models import (
86
86
  )
87
87
  from shotgun.attachments import FileAttachment
88
88
  from shotgun.exceptions import IncompleteToolCallError
89
+ from shotgun.posthog_telemetry import flush as flush_telemetry
89
90
  from shotgun.posthog_telemetry import track_event
90
91
  from shotgun.tui.screens.chat_screen.hint_message import HintMessage
91
92
  from shotgun.tui.screens.chat_screen.welcome_message import WelcomeMessage
92
93
  from shotgun.utils.source_detection import detect_source
93
94
 
94
95
  from .conversation.history.compaction import apply_persistent_compaction
96
+ from .conversation.history.constants import MAX_UI_HISTORY_MESSAGES
95
97
  from .export import create_export_agent
96
98
  from .messages import AgentSystemPrompt, InternalPromptPart
97
99
  from .models import AgentDeps, AgentRuntimeOptions
@@ -422,6 +424,27 @@ class AgentManager(Widget):
422
424
  )
423
425
  self._agents_initialized = True
424
426
 
427
+ async def cleanup_agents(self) -> None:
428
+ """Release all agent instances and their held resources."""
429
+ if self._router_deps is not None:
430
+ self._router_deps.sub_agent_cache.clear()
431
+ self._router_deps.parent_stream_handler = None
432
+ self._router_deps.on_plan_changed = None
433
+
434
+ self._research_agent = None
435
+ self._research_deps = None
436
+ self._plan_agent = None
437
+ self._plan_deps = None
438
+ self._tasks_agent = None
439
+ self._tasks_deps = None
440
+ self._specify_agent = None
441
+ self._specify_deps = None
442
+ self._export_agent = None
443
+ self._export_deps = None
444
+ self._router_agent = None
445
+ self._router_deps = None
446
+ self._agents_initialized = False
447
+
425
448
  @property
426
449
  def research_agent(self) -> ShotgunAgent:
427
450
  """Get research agent (must call _ensure_agents_initialized first)."""
@@ -1103,6 +1126,9 @@ class AgentManager(Widget):
1103
1126
  raise
1104
1127
  finally:
1105
1128
  self._stream_state = None
1129
+ # Flush PostHog events between agent runs to prevent
1130
+ # unbounded queue growth during long-running sessions
1131
+ flush_telemetry()
1106
1132
 
1107
1133
  # Agent ALWAYS returns AgentResponse with structured output
1108
1134
  agent_response = result.output
@@ -1403,6 +1429,9 @@ class AgentManager(Widget):
1403
1429
  extra={"agent_mode": self._current_agent_type.value},
1404
1430
  )
1405
1431
 
1432
+ # Prune old hint/welcome messages to prevent unbounded UI history growth
1433
+ self._prune_ui_message_history()
1434
+
1406
1435
  # Post final UI update after compaction completes
1407
1436
  # This ensures widgets that depend on message_history (like context indicator)
1408
1437
  # receive the updated history after compaction
@@ -1663,6 +1692,11 @@ class AgentManager(Widget):
1663
1692
  # Mark as sub-agent response for rendering with ↳ prefix
1664
1693
  final_message._shotgun_is_sub_agent = True # type: ignore[attr-defined]
1665
1694
  self._sub_agent_messages.append(final_message)
1695
+ if len(self._sub_agent_messages) > 200:
1696
+ logger.warning(
1697
+ "_sub_agent_messages exceeded 200 entries, truncating to most recent 100"
1698
+ )
1699
+ self._sub_agent_messages = self._sub_agent_messages[-100:]
1666
1700
 
1667
1701
  state.current_response = None
1668
1702
 
@@ -1696,6 +1730,16 @@ class AgentManager(Widget):
1696
1730
  self.ui_message_history.insert(insert_idx + j, msg)
1697
1731
  self._sub_agent_messages = []
1698
1732
 
1733
+ def _prune_ui_message_history(self) -> None:
1734
+ """Keep only the last MAX_UI_HISTORY_MESSAGES items in ui_message_history.
1735
+
1736
+ Acts as a circular buffer — oldest messages of any type are dropped.
1737
+ """
1738
+ if len(self.ui_message_history) <= MAX_UI_HISTORY_MESSAGES:
1739
+ return
1740
+
1741
+ self.ui_message_history = self.ui_message_history[-MAX_UI_HISTORY_MESSAGES:]
1742
+
1699
1743
  def _build_partial_response(
1700
1744
  self, parts: list[ModelResponsePart | ToolCallPartDelta]
1701
1745
  ) -> ModelResponse | None:
@@ -1,6 +1,7 @@
1
1
  """Provider management for LLM configuration."""
2
2
 
3
3
  from copy import deepcopy
4
+ from functools import lru_cache
4
5
 
5
6
  from pydantic import SecretStr
6
7
  from pydantic_ai._json_schema import JsonSchema, JsonSchemaTransformer
@@ -105,10 +106,9 @@ class OllamaCompatibleJsonSchemaTransformer(JsonSchemaTransformer):
105
106
  return schema
106
107
 
107
108
 
108
- # Global cache for Model instances (singleton pattern)
109
- _model_cache: dict[
110
- tuple[ProviderType, KeyProvider, ModelName | str, str, str | None], Model
111
- ] = {}
109
+ # Max number of cached Model instances (LRU eviction)
110
+ _MAX_MODEL_CACHE_SIZE = 8
111
+
112
112
 
113
113
  # Module-level model override for OpenAI-compatible mode (set via --model CLI flag)
114
114
  _openai_compat_model_override: str | None = None
@@ -226,6 +226,7 @@ def get_default_model_for_provider(config: ShotgunConfig) -> ModelName:
226
226
  return ModelName.CLAUDE_OPUS_4_5
227
227
 
228
228
 
229
+ @lru_cache(maxsize=_MAX_MODEL_CACHE_SIZE)
229
230
  def get_or_create_model(
230
231
  provider: ProviderType,
231
232
  key_provider: "KeyProvider",
@@ -235,6 +236,8 @@ def get_or_create_model(
235
236
  ) -> Model:
236
237
  """Get or create a singleton Model instance.
237
238
 
239
+ Bounded by ``functools.lru_cache`` (maxsize=8) with automatic LRU eviction.
240
+
238
241
  Args:
239
242
  provider: Actual LLM provider (openai, anthropic, google)
240
243
  key_provider: Authentication method (byok or shotgun)
@@ -248,111 +251,90 @@ def get_or_create_model(
248
251
  Raises:
249
252
  ValueError: If provider is not supported
250
253
  """
251
- cache_key = (provider, key_provider, model_name, api_key, base_url)
252
-
253
- if cache_key not in _model_cache:
254
- logger.debug(
255
- "Creating new %s model instance via %s: %s",
256
- provider.value,
257
- key_provider.value,
258
- model_name,
254
+ logger.debug(
255
+ "Creating new %s model instance via %s: %s",
256
+ provider.value,
257
+ key_provider.value,
258
+ model_name,
259
+ )
260
+
261
+ # Get max_tokens from MODEL_SPECS (only for known models)
262
+ if isinstance(model_name, ModelName) and model_name in MODEL_SPECS:
263
+ max_tokens = MODEL_SPECS[model_name].max_output_tokens
264
+ else:
265
+ max_tokens = {
266
+ ProviderType.OPENAI: 16_000,
267
+ ProviderType.ANTHROPIC: 32_000,
268
+ ProviderType.GOOGLE: 64_000,
269
+ ProviderType.OPENAI_COMPATIBLE: 16_000,
270
+ }.get(provider, 16_000)
271
+
272
+ # Handle OpenAI-compatible endpoints (uses provided base_url or settings)
273
+ if provider == ProviderType.OPENAI_COMPATIBLE:
274
+ return _create_openai_compat_model(
275
+ api_key, str(model_name), max_tokens, base_url
276
+ )
277
+
278
+ # At this point, model_name must be a ModelName (string handled above via OPENAI_COMPATIBLE)
279
+ if not isinstance(model_name, ModelName):
280
+ raise ValueError(
281
+ f"Unknown model name: {model_name}. "
282
+ "For custom models, set SHOTGUN_OPENAI_COMPAT_BASE_URL."
259
283
  )
260
284
 
261
- # Get max_tokens from MODEL_SPECS (only for known models)
262
- if isinstance(model_name, ModelName) and model_name in MODEL_SPECS:
263
- max_tokens = MODEL_SPECS[model_name].max_output_tokens
285
+ if key_provider == KeyProvider.SHOTGUN:
286
+ # Shotgun Account uses LiteLLM proxy with native model types where possible
287
+ if model_name in MODEL_SPECS:
288
+ litellm_model_name = MODEL_SPECS[model_name].litellm_proxy_model_name
264
289
  else:
265
- # Fallback defaults based on provider
266
- max_tokens = {
267
- ProviderType.OPENAI: 16_000,
268
- ProviderType.ANTHROPIC: 32_000,
269
- ProviderType.GOOGLE: 64_000,
270
- ProviderType.OPENAI_COMPATIBLE: 16_000,
271
- }.get(provider, 16_000)
272
-
273
- # Handle OpenAI-compatible endpoints (uses provided base_url or settings)
274
- if provider == ProviderType.OPENAI_COMPATIBLE:
275
- _model_cache[cache_key] = _create_openai_compat_model(
276
- api_key, str(model_name), max_tokens, base_url
290
+ litellm_model_name = f"openai/{model_name.value}"
291
+
292
+ if provider == ProviderType.ANTHROPIC:
293
+ anthropic_provider = create_anthropic_proxy_provider(api_key)
294
+ return AnthropicModel(
295
+ model_name.value,
296
+ provider=anthropic_provider,
297
+ settings=AnthropicModelSettings(
298
+ max_tokens=max_tokens,
299
+ timeout=600,
300
+ ),
277
301
  )
278
- return _model_cache[cache_key]
279
-
280
- # Use LiteLLM proxy for Shotgun Account, native providers for BYOK
281
- # At this point, model_name must be a ModelName (string handled above via OPENAI_COMPATIBLE)
282
- if not isinstance(model_name, ModelName):
283
- raise ValueError(
284
- f"Unknown model name: {model_name}. "
285
- "For custom models, set SHOTGUN_OPENAI_COMPAT_BASE_URL."
302
+ else:
303
+ litellm_provider = create_litellm_provider(api_key)
304
+ return OpenAIChatModel(
305
+ litellm_model_name,
306
+ provider=litellm_provider,
307
+ settings=ModelSettings(max_tokens=max_tokens),
308
+ )
309
+ elif key_provider == KeyProvider.BYOK:
310
+ if provider == ProviderType.OPENAI:
311
+ openai_provider = OpenAIProvider(api_key=api_key)
312
+ return OpenAIResponsesModel(
313
+ model_name,
314
+ provider=openai_provider,
315
+ settings=ModelSettings(max_tokens=max_tokens),
316
+ )
317
+ elif provider == ProviderType.ANTHROPIC:
318
+ anthropic_provider = AnthropicProvider(api_key=api_key)
319
+ return AnthropicModel(
320
+ model_name,
321
+ provider=anthropic_provider,
322
+ settings=AnthropicModelSettings(
323
+ max_tokens=max_tokens,
324
+ timeout=600,
325
+ ),
326
+ )
327
+ elif provider == ProviderType.GOOGLE:
328
+ google_provider = GoogleProvider(api_key=api_key)
329
+ return GoogleModel(
330
+ model_name,
331
+ provider=google_provider,
332
+ settings=ModelSettings(max_tokens=max_tokens),
286
333
  )
287
-
288
- if key_provider == KeyProvider.SHOTGUN:
289
- # Shotgun Account uses LiteLLM proxy with native model types where possible
290
- if model_name in MODEL_SPECS:
291
- litellm_model_name = MODEL_SPECS[model_name].litellm_proxy_model_name
292
- else:
293
- # Fallback for unmapped models
294
- litellm_model_name = f"openai/{model_name.value}"
295
-
296
- # Use native provider types to preserve API formats and features
297
- if provider == ProviderType.ANTHROPIC:
298
- # Anthropic: Use native AnthropicProvider with /anthropic endpoint
299
- # This preserves Anthropic-specific features like tool_choice
300
- # Note: Web search for Shotgun Account uses Gemini only (not Anthropic)
301
- # Note: Anthropic API expects model name without prefix (e.g., "claude-sonnet-4-5")
302
- anthropic_provider = create_anthropic_proxy_provider(api_key)
303
- _model_cache[cache_key] = AnthropicModel(
304
- model_name.value, # Use model name without "anthropic/" prefix
305
- provider=anthropic_provider,
306
- settings=AnthropicModelSettings(
307
- max_tokens=max_tokens,
308
- timeout=600, # 10 minutes timeout for large responses
309
- ),
310
- )
311
- else:
312
- # OpenAI and Google: Use LiteLLMProvider (OpenAI-compatible format)
313
- # Google's GoogleProvider doesn't support base_url, so use LiteLLM
314
- # Note: Use OpenAIChatModel (not OpenAIResponsesModel) because LiteLLM's
315
- # streaming Responses API doesn't return tool calls properly for Gemini 3
316
- litellm_provider = create_litellm_provider(api_key)
317
- _model_cache[cache_key] = OpenAIChatModel(
318
- litellm_model_name,
319
- provider=litellm_provider,
320
- settings=ModelSettings(max_tokens=max_tokens),
321
- )
322
- elif key_provider == KeyProvider.BYOK:
323
- # Use native provider implementations with user's API keys
324
- if provider == ProviderType.OPENAI:
325
- openai_provider = OpenAIProvider(api_key=api_key)
326
- _model_cache[cache_key] = OpenAIResponsesModel(
327
- model_name,
328
- provider=openai_provider,
329
- settings=ModelSettings(max_tokens=max_tokens),
330
- )
331
- elif provider == ProviderType.ANTHROPIC:
332
- anthropic_provider = AnthropicProvider(api_key=api_key)
333
- _model_cache[cache_key] = AnthropicModel(
334
- model_name,
335
- provider=anthropic_provider,
336
- settings=AnthropicModelSettings(
337
- max_tokens=max_tokens,
338
- timeout=600, # 10 minutes timeout for large responses
339
- ),
340
- )
341
- elif provider == ProviderType.GOOGLE:
342
- google_provider = GoogleProvider(api_key=api_key)
343
- _model_cache[cache_key] = GoogleModel(
344
- model_name,
345
- provider=google_provider,
346
- settings=ModelSettings(max_tokens=max_tokens),
347
- )
348
- else:
349
- raise ValueError(f"Unsupported provider: {provider}")
350
334
  else:
351
- raise ValueError(f"Unsupported key provider: {key_provider}")
335
+ raise ValueError(f"Unsupported provider: {provider}")
352
336
  else:
353
- logger.debug("Reusing cached %s model instance: %s", provider.value, model_name)
354
-
355
- return _model_cache[cache_key]
337
+ raise ValueError(f"Unsupported key provider: {key_provider}")
356
338
 
357
339
 
358
340
  async def get_provider_model(
@@ -20,6 +20,7 @@ from shotgun.agents.conversation.history.token_counting.utils import (
20
20
  )
21
21
  from shotgun.agents.conversation.history.token_estimation import (
22
22
  estimate_tokens_from_messages,
23
+ get_last_api_token_count,
23
24
  )
24
25
  from shotgun.agents.messages import AgentSystemPrompt, SystemStatusPrompt
25
26
  from shotgun.logging_config import get_logger
@@ -66,14 +67,9 @@ class ContextAnalyzer:
66
67
  TokenAllocation with token counts by message/tool type
67
68
  """
68
69
  # Step 1: Find the last response's usage data (ground truth for input tokens)
69
- last_input_tokens = 0
70
+ last_input_tokens = get_last_api_token_count(message_history)
70
71
  total_output_tokens = 0
71
72
 
72
- for msg in reversed(message_history):
73
- if isinstance(msg, ModelResponse) and msg.usage:
74
- last_input_tokens = msg.usage.input_tokens + msg.usage.cache_read_tokens
75
- break
76
-
77
73
  if last_input_tokens == 0:
78
74
  # Fallback to token estimation (no logging to reduce verbosity)
79
75
  last_input_tokens = await estimate_tokens_from_messages(
@@ -7,7 +7,7 @@ from shotgun.agents.models import AgentDeps
7
7
  from shotgun.logging_config import get_logger
8
8
  from shotgun.posthog_telemetry import track_event
9
9
 
10
- from .token_estimation import estimate_tokens_from_messages
10
+ from .token_estimation import estimate_tokens_from_messages, get_last_api_token_count
11
11
 
12
12
  logger = get_logger(__name__)
13
13
 
@@ -36,7 +36,20 @@ async def apply_persistent_compaction(
36
36
 
37
37
  try:
38
38
  # Count tokens to decide if LLM-based compaction is needed
39
- estimated_tokens = await estimate_tokens_from_messages(messages, deps.llm_model)
39
+ # Prefer API usage data (accurate) over text-based estimation (undercounts)
40
+ api_tokens = get_last_api_token_count(messages)
41
+ if api_tokens > 0:
42
+ estimated_tokens = api_tokens
43
+ logger.debug(
44
+ f"Using API usage data for compaction check: {estimated_tokens} tokens"
45
+ )
46
+ else:
47
+ estimated_tokens = await estimate_tokens_from_messages(
48
+ messages, deps.llm_model
49
+ )
50
+ logger.debug(
51
+ f"Using text estimation for compaction check: {estimated_tokens} tokens (no API usage data)"
52
+ )
40
53
 
41
54
  # Create minimal usage info for compaction check
42
55
  usage = RequestUsage(
@@ -15,6 +15,9 @@ CHUNK_TARGET_RATIO = 0.60 # Target chunk size as % of max_input_tokens
15
15
  CHUNK_SAFE_RATIO = 0.70 # Max safe ratio before triggering chunked compaction
16
16
  RETENTION_WINDOW_MESSAGES = 5 # Keep last N message groups outside compaction
17
17
 
18
+ # UI history pruning — keep the last N messages (any type) as a circular buffer
19
+ MAX_UI_HISTORY_MESSAGES = 50
20
+
18
21
 
19
22
  class SummaryType(Enum):
20
23
  """Types of summarization requests for logging."""
@@ -35,6 +35,7 @@ from .token_estimation import (
35
35
  from .token_estimation import (
36
36
  estimate_post_summary_tokens,
37
37
  estimate_tokens_from_messages,
38
+ estimate_tokens_hybrid,
38
39
  )
39
40
 
40
41
  if TYPE_CHECKING:
@@ -453,7 +454,7 @@ async def token_limit_compactor(
453
454
  else:
454
455
  # Check if total conversation exceeds threshold for full compaction
455
456
  total_tokens = await _safe_token_estimation(
456
- estimate_tokens_from_messages,
457
+ estimate_tokens_hybrid,
457
458
  deps.llm_model.name,
458
459
  model_max_tokens,
459
460
  messages,
@@ -6,7 +6,7 @@ and libraries, replacing the old character-based estimation approach.
6
6
 
7
7
  from typing import TYPE_CHECKING, Union
8
8
 
9
- from pydantic_ai.messages import ModelMessage
9
+ from pydantic_ai.messages import ModelMessage, ModelResponse
10
10
 
11
11
  from shotgun.agents.config.models import ModelConfig
12
12
 
@@ -15,9 +15,28 @@ if TYPE_CHECKING:
15
15
 
16
16
  from shotgun.agents.models import AgentDeps
17
17
 
18
+ from shotgun.logging_config import get_logger
19
+
18
20
  from .constants import INPUT_BUFFER_TOKENS, MIN_SUMMARY_TOKENS
19
21
  from .token_counting import count_tokens_from_messages as _count_tokens_from_messages
20
22
 
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ def get_last_api_token_count(messages: list[ModelMessage]) -> int:
27
+ """Extract the total input token count from the last ModelResponse's usage data.
28
+
29
+ API usage data is the most accurate source for token counts because it includes
30
+ message framing, tool schemas, system prompts, and binary content that text-based
31
+ estimation misses.
32
+
33
+ Returns 0 if no ModelResponse with nonzero usage data is found.
34
+ """
35
+ for msg in reversed(messages):
36
+ if isinstance(msg, ModelResponse) and msg.usage:
37
+ return msg.usage.input_tokens + msg.usage.cache_read_tokens
38
+ return 0
39
+
21
40
 
22
41
  async def estimate_tokens_from_messages(
23
42
  messages: list[ModelMessage], model_config: ModelConfig
@@ -41,6 +60,54 @@ async def estimate_tokens_from_messages(
41
60
  return await _count_tokens_from_messages(messages, model_config)
42
61
 
43
62
 
63
+ async def estimate_tokens_hybrid(
64
+ messages: list[ModelMessage], model_config: ModelConfig
65
+ ) -> int:
66
+ """Estimate total tokens using API usage data plus delta counting.
67
+
68
+ This is more efficient than counting all messages from scratch. It uses the
69
+ last ModelResponse's usage data (which includes framing, schemas, etc.) as
70
+ the base count, then only counts the new messages added since that response.
71
+
72
+ Falls back to full counting when no API usage data is available (e.g. first turn).
73
+
74
+ Args:
75
+ messages: List of messages to count tokens for
76
+ model_config: Model configuration with provider info
77
+
78
+ Returns:
79
+ Estimated token count
80
+ """
81
+ # Single backward pass to find the last ModelResponse with usage data
82
+ api_count = 0
83
+ last_response_idx = -1
84
+ for i in range(len(messages) - 1, -1, -1):
85
+ msg = messages[i]
86
+ if isinstance(msg, ModelResponse) and msg.usage:
87
+ api_count = msg.usage.input_tokens + msg.usage.cache_read_tokens
88
+ if api_count > 0:
89
+ last_response_idx = i
90
+ break
91
+
92
+ if api_count == 0:
93
+ # First turn or no usage data — fall back to full counting
94
+ return await estimate_tokens_from_messages(messages, model_config)
95
+
96
+ delta_messages = messages[last_response_idx + 1 :]
97
+
98
+ if not delta_messages:
99
+ return api_count
100
+
101
+ delta_count = await estimate_tokens_from_messages(delta_messages, model_config)
102
+
103
+ logger.debug(
104
+ f"Hybrid token estimate: api_count={api_count}, delta_count={delta_count}, "
105
+ f"delta_messages={len(delta_messages)}, total={api_count + delta_count}"
106
+ )
107
+
108
+ return api_count + delta_count
109
+
110
+
44
111
  async def estimate_post_summary_tokens(
45
112
  messages: list[ModelMessage], summary_index: int, model_config: ModelConfig
46
113
  ) -> int:
@@ -24,6 +24,38 @@ from .filters import (
24
24
 
25
25
  SerializedMessage = dict[str, Any]
26
26
 
27
+ # Mapping of custom part_kind values to their pydantic-ai base part_kind.
28
+ # Custom subclasses (e.g. SystemStatusPrompt, InternalPromptPart) override
29
+ # part_kind with values that ModelMessagesTypeAdapter doesn't recognise,
30
+ # causing deserialization to fail. Remapping before validation fixes this.
31
+ _CUSTOM_PART_KIND_MAP: dict[str, str] = {
32
+ "system-status": "user-prompt", # SystemStatusPrompt -> UserPromptPart
33
+ "internal-prompt": "user-prompt", # InternalPromptPart -> UserPromptPart
34
+ }
35
+
36
+
37
+ def _sanitize_parts(messages: list[SerializedMessage]) -> list[SerializedMessage]:
38
+ """Remap custom part_kind values so ModelMessagesTypeAdapter can deserialize them."""
39
+ sanitized: list[SerializedMessage] = []
40
+ for msg in messages:
41
+ parts = msg.get("parts")
42
+ if not parts:
43
+ sanitized.append(msg)
44
+ continue
45
+ new_parts = []
46
+ changed = False
47
+ for part in parts:
48
+ pk = part.get("part_kind")
49
+ if pk in _CUSTOM_PART_KIND_MAP:
50
+ part = {**part, "part_kind": _CUSTOM_PART_KIND_MAP[pk]}
51
+ changed = True
52
+ new_parts.append(part)
53
+ if changed:
54
+ sanitized.append({**msg, "parts": new_parts})
55
+ else:
56
+ sanitized.append(msg)
57
+ return sanitized
58
+
27
59
 
28
60
  class FileReference(BaseModel):
29
61
  """Placeholder for binary content that was loaded from a file.
@@ -162,8 +194,9 @@ class ConversationHistory(BaseModel):
162
194
  if not self.agent_history:
163
195
  return []
164
196
 
165
- # Deserialize from JSON format back to ModelMessage objects
166
- return ModelMessagesTypeAdapter.validate_python(self.agent_history)
197
+ # Sanitize custom part_kind values before deserialization
198
+ sanitized = _sanitize_parts(self.agent_history)
199
+ return ModelMessagesTypeAdapter.validate_python(sanitized)
167
200
 
168
201
  def get_ui_messages(self) -> list[ModelMessage | HintMessage | WelcomeMessage]:
169
202
  """Get ui_history as a list of Model, hint, or welcome messages."""
@@ -189,7 +222,9 @@ class ConversationHistory(BaseModel):
189
222
  payload = item
190
223
  if isinstance(payload, dict):
191
224
  payload = {k: v for k, v in payload.items() if k != "message_type"}
192
- deserialized = ModelMessagesTypeAdapter.validate_python([payload])
225
+ deserialized = ModelMessagesTypeAdapter.validate_python(
226
+ _sanitize_parts([payload])
227
+ )
193
228
  messages.append(deserialized[0])
194
229
 
195
230
  return messages