shotgun-sh 0.1.0__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.
- shotgun/__init__.py +5 -0
- shotgun/agents/__init__.py +1 -0
- shotgun/agents/agent_manager.py +651 -0
- shotgun/agents/common.py +549 -0
- shotgun/agents/config/__init__.py +13 -0
- shotgun/agents/config/constants.py +17 -0
- shotgun/agents/config/manager.py +294 -0
- shotgun/agents/config/models.py +185 -0
- shotgun/agents/config/provider.py +206 -0
- shotgun/agents/conversation_history.py +106 -0
- shotgun/agents/conversation_manager.py +105 -0
- shotgun/agents/export.py +96 -0
- shotgun/agents/history/__init__.py +5 -0
- shotgun/agents/history/compaction.py +85 -0
- shotgun/agents/history/constants.py +19 -0
- shotgun/agents/history/context_extraction.py +108 -0
- shotgun/agents/history/history_building.py +104 -0
- shotgun/agents/history/history_processors.py +426 -0
- shotgun/agents/history/message_utils.py +84 -0
- shotgun/agents/history/token_counting.py +429 -0
- shotgun/agents/history/token_estimation.py +138 -0
- shotgun/agents/messages.py +35 -0
- shotgun/agents/models.py +275 -0
- shotgun/agents/plan.py +98 -0
- shotgun/agents/research.py +108 -0
- shotgun/agents/specify.py +98 -0
- shotgun/agents/tasks.py +96 -0
- shotgun/agents/tools/__init__.py +34 -0
- shotgun/agents/tools/codebase/__init__.py +28 -0
- shotgun/agents/tools/codebase/codebase_shell.py +256 -0
- shotgun/agents/tools/codebase/directory_lister.py +141 -0
- shotgun/agents/tools/codebase/file_read.py +144 -0
- shotgun/agents/tools/codebase/models.py +252 -0
- shotgun/agents/tools/codebase/query_graph.py +67 -0
- shotgun/agents/tools/codebase/retrieve_code.py +81 -0
- shotgun/agents/tools/file_management.py +218 -0
- shotgun/agents/tools/user_interaction.py +37 -0
- shotgun/agents/tools/web_search/__init__.py +60 -0
- shotgun/agents/tools/web_search/anthropic.py +144 -0
- shotgun/agents/tools/web_search/gemini.py +85 -0
- shotgun/agents/tools/web_search/openai.py +98 -0
- shotgun/agents/tools/web_search/utils.py +20 -0
- shotgun/build_constants.py +20 -0
- shotgun/cli/__init__.py +1 -0
- shotgun/cli/codebase/__init__.py +5 -0
- shotgun/cli/codebase/commands.py +202 -0
- shotgun/cli/codebase/models.py +21 -0
- shotgun/cli/config.py +275 -0
- shotgun/cli/export.py +81 -0
- shotgun/cli/models.py +10 -0
- shotgun/cli/plan.py +73 -0
- shotgun/cli/research.py +85 -0
- shotgun/cli/specify.py +69 -0
- shotgun/cli/tasks.py +78 -0
- shotgun/cli/update.py +152 -0
- shotgun/cli/utils.py +25 -0
- shotgun/codebase/__init__.py +12 -0
- shotgun/codebase/core/__init__.py +46 -0
- shotgun/codebase/core/change_detector.py +358 -0
- shotgun/codebase/core/code_retrieval.py +243 -0
- shotgun/codebase/core/ingestor.py +1497 -0
- shotgun/codebase/core/language_config.py +297 -0
- shotgun/codebase/core/manager.py +1662 -0
- shotgun/codebase/core/nl_query.py +331 -0
- shotgun/codebase/core/parser_loader.py +128 -0
- shotgun/codebase/models.py +111 -0
- shotgun/codebase/service.py +206 -0
- shotgun/logging_config.py +227 -0
- shotgun/main.py +167 -0
- shotgun/posthog_telemetry.py +158 -0
- shotgun/prompts/__init__.py +5 -0
- shotgun/prompts/agents/__init__.py +1 -0
- shotgun/prompts/agents/export.j2 +350 -0
- shotgun/prompts/agents/partials/codebase_understanding.j2 +87 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +37 -0
- shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
- shotgun/prompts/agents/partials/interactive_mode.j2 +26 -0
- shotgun/prompts/agents/plan.j2 +144 -0
- shotgun/prompts/agents/research.j2 +69 -0
- shotgun/prompts/agents/specify.j2 +51 -0
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +19 -0
- shotgun/prompts/agents/state/system_state.j2 +31 -0
- shotgun/prompts/agents/tasks.j2 +143 -0
- shotgun/prompts/codebase/__init__.py +1 -0
- shotgun/prompts/codebase/cypher_query_patterns.j2 +223 -0
- shotgun/prompts/codebase/cypher_system.j2 +28 -0
- shotgun/prompts/codebase/enhanced_query_context.j2 +10 -0
- shotgun/prompts/codebase/partials/cypher_rules.j2 +24 -0
- shotgun/prompts/codebase/partials/graph_schema.j2 +30 -0
- shotgun/prompts/codebase/partials/temporal_context.j2 +21 -0
- shotgun/prompts/history/__init__.py +1 -0
- shotgun/prompts/history/incremental_summarization.j2 +53 -0
- shotgun/prompts/history/summarization.j2 +46 -0
- shotgun/prompts/loader.py +140 -0
- shotgun/py.typed +0 -0
- shotgun/sdk/__init__.py +13 -0
- shotgun/sdk/codebase.py +219 -0
- shotgun/sdk/exceptions.py +17 -0
- shotgun/sdk/models.py +189 -0
- shotgun/sdk/services.py +23 -0
- shotgun/sentry_telemetry.py +87 -0
- shotgun/telemetry.py +93 -0
- shotgun/tui/__init__.py +0 -0
- shotgun/tui/app.py +116 -0
- shotgun/tui/commands/__init__.py +76 -0
- shotgun/tui/components/prompt_input.py +69 -0
- shotgun/tui/components/spinner.py +86 -0
- shotgun/tui/components/splash.py +25 -0
- shotgun/tui/components/vertical_tail.py +13 -0
- shotgun/tui/screens/chat.py +782 -0
- shotgun/tui/screens/chat.tcss +43 -0
- shotgun/tui/screens/chat_screen/__init__.py +0 -0
- shotgun/tui/screens/chat_screen/command_providers.py +219 -0
- shotgun/tui/screens/chat_screen/hint_message.py +40 -0
- shotgun/tui/screens/chat_screen/history.py +221 -0
- shotgun/tui/screens/directory_setup.py +113 -0
- shotgun/tui/screens/provider_config.py +221 -0
- shotgun/tui/screens/splash.py +31 -0
- shotgun/tui/styles.tcss +10 -0
- shotgun/tui/utils/__init__.py +5 -0
- shotgun/tui/utils/mode_progress.py +257 -0
- shotgun/utils/__init__.py +5 -0
- shotgun/utils/env_utils.py +35 -0
- shotgun/utils/file_system_utils.py +36 -0
- shotgun/utils/update_checker.py +375 -0
- shotgun_sh-0.1.0.dist-info/METADATA +466 -0
- shotgun_sh-0.1.0.dist-info/RECORD +130 -0
- shotgun_sh-0.1.0.dist-info/WHEEL +4 -0
- shotgun_sh-0.1.0.dist-info/entry_points.txt +2 -0
- shotgun_sh-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Web search tools for Pydantic AI agents.
|
|
2
|
+
|
|
3
|
+
Provides web search capabilities for multiple LLM providers:
|
|
4
|
+
- OpenAI: Uses Responses API with web_search tool
|
|
5
|
+
- Anthropic: Uses Messages API with web_search_20250305 tool
|
|
6
|
+
- Gemini: Uses grounding with Google Search
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
|
|
11
|
+
from shotgun.agents.config.models import ProviderType
|
|
12
|
+
from shotgun.logging_config import get_logger
|
|
13
|
+
|
|
14
|
+
from .anthropic import anthropic_web_search_tool
|
|
15
|
+
from .gemini import gemini_web_search_tool
|
|
16
|
+
from .openai import openai_web_search_tool
|
|
17
|
+
from .utils import is_provider_available
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
# Type alias for web search tools
|
|
22
|
+
WebSearchTool = Callable[[str], str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_available_web_search_tools() -> list[WebSearchTool]:
|
|
26
|
+
"""Get list of available web search tools based on configured API keys.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of web search tool functions that have API keys configured
|
|
30
|
+
"""
|
|
31
|
+
tools: list[WebSearchTool] = []
|
|
32
|
+
|
|
33
|
+
if is_provider_available(ProviderType.OPENAI):
|
|
34
|
+
logger.debug("✅ OpenAI web search tool available")
|
|
35
|
+
tools.append(openai_web_search_tool)
|
|
36
|
+
|
|
37
|
+
if is_provider_available(ProviderType.ANTHROPIC):
|
|
38
|
+
logger.debug("✅ Anthropic web search tool available")
|
|
39
|
+
tools.append(anthropic_web_search_tool)
|
|
40
|
+
|
|
41
|
+
if is_provider_available(ProviderType.GOOGLE):
|
|
42
|
+
logger.debug("✅ Gemini web search tool available")
|
|
43
|
+
tools.append(gemini_web_search_tool)
|
|
44
|
+
|
|
45
|
+
if not tools:
|
|
46
|
+
logger.warning("⚠️ No web search tools available - no API keys configured")
|
|
47
|
+
else:
|
|
48
|
+
logger.info("🔍 %d web search tool(s) available", len(tools))
|
|
49
|
+
|
|
50
|
+
return tools
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"openai_web_search_tool",
|
|
55
|
+
"anthropic_web_search_tool",
|
|
56
|
+
"gemini_web_search_tool",
|
|
57
|
+
"get_available_web_search_tools",
|
|
58
|
+
"is_provider_available",
|
|
59
|
+
"WebSearchTool",
|
|
60
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Anthropic web search tool implementation."""
|
|
2
|
+
|
|
3
|
+
import anthropic
|
|
4
|
+
from opentelemetry import trace
|
|
5
|
+
|
|
6
|
+
from shotgun.agents.config import get_provider_model
|
|
7
|
+
from shotgun.agents.config.models import ProviderType
|
|
8
|
+
from shotgun.logging_config import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def anthropic_web_search_tool(query: str) -> str:
|
|
14
|
+
"""Perform a web search using Anthropic's Claude API with streaming.
|
|
15
|
+
|
|
16
|
+
This tool uses Anthropic's web search capabilities to find current information
|
|
17
|
+
about the given query. Results are streamed for faster response times.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
query: The search query
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Search results as a formatted string
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("🔧 Invoking Anthropic web_search_tool with query: %s", query)
|
|
26
|
+
|
|
27
|
+
span = trace.get_current_span()
|
|
28
|
+
span.set_attribute("input.value", f"**Query:** {query}\n")
|
|
29
|
+
|
|
30
|
+
logger.debug("📡 Executing Anthropic web search with streaming prompt: %s", query)
|
|
31
|
+
|
|
32
|
+
# Get API key from centralized configuration
|
|
33
|
+
try:
|
|
34
|
+
model_config = get_provider_model(ProviderType.ANTHROPIC)
|
|
35
|
+
api_key = model_config.api_key
|
|
36
|
+
except ValueError as e:
|
|
37
|
+
error_msg = f"Anthropic API key not configured: {str(e)}"
|
|
38
|
+
logger.error("❌ %s", error_msg)
|
|
39
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
40
|
+
return error_msg
|
|
41
|
+
|
|
42
|
+
client = anthropic.Anthropic(api_key=api_key)
|
|
43
|
+
|
|
44
|
+
# Use the Messages API with web search tool and streaming
|
|
45
|
+
try:
|
|
46
|
+
result_text = ""
|
|
47
|
+
|
|
48
|
+
with client.messages.stream(
|
|
49
|
+
model="claude-3-5-sonnet-latest",
|
|
50
|
+
max_tokens=8192, # Maximum for Claude 3.5 Sonnet
|
|
51
|
+
messages=[{"role": "user", "content": f"Search for: {query}"}],
|
|
52
|
+
tools=[
|
|
53
|
+
{
|
|
54
|
+
"type": "web_search_20250305",
|
|
55
|
+
"name": "web_search",
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
tool_choice={"type": "tool", "name": "web_search"},
|
|
59
|
+
) as stream:
|
|
60
|
+
logger.debug("🌊 Started streaming Anthropic web search response")
|
|
61
|
+
|
|
62
|
+
for event in stream:
|
|
63
|
+
if event.type == "content_block_delta":
|
|
64
|
+
if hasattr(event.delta, "text"):
|
|
65
|
+
result_text += event.delta.text
|
|
66
|
+
elif event.type == "message_start":
|
|
67
|
+
logger.debug("🚀 Streaming started")
|
|
68
|
+
elif event.type == "message_stop":
|
|
69
|
+
logger.debug("✅ Streaming completed")
|
|
70
|
+
|
|
71
|
+
if not result_text:
|
|
72
|
+
result_text = "No content returned from search"
|
|
73
|
+
|
|
74
|
+
logger.debug("📄 Anthropic web search result: %d characters", len(result_text))
|
|
75
|
+
logger.debug(
|
|
76
|
+
"🔍 Result preview: %s...",
|
|
77
|
+
result_text[:100] if result_text else "No result",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
span.set_attribute("output.value", f"**Results:**\n {result_text}\n")
|
|
81
|
+
|
|
82
|
+
return result_text
|
|
83
|
+
except Exception as e:
|
|
84
|
+
error_msg = f"Error performing Anthropic web search: {str(e)}"
|
|
85
|
+
logger.error("❌ Anthropic web search failed: %s", str(e))
|
|
86
|
+
logger.debug("💥 Full error details: %s", error_msg)
|
|
87
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
88
|
+
return error_msg
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main() -> None:
|
|
92
|
+
"""Main function for testing the Anthropic web search tool."""
|
|
93
|
+
import os
|
|
94
|
+
import sys
|
|
95
|
+
|
|
96
|
+
from shotgun.logging_config import setup_logger
|
|
97
|
+
|
|
98
|
+
# Use project's logging configuration instead of basicConfig
|
|
99
|
+
setup_logger(__name__)
|
|
100
|
+
|
|
101
|
+
if len(sys.argv) < 2:
|
|
102
|
+
print(
|
|
103
|
+
"Usage: python -m shotgun.agents.tools.web_search.anthropic <search_query>"
|
|
104
|
+
)
|
|
105
|
+
print(
|
|
106
|
+
"Example: python -m shotgun.agents.tools.web_search.anthropic 'latest Python updates'"
|
|
107
|
+
)
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
# Join all arguments as the search query
|
|
111
|
+
query = " ".join(sys.argv[1:])
|
|
112
|
+
|
|
113
|
+
print("🔍 Testing Anthropic Web Search with streaming")
|
|
114
|
+
print(f"📝 Query: {query}")
|
|
115
|
+
print("=" * 60)
|
|
116
|
+
|
|
117
|
+
# Check if API key is available
|
|
118
|
+
if not (
|
|
119
|
+
os.getenv("ANTHROPIC_API_KEY")
|
|
120
|
+
or (
|
|
121
|
+
callable(get_provider_model)
|
|
122
|
+
and get_provider_model(ProviderType.ANTHROPIC).api_key
|
|
123
|
+
)
|
|
124
|
+
):
|
|
125
|
+
print("❌ Error: ANTHROPIC_API_KEY environment variable not set")
|
|
126
|
+
print(" Please set it with: export ANTHROPIC_API_KEY=your_key_here")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = anthropic_web_search_tool(query)
|
|
131
|
+
print(f"✅ Search completed! Result length: {len(result)} characters")
|
|
132
|
+
print("=" * 60)
|
|
133
|
+
print("📄 RESULTS:")
|
|
134
|
+
print("=" * 60)
|
|
135
|
+
print(result)
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
print("\n⏹️ Search interrupted by user")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(f"❌ Error during search: {e}")
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Gemini web search tool implementation."""
|
|
2
|
+
|
|
3
|
+
import google.generativeai as genai
|
|
4
|
+
from opentelemetry import trace
|
|
5
|
+
|
|
6
|
+
from shotgun.agents.config import get_provider_model
|
|
7
|
+
from shotgun.agents.config.models import ProviderType
|
|
8
|
+
from shotgun.logging_config import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def gemini_web_search_tool(query: str) -> str:
|
|
14
|
+
"""Perform a web search using Google's Gemini API with grounding.
|
|
15
|
+
|
|
16
|
+
This tool uses Gemini's Google Search grounding to find current information
|
|
17
|
+
about the given query.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
query: The search query
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Search results as a formatted string
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("🔧 Invoking Gemini web_search_tool with query: %s", query)
|
|
26
|
+
|
|
27
|
+
span = trace.get_current_span()
|
|
28
|
+
span.set_attribute("input.value", f"**Query:** {query}\n")
|
|
29
|
+
|
|
30
|
+
logger.debug("📡 Executing Gemini web search with prompt: %s", query)
|
|
31
|
+
|
|
32
|
+
# Get API key from centralized configuration
|
|
33
|
+
try:
|
|
34
|
+
model_config = get_provider_model(ProviderType.GOOGLE)
|
|
35
|
+
api_key = model_config.api_key
|
|
36
|
+
except ValueError as e:
|
|
37
|
+
error_msg = f"Gemini API key not configured: {str(e)}"
|
|
38
|
+
logger.error("❌ %s", error_msg)
|
|
39
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
40
|
+
return error_msg
|
|
41
|
+
|
|
42
|
+
genai.configure(api_key=api_key) # type: ignore[attr-defined]
|
|
43
|
+
|
|
44
|
+
# Create model without built-in tools to avoid conflict with Pydantic AI
|
|
45
|
+
# Using prompt-based search approach instead
|
|
46
|
+
model = genai.GenerativeModel("gemini-2.5-pro") # type: ignore[attr-defined]
|
|
47
|
+
|
|
48
|
+
# Create a search-optimized prompt that leverages Gemini's knowledge
|
|
49
|
+
search_prompt = f"""Please provide current and accurate information about the following query:
|
|
50
|
+
|
|
51
|
+
Query: {query}
|
|
52
|
+
|
|
53
|
+
Instructions:
|
|
54
|
+
- Provide comprehensive, factual information
|
|
55
|
+
- Include relevant details and context
|
|
56
|
+
- Focus on current and recent information
|
|
57
|
+
- Be specific and accurate in your response"""
|
|
58
|
+
|
|
59
|
+
# Generate response using the model's knowledge
|
|
60
|
+
try:
|
|
61
|
+
response = model.generate_content(
|
|
62
|
+
search_prompt,
|
|
63
|
+
generation_config=genai.GenerationConfig( # type: ignore[attr-defined]
|
|
64
|
+
temperature=0.3,
|
|
65
|
+
max_output_tokens=8192,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
result_text = response.text or "No content returned from search"
|
|
70
|
+
|
|
71
|
+
logger.debug("📄 Gemini web search result: %d characters", len(result_text))
|
|
72
|
+
logger.debug(
|
|
73
|
+
"🔍 Result preview: %s...",
|
|
74
|
+
result_text[:100] if result_text else "No result",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
span.set_attribute("output.value", f"**Results:**\n {result_text}\n")
|
|
78
|
+
|
|
79
|
+
return result_text
|
|
80
|
+
except Exception as e:
|
|
81
|
+
error_msg = f"Error performing Gemini web search: {str(e)}"
|
|
82
|
+
logger.error("❌ Gemini web search failed: %s", str(e))
|
|
83
|
+
logger.debug("💥 Full error details: %s", error_msg)
|
|
84
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
85
|
+
return error_msg
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""OpenAI web search tool implementation."""
|
|
2
|
+
|
|
3
|
+
from openai import OpenAI
|
|
4
|
+
from opentelemetry import trace
|
|
5
|
+
|
|
6
|
+
from shotgun.agents.config import get_provider_model
|
|
7
|
+
from shotgun.agents.config.models import ProviderType
|
|
8
|
+
from shotgun.logging_config import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def openai_web_search_tool(query: str) -> str:
|
|
14
|
+
"""Perform a web search and return results.
|
|
15
|
+
|
|
16
|
+
This tool uses OpenAI's web search capabilities to find current information
|
|
17
|
+
about the given query.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
query: The search query
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Search results as a formatted string
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("🔧 Invoking OpenAI web_search_tool with query: %s", query)
|
|
26
|
+
|
|
27
|
+
span = trace.get_current_span()
|
|
28
|
+
span.set_attribute("input.value", f"**Query:** {query}\n")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
logger.debug("📡 Executing OpenAI web search with prompt: %s", query)
|
|
32
|
+
|
|
33
|
+
# Get API key from centralized configuration
|
|
34
|
+
try:
|
|
35
|
+
model_config = get_provider_model(ProviderType.OPENAI)
|
|
36
|
+
api_key = model_config.api_key
|
|
37
|
+
except ValueError as e:
|
|
38
|
+
error_msg = f"OpenAI API key not configured: {str(e)}"
|
|
39
|
+
logger.error("❌ %s", error_msg)
|
|
40
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
41
|
+
return error_msg
|
|
42
|
+
|
|
43
|
+
prompt = f"""Please provide current and accurate information about the following query:
|
|
44
|
+
|
|
45
|
+
Query: {query}
|
|
46
|
+
|
|
47
|
+
Instructions:
|
|
48
|
+
- Provide comprehensive, factual information
|
|
49
|
+
- Include relevant details and context
|
|
50
|
+
- Focus on current and recent information
|
|
51
|
+
- Be specific and accurate in your response
|
|
52
|
+
- You can't ask the user for details, so assume the most relevant details for the query
|
|
53
|
+
|
|
54
|
+
ALWAYS PROVIDE THE SOURCES (urls) TO BACK UP THE INFORMATION YOU PROVIDE.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
client = OpenAI(api_key=api_key)
|
|
58
|
+
response = client.responses.create( # type: ignore[call-overload]
|
|
59
|
+
model="gpt-5-mini",
|
|
60
|
+
input=[
|
|
61
|
+
{"role": "user", "content": [{"type": "input_text", "text": prompt}]}
|
|
62
|
+
],
|
|
63
|
+
text={
|
|
64
|
+
"format": {"type": "text"},
|
|
65
|
+
"verbosity": "high",
|
|
66
|
+
}, # Increased from medium
|
|
67
|
+
reasoning={"effort": "medium", "summary": "auto"},
|
|
68
|
+
tools=[
|
|
69
|
+
{
|
|
70
|
+
"type": "web_search",
|
|
71
|
+
"user_location": {"type": "approximate"},
|
|
72
|
+
"search_context_size": "high", # Increased from low for more context
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
store=False,
|
|
76
|
+
include=[
|
|
77
|
+
"reasoning.encrypted_content",
|
|
78
|
+
"web_search_call.action.sources", # pyright: ignore[reportArgumentType]
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
result_text = response.output_text or "No content returned"
|
|
83
|
+
|
|
84
|
+
logger.debug("📄 Web search result: %d characters", len(result_text))
|
|
85
|
+
logger.debug(
|
|
86
|
+
"🔍 Result preview: %s...",
|
|
87
|
+
result_text[:100] if result_text else "No result",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
span.set_attribute("output.value", f"**Results:**\n {result_text}\n")
|
|
91
|
+
|
|
92
|
+
return result_text
|
|
93
|
+
except Exception as e:
|
|
94
|
+
error_msg = f"Error performing web search: {str(e)}"
|
|
95
|
+
logger.error("❌ Web search failed: %s", str(e))
|
|
96
|
+
logger.debug("💥 Full error details: %s", error_msg)
|
|
97
|
+
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
98
|
+
return error_msg
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Utility functions for web search tools."""
|
|
2
|
+
|
|
3
|
+
from shotgun.agents.config import get_provider_model
|
|
4
|
+
from shotgun.agents.config.models import ProviderType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_provider_available(provider: ProviderType) -> bool:
|
|
8
|
+
"""Check if a provider has API key configured.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
provider: The provider to check
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
True if the provider has valid credentials configured (from config or env)
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
get_provider_model(provider)
|
|
18
|
+
return True
|
|
19
|
+
except ValueError:
|
|
20
|
+
return False
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Build-time constants generated during packaging.
|
|
2
|
+
|
|
3
|
+
This file is auto-generated during the build process.
|
|
4
|
+
DO NOT EDIT MANUALLY.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Sentry DSN embedded at build time (empty string if not provided)
|
|
8
|
+
SENTRY_DSN = 'https://2818a6d165c64eccc94cfd51ce05d6aa@o4506813296738304.ingest.us.sentry.io/4510045952409600'
|
|
9
|
+
|
|
10
|
+
# PostHog configuration embedded at build time (empty strings if not provided)
|
|
11
|
+
POSTHOG_API_KEY = ''
|
|
12
|
+
POSTHOG_PROJECT_ID = '191396'
|
|
13
|
+
|
|
14
|
+
# Logfire configuration embedded at build time (only for dev builds)
|
|
15
|
+
LOGFIRE_ENABLED = ''
|
|
16
|
+
LOGFIRE_TOKEN = ''
|
|
17
|
+
|
|
18
|
+
# Build metadata
|
|
19
|
+
BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
|
|
20
|
+
IS_DEV_BUILD = True
|
shotgun/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Commands package for shotgun CLI."""
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Codebase management CLI commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import traceback
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from shotgun.codebase.models import CodebaseGraph, QueryType
|
|
11
|
+
from shotgun.logging_config import get_logger
|
|
12
|
+
from shotgun.sdk.codebase import CodebaseSDK
|
|
13
|
+
from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
|
|
14
|
+
|
|
15
|
+
from ..models import OutputFormat
|
|
16
|
+
from ..utils import output_result
|
|
17
|
+
from .models import ErrorResult
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(
|
|
20
|
+
name="codebase",
|
|
21
|
+
help="Manage and query code knowledge graphs",
|
|
22
|
+
no_args_is_help=True,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Set up logger but it will be suppressed by default
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command(name="list")
|
|
30
|
+
def list_codebases(
|
|
31
|
+
format_type: Annotated[
|
|
32
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
33
|
+
] = OutputFormat.TEXT,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""List all indexed codebases."""
|
|
36
|
+
sdk = CodebaseSDK()
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
result = asyncio.run(sdk.list_codebases())
|
|
40
|
+
output_result(result, format_type)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
error_result = ErrorResult(
|
|
43
|
+
error_message=f"Error listing codebases: {e}",
|
|
44
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
45
|
+
)
|
|
46
|
+
output_result(error_result, format_type)
|
|
47
|
+
raise typer.Exit(1) from e
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command()
|
|
51
|
+
def index(
|
|
52
|
+
path: Annotated[str, typer.Argument(help="Path to repository to index")],
|
|
53
|
+
name: Annotated[
|
|
54
|
+
str, typer.Option("--name", "-n", help="Human-readable name for the codebase")
|
|
55
|
+
],
|
|
56
|
+
format_type: Annotated[
|
|
57
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
58
|
+
] = OutputFormat.TEXT,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Index a new codebase."""
|
|
61
|
+
sdk = CodebaseSDK()
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
repo_path = Path(path)
|
|
65
|
+
result = asyncio.run(sdk.index_codebase(repo_path, name))
|
|
66
|
+
output_result(result, format_type)
|
|
67
|
+
except InvalidPathError as e:
|
|
68
|
+
error_result = ErrorResult(error_message=str(e))
|
|
69
|
+
output_result(error_result, format_type)
|
|
70
|
+
raise typer.Exit(1) from e
|
|
71
|
+
except Exception as e:
|
|
72
|
+
error_result = ErrorResult(
|
|
73
|
+
error_message=f"Error indexing codebase: {e}",
|
|
74
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
75
|
+
)
|
|
76
|
+
output_result(error_result, format_type)
|
|
77
|
+
raise typer.Exit(1) from e
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command()
|
|
81
|
+
def delete(
|
|
82
|
+
graph_id: Annotated[str, typer.Argument(help="Graph ID to delete")],
|
|
83
|
+
format_type: Annotated[
|
|
84
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
85
|
+
] = OutputFormat.TEXT,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Delete an indexed codebase."""
|
|
88
|
+
sdk = CodebaseSDK()
|
|
89
|
+
|
|
90
|
+
# CLI-specific confirmation callback
|
|
91
|
+
def cli_confirm(graph: CodebaseGraph) -> bool:
|
|
92
|
+
return typer.confirm(
|
|
93
|
+
f"Are you sure you want to delete codebase '{graph.name}' ({graph_id})?"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
result = asyncio.run(sdk.delete_codebase(graph_id, cli_confirm))
|
|
98
|
+
output_result(result, format_type)
|
|
99
|
+
if not result.deleted and not result.cancelled:
|
|
100
|
+
raise typer.Exit(1)
|
|
101
|
+
except CodebaseNotFoundError as e:
|
|
102
|
+
error_result = ErrorResult(error_message=str(e))
|
|
103
|
+
output_result(error_result, format_type)
|
|
104
|
+
raise typer.Exit(1) from e
|
|
105
|
+
except Exception as e:
|
|
106
|
+
error_result = ErrorResult(
|
|
107
|
+
error_message=f"Error deleting codebase: {e}",
|
|
108
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
109
|
+
)
|
|
110
|
+
output_result(error_result, format_type)
|
|
111
|
+
raise typer.Exit(1) from e
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def info(
|
|
116
|
+
graph_id: Annotated[str, typer.Argument(help="Graph ID to show info for")],
|
|
117
|
+
format_type: Annotated[
|
|
118
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
119
|
+
] = OutputFormat.TEXT,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Show detailed information about a codebase."""
|
|
122
|
+
sdk = CodebaseSDK()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
result = asyncio.run(sdk.get_info(graph_id))
|
|
126
|
+
output_result(result, format_type)
|
|
127
|
+
except CodebaseNotFoundError as e:
|
|
128
|
+
error_result = ErrorResult(error_message=str(e))
|
|
129
|
+
output_result(error_result, format_type)
|
|
130
|
+
raise typer.Exit(1) from e
|
|
131
|
+
except Exception as e:
|
|
132
|
+
error_result = ErrorResult(
|
|
133
|
+
error_message=f"Error getting codebase info: {e}",
|
|
134
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
135
|
+
)
|
|
136
|
+
output_result(error_result, format_type)
|
|
137
|
+
raise typer.Exit(1) from e
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command()
|
|
141
|
+
def query(
|
|
142
|
+
graph_id: Annotated[str, typer.Argument(help="Graph ID to query")],
|
|
143
|
+
query_text: Annotated[
|
|
144
|
+
str, typer.Argument(help="Query text (natural language or Cypher)")
|
|
145
|
+
],
|
|
146
|
+
cypher: Annotated[
|
|
147
|
+
bool,
|
|
148
|
+
typer.Option(
|
|
149
|
+
"--cypher", help="Treat query as Cypher instead of natural language"
|
|
150
|
+
),
|
|
151
|
+
] = False,
|
|
152
|
+
format_type: Annotated[
|
|
153
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
154
|
+
] = OutputFormat.TEXT,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Query a codebase using natural language or Cypher."""
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
sdk = CodebaseSDK()
|
|
160
|
+
query_type = QueryType.CYPHER if cypher else QueryType.NATURAL_LANGUAGE
|
|
161
|
+
result = asyncio.run(sdk.query_codebase(graph_id, query_text, query_type))
|
|
162
|
+
output_result(result, format_type)
|
|
163
|
+
except CodebaseNotFoundError as e:
|
|
164
|
+
error_result = ErrorResult(error_message=str(e))
|
|
165
|
+
output_result(error_result, format_type)
|
|
166
|
+
raise typer.Exit(1) from e
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
error_result = ErrorResult(
|
|
170
|
+
error_message=f"Error executing query: {e}",
|
|
171
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
172
|
+
)
|
|
173
|
+
output_result(error_result, format_type)
|
|
174
|
+
raise typer.Exit(1) from e
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@app.command()
|
|
178
|
+
def reindex(
|
|
179
|
+
graph_id: Annotated[str, typer.Argument(help="Graph ID to reindex")],
|
|
180
|
+
format_type: Annotated[
|
|
181
|
+
OutputFormat, typer.Option("--format", "-f", help="Output format")
|
|
182
|
+
] = OutputFormat.TEXT,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Reindex an existing codebase."""
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
sdk = CodebaseSDK()
|
|
188
|
+
result = asyncio.run(sdk.reindex_codebase(graph_id))
|
|
189
|
+
# Stats are always shown now that verbose is controlled by env var
|
|
190
|
+
output_result(result, format_type)
|
|
191
|
+
except CodebaseNotFoundError as e:
|
|
192
|
+
error_result = ErrorResult(error_message=str(e))
|
|
193
|
+
output_result(error_result, format_type)
|
|
194
|
+
raise typer.Exit(1) from e
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
error_result = ErrorResult(
|
|
198
|
+
error_message=f"Error reindexing codebase: {e}",
|
|
199
|
+
details=f"Full traceback:\n{traceback.format_exc()}",
|
|
200
|
+
)
|
|
201
|
+
output_result(error_result, format_type)
|
|
202
|
+
raise typer.Exit(1) from e
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Re-export SDK models for backward compatibility."""
|
|
2
|
+
|
|
3
|
+
from shotgun.sdk.models import (
|
|
4
|
+
DeleteResult,
|
|
5
|
+
ErrorResult,
|
|
6
|
+
IndexResult,
|
|
7
|
+
InfoResult,
|
|
8
|
+
ListResult,
|
|
9
|
+
QueryCommandResult,
|
|
10
|
+
ReindexResult,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ListResult",
|
|
15
|
+
"IndexResult",
|
|
16
|
+
"DeleteResult",
|
|
17
|
+
"InfoResult",
|
|
18
|
+
"QueryCommandResult",
|
|
19
|
+
"ReindexResult",
|
|
20
|
+
"ErrorResult",
|
|
21
|
+
]
|