shotgun-sh 0.1.16.dev2__tar.gz → 0.2.1__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.
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/.gitignore +3 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/PKG-INFO +2 -2
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/pyproject.toml +2 -2
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/common.py +4 -5
- shotgun_sh-0.2.1/src/shotgun/agents/config/constants.py +34 -0
- shotgun_sh-0.2.1/src/shotgun/agents/config/manager.py +457 -0
- shotgun_sh-0.2.1/src/shotgun/agents/config/models.py +175 -0
- shotgun_sh-0.2.1/src/shotgun/agents/config/provider.py +295 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/compaction.py +1 -1
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/history_processors.py +18 -9
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/__init__.py +31 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/anthropic.py +89 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/base.py +67 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/openai.py +80 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +119 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/tokenizer_cache.py +90 -0
- shotgun_sh-0.2.1/src/shotgun/agents/history/token_counting/utils.py +147 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/token_estimation.py +12 -12
- shotgun_sh-0.2.1/src/shotgun/agents/llm.py +62 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/models.py +2 -2
- shotgun_sh-0.2.1/src/shotgun/agents/tools/web_search/__init__.py +87 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/web_search/anthropic.py +54 -50
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/web_search/gemini.py +31 -20
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/web_search/openai.py +4 -4
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/build_constants.py +2 -2
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/config.py +34 -63
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/feedback.py +4 -2
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/models.py +2 -2
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/ingestor.py +47 -8
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/manager.py +7 -3
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/models.py +4 -4
- shotgun_sh-0.2.1/src/shotgun/llm_proxy/__init__.py +16 -0
- shotgun_sh-0.2.1/src/shotgun/llm_proxy/clients.py +39 -0
- shotgun_sh-0.2.1/src/shotgun/llm_proxy/constants.py +8 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/main.py +6 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/posthog_telemetry.py +15 -11
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sentry_telemetry.py +3 -3
- shotgun_sh-0.2.1/src/shotgun/shotgun_web/__init__.py +19 -0
- shotgun_sh-0.2.1/src/shotgun/shotgun_web/client.py +138 -0
- shotgun_sh-0.2.1/src/shotgun/shotgun_web/constants.py +17 -0
- shotgun_sh-0.2.1/src/shotgun/shotgun_web/models.py +47 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/telemetry.py +7 -4
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/app.py +26 -8
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat.py +2 -8
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat_screen/command_providers.py +118 -11
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat_screen/history.py +3 -1
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/feedback.py +2 -2
- shotgun_sh-0.2.1/src/shotgun/tui/screens/model_picker.py +327 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/provider_config.py +118 -28
- shotgun_sh-0.2.1/src/shotgun/tui/screens/shotgun_auth.py +295 -0
- shotgun_sh-0.2.1/src/shotgun/tui/screens/welcome.py +176 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/utils/env_utils.py +12 -0
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/config/constants.py +0 -17
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/config/manager.py +0 -294
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/config/models.py +0 -185
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/config/provider.py +0 -206
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/history/token_counting.py +0 -429
- shotgun_sh-0.1.16.dev2/src/shotgun/agents/tools/web_search/__init__.py +0 -60
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/LICENSE +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/README.md +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/hatch_build.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/agent_manager.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/user_interaction.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.1.16.dev2 → shotgun_sh-0.2.1}/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
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -23,7 +23,6 @@ Classifier: Topic :: Utilities
|
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
24
|
Requires-Dist: anthropic>=0.39.0
|
|
25
25
|
Requires-Dist: genai-prices>=0.0.27
|
|
26
|
-
Requires-Dist: google-generativeai>=0.8.5
|
|
27
26
|
Requires-Dist: httpx>=0.27.0
|
|
28
27
|
Requires-Dist: jinja2>=3.1.0
|
|
29
28
|
Requires-Dist: kuzu>=0.7.0
|
|
@@ -33,6 +32,7 @@ Requires-Dist: packaging>=23.0
|
|
|
33
32
|
Requires-Dist: posthog>=3.0.0
|
|
34
33
|
Requires-Dist: pydantic-ai>=0.0.14
|
|
35
34
|
Requires-Dist: rich>=13.0.0
|
|
35
|
+
Requires-Dist: sentencepiece>=0.2.0
|
|
36
36
|
Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
|
|
37
37
|
Requires-Dist: textual-dev>=1.7.0
|
|
38
38
|
Requires-Dist: textual>=6.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "shotgun-sh"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
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
|
"genai-prices>=0.0.27",
|
|
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,
|
|
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
|
|
118
|
+
# Get configured model or fall back to first available provider
|
|
119
119
|
try:
|
|
120
120
|
model_config = get_provider_model(provider)
|
|
121
|
-
|
|
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,34 @@
|
|
|
1
|
+
"""Configuration constants for Shotgun agents."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum, auto
|
|
4
|
+
|
|
5
|
+
# Field names
|
|
6
|
+
API_KEY_FIELD = "api_key"
|
|
7
|
+
SUPABASE_JWT_FIELD = "supabase_jwt"
|
|
8
|
+
SHOTGUN_INSTANCE_ID_FIELD = "shotgun_instance_id"
|
|
9
|
+
CONFIG_VERSION_FIELD = "config_version"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigSection(StrEnum):
|
|
13
|
+
"""Configuration file section names (JSON keys)."""
|
|
14
|
+
|
|
15
|
+
OPENAI = auto()
|
|
16
|
+
ANTHROPIC = auto()
|
|
17
|
+
GOOGLE = auto()
|
|
18
|
+
SHOTGUN = auto()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Backwards compatibility - deprecated
|
|
22
|
+
OPENAI_PROVIDER = ConfigSection.OPENAI.value
|
|
23
|
+
ANTHROPIC_PROVIDER = ConfigSection.ANTHROPIC.value
|
|
24
|
+
GOOGLE_PROVIDER = ConfigSection.GOOGLE.value
|
|
25
|
+
SHOTGUN_PROVIDER = ConfigSection.SHOTGUN.value
|
|
26
|
+
|
|
27
|
+
# Environment variable names
|
|
28
|
+
OPENAI_API_KEY_ENV = "OPENAI_API_KEY"
|
|
29
|
+
ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY"
|
|
30
|
+
GEMINI_API_KEY_ENV = "GEMINI_API_KEY"
|
|
31
|
+
SHOTGUN_API_KEY_ENV = "SHOTGUN_API_KEY"
|
|
32
|
+
|
|
33
|
+
# Token limits
|
|
34
|
+
MEDIUM_TEXT_8K_TOKENS = 8192 # Default max_tokens for web search requests
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""Configuration manager for Shotgun CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import SecretStr
|
|
9
|
+
|
|
10
|
+
from shotgun.logging_config import get_logger
|
|
11
|
+
from shotgun.utils import get_shotgun_home
|
|
12
|
+
|
|
13
|
+
from .constants import (
|
|
14
|
+
API_KEY_FIELD,
|
|
15
|
+
SHOTGUN_INSTANCE_ID_FIELD,
|
|
16
|
+
SUPABASE_JWT_FIELD,
|
|
17
|
+
ConfigSection,
|
|
18
|
+
)
|
|
19
|
+
from .models import (
|
|
20
|
+
AnthropicConfig,
|
|
21
|
+
GoogleConfig,
|
|
22
|
+
ModelName,
|
|
23
|
+
OpenAIConfig,
|
|
24
|
+
ProviderType,
|
|
25
|
+
ShotgunAccountConfig,
|
|
26
|
+
ShotgunConfig,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
# Type alias for provider configuration objects
|
|
32
|
+
ProviderConfig = OpenAIConfig | AnthropicConfig | GoogleConfig | ShotgunAccountConfig
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ConfigManager:
|
|
36
|
+
"""Manager for Shotgun configuration."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config_path: Path | None = None):
|
|
39
|
+
"""Initialize ConfigManager.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config_path: Path to config file. If None, uses default ~/.shotgun-sh/config.json
|
|
43
|
+
"""
|
|
44
|
+
if config_path is None:
|
|
45
|
+
self.config_path = get_shotgun_home() / "config.json"
|
|
46
|
+
else:
|
|
47
|
+
self.config_path = config_path
|
|
48
|
+
|
|
49
|
+
self._config: ShotgunConfig | None = None
|
|
50
|
+
|
|
51
|
+
def load(self, force_reload: bool = True) -> ShotgunConfig:
|
|
52
|
+
"""Load configuration from file.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
force_reload: If True, reload from disk even if cached (default: True)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
ShotgunConfig: Loaded configuration or default config if file doesn't exist
|
|
59
|
+
"""
|
|
60
|
+
if self._config is not None and not force_reload:
|
|
61
|
+
return self._config
|
|
62
|
+
|
|
63
|
+
if not self.config_path.exists():
|
|
64
|
+
logger.info(
|
|
65
|
+
"Configuration file not found, creating new config at: %s",
|
|
66
|
+
self.config_path,
|
|
67
|
+
)
|
|
68
|
+
# Create new config with generated shotgun_instance_id
|
|
69
|
+
self._config = self.initialize()
|
|
70
|
+
return self._config
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
with open(self.config_path, encoding="utf-8") as f:
|
|
74
|
+
data = json.load(f)
|
|
75
|
+
|
|
76
|
+
# Migration: Rename user_id to shotgun_instance_id (config v2 -> v3)
|
|
77
|
+
if "user_id" in data and SHOTGUN_INSTANCE_ID_FIELD not in data:
|
|
78
|
+
data[SHOTGUN_INSTANCE_ID_FIELD] = data.pop("user_id")
|
|
79
|
+
data["config_version"] = 3
|
|
80
|
+
logger.info(
|
|
81
|
+
"Migrated config v2->v3: renamed user_id to shotgun_instance_id"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Convert plain text secrets to SecretStr objects
|
|
85
|
+
self._convert_secrets_to_secretstr(data)
|
|
86
|
+
|
|
87
|
+
self._config = ShotgunConfig.model_validate(data)
|
|
88
|
+
logger.debug("Configuration loaded successfully from %s", self.config_path)
|
|
89
|
+
|
|
90
|
+
# Validate selected_model if in BYOK mode (no Shotgun key)
|
|
91
|
+
if not self._provider_has_api_key(self._config.shotgun):
|
|
92
|
+
should_save = False
|
|
93
|
+
|
|
94
|
+
# If selected_model is set, verify its provider has a key
|
|
95
|
+
if self._config.selected_model:
|
|
96
|
+
from .models import MODEL_SPECS
|
|
97
|
+
|
|
98
|
+
if self._config.selected_model in MODEL_SPECS:
|
|
99
|
+
spec = MODEL_SPECS[self._config.selected_model]
|
|
100
|
+
if not self.has_provider_key(spec.provider):
|
|
101
|
+
logger.info(
|
|
102
|
+
"Selected model %s provider has no API key, finding available model",
|
|
103
|
+
self._config.selected_model.value,
|
|
104
|
+
)
|
|
105
|
+
self._config.selected_model = None
|
|
106
|
+
should_save = True
|
|
107
|
+
else:
|
|
108
|
+
logger.info(
|
|
109
|
+
"Selected model %s not found in MODEL_SPECS, resetting",
|
|
110
|
+
self._config.selected_model.value,
|
|
111
|
+
)
|
|
112
|
+
self._config.selected_model = None
|
|
113
|
+
should_save = True
|
|
114
|
+
|
|
115
|
+
# If no selected_model or it was invalid, find first available model
|
|
116
|
+
if not self._config.selected_model:
|
|
117
|
+
for provider in ProviderType:
|
|
118
|
+
if self.has_provider_key(provider):
|
|
119
|
+
# Set to that provider's default model
|
|
120
|
+
from .models import MODEL_SPECS, ModelName
|
|
121
|
+
|
|
122
|
+
# Find default model for this provider
|
|
123
|
+
provider_models = {
|
|
124
|
+
ProviderType.OPENAI: ModelName.GPT_5,
|
|
125
|
+
ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
|
|
126
|
+
ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if provider in provider_models:
|
|
130
|
+
self._config.selected_model = provider_models[provider]
|
|
131
|
+
logger.info(
|
|
132
|
+
"Set selected_model to %s (first available provider)",
|
|
133
|
+
self._config.selected_model.value,
|
|
134
|
+
)
|
|
135
|
+
should_save = True
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
if should_save:
|
|
139
|
+
self.save(self._config)
|
|
140
|
+
|
|
141
|
+
return self._config
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(
|
|
145
|
+
"Failed to load configuration from %s: %s", self.config_path, e
|
|
146
|
+
)
|
|
147
|
+
logger.info("Creating new configuration with generated shotgun_instance_id")
|
|
148
|
+
self._config = self.initialize()
|
|
149
|
+
return self._config
|
|
150
|
+
|
|
151
|
+
def save(self, config: ShotgunConfig | None = None) -> None:
|
|
152
|
+
"""Save configuration to file.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
config: Configuration to save. If None, saves current loaded config
|
|
156
|
+
"""
|
|
157
|
+
if config is None:
|
|
158
|
+
if self._config:
|
|
159
|
+
config = self._config
|
|
160
|
+
else:
|
|
161
|
+
# Create a new config with generated shotgun_instance_id
|
|
162
|
+
config = ShotgunConfig(
|
|
163
|
+
shotgun_instance_id=str(uuid.uuid4()),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Ensure directory exists
|
|
167
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
# Convert SecretStr to plain text for JSON serialization
|
|
171
|
+
data = config.model_dump()
|
|
172
|
+
self._convert_secretstr_to_plain(data)
|
|
173
|
+
|
|
174
|
+
with open(self.config_path, "w", encoding="utf-8") as f:
|
|
175
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
176
|
+
|
|
177
|
+
logger.debug("Configuration saved to %s", self.config_path)
|
|
178
|
+
self._config = config
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.error("Failed to save configuration to %s: %s", self.config_path, e)
|
|
182
|
+
raise
|
|
183
|
+
|
|
184
|
+
def update_provider(self, provider: ProviderType | str, **kwargs: Any) -> None:
|
|
185
|
+
"""Update provider configuration.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
provider: Provider to update
|
|
189
|
+
**kwargs: Configuration fields to update (only api_key supported)
|
|
190
|
+
"""
|
|
191
|
+
config = self.load()
|
|
192
|
+
|
|
193
|
+
# Get provider config and check if it's shotgun
|
|
194
|
+
provider_config, is_shotgun = self._get_provider_config_and_type(
|
|
195
|
+
config, provider
|
|
196
|
+
)
|
|
197
|
+
# For non-shotgun providers, we need the enum for default provider logic
|
|
198
|
+
provider_enum = None if is_shotgun else self._ensure_provider_enum(provider)
|
|
199
|
+
|
|
200
|
+
# Only support api_key updates
|
|
201
|
+
if API_KEY_FIELD in kwargs:
|
|
202
|
+
api_key_value = kwargs[API_KEY_FIELD]
|
|
203
|
+
provider_config.api_key = (
|
|
204
|
+
SecretStr(api_key_value) if api_key_value is not None else None
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Reject other fields
|
|
208
|
+
unsupported_fields = set(kwargs.keys()) - {API_KEY_FIELD}
|
|
209
|
+
if unsupported_fields:
|
|
210
|
+
raise ValueError(f"Unsupported configuration fields: {unsupported_fields}")
|
|
211
|
+
|
|
212
|
+
# If no other providers have keys configured and we just added one,
|
|
213
|
+
# set selected_model to that provider's default model (only for LLM providers, not shotgun)
|
|
214
|
+
if not is_shotgun and API_KEY_FIELD in kwargs and api_key_value is not None:
|
|
215
|
+
# provider_enum is guaranteed to be non-None here since is_shotgun is False
|
|
216
|
+
if provider_enum is None:
|
|
217
|
+
raise RuntimeError("Provider enum should not be None for LLM providers")
|
|
218
|
+
other_providers = [p for p in ProviderType if p != provider_enum]
|
|
219
|
+
has_other_keys = any(self.has_provider_key(p) for p in other_providers)
|
|
220
|
+
if not has_other_keys:
|
|
221
|
+
# Set selected_model to this provider's default model
|
|
222
|
+
from .models import ModelName
|
|
223
|
+
|
|
224
|
+
provider_models = {
|
|
225
|
+
ProviderType.OPENAI: ModelName.GPT_5,
|
|
226
|
+
ProviderType.ANTHROPIC: ModelName.CLAUDE_SONNET_4_5,
|
|
227
|
+
ProviderType.GOOGLE: ModelName.GEMINI_2_5_PRO,
|
|
228
|
+
}
|
|
229
|
+
if provider_enum in provider_models:
|
|
230
|
+
config.selected_model = provider_models[provider_enum]
|
|
231
|
+
|
|
232
|
+
self.save(config)
|
|
233
|
+
|
|
234
|
+
def clear_provider_key(self, provider: ProviderType | str) -> None:
|
|
235
|
+
"""Remove the API key for the given provider (LLM provider or shotgun)."""
|
|
236
|
+
config = self.load()
|
|
237
|
+
|
|
238
|
+
# Get provider config (shotgun or LLM provider)
|
|
239
|
+
provider_config, _ = self._get_provider_config_and_type(config, provider)
|
|
240
|
+
|
|
241
|
+
provider_config.api_key = None
|
|
242
|
+
self.save(config)
|
|
243
|
+
|
|
244
|
+
def update_selected_model(self, model_name: "ModelName") -> None:
|
|
245
|
+
"""Update the selected model.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
model_name: Model to select
|
|
249
|
+
"""
|
|
250
|
+
config = self.load()
|
|
251
|
+
config.selected_model = model_name
|
|
252
|
+
self.save(config)
|
|
253
|
+
|
|
254
|
+
def has_provider_key(self, provider: ProviderType | str) -> bool:
|
|
255
|
+
"""Check if the given provider has a non-empty API key configured.
|
|
256
|
+
|
|
257
|
+
This checks only the configuration file.
|
|
258
|
+
"""
|
|
259
|
+
# Use force_reload=False to avoid infinite loop when called from load()
|
|
260
|
+
config = self.load(force_reload=False)
|
|
261
|
+
provider_enum = self._ensure_provider_enum(provider)
|
|
262
|
+
provider_config = self._get_provider_config(config, provider_enum)
|
|
263
|
+
|
|
264
|
+
return self._provider_has_api_key(provider_config)
|
|
265
|
+
|
|
266
|
+
def has_any_provider_key(self) -> bool:
|
|
267
|
+
"""Determine whether any provider has a configured API key."""
|
|
268
|
+
# Use force_reload=False to avoid infinite loop when called from load()
|
|
269
|
+
config = self.load(force_reload=False)
|
|
270
|
+
# Check LLM provider keys (BYOK)
|
|
271
|
+
has_llm_key = any(
|
|
272
|
+
self._provider_has_api_key(self._get_provider_config(config, provider))
|
|
273
|
+
for provider in (
|
|
274
|
+
ProviderType.OPENAI,
|
|
275
|
+
ProviderType.ANTHROPIC,
|
|
276
|
+
ProviderType.GOOGLE,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
# Also check Shotgun Account key
|
|
280
|
+
has_shotgun_key = self._provider_has_api_key(config.shotgun)
|
|
281
|
+
return has_llm_key or has_shotgun_key
|
|
282
|
+
|
|
283
|
+
def initialize(self) -> ShotgunConfig:
|
|
284
|
+
"""Initialize configuration with defaults and save to file.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Default ShotgunConfig
|
|
288
|
+
"""
|
|
289
|
+
# Generate unique shotgun instance ID for new config
|
|
290
|
+
config = ShotgunConfig(
|
|
291
|
+
shotgun_instance_id=str(uuid.uuid4()),
|
|
292
|
+
)
|
|
293
|
+
self.save(config)
|
|
294
|
+
logger.info(
|
|
295
|
+
"Configuration initialized at %s with shotgun_instance_id: %s",
|
|
296
|
+
self.config_path,
|
|
297
|
+
config.shotgun_instance_id,
|
|
298
|
+
)
|
|
299
|
+
return config
|
|
300
|
+
|
|
301
|
+
def _convert_secrets_to_secretstr(self, data: dict[str, Any]) -> None:
|
|
302
|
+
"""Convert plain text secrets in data to SecretStr objects."""
|
|
303
|
+
for section in ConfigSection:
|
|
304
|
+
if section.value in data and isinstance(data[section.value], dict):
|
|
305
|
+
# Convert API key
|
|
306
|
+
if (
|
|
307
|
+
API_KEY_FIELD in data[section.value]
|
|
308
|
+
and data[section.value][API_KEY_FIELD] is not None
|
|
309
|
+
):
|
|
310
|
+
data[section.value][API_KEY_FIELD] = SecretStr(
|
|
311
|
+
data[section.value][API_KEY_FIELD]
|
|
312
|
+
)
|
|
313
|
+
# Convert supabase JWT (shotgun section only)
|
|
314
|
+
if (
|
|
315
|
+
section == ConfigSection.SHOTGUN
|
|
316
|
+
and SUPABASE_JWT_FIELD in data[section.value]
|
|
317
|
+
and data[section.value][SUPABASE_JWT_FIELD] is not None
|
|
318
|
+
):
|
|
319
|
+
data[section.value][SUPABASE_JWT_FIELD] = SecretStr(
|
|
320
|
+
data[section.value][SUPABASE_JWT_FIELD]
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def _convert_secretstr_to_plain(self, data: dict[str, Any]) -> None:
|
|
324
|
+
"""Convert SecretStr objects in data to plain text for JSON serialization."""
|
|
325
|
+
for section in ConfigSection:
|
|
326
|
+
if section.value in data and isinstance(data[section.value], dict):
|
|
327
|
+
# Convert API key
|
|
328
|
+
if (
|
|
329
|
+
API_KEY_FIELD in data[section.value]
|
|
330
|
+
and data[section.value][API_KEY_FIELD] is not None
|
|
331
|
+
):
|
|
332
|
+
if hasattr(data[section.value][API_KEY_FIELD], "get_secret_value"):
|
|
333
|
+
data[section.value][API_KEY_FIELD] = data[section.value][
|
|
334
|
+
API_KEY_FIELD
|
|
335
|
+
].get_secret_value()
|
|
336
|
+
# Convert supabase JWT (shotgun section only)
|
|
337
|
+
if (
|
|
338
|
+
section == ConfigSection.SHOTGUN
|
|
339
|
+
and SUPABASE_JWT_FIELD in data[section.value]
|
|
340
|
+
and data[section.value][SUPABASE_JWT_FIELD] is not None
|
|
341
|
+
):
|
|
342
|
+
if hasattr(
|
|
343
|
+
data[section.value][SUPABASE_JWT_FIELD], "get_secret_value"
|
|
344
|
+
):
|
|
345
|
+
data[section.value][SUPABASE_JWT_FIELD] = data[section.value][
|
|
346
|
+
SUPABASE_JWT_FIELD
|
|
347
|
+
].get_secret_value()
|
|
348
|
+
|
|
349
|
+
def _ensure_provider_enum(self, provider: ProviderType | str) -> ProviderType:
|
|
350
|
+
"""Normalize provider values to ProviderType enum."""
|
|
351
|
+
return (
|
|
352
|
+
provider if isinstance(provider, ProviderType) else ProviderType(provider)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _get_provider_config(
|
|
356
|
+
self, config: ShotgunConfig, provider: ProviderType
|
|
357
|
+
) -> Any:
|
|
358
|
+
"""Retrieve the provider-specific configuration section."""
|
|
359
|
+
if provider == ProviderType.OPENAI:
|
|
360
|
+
return config.openai
|
|
361
|
+
if provider == ProviderType.ANTHROPIC:
|
|
362
|
+
return config.anthropic
|
|
363
|
+
if provider == ProviderType.GOOGLE:
|
|
364
|
+
return config.google
|
|
365
|
+
raise ValueError(f"Unsupported provider: {provider}")
|
|
366
|
+
|
|
367
|
+
def _provider_has_api_key(self, provider_config: Any) -> bool:
|
|
368
|
+
"""Return True if the provider config contains a usable API key."""
|
|
369
|
+
api_key = getattr(provider_config, API_KEY_FIELD, None)
|
|
370
|
+
if api_key is None:
|
|
371
|
+
return False
|
|
372
|
+
|
|
373
|
+
if isinstance(api_key, SecretStr):
|
|
374
|
+
value = api_key.get_secret_value()
|
|
375
|
+
else:
|
|
376
|
+
value = str(api_key)
|
|
377
|
+
|
|
378
|
+
return bool(value.strip())
|
|
379
|
+
|
|
380
|
+
def _is_shotgun_provider(self, provider: ProviderType | str) -> bool:
|
|
381
|
+
"""Check if provider string represents Shotgun Account.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
provider: Provider type or string
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
True if provider is shotgun account
|
|
388
|
+
"""
|
|
389
|
+
return (
|
|
390
|
+
isinstance(provider, str)
|
|
391
|
+
and provider.lower() == ConfigSection.SHOTGUN.value
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
def _get_provider_config_and_type(
|
|
395
|
+
self, config: ShotgunConfig, provider: ProviderType | str
|
|
396
|
+
) -> tuple[ProviderConfig, bool]:
|
|
397
|
+
"""Get provider config, handling shotgun as special case.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
config: Shotgun configuration
|
|
401
|
+
provider: Provider type or string
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Tuple of (provider_config, is_shotgun)
|
|
405
|
+
"""
|
|
406
|
+
if self._is_shotgun_provider(provider):
|
|
407
|
+
return (config.shotgun, True)
|
|
408
|
+
|
|
409
|
+
provider_enum = self._ensure_provider_enum(provider)
|
|
410
|
+
return (self._get_provider_config(config, provider_enum), False)
|
|
411
|
+
|
|
412
|
+
def get_shotgun_instance_id(self) -> str:
|
|
413
|
+
"""Get the shotgun instance ID from configuration.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
The unique shotgun instance ID string
|
|
417
|
+
"""
|
|
418
|
+
config = self.load()
|
|
419
|
+
return config.shotgun_instance_id
|
|
420
|
+
|
|
421
|
+
def update_shotgun_account(
|
|
422
|
+
self, api_key: str | None = None, supabase_jwt: str | None = None
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Update Shotgun Account configuration.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
api_key: LiteLLM proxy API key (optional)
|
|
428
|
+
supabase_jwt: Supabase authentication JWT (optional)
|
|
429
|
+
"""
|
|
430
|
+
config = self.load()
|
|
431
|
+
|
|
432
|
+
if api_key is not None:
|
|
433
|
+
config.shotgun.api_key = SecretStr(api_key) if api_key else None
|
|
434
|
+
|
|
435
|
+
if supabase_jwt is not None:
|
|
436
|
+
config.shotgun.supabase_jwt = (
|
|
437
|
+
SecretStr(supabase_jwt) if supabase_jwt else None
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
self.save(config)
|
|
441
|
+
logger.info("Updated Shotgun Account configuration")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# Global singleton instance
|
|
445
|
+
_config_manager_instance: ConfigManager | None = None
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def get_config_manager() -> ConfigManager:
|
|
449
|
+
"""Get the global singleton ConfigManager instance.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
The singleton ConfigManager instance
|
|
453
|
+
"""
|
|
454
|
+
global _config_manager_instance
|
|
455
|
+
if _config_manager_instance is None:
|
|
456
|
+
_config_manager_instance = ConfigManager()
|
|
457
|
+
return _config_manager_instance
|