shotgun-sh 0.1.0.dev13__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 (40) hide show
  1. shotgun/agents/agent_manager.py +16 -3
  2. shotgun/agents/artifact_state.py +58 -0
  3. shotgun/agents/common.py +48 -14
  4. shotgun/agents/config/models.py +61 -0
  5. shotgun/agents/history/compaction.py +85 -0
  6. shotgun/agents/history/constants.py +19 -0
  7. shotgun/agents/history/context_extraction.py +108 -0
  8. shotgun/agents/history/history_building.py +104 -0
  9. shotgun/agents/history/history_processors.py +354 -157
  10. shotgun/agents/history/message_utils.py +46 -0
  11. shotgun/agents/history/token_counting.py +429 -0
  12. shotgun/agents/history/token_estimation.py +138 -0
  13. shotgun/agents/models.py +125 -1
  14. shotgun/agents/tools/artifact_management.py +56 -24
  15. shotgun/agents/tools/file_management.py +30 -11
  16. shotgun/agents/tools/web_search/anthropic.py +78 -17
  17. shotgun/agents/tools/web_search/gemini.py +1 -1
  18. shotgun/agents/tools/web_search/openai.py +16 -2
  19. shotgun/artifacts/manager.py +2 -1
  20. shotgun/artifacts/models.py +6 -4
  21. shotgun/codebase/core/nl_query.py +4 -4
  22. shotgun/prompts/agents/partials/artifact_system.j2 +4 -1
  23. shotgun/prompts/agents/partials/codebase_understanding.j2 +1 -2
  24. shotgun/prompts/agents/plan.j2 +9 -7
  25. shotgun/prompts/agents/research.j2 +7 -5
  26. shotgun/prompts/agents/specify.j2 +8 -7
  27. shotgun/prompts/agents/state/artifact_templates_available.j2 +18 -0
  28. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +3 -1
  29. shotgun/prompts/agents/state/existing_artifacts_available.j2 +23 -0
  30. shotgun/prompts/agents/state/system_state.j2 +9 -1
  31. shotgun/prompts/history/incremental_summarization.j2 +53 -0
  32. shotgun/sdk/services.py +14 -0
  33. shotgun/tui/app.py +1 -1
  34. shotgun/tui/screens/chat.py +4 -2
  35. shotgun/utils/file_system_utils.py +6 -1
  36. {shotgun_sh-0.1.0.dev13.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/METADATA +2 -1
  37. {shotgun_sh-0.1.0.dev13.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/RECORD +40 -29
  38. {shotgun_sh-0.1.0.dev13.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/WHEEL +0 -0
  39. {shotgun_sh-0.1.0.dev13.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/entry_points.txt +0 -0
  40. {shotgun_sh-0.1.0.dev13.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/models.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  from asyncio import Future, Queue
4
4
  from collections.abc import Callable
5
+ from datetime import datetime
6
+ from enum import Enum
5
7
  from pathlib import Path
6
8
  from typing import TYPE_CHECKING
7
9
 
@@ -11,6 +13,7 @@ from pydantic_ai import RunContext
11
13
  from .config.models import ModelConfig
12
14
 
13
15
  if TYPE_CHECKING:
16
+ from shotgun.artifacts.service import ArtifactService
14
17
  from shotgun.codebase.service import CodebaseService
15
18
 
16
19
 
@@ -74,6 +77,117 @@ class AgentRuntimeOptions(BaseModel):
74
77
  )
75
78
 
76
79
 
80
+ class FileOperationType(str, Enum):
81
+ """Types of file operations that can be tracked."""
82
+
83
+ CREATED = "created"
84
+ UPDATED = "updated"
85
+ DELETED = "deleted"
86
+
87
+
88
+ class FileOperation(BaseModel):
89
+ """Single file operation record."""
90
+
91
+ file_path: str = Field(
92
+ description="Full absolute path to the file",
93
+ )
94
+ operation: FileOperationType = Field(
95
+ description="Type of operation performed",
96
+ )
97
+ timestamp: datetime = Field(
98
+ default_factory=datetime.now,
99
+ description="When the operation occurred",
100
+ )
101
+
102
+
103
+ class FileOperationTracker(BaseModel):
104
+ """Tracks file operations during a single agent run."""
105
+
106
+ operations: list[FileOperation] = Field(
107
+ default_factory=list,
108
+ description="List of file operations performed",
109
+ )
110
+
111
+ def add_operation(
112
+ self, file_path: Path | str, operation: FileOperationType
113
+ ) -> None:
114
+ """Record a file operation.
115
+
116
+ Args:
117
+ file_path: Path to the file (will be converted to absolute)
118
+ operation: Type of operation performed
119
+ """
120
+ # Convert to absolute path string
121
+ if isinstance(file_path, Path):
122
+ absolute_path = str(file_path.resolve())
123
+ else:
124
+ absolute_path = str(Path(file_path).resolve())
125
+
126
+ self.operations.append(
127
+ FileOperation(file_path=absolute_path, operation=operation)
128
+ )
129
+
130
+ def clear(self) -> None:
131
+ """Clear all tracked operations for a new run."""
132
+ self.operations = []
133
+
134
+ def get_summary(self) -> dict[FileOperationType, list[str]]:
135
+ """Get operations grouped by type.
136
+
137
+ Returns:
138
+ Dictionary mapping operation types to lists of file paths
139
+ """
140
+ summary: dict[FileOperationType, list[str]] = {
141
+ FileOperationType.CREATED: [],
142
+ FileOperationType.UPDATED: [],
143
+ FileOperationType.DELETED: [],
144
+ }
145
+
146
+ for op in self.operations:
147
+ summary[op.operation].append(op.file_path)
148
+
149
+ # Remove duplicates while preserving order
150
+ for op_type in summary:
151
+ seen = set()
152
+ unique_paths = []
153
+ for path in summary[op_type]:
154
+ if path not in seen:
155
+ seen.add(path)
156
+ unique_paths.append(path)
157
+ summary[op_type] = unique_paths
158
+
159
+ return summary
160
+
161
+ def format_summary(self) -> str:
162
+ """Generate human-readable summary for the user.
163
+
164
+ Returns:
165
+ Formatted string showing files modified during the run
166
+ """
167
+ if not self.operations:
168
+ return "No files were modified during this run."
169
+
170
+ summary = self.get_summary()
171
+ lines = ["Files modified during this run:"]
172
+
173
+ if summary[FileOperationType.CREATED]:
174
+ lines.append("\nCreated:")
175
+ for path in summary[FileOperationType.CREATED]:
176
+ lines.append(f" - {path}")
177
+
178
+ if summary[FileOperationType.UPDATED]:
179
+ lines.append("\nUpdated:")
180
+ for path in summary[FileOperationType.UPDATED]:
181
+ lines.append(f" - {path}")
182
+
183
+ if summary[FileOperationType.DELETED]:
184
+ lines.append("\nDeleted:")
185
+ for path in summary[FileOperationType.DELETED]:
186
+ lines.append(f" - {path}")
187
+
188
+ return "\n".join(lines)
189
+
190
+
77
191
  class AgentDeps(AgentRuntimeOptions):
78
192
  """Dependencies passed to all agents for configuration and runtime behavior."""
79
193
 
@@ -85,16 +199,26 @@ class AgentDeps(AgentRuntimeOptions):
85
199
  description="Codebase service for code analysis tools",
86
200
  )
87
201
 
202
+ artifact_service: "ArtifactService" = Field(
203
+ description="Artifact service for managing structured artifacts",
204
+ )
205
+
88
206
  system_prompt_fn: Callable[[RunContext["AgentDeps"]], str] = Field(
89
207
  description="Function that generates the system prompt for this agent",
90
208
  )
91
209
 
210
+ file_tracker: FileOperationTracker = Field(
211
+ default_factory=FileOperationTracker,
212
+ description="Tracker for file operations during agent run",
213
+ )
214
+
92
215
 
93
216
  # Rebuild model to resolve forward references after imports are available
94
217
  try:
218
+ from shotgun.artifacts.service import ArtifactService
95
219
  from shotgun.codebase.service import CodebaseService
96
220
 
97
221
  AgentDeps.model_rebuild()
98
222
  except ImportError:
99
- # CodebaseService may not be available in all contexts
223
+ # Services may not be available in all contexts
100
224
  pass
@@ -4,25 +4,17 @@ These tools provide agents with the ability to create and manage structured
4
4
  artifacts instead of flat markdown files.
5
5
  """
6
6
 
7
- from shotgun.artifacts.service import ArtifactService
7
+ from pydantic_ai import RunContext
8
+
9
+ from shotgun.agents.models import AgentDeps
8
10
  from shotgun.artifacts.utils import handle_agent_mode_parsing
9
11
  from shotgun.logging_config import setup_logger
10
12
 
11
13
  logger = setup_logger(__name__)
12
14
 
13
- # Global artifact service instance
14
- _artifact_service: ArtifactService | None = None
15
-
16
-
17
- def get_artifact_service() -> ArtifactService:
18
- """Get or create the global artifact service instance."""
19
- global _artifact_service
20
- if _artifact_service is None:
21
- _artifact_service = ArtifactService()
22
- return _artifact_service
23
-
24
15
 
25
- def create_artifact(
16
+ async def create_artifact(
17
+ ctx: RunContext[AgentDeps],
26
18
  artifact_id: str,
27
19
  agent_mode: str,
28
20
  name: str,
@@ -31,6 +23,7 @@ def create_artifact(
31
23
  """Create a new artifact.
32
24
 
33
25
  Args:
26
+ ctx: RunContext containing AgentDeps with artifact service
34
27
  artifact_id: Unique identifier for the artifact (slug format)
35
28
  agent_mode: Agent mode (research, plan, tasks)
36
29
  name: Human-readable name for the artifact
@@ -55,12 +48,21 @@ def create_artifact(
55
48
  return "Error: Invalid agent mode"
56
49
 
57
50
  try:
58
- service = get_artifact_service()
51
+ service = ctx.deps.artifact_service
59
52
 
60
53
  # Pass template_id if provided and not empty
61
54
  template_to_use = template_id.strip() if template_id.strip() else None
62
55
  artifact = service.create_artifact(artifact_id, mode, name, template_to_use)
63
56
 
57
+ # Track the artifact file creation
58
+ from shotgun.agents.models import FileOperationType
59
+ from shotgun.utils.file_system_utils import get_shotgun_base_path
60
+
61
+ artifact_path = (
62
+ get_shotgun_base_path() / mode.value / artifact_id / "artifact.yaml"
63
+ )
64
+ ctx.deps.file_tracker.add_operation(artifact_path, FileOperationType.CREATED)
65
+
64
66
  success_msg = (
65
67
  f"Created artifact '{artifact_id}' in {agent_mode} mode with name '{name}'"
66
68
  )
@@ -110,10 +112,15 @@ def create_artifact(
110
112
  return f"Error: {error_msg}"
111
113
 
112
114
 
113
- def read_artifact(artifact_id: str, agent_mode: str) -> str:
115
+ async def read_artifact(
116
+ ctx: RunContext[AgentDeps],
117
+ artifact_id: str,
118
+ agent_mode: str,
119
+ ) -> str:
114
120
  """Read all sections of an artifact.
115
121
 
116
122
  Args:
123
+ ctx: RunContext containing AgentDeps with artifact service
117
124
  artifact_id: Artifact identifier
118
125
  agent_mode: Agent mode (research, plan, tasks)
119
126
 
@@ -135,7 +142,7 @@ def read_artifact(artifact_id: str, agent_mode: str) -> str:
135
142
  return "Error: Invalid agent mode"
136
143
 
137
144
  try:
138
- service = get_artifact_service()
145
+ service = ctx.deps.artifact_service
139
146
  artifact = service.get_artifact(artifact_id, mode, "")
140
147
 
141
148
  if not artifact.sections:
@@ -207,7 +214,8 @@ def read_artifact(artifact_id: str, agent_mode: str) -> str:
207
214
  return f"Error: {error_msg}"
208
215
 
209
216
 
210
- def write_artifact_section(
217
+ async def write_artifact_section(
218
+ ctx: RunContext[AgentDeps],
211
219
  artifact_id: str,
212
220
  agent_mode: str,
213
221
  section_number: int,
@@ -220,6 +228,7 @@ def write_artifact_section(
220
228
  Creates the artifact and/or section if they don't exist.
221
229
 
222
230
  Args:
231
+ ctx: RunContext containing AgentDeps with artifact service
223
232
  artifact_id: Artifact identifier
224
233
  agent_mode: Agent mode (research, plan, tasks)
225
234
  section_number: Section number (1, 2, 3, etc.)
@@ -251,13 +260,26 @@ def write_artifact_section(
251
260
  return "Error: Agent mode validation failed"
252
261
 
253
262
  try:
254
- service = get_artifact_service()
263
+ service = ctx.deps.artifact_service
255
264
 
256
265
  # Get or create the section
257
266
  section, created = service.get_or_create_section(
258
267
  artifact_id, mode, section_number, section_slug, section_title, content
259
268
  )
260
269
 
270
+ # Track the section file operation
271
+ from shotgun.agents.models import FileOperationType
272
+ from shotgun.utils.file_system_utils import get_shotgun_base_path
273
+
274
+ section_path = (
275
+ get_shotgun_base_path()
276
+ / mode.value
277
+ / artifact_id
278
+ / f"{section_number:02d}_{section_slug}.md"
279
+ )
280
+ operation = FileOperationType.CREATED if created else FileOperationType.UPDATED
281
+ ctx.deps.file_tracker.add_operation(section_path, operation)
282
+
261
283
  if created:
262
284
  success_msg = (
263
285
  f"Created section {section_number} '{section_title}' "
@@ -283,7 +305,8 @@ def write_artifact_section(
283
305
  return f"Error: {error_msg}"
284
306
 
285
307
 
286
- def read_artifact_section(
308
+ async def read_artifact_section(
309
+ ctx: RunContext[AgentDeps],
287
310
  artifact_id: str,
288
311
  agent_mode: str,
289
312
  section_number: int,
@@ -291,6 +314,7 @@ def read_artifact_section(
291
314
  """Read content from a specific section of an artifact.
292
315
 
293
316
  Args:
317
+ ctx: RunContext containing AgentDeps with artifact service
294
318
  artifact_id: Artifact identifier
295
319
  agent_mode: Agent mode (research, plan, tasks)
296
320
  section_number: Section number
@@ -319,7 +343,7 @@ def read_artifact_section(
319
343
  return "Error: Agent mode validation failed"
320
344
 
321
345
  try:
322
- service = get_artifact_service()
346
+ service = ctx.deps.artifact_service
323
347
 
324
348
  section = service.get_section(artifact_id, mode, section_number)
325
349
 
@@ -341,10 +365,14 @@ def read_artifact_section(
341
365
  return f"Error: {error_msg}"
342
366
 
343
367
 
344
- def list_artifacts(agent_mode: str | None = None) -> str:
368
+ async def list_artifacts(
369
+ ctx: RunContext[AgentDeps],
370
+ agent_mode: str | None = None,
371
+ ) -> str:
345
372
  """List all artifacts, optionally filtered by agent mode.
346
373
 
347
374
  Args:
375
+ ctx: RunContext containing AgentDeps with artifact service
348
376
  agent_mode: Optional agent mode filter (research, plan, tasks)
349
377
 
350
378
  Returns:
@@ -357,7 +385,7 @@ def list_artifacts(agent_mode: str | None = None) -> str:
357
385
  logger.debug("🔧 Listing artifacts for mode: %s", agent_mode or "all")
358
386
 
359
387
  try:
360
- service = get_artifact_service()
388
+ service = ctx.deps.artifact_service
361
389
 
362
390
  mode = None
363
391
  if agent_mode:
@@ -399,10 +427,14 @@ def list_artifacts(agent_mode: str | None = None) -> str:
399
427
  return f"Error: {error_msg}"
400
428
 
401
429
 
402
- def list_artifact_templates(agent_mode: str | None = None) -> str:
430
+ async def list_artifact_templates(
431
+ ctx: RunContext[AgentDeps],
432
+ agent_mode: str | None = None,
433
+ ) -> str:
403
434
  """List available artifact templates, optionally filtered by agent mode.
404
435
 
405
436
  Args:
437
+ ctx: RunContext containing AgentDeps with artifact service
406
438
  agent_mode: Optional agent mode filter (research, plan, tasks)
407
439
 
408
440
  Returns:
@@ -415,7 +447,7 @@ def list_artifact_templates(agent_mode: str | None = None) -> str:
415
447
  logger.debug("🔧 Listing templates for mode: %s", agent_mode or "all")
416
448
 
417
449
  try:
418
- service = get_artifact_service()
450
+ service = ctx.deps.artifact_service
419
451
 
420
452
  mode = None
421
453
  if agent_mode:
@@ -6,16 +6,15 @@ These tools are restricted to the .shotgun directory for security.
6
6
  from pathlib import Path
7
7
  from typing import Literal
8
8
 
9
+ from pydantic_ai import RunContext
10
+
11
+ from shotgun.agents.models import AgentDeps, FileOperationType
9
12
  from shotgun.logging_config import get_logger
13
+ from shotgun.utils.file_system_utils import get_shotgun_base_path
10
14
 
11
15
  logger = get_logger(__name__)
12
16
 
13
17
 
14
- def get_shotgun_base_path() -> Path:
15
- """Get the absolute path to the .shotgun directory."""
16
- return Path.cwd() / ".shotgun"
17
-
18
-
19
18
  def _validate_shotgun_path(filename: str) -> Path:
20
19
  """Validate and resolve a file path within the .shotgun directory.
21
20
 
@@ -44,7 +43,7 @@ def _validate_shotgun_path(filename: str) -> Path:
44
43
  return full_path
45
44
 
46
45
 
47
- def read_file(filename: str) -> str:
46
+ async def read_file(ctx: RunContext[AgentDeps], filename: str) -> str:
48
47
  """Read a file from the .shotgun directory.
49
48
 
50
49
  Args:
@@ -75,7 +74,12 @@ def read_file(filename: str) -> str:
75
74
  return error_msg
76
75
 
77
76
 
78
- def write_file(filename: str, content: str, mode: Literal["w", "a"] = "w") -> str:
77
+ async def write_file(
78
+ ctx: RunContext[AgentDeps],
79
+ filename: str,
80
+ content: str,
81
+ mode: Literal["w", "a"] = "w",
82
+ ) -> str:
79
83
  """Write content to a file in the .shotgun directory.
80
84
 
81
85
  Args:
@@ -97,6 +101,16 @@ def write_file(filename: str, content: str, mode: Literal["w", "a"] = "w") -> st
97
101
  try:
98
102
  file_path = _validate_shotgun_path(filename)
99
103
 
104
+ # Determine operation type
105
+ if mode == "a":
106
+ operation = FileOperationType.UPDATED
107
+ else:
108
+ operation = (
109
+ FileOperationType.CREATED
110
+ if not file_path.exists()
111
+ else FileOperationType.UPDATED
112
+ )
113
+
100
114
  # Ensure parent directory exists
101
115
  file_path.parent.mkdir(parents=True, exist_ok=True)
102
116
 
@@ -105,11 +119,16 @@ def write_file(filename: str, content: str, mode: Literal["w", "a"] = "w") -> st
105
119
  with open(file_path, "a", encoding="utf-8") as f:
106
120
  f.write(content)
107
121
  logger.debug("📄 Appended %d characters to %s", len(content), filename)
108
- return f"Successfully appended {len(content)} characters to {filename}"
122
+ result = f"Successfully appended {len(content)} characters to {filename}"
109
123
  else:
110
124
  file_path.write_text(content, encoding="utf-8")
111
125
  logger.debug("📄 Wrote %d characters to %s", len(content), filename)
112
- return f"Successfully wrote {len(content)} characters to {filename}"
126
+ result = f"Successfully wrote {len(content)} characters to {filename}"
127
+
128
+ # Track the file operation
129
+ ctx.deps.file_tracker.add_operation(file_path, operation)
130
+
131
+ return result
113
132
 
114
133
  except Exception as e:
115
134
  error_msg = f"Error writing file '{filename}': {str(e)}"
@@ -117,7 +136,7 @@ def write_file(filename: str, content: str, mode: Literal["w", "a"] = "w") -> st
117
136
  return error_msg
118
137
 
119
138
 
120
- def append_file(filename: str, content: str) -> str:
139
+ async def append_file(ctx: RunContext[AgentDeps], filename: str, content: str) -> str:
121
140
  """Append content to a file in the .shotgun directory.
122
141
 
123
142
  Args:
@@ -127,4 +146,4 @@ def append_file(filename: str, content: str) -> str:
127
146
  Returns:
128
147
  Success message or error message
129
148
  """
130
- return write_file(filename, content, mode="a")
149
+ return await write_file(ctx, filename, content, mode="a")
@@ -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",
@@ -7,6 +7,7 @@ from typing import Any
7
7
  import yaml
8
8
 
9
9
  from shotgun.logging_config import setup_logger
10
+ from shotgun.utils.file_system_utils import get_shotgun_base_path
10
11
 
11
12
  from .exceptions import (
12
13
  ArtifactFileSystemError,
@@ -32,7 +33,7 @@ class ArtifactManager:
32
33
  base_path: Base path for artifacts. Defaults to .shotgun in current directory.
33
34
  """
34
35
  if base_path is None:
35
- base_path = Path.cwd() / ".shotgun"
36
+ base_path = get_shotgun_base_path()
36
37
  elif isinstance(base_path, str):
37
38
  base_path = Path(base_path)
38
39