shotgun-sh 0.1.15.dev1__tar.gz → 0.2.1.dev1__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.

Potentially problematic release.


This version of shotgun-sh might be problematic. Click here for more details.

Files changed (152) hide show
  1. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/.gitignore +3 -0
  2. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/PKG-INFO +2 -2
  3. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/pyproject.toml +2 -2
  4. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/common.py +4 -5
  5. shotgun_sh-0.2.1.dev1/src/shotgun/agents/config/constants.py +33 -0
  6. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/config/manager.py +147 -39
  7. shotgun_sh-0.2.1.dev1/src/shotgun/agents/config/models.py +158 -0
  8. shotgun_sh-0.2.1.dev1/src/shotgun/agents/config/provider.py +309 -0
  9. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/compaction.py +1 -1
  10. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/history_processors.py +18 -9
  11. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/__init__.py +31 -0
  12. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/anthropic.py +89 -0
  13. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/base.py +67 -0
  14. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/openai.py +80 -0
  15. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +119 -0
  16. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/tokenizer_cache.py +90 -0
  17. shotgun_sh-0.2.1.dev1/src/shotgun/agents/history/token_counting/utils.py +147 -0
  18. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/token_estimation.py +12 -12
  19. shotgun_sh-0.2.1.dev1/src/shotgun/agents/llm.py +62 -0
  20. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/models.py +2 -2
  21. shotgun_sh-0.2.1.dev1/src/shotgun/agents/tools/web_search/__init__.py +87 -0
  22. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/web_search/anthropic.py +46 -40
  23. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/web_search/gemini.py +31 -20
  24. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/web_search/openai.py +4 -4
  25. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/config.py +14 -55
  26. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/models.py +2 -2
  27. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/models.py +4 -4
  28. shotgun_sh-0.2.1.dev1/src/shotgun/llm_proxy/__init__.py +16 -0
  29. shotgun_sh-0.2.1.dev1/src/shotgun/llm_proxy/clients.py +39 -0
  30. shotgun_sh-0.2.1.dev1/src/shotgun/llm_proxy/constants.py +8 -0
  31. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/main.py +6 -0
  32. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/posthog_telemetry.py +5 -3
  33. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/app.py +2 -0
  34. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat_screen/command_providers.py +20 -0
  35. shotgun_sh-0.2.1.dev1/src/shotgun/tui/screens/model_picker.py +214 -0
  36. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/provider_config.py +39 -26
  37. shotgun_sh-0.1.15.dev1/src/shotgun/agents/config/constants.py +0 -17
  38. shotgun_sh-0.1.15.dev1/src/shotgun/agents/config/models.py +0 -185
  39. shotgun_sh-0.1.15.dev1/src/shotgun/agents/config/provider.py +0 -206
  40. shotgun_sh-0.1.15.dev1/src/shotgun/agents/history/token_counting.py +0 -429
  41. shotgun_sh-0.1.15.dev1/src/shotgun/agents/tools/web_search/__init__.py +0 -60
  42. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/LICENSE +0 -0
  43. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/README.md +0 -0
  44. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/hatch_build.py +0 -0
  45. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/__init__.py +0 -0
  46. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/__init__.py +0 -0
  47. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/agent_manager.py +0 -0
  48. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/config/__init__.py +0 -0
  49. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/conversation_history.py +0 -0
  50. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/conversation_manager.py +0 -0
  51. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/export.py +0 -0
  52. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/__init__.py +0 -0
  53. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/constants.py +0 -0
  54. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/context_extraction.py +0 -0
  55. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/history_building.py +0 -0
  56. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/history/message_utils.py +0 -0
  57. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/messages.py +0 -0
  58. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/plan.py +0 -0
  59. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/research.py +0 -0
  60. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/specify.py +0 -0
  61. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tasks.py +0 -0
  62. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/__init__.py +0 -0
  63. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  64. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  65. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  66. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  67. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/models.py +0 -0
  68. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  69. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  70. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/file_management.py +0 -0
  71. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/user_interaction.py +0 -0
  72. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  73. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/build_constants.py +0 -0
  74. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/__init__.py +0 -0
  75. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/codebase/__init__.py +0 -0
  76. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/codebase/commands.py +0 -0
  77. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/codebase/models.py +0 -0
  78. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/export.py +0 -0
  79. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/feedback.py +0 -0
  80. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/plan.py +0 -0
  81. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/research.py +0 -0
  82. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/specify.py +0 -0
  83. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/tasks.py +0 -0
  84. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/update.py +0 -0
  85. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/cli/utils.py +0 -0
  86. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/__init__.py +0 -0
  87. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/__init__.py +0 -0
  88. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/change_detector.py +0 -0
  89. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  90. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/cypher_models.py +0 -0
  91. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/ingestor.py +0 -0
  92. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/language_config.py +0 -0
  93. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/manager.py +0 -0
  94. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/nl_query.py +0 -0
  95. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/core/parser_loader.py +0 -0
  96. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/codebase/service.py +0 -0
  97. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/logging_config.py +0 -0
  98. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/__init__.py +0 -0
  99. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/__init__.py +0 -0
  100. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/export.j2 +0 -0
  101. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  102. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  103. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  104. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  105. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/plan.j2 +0 -0
  106. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/research.j2 +0 -0
  107. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/specify.j2 +0 -0
  108. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  109. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  110. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  111. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/__init__.py +0 -0
  112. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  113. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  114. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  115. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  116. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  117. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  118. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/history/__init__.py +0 -0
  119. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  120. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/history/summarization.j2 +0 -0
  121. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/prompts/loader.py +0 -0
  122. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/py.typed +0 -0
  123. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sdk/__init__.py +0 -0
  124. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sdk/codebase.py +0 -0
  125. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sdk/exceptions.py +0 -0
  126. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sdk/models.py +0 -0
  127. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sdk/services.py +0 -0
  128. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/sentry_telemetry.py +0 -0
  129. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/telemetry.py +0 -0
  130. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/__init__.py +0 -0
  131. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/commands/__init__.py +0 -0
  132. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/components/prompt_input.py +0 -0
  133. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/components/spinner.py +0 -0
  134. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/components/splash.py +0 -0
  135. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/components/vertical_tail.py +0 -0
  136. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/filtered_codebase_service.py +0 -0
  137. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat.py +0 -0
  138. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat.tcss +0 -0
  139. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  140. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  141. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
  142. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/directory_setup.py +0 -0
  143. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/feedback.py +0 -0
  144. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/screens/splash.py +0 -0
  145. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/styles.tcss +0 -0
  146. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/utils/__init__.py +0 -0
  147. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/tui/utils/mode_progress.py +0 -0
  148. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/utils/__init__.py +0 -0
  149. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/utils/env_utils.py +0 -0
  150. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/utils/file_system_utils.py +0 -0
  151. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/utils/source_detection.py +0 -0
  152. {shotgun_sh-0.1.15.dev1 → shotgun_sh-0.2.1.dev1}/src/shotgun/utils/update_checker.py +0 -0
@@ -15,6 +15,9 @@ src/shotgun/build_constants.py
15
15
 
16
16
  .DS_Store
17
17
 
18
+ # Tokenizer model files (downloaded on first use)
19
+ *.model
20
+
18
21
  # Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode
19
22
  # Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode
20
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.1.15.dev1
3
+ Version: 0.2.1.dev1
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
@@ -22,7 +22,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Utilities
23
23
  Requires-Python: >=3.11
24
24
  Requires-Dist: anthropic>=0.39.0
25
- Requires-Dist: google-generativeai>=0.8.5
26
25
  Requires-Dist: httpx>=0.27.0
27
26
  Requires-Dist: jinja2>=3.1.0
28
27
  Requires-Dist: kuzu>=0.7.0
@@ -32,6 +31,7 @@ Requires-Dist: packaging>=23.0
32
31
  Requires-Dist: posthog>=3.0.0
33
32
  Requires-Dist: pydantic-ai>=0.0.14
34
33
  Requires-Dist: rich>=13.0.0
34
+ Requires-Dist: sentencepiece>=0.2.0
35
35
  Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
36
36
  Requires-Dist: textual-dev>=1.7.0
37
37
  Requires-Dist: textual>=6.1.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.1.15.dev1"
3
+ version = "0.2.1.dev1"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -42,8 +42,8 @@ dependencies = [
42
42
  "watchdog>=4.0.0",
43
43
  "openai>=1.0.0",
44
44
  "anthropic>=0.39.0",
45
- "google-generativeai>=0.8.5",
46
45
  "tiktoken>=0.7.0",
46
+ "sentencepiece>=0.2.0",
47
47
  "packaging>=23.0",
48
48
  ]
49
49
 
@@ -18,7 +18,7 @@ from pydantic_ai.messages import (
18
18
  ModelRequest,
19
19
  )
20
20
 
21
- from shotgun.agents.config import ProviderType, get_config_manager, get_provider_model
21
+ from shotgun.agents.config import ProviderType, get_provider_model
22
22
  from shotgun.agents.models import AgentType
23
23
  from shotgun.logging_config import get_logger
24
24
  from shotgun.prompts import PromptLoader
@@ -115,14 +115,13 @@ def create_base_agent(
115
115
  """
116
116
  ensure_shotgun_directory_exists()
117
117
 
118
- # Get configured model or fall back to hardcoded default
118
+ # Get configured model or fall back to first available provider
119
119
  try:
120
120
  model_config = get_provider_model(provider)
121
- config_manager = get_config_manager()
122
- provider_name = provider or config_manager.load().default_provider
121
+ provider_name = model_config.provider
123
122
  logger.debug(
124
123
  "🤖 Creating agent with configured %s model: %s",
125
- provider_name.upper(),
124
+ provider_name.value.upper(),
126
125
  model_config.name,
127
126
  )
128
127
  # Use the Model instance directly (has API key baked in)
@@ -0,0 +1,33 @@
1
+ """Configuration constants for Shotgun agents."""
2
+
3
+ from enum import StrEnum, auto
4
+
5
+ # Field names
6
+ API_KEY_FIELD = "api_key"
7
+ USER_ID_FIELD = "user_id"
8
+ CONFIG_VERSION_FIELD = "config_version"
9
+
10
+
11
+ class ConfigSection(StrEnum):
12
+ """Configuration file section names (JSON keys)."""
13
+
14
+ OPENAI = auto()
15
+ ANTHROPIC = auto()
16
+ GOOGLE = auto()
17
+ SHOTGUN = auto()
18
+
19
+
20
+ # Backwards compatibility - deprecated
21
+ OPENAI_PROVIDER = ConfigSection.OPENAI.value
22
+ ANTHROPIC_PROVIDER = ConfigSection.ANTHROPIC.value
23
+ GOOGLE_PROVIDER = ConfigSection.GOOGLE.value
24
+ SHOTGUN_PROVIDER = ConfigSection.SHOTGUN.value
25
+
26
+ # Environment variable names
27
+ OPENAI_API_KEY_ENV = "OPENAI_API_KEY"
28
+ ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY"
29
+ GEMINI_API_KEY_ENV = "GEMINI_API_KEY"
30
+ SHOTGUN_API_KEY_ENV = "SHOTGUN_API_KEY"
31
+
32
+ # Token limits
33
+ MEDIUM_TEXT_8K_TOKENS = 8192 # Default max_tokens for web search requests
@@ -13,17 +13,26 @@ from shotgun.utils import get_shotgun_home
13
13
 
14
14
  from .constants import (
15
15
  ANTHROPIC_API_KEY_ENV,
16
- ANTHROPIC_PROVIDER,
17
16
  API_KEY_FIELD,
18
17
  GEMINI_API_KEY_ENV,
19
- GOOGLE_PROVIDER,
20
18
  OPENAI_API_KEY_ENV,
21
- OPENAI_PROVIDER,
19
+ ConfigSection,
20
+ )
21
+ from .models import (
22
+ AnthropicConfig,
23
+ GoogleConfig,
24
+ ModelName,
25
+ OpenAIConfig,
26
+ ProviderType,
27
+ ShotgunAccountConfig,
28
+ ShotgunConfig,
22
29
  )
23
- from .models import ProviderType, ShotgunConfig
24
30
 
25
31
  logger = get_logger(__name__)
26
32
 
33
+ # Type alias for provider configuration objects
34
+ ProviderConfig = OpenAIConfig | AnthropicConfig | GoogleConfig | ShotgunAccountConfig
35
+
27
36
 
28
37
  class ConfigManager:
29
38
  """Manager for Shotgun configuration."""
@@ -69,20 +78,56 @@ class ConfigManager:
69
78
  self._config = ShotgunConfig.model_validate(data)
70
79
  logger.debug("Configuration loaded successfully from %s", self.config_path)
71
80
 
72
- # Check if the default provider has a key, if not find one that does
73
- if not self.has_provider_key(self._config.default_provider):
74
- original_default = self._config.default_provider
75
- # Find first provider with a configured key
76
- for provider in ProviderType:
77
- if self.has_provider_key(provider):
81
+ # Validate selected_model if in BYOK mode (no Shotgun key)
82
+ if not self._provider_has_api_key(self._config.shotgun):
83
+ should_save = False
84
+
85
+ # If selected_model is set, verify its provider has a key
86
+ if self._config.selected_model:
87
+ from .models import MODEL_SPECS
88
+
89
+ if self._config.selected_model in MODEL_SPECS:
90
+ spec = MODEL_SPECS[self._config.selected_model]
91
+ if not self.has_provider_key(spec.provider):
92
+ logger.info(
93
+ "Selected model %s provider has no API key, finding available model",
94
+ self._config.selected_model.value,
95
+ )
96
+ self._config.selected_model = None
97
+ should_save = True
98
+ else:
78
99
  logger.info(
79
- "Default provider %s has no API key, updating to %s",
80
- original_default.value,
81
- provider.value,
100
+ "Selected model %s not found in MODEL_SPECS, resetting",
101
+ self._config.selected_model.value,
82
102
  )
83
- self._config.default_provider = provider
84
- self.save(self._config)
85
- break
103
+ self._config.selected_model = None
104
+ should_save = True
105
+
106
+ # If no selected_model or it was invalid, find first available model
107
+ if not self._config.selected_model:
108
+ for provider in ProviderType:
109
+ if self.has_provider_key(provider):
110
+ # Set to that provider's default model
111
+ from .models import MODEL_SPECS, ModelName
112
+
113
+ # Find default model for this provider
114
+ provider_models = {
115
+ ProviderType.OPENAI: ModelName.GPT_5,
116
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
117
+ ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
118
+ }
119
+
120
+ if provider in provider_models:
121
+ self._config.selected_model = provider_models[provider]
122
+ logger.info(
123
+ "Set selected_model to %s (first available provider)",
124
+ self._config.selected_model.value,
125
+ )
126
+ should_save = True
127
+ break
128
+
129
+ if should_save:
130
+ self.save(self._config)
86
131
 
87
132
  return self._config
88
133
 
@@ -107,7 +152,6 @@ class ConfigManager:
107
152
  # Create a new config with generated user_id
108
153
  config = ShotgunConfig(
109
154
  user_id=str(uuid.uuid4()),
110
- config_version=1,
111
155
  )
112
156
 
113
157
  # Ensure directory exists
@@ -136,8 +180,13 @@ class ConfigManager:
136
180
  **kwargs: Configuration fields to update (only api_key supported)
137
181
  """
138
182
  config = self.load()
139
- provider_enum = self._ensure_provider_enum(provider)
140
- provider_config = self._get_provider_config(config, provider_enum)
183
+
184
+ # Get provider config and check if it's shotgun
185
+ provider_config, is_shotgun = self._get_provider_config_and_type(
186
+ config, provider
187
+ )
188
+ # For non-shotgun providers, we need the enum for default provider logic
189
+ provider_enum = None if is_shotgun else self._ensure_provider_enum(provider)
141
190
 
142
191
  # Only support api_key updates
143
192
  if API_KEY_FIELD in kwargs:
@@ -152,23 +201,47 @@ class ConfigManager:
152
201
  raise ValueError(f"Unsupported configuration fields: {unsupported_fields}")
153
202
 
154
203
  # If no other providers have keys configured and we just added one,
155
- # set this provider as the default
156
- if API_KEY_FIELD in kwargs and api_key_value is not None:
204
+ # set selected_model to that provider's default model (only for LLM providers, not shotgun)
205
+ if not is_shotgun and API_KEY_FIELD in kwargs and api_key_value is not None:
206
+ # provider_enum is guaranteed to be non-None here since is_shotgun is False
207
+ if provider_enum is None:
208
+ raise RuntimeError("Provider enum should not be None for LLM providers")
157
209
  other_providers = [p for p in ProviderType if p != provider_enum]
158
210
  has_other_keys = any(self.has_provider_key(p) for p in other_providers)
159
211
  if not has_other_keys:
160
- config.default_provider = provider_enum
212
+ # Set selected_model to this provider's default model
213
+ from .models import ModelName
214
+
215
+ provider_models = {
216
+ ProviderType.OPENAI: ModelName.GPT_5,
217
+ ProviderType.ANTHROPIC: ModelName.CLAUDE_OPUS_4_1,
218
+ ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
219
+ }
220
+ if provider_enum in provider_models:
221
+ config.selected_model = provider_models[provider_enum]
161
222
 
162
223
  self.save(config)
163
224
 
164
225
  def clear_provider_key(self, provider: ProviderType | str) -> None:
165
- """Remove the API key for the given provider."""
226
+ """Remove the API key for the given provider (LLM provider or shotgun)."""
166
227
  config = self.load()
167
- provider_enum = self._ensure_provider_enum(provider)
168
- provider_config = self._get_provider_config(config, provider_enum)
228
+
229
+ # Get provider config (shotgun or LLM provider)
230
+ provider_config, _ = self._get_provider_config_and_type(config, provider)
231
+
169
232
  provider_config.api_key = None
170
233
  self.save(config)
171
234
 
235
+ def update_selected_model(self, model_name: "ModelName") -> None:
236
+ """Update the selected model.
237
+
238
+ Args:
239
+ model_name: Model to select
240
+ """
241
+ config = self.load()
242
+ config.selected_model = model_name
243
+ self.save(config)
244
+
172
245
  def has_provider_key(self, provider: ProviderType | str) -> bool:
173
246
  """Check if the given provider has a non-empty API key configured.
174
247
 
@@ -195,7 +268,8 @@ class ConfigManager:
195
268
  def has_any_provider_key(self) -> bool:
196
269
  """Determine whether any provider has a configured API key."""
197
270
  config = self.load()
198
- return any(
271
+ # Check LLM provider keys (BYOK)
272
+ has_llm_key = any(
199
273
  self._provider_has_api_key(self._get_provider_config(config, provider))
200
274
  for provider in (
201
275
  ProviderType.OPENAI,
@@ -203,6 +277,9 @@ class ConfigManager:
203
277
  ProviderType.GOOGLE,
204
278
  )
205
279
  )
280
+ # Also check Shotgun Account key
281
+ has_shotgun_key = self._provider_has_api_key(config.shotgun)
282
+ return has_llm_key or has_shotgun_key
206
283
 
207
284
  def initialize(self) -> ShotgunConfig:
208
285
  """Initialize configuration with defaults and save to file.
@@ -213,7 +290,6 @@ class ConfigManager:
213
290
  # Generate unique user ID for new config
214
291
  config = ShotgunConfig(
215
292
  user_id=str(uuid.uuid4()),
216
- config_version=1,
217
293
  )
218
294
  self.save(config)
219
295
  logger.info(
@@ -225,26 +301,26 @@ class ConfigManager:
225
301
 
226
302
  def _convert_secrets_to_secretstr(self, data: dict[str, Any]) -> None:
227
303
  """Convert plain text secrets in data to SecretStr objects."""
228
- for provider in [OPENAI_PROVIDER, ANTHROPIC_PROVIDER, GOOGLE_PROVIDER]:
229
- if provider in data and isinstance(data[provider], dict):
304
+ for section in ConfigSection:
305
+ if section.value in data and isinstance(data[section.value], dict):
230
306
  if (
231
- API_KEY_FIELD in data[provider]
232
- and data[provider][API_KEY_FIELD] is not None
307
+ API_KEY_FIELD in data[section.value]
308
+ and data[section.value][API_KEY_FIELD] is not None
233
309
  ):
234
- data[provider][API_KEY_FIELD] = SecretStr(
235
- data[provider][API_KEY_FIELD]
310
+ data[section.value][API_KEY_FIELD] = SecretStr(
311
+ data[section.value][API_KEY_FIELD]
236
312
  )
237
313
 
238
314
  def _convert_secretstr_to_plain(self, data: dict[str, Any]) -> None:
239
315
  """Convert SecretStr objects in data to plain text for JSON serialization."""
240
- for provider in [OPENAI_PROVIDER, ANTHROPIC_PROVIDER, GOOGLE_PROVIDER]:
241
- if provider in data and isinstance(data[provider], dict):
316
+ for section in ConfigSection:
317
+ if section.value in data and isinstance(data[section.value], dict):
242
318
  if (
243
- API_KEY_FIELD in data[provider]
244
- and data[provider][API_KEY_FIELD] is not None
319
+ API_KEY_FIELD in data[section.value]
320
+ and data[section.value][API_KEY_FIELD] is not None
245
321
  ):
246
- if hasattr(data[provider][API_KEY_FIELD], "get_secret_value"):
247
- data[provider][API_KEY_FIELD] = data[provider][
322
+ if hasattr(data[section.value][API_KEY_FIELD], "get_secret_value"):
323
+ data[section.value][API_KEY_FIELD] = data[section.value][
248
324
  API_KEY_FIELD
249
325
  ].get_secret_value()
250
326
 
@@ -279,6 +355,38 @@ class ConfigManager:
279
355
 
280
356
  return bool(value.strip())
281
357
 
358
+ def _is_shotgun_provider(self, provider: ProviderType | str) -> bool:
359
+ """Check if provider string represents Shotgun Account.
360
+
361
+ Args:
362
+ provider: Provider type or string
363
+
364
+ Returns:
365
+ True if provider is shotgun account
366
+ """
367
+ return (
368
+ isinstance(provider, str)
369
+ and provider.lower() == ConfigSection.SHOTGUN.value
370
+ )
371
+
372
+ def _get_provider_config_and_type(
373
+ self, config: ShotgunConfig, provider: ProviderType | str
374
+ ) -> tuple[ProviderConfig, bool]:
375
+ """Get provider config, handling shotgun as special case.
376
+
377
+ Args:
378
+ config: Shotgun configuration
379
+ provider: Provider type or string
380
+
381
+ Returns:
382
+ Tuple of (provider_config, is_shotgun)
383
+ """
384
+ if self._is_shotgun_provider(provider):
385
+ return (config.shotgun, True)
386
+
387
+ provider_enum = self._ensure_provider_enum(provider)
388
+ return (self._get_provider_config(config, provider_enum), False)
389
+
282
390
  def get_user_id(self) -> str:
283
391
  """Get the user ID from configuration.
284
392
 
@@ -0,0 +1,158 @@
1
+ """Pydantic models for configuration."""
2
+
3
+ from enum import StrEnum
4
+
5
+ from pydantic import BaseModel, Field, PrivateAttr, SecretStr
6
+ from pydantic_ai.models import Model
7
+
8
+
9
+ class ProviderType(StrEnum):
10
+ """Provider types for AI services."""
11
+
12
+ OPENAI = "openai"
13
+ ANTHROPIC = "anthropic"
14
+ GOOGLE = "google"
15
+
16
+
17
+ class KeyProvider(StrEnum):
18
+ """Authentication method for accessing AI models."""
19
+
20
+ BYOK = "byok" # Bring Your Own Key (individual provider keys)
21
+ SHOTGUN = "shotgun" # Shotgun Account (unified LiteLLM proxy)
22
+
23
+
24
+ class ModelName(StrEnum):
25
+ """Available AI model names."""
26
+
27
+ GPT_5 = "gpt-5"
28
+ GPT_5_MINI = "gpt-5-mini"
29
+ CLAUDE_OPUS_4_1 = "claude-opus-4-1"
30
+ GEMINI_2_5_PRO = "gemini-2.5-pro"
31
+ GEMINI_2_5_FLASH = "gemini-2.5-flash"
32
+
33
+
34
+ class ModelSpec(BaseModel):
35
+ """Static specification for a model - just metadata."""
36
+
37
+ name: ModelName # Model identifier
38
+ provider: ProviderType
39
+ max_input_tokens: int
40
+ max_output_tokens: int
41
+ litellm_proxy_model_name: (
42
+ str # LiteLLM format (e.g., "openai/gpt-5", "gemini/gemini-2-pro")
43
+ )
44
+
45
+
46
+ class ModelConfig(BaseModel):
47
+ """A fully configured model with API key and settings."""
48
+
49
+ name: ModelName # Model identifier
50
+ provider: ProviderType # Actual LLM provider (openai, anthropic, google)
51
+ key_provider: KeyProvider # Authentication method (byok or shotgun)
52
+ max_input_tokens: int
53
+ max_output_tokens: int
54
+ api_key: str
55
+ _model_instance: Model | None = PrivateAttr(default=None)
56
+
57
+ class Config:
58
+ arbitrary_types_allowed = True
59
+
60
+ @property
61
+ def model_instance(self) -> Model:
62
+ """Lazy load the Model instance."""
63
+ if self._model_instance is None:
64
+ from .provider import get_or_create_model
65
+
66
+ self._model_instance = get_or_create_model(
67
+ self.provider, self.key_provider, self.name, self.api_key
68
+ )
69
+ return self._model_instance
70
+
71
+ @property
72
+ def pydantic_model_name(self) -> str:
73
+ """Compute the full Pydantic AI model identifier. For backward compatibility."""
74
+ provider_prefix = {
75
+ ProviderType.OPENAI: "openai",
76
+ ProviderType.ANTHROPIC: "anthropic",
77
+ ProviderType.GOOGLE: "google-gla",
78
+ }
79
+ return f"{provider_prefix[self.provider]}:{self.name}"
80
+
81
+
82
+ # Model specifications registry (static metadata)
83
+ MODEL_SPECS: dict[ModelName, ModelSpec] = {
84
+ ModelName.GPT_5: ModelSpec(
85
+ name=ModelName.GPT_5,
86
+ provider=ProviderType.OPENAI,
87
+ max_input_tokens=400_000,
88
+ max_output_tokens=128_000,
89
+ litellm_proxy_model_name="openai/gpt-5",
90
+ ),
91
+ ModelName.GPT_5_MINI: ModelSpec(
92
+ name=ModelName.GPT_5_MINI,
93
+ provider=ProviderType.OPENAI,
94
+ max_input_tokens=400_000,
95
+ max_output_tokens=128_000,
96
+ litellm_proxy_model_name="openai/gpt-5-mini",
97
+ ),
98
+ ModelName.CLAUDE_OPUS_4_1: ModelSpec(
99
+ name=ModelName.CLAUDE_OPUS_4_1,
100
+ provider=ProviderType.ANTHROPIC,
101
+ max_input_tokens=200_000,
102
+ max_output_tokens=32_000,
103
+ litellm_proxy_model_name="anthropic/claude-opus-4-1",
104
+ ),
105
+ ModelName.GEMINI_2_5_PRO: ModelSpec(
106
+ name=ModelName.GEMINI_2_5_PRO,
107
+ provider=ProviderType.GOOGLE,
108
+ max_input_tokens=1_000_000,
109
+ max_output_tokens=64_000,
110
+ litellm_proxy_model_name="gemini/gemini-2.5-pro",
111
+ ),
112
+ ModelName.GEMINI_2_5_FLASH: ModelSpec(
113
+ name=ModelName.GEMINI_2_5_FLASH,
114
+ provider=ProviderType.GOOGLE,
115
+ max_input_tokens=1_000_000,
116
+ max_output_tokens=64_000,
117
+ litellm_proxy_model_name="gemini/gemini-2.5-flash",
118
+ ),
119
+ }
120
+
121
+
122
+ class OpenAIConfig(BaseModel):
123
+ """Configuration for OpenAI provider."""
124
+
125
+ api_key: SecretStr | None = None
126
+
127
+
128
+ class AnthropicConfig(BaseModel):
129
+ """Configuration for Anthropic provider."""
130
+
131
+ api_key: SecretStr | None = None
132
+
133
+
134
+ class GoogleConfig(BaseModel):
135
+ """Configuration for Google provider."""
136
+
137
+ api_key: SecretStr | None = None
138
+
139
+
140
+ class ShotgunAccountConfig(BaseModel):
141
+ """Configuration for Shotgun Account (LiteLLM proxy)."""
142
+
143
+ api_key: SecretStr | None = None
144
+
145
+
146
+ class ShotgunConfig(BaseModel):
147
+ """Main configuration for Shotgun CLI."""
148
+
149
+ openai: OpenAIConfig = Field(default_factory=OpenAIConfig)
150
+ anthropic: AnthropicConfig = Field(default_factory=AnthropicConfig)
151
+ google: GoogleConfig = Field(default_factory=GoogleConfig)
152
+ shotgun: ShotgunAccountConfig = Field(default_factory=ShotgunAccountConfig)
153
+ selected_model: ModelName | None = Field(
154
+ default=None,
155
+ description="User-selected model",
156
+ )
157
+ user_id: str = Field(description="Unique anonymous user identifier")
158
+ config_version: int = Field(default=2, description="Configuration schema version")