shotgun-sh 0.1.0.dev12__py3-none-any.whl → 0.1.0.dev14__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/agents/agent_manager.py +16 -3
- shotgun/agents/artifact_state.py +58 -0
- shotgun/agents/common.py +137 -88
- shotgun/agents/config/constants.py +18 -0
- shotgun/agents/config/manager.py +68 -16
- shotgun/agents/config/models.py +61 -0
- shotgun/agents/config/provider.py +11 -6
- 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 +354 -157
- shotgun/agents/history/message_utils.py +46 -0
- shotgun/agents/history/token_counting.py +429 -0
- shotgun/agents/history/token_estimation.py +138 -0
- shotgun/agents/models.py +131 -1
- shotgun/agents/plan.py +15 -37
- shotgun/agents/research.py +10 -45
- shotgun/agents/specify.py +97 -0
- shotgun/agents/tasks.py +7 -36
- shotgun/agents/tools/artifact_management.py +482 -0
- shotgun/agents/tools/file_management.py +31 -12
- shotgun/agents/tools/web_search/anthropic.py +78 -17
- shotgun/agents/tools/web_search/gemini.py +1 -1
- shotgun/agents/tools/web_search/openai.py +16 -2
- shotgun/artifacts/__init__.py +17 -0
- shotgun/artifacts/exceptions.py +89 -0
- shotgun/artifacts/manager.py +530 -0
- shotgun/artifacts/models.py +334 -0
- shotgun/artifacts/service.py +463 -0
- shotgun/artifacts/templates/__init__.py +10 -0
- shotgun/artifacts/templates/loader.py +252 -0
- shotgun/artifacts/templates/models.py +136 -0
- shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +66 -0
- shotgun/artifacts/templates/research/market_research.yaml +585 -0
- shotgun/artifacts/templates/research/sdk_comparison.yaml +257 -0
- shotgun/artifacts/templates/specify/prd.yaml +331 -0
- shotgun/artifacts/templates/specify/product_spec.yaml +301 -0
- shotgun/artifacts/utils.py +76 -0
- shotgun/cli/plan.py +1 -4
- shotgun/cli/specify.py +69 -0
- shotgun/cli/tasks.py +0 -4
- shotgun/codebase/core/nl_query.py +4 -4
- shotgun/logging_config.py +23 -7
- shotgun/main.py +7 -6
- shotgun/prompts/agents/partials/artifact_system.j2 +35 -0
- shotgun/prompts/agents/partials/codebase_understanding.j2 +1 -2
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
- shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
- shotgun/prompts/agents/partials/interactive_mode.j2 +10 -2
- shotgun/prompts/agents/plan.j2 +33 -32
- shotgun/prompts/agents/research.j2 +39 -29
- shotgun/prompts/agents/specify.j2 +32 -0
- shotgun/prompts/agents/state/artifact_templates_available.j2 +18 -0
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +3 -1
- shotgun/prompts/agents/state/existing_artifacts_available.j2 +23 -0
- shotgun/prompts/agents/state/system_state.j2 +9 -1
- shotgun/prompts/agents/tasks.j2 +27 -12
- shotgun/prompts/history/incremental_summarization.j2 +53 -0
- shotgun/sdk/artifact_models.py +186 -0
- shotgun/sdk/artifacts.py +448 -0
- shotgun/sdk/services.py +14 -0
- shotgun/tui/app.py +26 -7
- shotgun/tui/screens/chat.py +32 -5
- shotgun/tui/screens/directory_setup.py +113 -0
- shotgun/utils/file_system_utils.py +6 -1
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/METADATA +3 -2
- shotgun_sh-0.1.0.dev14.dist-info/RECORD +138 -0
- shotgun/prompts/user/research.j2 +0 -5
- shotgun_sh-0.1.0.dev12.dist-info/RECORD +0 -104
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,10 +11,10 @@ logger = get_logger(__name__)
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def anthropic_web_search_tool(query: str) -> str:
|
|
14
|
-
"""Perform a web search using Anthropic's Claude API.
|
|
14
|
+
"""Perform a web search using Anthropic's Claude API with streaming.
|
|
15
15
|
|
|
16
16
|
This tool uses Anthropic's web search capabilities to find current information
|
|
17
|
-
about the given query.
|
|
17
|
+
about the given query. Results are streamed for faster response times.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
20
|
query: The search query
|
|
@@ -27,7 +27,7 @@ def anthropic_web_search_tool(query: str) -> str:
|
|
|
27
27
|
span = trace.get_current_span()
|
|
28
28
|
span.set_attribute("input.value", f"**Query:** {query}\n")
|
|
29
29
|
|
|
30
|
-
logger.debug("📡 Executing Anthropic web search with prompt: %s", query)
|
|
30
|
+
logger.debug("📡 Executing Anthropic web search with streaming prompt: %s", query)
|
|
31
31
|
|
|
32
32
|
# Get API key from centralized configuration
|
|
33
33
|
try:
|
|
@@ -41,11 +41,13 @@ def anthropic_web_search_tool(query: str) -> str:
|
|
|
41
41
|
|
|
42
42
|
client = anthropic.Anthropic(api_key=api_key)
|
|
43
43
|
|
|
44
|
-
# Use the Messages API with web search tool
|
|
44
|
+
# Use the Messages API with web search tool and streaming
|
|
45
45
|
try:
|
|
46
|
-
|
|
46
|
+
result_text = ""
|
|
47
|
+
|
|
48
|
+
with client.messages.stream(
|
|
47
49
|
model="claude-3-5-sonnet-latest",
|
|
48
|
-
max_tokens=8192, #
|
|
50
|
+
max_tokens=8192, # Maximum for Claude 3.5 Sonnet
|
|
49
51
|
messages=[{"role": "user", "content": f"Search for: {query}"}],
|
|
50
52
|
tools=[
|
|
51
53
|
{
|
|
@@ -54,17 +56,17 @@ def anthropic_web_search_tool(query: str) -> str:
|
|
|
54
56
|
}
|
|
55
57
|
],
|
|
56
58
|
tool_choice={"type": "tool", "name": "web_search"},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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")
|
|
68
70
|
|
|
69
71
|
if not result_text:
|
|
70
72
|
result_text = "No content returned from search"
|
|
@@ -84,3 +86,62 @@ def anthropic_web_search_tool(query: str) -> str:
|
|
|
84
86
|
logger.debug("💥 Full error details: %s", error_msg)
|
|
85
87
|
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
86
88
|
return error_msg
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main() -> None:
|
|
92
|
+
"""Main function for testing the Anthropic web search tool."""
|
|
93
|
+
import logging
|
|
94
|
+
import os
|
|
95
|
+
import sys
|
|
96
|
+
|
|
97
|
+
# Set up basic console logging for testing
|
|
98
|
+
logging.basicConfig(
|
|
99
|
+
level=logging.DEBUG,
|
|
100
|
+
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
|
101
|
+
stream=sys.stdout,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if len(sys.argv) < 2:
|
|
105
|
+
print(
|
|
106
|
+
"Usage: python -m shotgun.agents.tools.web_search.anthropic <search_query>"
|
|
107
|
+
)
|
|
108
|
+
print(
|
|
109
|
+
"Example: python -m shotgun.agents.tools.web_search.anthropic 'latest Python updates'"
|
|
110
|
+
)
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
# Join all arguments as the search query
|
|
114
|
+
query = " ".join(sys.argv[1:])
|
|
115
|
+
|
|
116
|
+
print("🔍 Testing Anthropic Web Search with streaming")
|
|
117
|
+
print(f"📝 Query: {query}")
|
|
118
|
+
print("=" * 60)
|
|
119
|
+
|
|
120
|
+
# Check if API key is available
|
|
121
|
+
if not (
|
|
122
|
+
os.getenv("ANTHROPIC_API_KEY")
|
|
123
|
+
or (
|
|
124
|
+
callable(get_provider_model)
|
|
125
|
+
and get_provider_model(ProviderType.ANTHROPIC).api_key
|
|
126
|
+
)
|
|
127
|
+
):
|
|
128
|
+
print("❌ Error: ANTHROPIC_API_KEY environment variable not set")
|
|
129
|
+
print(" Please set it with: export ANTHROPIC_API_KEY=your_key_here")
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = anthropic_web_search_tool(query)
|
|
134
|
+
print(f"✅ Search completed! Result length: {len(result)} characters")
|
|
135
|
+
print("=" * 60)
|
|
136
|
+
print("📄 RESULTS:")
|
|
137
|
+
print("=" * 60)
|
|
138
|
+
print(result)
|
|
139
|
+
except KeyboardInterrupt:
|
|
140
|
+
print("\n⏹️ Search interrupted by user")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"❌ Error during search: {e}")
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
main()
|
|
@@ -40,17 +40,31 @@ def openai_web_search_tool(query: str) -> str:
|
|
|
40
40
|
span.set_attribute("output.value", f"**Error:**\n {error_msg}\n")
|
|
41
41
|
return error_msg
|
|
42
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
|
+
|
|
43
57
|
client = OpenAI(api_key=api_key)
|
|
44
58
|
response = client.responses.create( # type: ignore[call-overload]
|
|
45
59
|
model="gpt-5-mini",
|
|
46
60
|
input=[
|
|
47
|
-
{"role": "user", "content": [{"type": "input_text", "text":
|
|
61
|
+
{"role": "user", "content": [{"type": "input_text", "text": prompt}]}
|
|
48
62
|
],
|
|
49
63
|
text={
|
|
50
64
|
"format": {"type": "text"},
|
|
51
65
|
"verbosity": "high",
|
|
52
66
|
}, # Increased from medium
|
|
53
|
-
reasoning={"effort": "
|
|
67
|
+
reasoning={"effort": "medium", "summary": "auto"},
|
|
54
68
|
tools=[
|
|
55
69
|
{
|
|
56
70
|
"type": "web_search",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Artifact system for managing structured content in .shotgun directory."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"ArtifactService",
|
|
5
|
+
"ArtifactManager",
|
|
6
|
+
"Artifact",
|
|
7
|
+
"ArtifactSection",
|
|
8
|
+
"ArtifactSummary",
|
|
9
|
+
"AgentMode",
|
|
10
|
+
"generate_artifact_name",
|
|
11
|
+
"parse_agent_mode_string",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
from .manager import ArtifactManager
|
|
15
|
+
from .models import AgentMode, Artifact, ArtifactSection, ArtifactSummary
|
|
16
|
+
from .service import ArtifactService
|
|
17
|
+
from .utils import generate_artifact_name, parse_agent_mode_string
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Exception classes for the artifact system."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ArtifactError(Exception):
|
|
5
|
+
"""Base exception for all artifact-related errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArtifactNotFoundError(ArtifactError):
|
|
9
|
+
"""Raised when an artifact is not found."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, artifact_id: str, agent_mode: str | None = None) -> None:
|
|
12
|
+
if agent_mode:
|
|
13
|
+
message = f"Artifact '{artifact_id}' not found in agent mode '{agent_mode}'"
|
|
14
|
+
else:
|
|
15
|
+
message = f"Artifact '{artifact_id}' not found"
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.artifact_id = artifact_id
|
|
18
|
+
self.agent_mode = agent_mode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SectionNotFoundError(ArtifactError):
|
|
22
|
+
"""Raised when a section is not found within an artifact."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, section_identifier: str | int, artifact_id: str) -> None:
|
|
25
|
+
message = (
|
|
26
|
+
f"Section '{section_identifier}' not found in artifact '{artifact_id}'"
|
|
27
|
+
)
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.section_identifier = section_identifier
|
|
30
|
+
self.artifact_id = artifact_id
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SectionAlreadyExistsError(ArtifactError):
|
|
34
|
+
"""Raised when trying to create a section that already exists."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, section_identifier: str | int, artifact_id: str) -> None:
|
|
37
|
+
message = (
|
|
38
|
+
f"Section '{section_identifier}' already exists in artifact '{artifact_id}'"
|
|
39
|
+
)
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
self.section_identifier = section_identifier
|
|
42
|
+
self.artifact_id = artifact_id
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ArtifactAlreadyExistsError(ArtifactError):
|
|
46
|
+
"""Raised when trying to create an artifact that already exists."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, artifact_id: str, agent_mode: str) -> None:
|
|
49
|
+
message = (
|
|
50
|
+
f"Artifact '{artifact_id}' already exists in agent mode '{agent_mode}'"
|
|
51
|
+
)
|
|
52
|
+
super().__init__(message)
|
|
53
|
+
self.artifact_id = artifact_id
|
|
54
|
+
self.agent_mode = agent_mode
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class InvalidArtifactPathError(ArtifactError):
|
|
58
|
+
"""Raised when an artifact path is invalid or outside allowed directories."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, path: str, reason: str | None = None) -> None:
|
|
61
|
+
message = f"Invalid artifact path: {path}"
|
|
62
|
+
if reason:
|
|
63
|
+
message += f" - {reason}"
|
|
64
|
+
super().__init__(message)
|
|
65
|
+
self.path = path
|
|
66
|
+
self.reason = reason
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ArtifactFileSystemError(ArtifactError):
|
|
70
|
+
"""Raised when file system operations fail."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, operation: str, path: str, reason: str) -> None:
|
|
73
|
+
message = f"File system error during {operation} on '{path}': {reason}"
|
|
74
|
+
super().__init__(message)
|
|
75
|
+
self.operation = operation
|
|
76
|
+
self.path = path
|
|
77
|
+
self.reason = reason
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ArtifactValidationError(ArtifactError):
|
|
81
|
+
"""Raised when artifact data validation fails."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str, field: str | None = None) -> None:
|
|
84
|
+
if field:
|
|
85
|
+
message = f"Validation error for field '{field}': {message}"
|
|
86
|
+
else:
|
|
87
|
+
message = f"Validation error: {message}"
|
|
88
|
+
super().__init__(message)
|
|
89
|
+
self.field = field
|