shotgun-sh 0.1.9__py3-none-any.whl → 0.2.11__py3-none-any.whl

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 (150) hide show
  1. shotgun/agents/agent_manager.py +761 -52
  2. shotgun/agents/common.py +80 -75
  3. shotgun/agents/config/constants.py +21 -10
  4. shotgun/agents/config/manager.py +322 -97
  5. shotgun/agents/config/models.py +114 -84
  6. shotgun/agents/config/provider.py +232 -88
  7. shotgun/agents/context_analyzer/__init__.py +28 -0
  8. shotgun/agents/context_analyzer/analyzer.py +471 -0
  9. shotgun/agents/context_analyzer/constants.py +9 -0
  10. shotgun/agents/context_analyzer/formatter.py +115 -0
  11. shotgun/agents/context_analyzer/models.py +212 -0
  12. shotgun/agents/conversation_history.py +125 -2
  13. shotgun/agents/conversation_manager.py +57 -19
  14. shotgun/agents/export.py +6 -7
  15. shotgun/agents/history/compaction.py +23 -3
  16. shotgun/agents/history/context_extraction.py +93 -6
  17. shotgun/agents/history/history_processors.py +179 -11
  18. shotgun/agents/history/token_counting/__init__.py +31 -0
  19. shotgun/agents/history/token_counting/anthropic.py +127 -0
  20. shotgun/agents/history/token_counting/base.py +78 -0
  21. shotgun/agents/history/token_counting/openai.py +90 -0
  22. shotgun/agents/history/token_counting/sentencepiece_counter.py +127 -0
  23. shotgun/agents/history/token_counting/tokenizer_cache.py +92 -0
  24. shotgun/agents/history/token_counting/utils.py +144 -0
  25. shotgun/agents/history/token_estimation.py +12 -12
  26. shotgun/agents/llm.py +62 -0
  27. shotgun/agents/models.py +59 -4
  28. shotgun/agents/plan.py +6 -7
  29. shotgun/agents/research.py +7 -8
  30. shotgun/agents/specify.py +6 -7
  31. shotgun/agents/tasks.py +6 -7
  32. shotgun/agents/tools/__init__.py +0 -2
  33. shotgun/agents/tools/codebase/codebase_shell.py +6 -0
  34. shotgun/agents/tools/codebase/directory_lister.py +6 -0
  35. shotgun/agents/tools/codebase/file_read.py +11 -2
  36. shotgun/agents/tools/codebase/query_graph.py +6 -0
  37. shotgun/agents/tools/codebase/retrieve_code.py +6 -0
  38. shotgun/agents/tools/file_management.py +82 -16
  39. shotgun/agents/tools/registry.py +217 -0
  40. shotgun/agents/tools/web_search/__init__.py +55 -16
  41. shotgun/agents/tools/web_search/anthropic.py +76 -51
  42. shotgun/agents/tools/web_search/gemini.py +50 -27
  43. shotgun/agents/tools/web_search/openai.py +26 -17
  44. shotgun/agents/tools/web_search/utils.py +2 -2
  45. shotgun/agents/usage_manager.py +164 -0
  46. shotgun/api_endpoints.py +15 -0
  47. shotgun/cli/clear.py +53 -0
  48. shotgun/cli/codebase/commands.py +71 -2
  49. shotgun/cli/compact.py +186 -0
  50. shotgun/cli/config.py +41 -67
  51. shotgun/cli/context.py +111 -0
  52. shotgun/cli/export.py +1 -1
  53. shotgun/cli/feedback.py +50 -0
  54. shotgun/cli/models.py +3 -2
  55. shotgun/cli/plan.py +1 -1
  56. shotgun/cli/research.py +1 -1
  57. shotgun/cli/specify.py +1 -1
  58. shotgun/cli/tasks.py +1 -1
  59. shotgun/cli/update.py +18 -5
  60. shotgun/codebase/core/change_detector.py +5 -3
  61. shotgun/codebase/core/code_retrieval.py +4 -2
  62. shotgun/codebase/core/ingestor.py +169 -19
  63. shotgun/codebase/core/manager.py +177 -13
  64. shotgun/codebase/core/nl_query.py +1 -1
  65. shotgun/codebase/models.py +28 -3
  66. shotgun/codebase/service.py +14 -2
  67. shotgun/exceptions.py +32 -0
  68. shotgun/llm_proxy/__init__.py +19 -0
  69. shotgun/llm_proxy/clients.py +44 -0
  70. shotgun/llm_proxy/constants.py +15 -0
  71. shotgun/logging_config.py +18 -27
  72. shotgun/main.py +91 -4
  73. shotgun/posthog_telemetry.py +87 -40
  74. shotgun/prompts/agents/export.j2 +18 -1
  75. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
  76. shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
  77. shotgun/prompts/agents/plan.j2 +1 -1
  78. shotgun/prompts/agents/research.j2 +1 -1
  79. shotgun/prompts/agents/specify.j2 +270 -3
  80. shotgun/prompts/agents/state/system_state.j2 +4 -0
  81. shotgun/prompts/agents/tasks.j2 +1 -1
  82. shotgun/prompts/codebase/partials/cypher_rules.j2 +13 -0
  83. shotgun/prompts/loader.py +2 -2
  84. shotgun/prompts/tools/web_search.j2 +14 -0
  85. shotgun/sdk/codebase.py +60 -2
  86. shotgun/sentry_telemetry.py +28 -21
  87. shotgun/settings.py +238 -0
  88. shotgun/shotgun_web/__init__.py +19 -0
  89. shotgun/shotgun_web/client.py +138 -0
  90. shotgun/shotgun_web/constants.py +21 -0
  91. shotgun/shotgun_web/models.py +47 -0
  92. shotgun/telemetry.py +24 -36
  93. shotgun/tui/app.py +275 -23
  94. shotgun/tui/commands/__init__.py +1 -1
  95. shotgun/tui/components/context_indicator.py +179 -0
  96. shotgun/tui/components/mode_indicator.py +70 -0
  97. shotgun/tui/components/status_bar.py +48 -0
  98. shotgun/tui/components/vertical_tail.py +6 -0
  99. shotgun/tui/containers.py +91 -0
  100. shotgun/tui/dependencies.py +39 -0
  101. shotgun/tui/filtered_codebase_service.py +46 -0
  102. shotgun/tui/protocols.py +45 -0
  103. shotgun/tui/screens/chat/__init__.py +5 -0
  104. shotgun/tui/screens/chat/chat.tcss +54 -0
  105. shotgun/tui/screens/chat/chat_screen.py +1234 -0
  106. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +64 -0
  107. shotgun/tui/screens/chat/codebase_index_selection.py +12 -0
  108. shotgun/tui/screens/chat/help_text.py +40 -0
  109. shotgun/tui/screens/chat/prompt_history.py +48 -0
  110. shotgun/tui/screens/chat.tcss +11 -0
  111. shotgun/tui/screens/chat_screen/command_providers.py +226 -11
  112. shotgun/tui/screens/chat_screen/history/__init__.py +22 -0
  113. shotgun/tui/screens/chat_screen/history/agent_response.py +66 -0
  114. shotgun/tui/screens/chat_screen/history/chat_history.py +116 -0
  115. shotgun/tui/screens/chat_screen/history/formatters.py +115 -0
  116. shotgun/tui/screens/chat_screen/history/partial_response.py +43 -0
  117. shotgun/tui/screens/chat_screen/history/user_question.py +42 -0
  118. shotgun/tui/screens/confirmation_dialog.py +151 -0
  119. shotgun/tui/screens/feedback.py +193 -0
  120. shotgun/tui/screens/github_issue.py +102 -0
  121. shotgun/tui/screens/model_picker.py +352 -0
  122. shotgun/tui/screens/onboarding.py +431 -0
  123. shotgun/tui/screens/pipx_migration.py +153 -0
  124. shotgun/tui/screens/provider_config.py +156 -39
  125. shotgun/tui/screens/shotgun_auth.py +295 -0
  126. shotgun/tui/screens/welcome.py +198 -0
  127. shotgun/tui/services/__init__.py +5 -0
  128. shotgun/tui/services/conversation_service.py +184 -0
  129. shotgun/tui/state/__init__.py +7 -0
  130. shotgun/tui/state/processing_state.py +185 -0
  131. shotgun/tui/utils/mode_progress.py +14 -7
  132. shotgun/tui/widgets/__init__.py +5 -0
  133. shotgun/tui/widgets/widget_coordinator.py +262 -0
  134. shotgun/utils/datetime_utils.py +77 -0
  135. shotgun/utils/env_utils.py +13 -0
  136. shotgun/utils/file_system_utils.py +22 -2
  137. shotgun/utils/marketing.py +110 -0
  138. shotgun/utils/source_detection.py +16 -0
  139. shotgun/utils/update_checker.py +73 -21
  140. shotgun_sh-0.2.11.dist-info/METADATA +130 -0
  141. shotgun_sh-0.2.11.dist-info/RECORD +194 -0
  142. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/entry_points.txt +1 -0
  143. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/licenses/LICENSE +1 -1
  144. shotgun/agents/history/token_counting.py +0 -429
  145. shotgun/agents/tools/user_interaction.py +0 -37
  146. shotgun/tui/screens/chat.py +0 -818
  147. shotgun/tui/screens/chat_screen/history.py +0 -222
  148. shotgun_sh-0.1.9.dist-info/METADATA +0 -466
  149. shotgun_sh-0.1.9.dist-info/RECORD +0 -131
  150. {shotgun_sh-0.1.9.dist-info → shotgun_sh-0.2.11.dist-info}/WHEEL +0 -0
shotgun/agents/common.py CHANGED
@@ -1,14 +1,12 @@
1
1
  """Common utilities for agent creation and management."""
2
2
 
3
- import asyncio
4
3
  from collections.abc import Callable
5
4
  from pathlib import Path
6
5
  from typing import Any
7
6
 
7
+ import aiofiles
8
8
  from pydantic_ai import (
9
9
  Agent,
10
- DeferredToolRequests,
11
- DeferredToolResults,
12
10
  RunContext,
13
11
  UsageLimits,
14
12
  )
@@ -18,21 +16,20 @@ from pydantic_ai.messages import (
18
16
  ModelRequest,
19
17
  )
20
18
 
21
- from shotgun.agents.config import ProviderType, get_config_manager, get_provider_model
22
- from shotgun.agents.models import AgentType
19
+ from shotgun.agents.config import ProviderType, get_provider_model
20
+ from shotgun.agents.models import AgentResponse, AgentType
23
21
  from shotgun.logging_config import get_logger
24
22
  from shotgun.prompts import PromptLoader
25
23
  from shotgun.sdk.services import get_codebase_service
26
24
  from shotgun.utils import ensure_shotgun_directory_exists
25
+ from shotgun.utils.datetime_utils import get_datetime_context
27
26
  from shotgun.utils.file_system_utils import get_shotgun_base_path
28
27
 
29
28
  from .history import token_limit_compactor
30
- from .history.compaction import apply_persistent_compaction
31
29
  from .messages import AgentSystemPrompt, SystemStatusPrompt
32
30
  from .models import AgentDeps, AgentRuntimeOptions, PipelineConfigEntry
33
31
  from .tools import (
34
32
  append_file,
35
- ask_user,
36
33
  codebase_shell,
37
34
  directory_lister,
38
35
  file_read,
@@ -72,7 +69,10 @@ async def add_system_status_message(
72
69
  existing_files = get_agent_existing_files(deps.agent_mode)
73
70
 
74
71
  # Extract table of contents from the agent's markdown file
75
- markdown_toc = extract_markdown_toc(deps.agent_mode)
72
+ markdown_toc = await extract_markdown_toc(deps.agent_mode)
73
+
74
+ # Get current datetime with timezone information
75
+ dt_context = get_datetime_context()
76
76
 
77
77
  system_state = prompt_loader.render(
78
78
  "agents/state/system_state.j2",
@@ -80,6 +80,9 @@ async def add_system_status_message(
80
80
  is_tui_context=deps.is_tui_context,
81
81
  existing_files=existing_files,
82
82
  markdown_toc=markdown_toc,
83
+ current_datetime=dt_context.datetime_formatted,
84
+ timezone_name=dt_context.timezone_name,
85
+ utc_offset=dt_context.utc_offset,
83
86
  )
84
87
 
85
88
  message_history.append(
@@ -92,14 +95,14 @@ async def add_system_status_message(
92
95
  return message_history
93
96
 
94
97
 
95
- def create_base_agent(
98
+ async def create_base_agent(
96
99
  system_prompt_fn: Callable[[RunContext[AgentDeps]], str],
97
100
  agent_runtime_options: AgentRuntimeOptions,
98
101
  load_codebase_understanding_tools: bool = True,
99
102
  additional_tools: list[Any] | None = None,
100
103
  provider: ProviderType | None = None,
101
104
  agent_mode: AgentType | None = None,
102
- ) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
105
+ ) -> tuple[Agent[AgentDeps, AgentResponse], AgentDeps]:
103
106
  """Create a base agent with common configuration.
104
107
 
105
108
  Args:
@@ -115,14 +118,13 @@ def create_base_agent(
115
118
  """
116
119
  ensure_shotgun_directory_exists()
117
120
 
118
- # Get configured model or fall back to hardcoded default
121
+ # Get configured model or fall back to first available provider
119
122
  try:
120
- model_config = get_provider_model(provider)
121
- config_manager = get_config_manager()
122
- provider_name = provider or config_manager.load().default_provider
123
+ model_config = await get_provider_model(provider)
124
+ provider_name = model_config.provider
123
125
  logger.debug(
124
126
  "🤖 Creating agent with configured %s model: %s",
125
- provider_name.upper(),
127
+ provider_name.value.upper(),
126
128
  model_config.name,
127
129
  )
128
130
  # Use the Model instance directly (has API key baked in)
@@ -158,7 +160,7 @@ def create_base_agent(
158
160
 
159
161
  agent = Agent(
160
162
  model,
161
- output_type=[str, DeferredToolRequests],
163
+ output_type=AgentResponse,
162
164
  deps_type=AgentDeps,
163
165
  instrument=True,
164
166
  history_processors=[history_processor],
@@ -173,11 +175,6 @@ def create_base_agent(
173
175
  for tool in additional_tools or []:
174
176
  agent.tool_plain(tool)
175
177
 
176
- # Register interactive tool conditionally based on deps
177
- if deps.interactive_mode:
178
- agent.tool(ask_user)
179
- logger.debug("📞 Interactive mode enabled - ask_user tool registered")
180
-
181
178
  # Register common file management tools (always available)
182
179
  agent.tool(write_file)
183
180
  agent.tool(append_file)
@@ -198,7 +195,7 @@ def create_base_agent(
198
195
  return agent, deps
199
196
 
200
197
 
201
- def _extract_file_toc_content(
198
+ async def _extract_file_toc_content(
202
199
  file_path: Path, max_depth: int | None = None, max_chars: int = 500
203
200
  ) -> str | None:
204
201
  """Extract TOC from a single file with depth and character limits.
@@ -215,7 +212,8 @@ def _extract_file_toc_content(
215
212
  return None
216
213
 
217
214
  try:
218
- content = file_path.read_text(encoding="utf-8")
215
+ async with aiofiles.open(file_path, encoding="utf-8") as f:
216
+ content = await f.read()
219
217
  lines = content.split("\n")
220
218
 
221
219
  # Extract headings
@@ -261,7 +259,7 @@ def _extract_file_toc_content(
261
259
  return None
262
260
 
263
261
 
264
- def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
262
+ async def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
265
263
  """Extract TOCs from current and prior agents' files in the pipeline.
266
264
 
267
265
  Shows full TOC of agent's own file and high-level summaries of prior agents'
@@ -313,22 +311,30 @@ def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
313
311
  for prior_file in config.prior_files:
314
312
  file_path = base_path / prior_file
315
313
  # Only show # and ## headings from prior files, max 500 chars each
316
- prior_toc = _extract_file_toc_content(file_path, max_depth=2, max_chars=500)
314
+ prior_toc = await _extract_file_toc_content(
315
+ file_path, max_depth=2, max_chars=500
316
+ )
317
317
  if prior_toc:
318
318
  # Add section with XML tags
319
319
  toc_sections.append(
320
- f'<TABLE_OF_CONTENTS file_name="{prior_file}">\n{prior_toc}\n</TABLE_OF_CONTENTS>'
320
+ f'<TABLE_OF_CONTENTS file_name="{prior_file}">\n'
321
+ f"{prior_toc}\n"
322
+ f"</TABLE_OF_CONTENTS>"
321
323
  )
322
324
 
323
325
  # Extract TOC from own file (full detail)
324
326
  if config.own_file:
325
327
  own_path = base_path / config.own_file
326
- own_toc = _extract_file_toc_content(own_path, max_depth=None, max_chars=2000)
328
+ own_toc = await _extract_file_toc_content(
329
+ own_path, max_depth=None, max_chars=2000
330
+ )
327
331
  if own_toc:
328
332
  # Put own file TOC at the beginning with XML tags
329
333
  toc_sections.insert(
330
334
  0,
331
- f'<TABLE_OF_CONTENTS file_name="{config.own_file}">\n{own_toc}\n</TABLE_OF_CONTENTS>',
335
+ f'<TABLE_OF_CONTENTS file_name="{config.own_file}">\n'
336
+ f"{own_toc}\n"
337
+ f"</TABLE_OF_CONTENTS>",
332
338
  )
333
339
 
334
340
  # Combine all sections
@@ -384,23 +390,48 @@ def get_agent_existing_files(agent_mode: AgentType | None = None) -> list[str]:
384
390
  relative_path = file_path.relative_to(base_path)
385
391
  existing_files.append(str(relative_path))
386
392
  else:
387
- # For other agents, check both .md file and directory with same name
388
- allowed_file = AGENT_DIRECTORIES[agent_mode]
389
-
390
- # Check for the .md file
391
- md_file_path = base_path / allowed_file
392
- if md_file_path.exists():
393
- existing_files.append(allowed_file)
394
-
395
- # Check for directory with same base name (e.g., research/ for research.md)
396
- base_name = allowed_file.replace(".md", "")
397
- dir_path = base_path / base_name
398
- if dir_path.exists() and dir_path.is_dir():
399
- # List all files in the directory
400
- for file_path in dir_path.rglob("*"):
401
- if file_path.is_file():
402
- relative_path = file_path.relative_to(base_path)
403
- existing_files.append(str(relative_path))
393
+ # For other agents, check files/directories they have access to
394
+ allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
395
+
396
+ # Convert single Path/string to list of Paths for uniform handling
397
+ if isinstance(allowed_paths_raw, str):
398
+ # Special case: "*" means export agent (shouldn't reach here but handle it)
399
+ allowed_paths = (
400
+ [Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
401
+ )
402
+ elif isinstance(allowed_paths_raw, Path):
403
+ allowed_paths = [allowed_paths_raw]
404
+ else:
405
+ # Already a list
406
+ allowed_paths = allowed_paths_raw
407
+
408
+ # Check each allowed path
409
+ for allowed_path in allowed_paths:
410
+ allowed_str = str(allowed_path)
411
+
412
+ # Check if it's a directory (no .md suffix)
413
+ if not allowed_path.suffix or not allowed_str.endswith(".md"):
414
+ # It's a directory - list all files within it
415
+ dir_path = base_path / allowed_str
416
+ if dir_path.exists() and dir_path.is_dir():
417
+ for file_path in dir_path.rglob("*"):
418
+ if file_path.is_file():
419
+ relative_path = file_path.relative_to(base_path)
420
+ existing_files.append(str(relative_path))
421
+ else:
422
+ # It's a file - check if it exists
423
+ file_path = base_path / allowed_str
424
+ if file_path.exists():
425
+ existing_files.append(allowed_str)
426
+
427
+ # Also check for associated directory (e.g., research/ for research.md)
428
+ base_name = allowed_str.replace(".md", "")
429
+ dir_path = base_path / base_name
430
+ if dir_path.exists() and dir_path.is_dir():
431
+ for file_path in dir_path.rglob("*"):
432
+ if file_path.is_file():
433
+ relative_path = file_path.relative_to(base_path)
434
+ existing_files.append(str(relative_path))
404
435
 
405
436
  return existing_files
406
437
 
@@ -470,7 +501,8 @@ async def add_system_prompt_message(
470
501
  message_history = message_history or []
471
502
 
472
503
  # Create a minimal RunContext to call the system prompt function
473
- # We'll pass None for model and usage since they're not used by our system prompt functions
504
+ # We'll pass None for model and usage since they're not used
505
+ # by our system prompt functions
474
506
  context = type(
475
507
  "RunContext", (), {"deps": deps, "retry": 0, "model": None, "usage": None}
476
508
  )()
@@ -494,12 +526,12 @@ async def add_system_prompt_message(
494
526
 
495
527
 
496
528
  async def run_agent(
497
- agent: Agent[AgentDeps, str | DeferredToolRequests],
529
+ agent: Agent[AgentDeps, AgentResponse],
498
530
  prompt: str,
499
531
  deps: AgentDeps,
500
532
  message_history: list[ModelMessage] | None = None,
501
533
  usage_limits: UsageLimits | None = None,
502
- ) -> AgentRunResult[str | DeferredToolRequests]:
534
+ ) -> AgentRunResult[AgentResponse]:
503
535
  # Clear file tracker for new run
504
536
  deps.file_tracker.clear()
505
537
  logger.debug("🔧 Cleared file tracker for new agent run")
@@ -514,33 +546,6 @@ async def run_agent(
514
546
  message_history=message_history,
515
547
  )
516
548
 
517
- # Apply persistent compaction to prevent cascading token growth across CLI commands
518
- messages = await apply_persistent_compaction(result.all_messages(), deps)
519
- while isinstance(result.output, DeferredToolRequests):
520
- logger.info("got deferred tool requests")
521
- await deps.queue.join()
522
- requests = result.output
523
- done, _ = await asyncio.wait(deps.tasks)
524
-
525
- task_results = [task.result() for task in done]
526
- task_results_by_tool_call_id = {
527
- result.tool_call_id: result.answer for result in task_results
528
- }
529
- logger.info("got task results", task_results_by_tool_call_id)
530
- results = DeferredToolResults()
531
- for call in requests.calls:
532
- results.calls[call.tool_call_id] = task_results_by_tool_call_id[
533
- call.tool_call_id
534
- ]
535
- result = await agent.run(
536
- deps=deps,
537
- usage_limits=usage_limits,
538
- message_history=messages,
539
- deferred_tool_results=results,
540
- )
541
- # Apply persistent compaction to prevent cascading token growth in multi-turn loops
542
- messages = await apply_persistent_compaction(result.all_messages(), deps)
543
-
544
549
  # Log file operations summary if any files were modified
545
550
  if deps.file_tracker.operations:
546
551
  summary = deps.file_tracker.format_summary()
@@ -1,17 +1,28 @@
1
1
  """Configuration constants for Shotgun agents."""
2
2
 
3
+ from enum import StrEnum, auto
4
+
3
5
  # Field names
4
6
  API_KEY_FIELD = "api_key"
5
- DEFAULT_PROVIDER_FIELD = "default_provider"
6
- USER_ID_FIELD = "user_id"
7
+ SUPABASE_JWT_FIELD = "supabase_jwt"
8
+ SHOTGUN_INSTANCE_ID_FIELD = "shotgun_instance_id"
7
9
  CONFIG_VERSION_FIELD = "config_version"
8
10
 
9
- # Provider names (for consistency with data dict keys)
10
- OPENAI_PROVIDER = "openai"
11
- ANTHROPIC_PROVIDER = "anthropic"
12
- GOOGLE_PROVIDER = "google"
13
11
 
14
- # Environment variable names
15
- OPENAI_API_KEY_ENV = "OPENAI_API_KEY"
16
- ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY"
17
- GEMINI_API_KEY_ENV = "GEMINI_API_KEY"
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
+ # Token limits
28
+ MEDIUM_TEXT_8K_TOKENS = 8192 # Default max_tokens for web search requests