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.

Files changed (73) hide show
  1. shotgun/agents/agent_manager.py +16 -3
  2. shotgun/agents/artifact_state.py +58 -0
  3. shotgun/agents/common.py +137 -88
  4. shotgun/agents/config/constants.py +18 -0
  5. shotgun/agents/config/manager.py +68 -16
  6. shotgun/agents/config/models.py +61 -0
  7. shotgun/agents/config/provider.py +11 -6
  8. shotgun/agents/history/compaction.py +85 -0
  9. shotgun/agents/history/constants.py +19 -0
  10. shotgun/agents/history/context_extraction.py +108 -0
  11. shotgun/agents/history/history_building.py +104 -0
  12. shotgun/agents/history/history_processors.py +354 -157
  13. shotgun/agents/history/message_utils.py +46 -0
  14. shotgun/agents/history/token_counting.py +429 -0
  15. shotgun/agents/history/token_estimation.py +138 -0
  16. shotgun/agents/models.py +131 -1
  17. shotgun/agents/plan.py +15 -37
  18. shotgun/agents/research.py +10 -45
  19. shotgun/agents/specify.py +97 -0
  20. shotgun/agents/tasks.py +7 -36
  21. shotgun/agents/tools/artifact_management.py +482 -0
  22. shotgun/agents/tools/file_management.py +31 -12
  23. shotgun/agents/tools/web_search/anthropic.py +78 -17
  24. shotgun/agents/tools/web_search/gemini.py +1 -1
  25. shotgun/agents/tools/web_search/openai.py +16 -2
  26. shotgun/artifacts/__init__.py +17 -0
  27. shotgun/artifacts/exceptions.py +89 -0
  28. shotgun/artifacts/manager.py +530 -0
  29. shotgun/artifacts/models.py +334 -0
  30. shotgun/artifacts/service.py +463 -0
  31. shotgun/artifacts/templates/__init__.py +10 -0
  32. shotgun/artifacts/templates/loader.py +252 -0
  33. shotgun/artifacts/templates/models.py +136 -0
  34. shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +66 -0
  35. shotgun/artifacts/templates/research/market_research.yaml +585 -0
  36. shotgun/artifacts/templates/research/sdk_comparison.yaml +257 -0
  37. shotgun/artifacts/templates/specify/prd.yaml +331 -0
  38. shotgun/artifacts/templates/specify/product_spec.yaml +301 -0
  39. shotgun/artifacts/utils.py +76 -0
  40. shotgun/cli/plan.py +1 -4
  41. shotgun/cli/specify.py +69 -0
  42. shotgun/cli/tasks.py +0 -4
  43. shotgun/codebase/core/nl_query.py +4 -4
  44. shotgun/logging_config.py +23 -7
  45. shotgun/main.py +7 -6
  46. shotgun/prompts/agents/partials/artifact_system.j2 +35 -0
  47. shotgun/prompts/agents/partials/codebase_understanding.j2 +1 -2
  48. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
  49. shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
  50. shotgun/prompts/agents/partials/interactive_mode.j2 +10 -2
  51. shotgun/prompts/agents/plan.j2 +33 -32
  52. shotgun/prompts/agents/research.j2 +39 -29
  53. shotgun/prompts/agents/specify.j2 +32 -0
  54. shotgun/prompts/agents/state/artifact_templates_available.j2 +18 -0
  55. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +3 -1
  56. shotgun/prompts/agents/state/existing_artifacts_available.j2 +23 -0
  57. shotgun/prompts/agents/state/system_state.j2 +9 -1
  58. shotgun/prompts/agents/tasks.j2 +27 -12
  59. shotgun/prompts/history/incremental_summarization.j2 +53 -0
  60. shotgun/sdk/artifact_models.py +186 -0
  61. shotgun/sdk/artifacts.py +448 -0
  62. shotgun/sdk/services.py +14 -0
  63. shotgun/tui/app.py +26 -7
  64. shotgun/tui/screens/chat.py +32 -5
  65. shotgun/tui/screens/directory_setup.py +113 -0
  66. shotgun/utils/file_system_utils.py +6 -1
  67. {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/METADATA +3 -2
  68. shotgun_sh-0.1.0.dev14.dist-info/RECORD +138 -0
  69. shotgun/prompts/user/research.j2 +0 -5
  70. shotgun_sh-0.1.0.dev12.dist-info/RECORD +0 -104
  71. {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/WHEEL +0 -0
  72. {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/entry_points.txt +0 -0
  73. {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
- response = client.messages.create(
46
+ result_text = ""
47
+
48
+ with client.messages.stream(
47
49
  model="claude-3-5-sonnet-latest",
48
- max_tokens=8192, # Increased from 4096 for more comprehensive results
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
- # Extract the search results from the response
60
- result_text = ""
61
- if hasattr(response, "content") and response.content:
62
- for content in response.content:
63
- if hasattr(content, "text"):
64
- result_text += content.text
65
- elif hasattr(content, "tool_use") and content.tool_use:
66
- # Handle tool use response
67
- result_text += f"Search performed for: {query}\n"
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()
@@ -62,7 +62,7 @@ Instructions:
62
62
  search_prompt,
63
63
  generation_config=genai.GenerationConfig( # type: ignore[attr-defined]
64
64
  temperature=0.3,
65
- max_output_tokens=8192, # Explicit limit for comprehensive results
65
+ max_output_tokens=8192,
66
66
  ),
67
67
  )
68
68
 
@@ -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": query}]}
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": "high", "summary": "auto"}, # Increased from medium
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