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.

Files changed (130) hide show
  1. shotgun/__init__.py +5 -0
  2. shotgun/agents/__init__.py +1 -0
  3. shotgun/agents/agent_manager.py +651 -0
  4. shotgun/agents/common.py +549 -0
  5. shotgun/agents/config/__init__.py +13 -0
  6. shotgun/agents/config/constants.py +17 -0
  7. shotgun/agents/config/manager.py +294 -0
  8. shotgun/agents/config/models.py +185 -0
  9. shotgun/agents/config/provider.py +206 -0
  10. shotgun/agents/conversation_history.py +106 -0
  11. shotgun/agents/conversation_manager.py +105 -0
  12. shotgun/agents/export.py +96 -0
  13. shotgun/agents/history/__init__.py +5 -0
  14. shotgun/agents/history/compaction.py +85 -0
  15. shotgun/agents/history/constants.py +19 -0
  16. shotgun/agents/history/context_extraction.py +108 -0
  17. shotgun/agents/history/history_building.py +104 -0
  18. shotgun/agents/history/history_processors.py +426 -0
  19. shotgun/agents/history/message_utils.py +84 -0
  20. shotgun/agents/history/token_counting.py +429 -0
  21. shotgun/agents/history/token_estimation.py +138 -0
  22. shotgun/agents/messages.py +35 -0
  23. shotgun/agents/models.py +275 -0
  24. shotgun/agents/plan.py +98 -0
  25. shotgun/agents/research.py +108 -0
  26. shotgun/agents/specify.py +98 -0
  27. shotgun/agents/tasks.py +96 -0
  28. shotgun/agents/tools/__init__.py +34 -0
  29. shotgun/agents/tools/codebase/__init__.py +28 -0
  30. shotgun/agents/tools/codebase/codebase_shell.py +256 -0
  31. shotgun/agents/tools/codebase/directory_lister.py +141 -0
  32. shotgun/agents/tools/codebase/file_read.py +144 -0
  33. shotgun/agents/tools/codebase/models.py +252 -0
  34. shotgun/agents/tools/codebase/query_graph.py +67 -0
  35. shotgun/agents/tools/codebase/retrieve_code.py +81 -0
  36. shotgun/agents/tools/file_management.py +218 -0
  37. shotgun/agents/tools/user_interaction.py +37 -0
  38. shotgun/agents/tools/web_search/__init__.py +60 -0
  39. shotgun/agents/tools/web_search/anthropic.py +144 -0
  40. shotgun/agents/tools/web_search/gemini.py +85 -0
  41. shotgun/agents/tools/web_search/openai.py +98 -0
  42. shotgun/agents/tools/web_search/utils.py +20 -0
  43. shotgun/build_constants.py +20 -0
  44. shotgun/cli/__init__.py +1 -0
  45. shotgun/cli/codebase/__init__.py +5 -0
  46. shotgun/cli/codebase/commands.py +202 -0
  47. shotgun/cli/codebase/models.py +21 -0
  48. shotgun/cli/config.py +275 -0
  49. shotgun/cli/export.py +81 -0
  50. shotgun/cli/models.py +10 -0
  51. shotgun/cli/plan.py +73 -0
  52. shotgun/cli/research.py +85 -0
  53. shotgun/cli/specify.py +69 -0
  54. shotgun/cli/tasks.py +78 -0
  55. shotgun/cli/update.py +152 -0
  56. shotgun/cli/utils.py +25 -0
  57. shotgun/codebase/__init__.py +12 -0
  58. shotgun/codebase/core/__init__.py +46 -0
  59. shotgun/codebase/core/change_detector.py +358 -0
  60. shotgun/codebase/core/code_retrieval.py +243 -0
  61. shotgun/codebase/core/ingestor.py +1497 -0
  62. shotgun/codebase/core/language_config.py +297 -0
  63. shotgun/codebase/core/manager.py +1662 -0
  64. shotgun/codebase/core/nl_query.py +331 -0
  65. shotgun/codebase/core/parser_loader.py +128 -0
  66. shotgun/codebase/models.py +111 -0
  67. shotgun/codebase/service.py +206 -0
  68. shotgun/logging_config.py +227 -0
  69. shotgun/main.py +167 -0
  70. shotgun/posthog_telemetry.py +158 -0
  71. shotgun/prompts/__init__.py +5 -0
  72. shotgun/prompts/agents/__init__.py +1 -0
  73. shotgun/prompts/agents/export.j2 +350 -0
  74. shotgun/prompts/agents/partials/codebase_understanding.j2 +87 -0
  75. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +37 -0
  76. shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
  77. shotgun/prompts/agents/partials/interactive_mode.j2 +26 -0
  78. shotgun/prompts/agents/plan.j2 +144 -0
  79. shotgun/prompts/agents/research.j2 +69 -0
  80. shotgun/prompts/agents/specify.j2 +51 -0
  81. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +19 -0
  82. shotgun/prompts/agents/state/system_state.j2 +31 -0
  83. shotgun/prompts/agents/tasks.j2 +143 -0
  84. shotgun/prompts/codebase/__init__.py +1 -0
  85. shotgun/prompts/codebase/cypher_query_patterns.j2 +223 -0
  86. shotgun/prompts/codebase/cypher_system.j2 +28 -0
  87. shotgun/prompts/codebase/enhanced_query_context.j2 +10 -0
  88. shotgun/prompts/codebase/partials/cypher_rules.j2 +24 -0
  89. shotgun/prompts/codebase/partials/graph_schema.j2 +30 -0
  90. shotgun/prompts/codebase/partials/temporal_context.j2 +21 -0
  91. shotgun/prompts/history/__init__.py +1 -0
  92. shotgun/prompts/history/incremental_summarization.j2 +53 -0
  93. shotgun/prompts/history/summarization.j2 +46 -0
  94. shotgun/prompts/loader.py +140 -0
  95. shotgun/py.typed +0 -0
  96. shotgun/sdk/__init__.py +13 -0
  97. shotgun/sdk/codebase.py +219 -0
  98. shotgun/sdk/exceptions.py +17 -0
  99. shotgun/sdk/models.py +189 -0
  100. shotgun/sdk/services.py +23 -0
  101. shotgun/sentry_telemetry.py +87 -0
  102. shotgun/telemetry.py +93 -0
  103. shotgun/tui/__init__.py +0 -0
  104. shotgun/tui/app.py +116 -0
  105. shotgun/tui/commands/__init__.py +76 -0
  106. shotgun/tui/components/prompt_input.py +69 -0
  107. shotgun/tui/components/spinner.py +86 -0
  108. shotgun/tui/components/splash.py +25 -0
  109. shotgun/tui/components/vertical_tail.py +13 -0
  110. shotgun/tui/screens/chat.py +782 -0
  111. shotgun/tui/screens/chat.tcss +43 -0
  112. shotgun/tui/screens/chat_screen/__init__.py +0 -0
  113. shotgun/tui/screens/chat_screen/command_providers.py +219 -0
  114. shotgun/tui/screens/chat_screen/hint_message.py +40 -0
  115. shotgun/tui/screens/chat_screen/history.py +221 -0
  116. shotgun/tui/screens/directory_setup.py +113 -0
  117. shotgun/tui/screens/provider_config.py +221 -0
  118. shotgun/tui/screens/splash.py +31 -0
  119. shotgun/tui/styles.tcss +10 -0
  120. shotgun/tui/utils/__init__.py +5 -0
  121. shotgun/tui/utils/mode_progress.py +257 -0
  122. shotgun/utils/__init__.py +5 -0
  123. shotgun/utils/env_utils.py +35 -0
  124. shotgun/utils/file_system_utils.py +36 -0
  125. shotgun/utils/update_checker.py +375 -0
  126. shotgun_sh-0.1.0.dist-info/METADATA +466 -0
  127. shotgun_sh-0.1.0.dist-info/RECORD +130 -0
  128. shotgun_sh-0.1.0.dist-info/WHEEL +4 -0
  129. shotgun_sh-0.1.0.dist-info/entry_points.txt +2 -0
  130. shotgun_sh-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,275 @@
1
+ """Pydantic models for agent dependencies and configuration."""
2
+
3
+ import os
4
+ from asyncio import Future, Queue
5
+ from collections.abc import Callable
6
+ from datetime import datetime
7
+ from enum import Enum, StrEnum
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+ from pydantic_ai import RunContext
13
+
14
+ from .config.models import ModelConfig
15
+
16
+ if TYPE_CHECKING:
17
+ from shotgun.codebase.service import CodebaseService
18
+
19
+
20
+ class AgentType(StrEnum):
21
+ """Enumeration for available agent types."""
22
+
23
+ RESEARCH = "research"
24
+ SPECIFY = "specify"
25
+ PLAN = "plan"
26
+ TASKS = "tasks"
27
+ EXPORT = "export"
28
+
29
+
30
+ class PipelineConfigEntry(BaseModel):
31
+ """Configuration for each agent in the pipeline.
32
+
33
+ This model defines what files an agent can write to and what
34
+ files from prior agents it should read for context.
35
+ """
36
+
37
+ own_file: str | None = Field(
38
+ default=None,
39
+ description="The file this agent writes to (None for export agent)",
40
+ )
41
+ prior_files: list[str] = Field(
42
+ default_factory=list,
43
+ description="Files from prior agents in pipeline to read for context",
44
+ )
45
+
46
+
47
+ class UserAnswer(BaseModel):
48
+ """A answer from the user."""
49
+
50
+ answer: str = Field(
51
+ description="The answer from the user",
52
+ )
53
+ tool_call_id: str = Field(
54
+ description="Tool call id",
55
+ )
56
+
57
+
58
+ class UserQuestion(BaseModel):
59
+ """A question asked by the user."""
60
+
61
+ model_config = ConfigDict(arbitrary_types_allowed=True)
62
+
63
+ question: str = Field(
64
+ description="The question asked by the user",
65
+ )
66
+ tool_call_id: str = Field(
67
+ description="Tool call id",
68
+ )
69
+ result: Future[UserAnswer] = Field(
70
+ description="Future that will contain the user's answer"
71
+ )
72
+
73
+
74
+ class AgentRuntimeOptions(BaseModel):
75
+ """User interface options for agents."""
76
+
77
+ model_config = ConfigDict(arbitrary_types_allowed=True)
78
+
79
+ interactive_mode: bool = Field(
80
+ default=True,
81
+ description="Whether agents can interact with users (ask questions, etc.)",
82
+ )
83
+
84
+ working_directory: Path = Field(
85
+ default_factory=lambda: Path.cwd(),
86
+ description="Working directory for agent operations",
87
+ )
88
+
89
+ is_tui_context: bool = Field(
90
+ default=False,
91
+ description="Whether the agent is running in TUI context",
92
+ )
93
+
94
+ max_iterations: int = Field(
95
+ default=10,
96
+ ge=1,
97
+ le=100,
98
+ description="Maximum number of iterations for agent loops",
99
+ )
100
+
101
+ queue: Queue[UserQuestion] = Field(
102
+ default_factory=Queue,
103
+ description="Queue for storing user responses",
104
+ )
105
+
106
+ tasks: list[Future[UserAnswer]] = Field(
107
+ default_factory=list,
108
+ description="Tasks for storing deferred tool results",
109
+ )
110
+
111
+
112
+ class FileOperationType(str, Enum):
113
+ """Types of file operations that can be tracked."""
114
+
115
+ CREATED = "created"
116
+ UPDATED = "updated"
117
+ DELETED = "deleted"
118
+
119
+
120
+ class FileOperation(BaseModel):
121
+ """Single file operation record."""
122
+
123
+ file_path: str = Field(
124
+ description="Full absolute path to the file",
125
+ )
126
+ operation: FileOperationType = Field(
127
+ description="Type of operation performed",
128
+ )
129
+ timestamp: datetime = Field(
130
+ default_factory=datetime.now,
131
+ description="When the operation occurred",
132
+ )
133
+
134
+
135
+ class FileOperationTracker(BaseModel):
136
+ """Tracks file operations during a single agent run."""
137
+
138
+ operations: list[FileOperation] = Field(
139
+ default_factory=list,
140
+ description="List of file operations performed",
141
+ )
142
+
143
+ def add_operation(
144
+ self, file_path: Path | str, operation: FileOperationType
145
+ ) -> None:
146
+ """Record a file operation.
147
+
148
+ Args:
149
+ file_path: Path to the file (will be converted to absolute)
150
+ operation: Type of operation performed
151
+ """
152
+ # Convert to absolute path string
153
+ if isinstance(file_path, Path):
154
+ absolute_path = str(file_path.resolve())
155
+ else:
156
+ absolute_path = str(Path(file_path).resolve())
157
+
158
+ self.operations.append(
159
+ FileOperation(file_path=absolute_path, operation=operation)
160
+ )
161
+
162
+ def clear(self) -> None:
163
+ """Clear all tracked operations for a new run."""
164
+ self.operations = []
165
+
166
+ def get_summary(self) -> dict[FileOperationType, list[str]]:
167
+ """Get operations grouped by type.
168
+
169
+ Returns:
170
+ Dictionary mapping operation types to lists of file paths
171
+ """
172
+ summary: dict[FileOperationType, list[str]] = {
173
+ FileOperationType.CREATED: [],
174
+ FileOperationType.UPDATED: [],
175
+ FileOperationType.DELETED: [],
176
+ }
177
+
178
+ for op in self.operations:
179
+ summary[op.operation].append(op.file_path)
180
+
181
+ # Remove duplicates while preserving order
182
+ for op_type in summary:
183
+ seen = set()
184
+ unique_paths = []
185
+ for path in summary[op_type]:
186
+ if path not in seen:
187
+ seen.add(path)
188
+ unique_paths.append(path)
189
+ summary[op_type] = unique_paths
190
+
191
+ return summary
192
+
193
+ def format_summary(self) -> str:
194
+ """Generate human-readable summary for the user.
195
+
196
+ Returns:
197
+ Formatted string showing files modified during the run
198
+ """
199
+ if not self.operations:
200
+ return "No files were modified during this run."
201
+
202
+ summary = self.get_summary()
203
+ lines = ["Files modified during this run:"]
204
+
205
+ if summary[FileOperationType.CREATED]:
206
+ lines.append("\nCreated:")
207
+ for path in summary[FileOperationType.CREATED]:
208
+ lines.append(f" - {path}")
209
+
210
+ if summary[FileOperationType.UPDATED]:
211
+ lines.append("\nUpdated:")
212
+ for path in summary[FileOperationType.UPDATED]:
213
+ lines.append(f" - {path}")
214
+
215
+ if summary[FileOperationType.DELETED]:
216
+ lines.append("\nDeleted:")
217
+ for path in summary[FileOperationType.DELETED]:
218
+ lines.append(f" - {path}")
219
+
220
+ return "\n".join(lines)
221
+
222
+ def get_display_path(self) -> str | None:
223
+ """Get a single file path or common parent directory for display.
224
+
225
+ Returns:
226
+ Path string to display, or None if no files were modified
227
+ """
228
+ if not self.operations:
229
+ return None
230
+
231
+ unique_paths = list({op.file_path for op in self.operations})
232
+
233
+ if len(unique_paths) == 1:
234
+ # Single file - return its path
235
+ return unique_paths[0]
236
+
237
+ # Multiple files - find common parent directory
238
+ common_path = os.path.commonpath(unique_paths)
239
+ return common_path
240
+
241
+
242
+ class AgentDeps(AgentRuntimeOptions):
243
+ """Dependencies passed to all agents for configuration and runtime behavior."""
244
+
245
+ llm_model: ModelConfig = Field(
246
+ description="Model configuration with token limits and provider info",
247
+ )
248
+
249
+ codebase_service: "CodebaseService" = Field(
250
+ description="Codebase service for code analysis tools",
251
+ )
252
+
253
+ system_prompt_fn: Callable[[RunContext["AgentDeps"]], str] = Field(
254
+ description="Function that generates the system prompt for this agent",
255
+ )
256
+
257
+ file_tracker: FileOperationTracker = Field(
258
+ default_factory=FileOperationTracker,
259
+ description="Tracker for file operations during agent run",
260
+ )
261
+
262
+ agent_mode: AgentType | None = Field(
263
+ default=None,
264
+ description="Current agent mode for file scoping",
265
+ )
266
+
267
+
268
+ # Rebuild model to resolve forward references after imports are available
269
+ try:
270
+ from shotgun.codebase.service import CodebaseService
271
+
272
+ AgentDeps.model_rebuild()
273
+ except ImportError:
274
+ # Services may not be available in all contexts
275
+ pass
shotgun/agents/plan.py ADDED
@@ -0,0 +1,98 @@
1
+ """Plan agent factory and functions using Pydantic AI with file-based memory."""
2
+
3
+ from functools import partial
4
+
5
+ from pydantic_ai import (
6
+ Agent,
7
+ DeferredToolRequests,
8
+ )
9
+ from pydantic_ai.agent import AgentRunResult
10
+ from pydantic_ai.messages import ModelMessage
11
+
12
+ from shotgun.agents.config import ProviderType
13
+ from shotgun.logging_config import get_logger
14
+
15
+ from .common import (
16
+ add_system_status_message,
17
+ build_agent_system_prompt,
18
+ create_base_agent,
19
+ create_usage_limits,
20
+ run_agent,
21
+ )
22
+ from .models import AgentDeps, AgentRuntimeOptions, AgentType
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ def create_plan_agent(
28
+ agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
29
+ ) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
30
+ """Create a plan agent with artifact management capabilities.
31
+
32
+ Args:
33
+ agent_runtime_options: Agent runtime options for the agent
34
+ provider: Optional provider override. If None, uses configured default
35
+
36
+ Returns:
37
+ Tuple of (Configured Pydantic AI agent for planning tasks, Agent dependencies)
38
+ """
39
+ logger.debug("Initializing plan agent")
40
+ # Use partial to create system prompt function for plan agent
41
+ system_prompt_fn = partial(build_agent_system_prompt, "plan")
42
+
43
+ agent, deps = create_base_agent(
44
+ system_prompt_fn,
45
+ agent_runtime_options,
46
+ load_codebase_understanding_tools=True,
47
+ additional_tools=None,
48
+ provider=provider,
49
+ agent_mode=AgentType.PLAN,
50
+ )
51
+ return agent, deps
52
+
53
+
54
+ async def run_plan_agent(
55
+ agent: Agent[AgentDeps, str | DeferredToolRequests],
56
+ goal: str,
57
+ deps: AgentDeps,
58
+ message_history: list[ModelMessage] | None = None,
59
+ ) -> AgentRunResult[str | DeferredToolRequests]:
60
+ """Create or update a plan based on the given goal using artifacts.
61
+
62
+ Args:
63
+ agent: The configured plan agent
64
+ goal: The planning goal or instruction
65
+ deps: Agent dependencies
66
+ message_history: Optional message history for conversation continuity
67
+
68
+ Returns:
69
+ AgentRunResult containing the planning process output
70
+ """
71
+ logger.debug("📋 Starting planning for goal: %s", goal)
72
+
73
+ # Simple prompt - the agent system prompt has all the artifact instructions
74
+ full_prompt = f"Create a comprehensive plan for: {goal}"
75
+
76
+ try:
77
+ # Create usage limits for responsible API usage
78
+ usage_limits = create_usage_limits()
79
+
80
+ message_history = await add_system_status_message(deps, message_history)
81
+
82
+ result = await run_agent(
83
+ agent=agent,
84
+ prompt=full_prompt,
85
+ deps=deps,
86
+ message_history=message_history,
87
+ usage_limits=usage_limits,
88
+ )
89
+
90
+ logger.debug("✅ Planning completed successfully")
91
+ return result
92
+
93
+ except Exception as e:
94
+ import traceback
95
+
96
+ logger.error("Full traceback:\n%s", traceback.format_exc())
97
+ logger.error("❌ Planning failed: %s", str(e))
98
+ raise
@@ -0,0 +1,108 @@
1
+ """Research agent factory and functions using Pydantic AI with file-based memory."""
2
+
3
+ from functools import partial
4
+
5
+ from pydantic_ai import (
6
+ Agent,
7
+ DeferredToolRequests,
8
+ )
9
+ from pydantic_ai.agent import AgentRunResult
10
+ from pydantic_ai.messages import (
11
+ ModelMessage,
12
+ )
13
+
14
+ from shotgun.agents.config import ProviderType
15
+ from shotgun.logging_config import get_logger
16
+
17
+ from .common import (
18
+ add_system_status_message,
19
+ build_agent_system_prompt,
20
+ create_base_agent,
21
+ create_usage_limits,
22
+ run_agent,
23
+ )
24
+ from .models import AgentDeps, AgentRuntimeOptions, AgentType
25
+ from .tools import get_available_web_search_tools
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ def create_research_agent(
31
+ agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
32
+ ) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
33
+ """Create a research agent with web search and artifact management capabilities.
34
+
35
+ Args:
36
+ agent_runtime_options: Agent runtime options for the agent
37
+ provider: Optional provider override. If None, uses configured default
38
+
39
+ Returns:
40
+ Tuple of (Configured Pydantic AI agent for research tasks, Agent dependencies)
41
+ """
42
+ logger.debug("Initializing research agent")
43
+
44
+ # Get available web search tools based on configured API keys
45
+ web_search_tools = get_available_web_search_tools()
46
+ if web_search_tools:
47
+ logger.info(
48
+ "Research agent configured with %d web search tool(s)",
49
+ len(web_search_tools),
50
+ )
51
+ else:
52
+ logger.warning("Research agent configured without web search tools")
53
+
54
+ # Use partial to create system prompt function for research agent
55
+ system_prompt_fn = partial(build_agent_system_prompt, "research")
56
+
57
+ agent, deps = create_base_agent(
58
+ system_prompt_fn,
59
+ agent_runtime_options,
60
+ load_codebase_understanding_tools=True,
61
+ additional_tools=web_search_tools,
62
+ provider=provider,
63
+ agent_mode=AgentType.RESEARCH,
64
+ )
65
+ return agent, deps
66
+
67
+
68
+ async def run_research_agent(
69
+ agent: Agent[AgentDeps, str | DeferredToolRequests],
70
+ query: str,
71
+ deps: AgentDeps,
72
+ message_history: list[ModelMessage] | None = None,
73
+ ) -> AgentRunResult[str | DeferredToolRequests]:
74
+ """Perform research on the given query and update research artifacts.
75
+
76
+ Args:
77
+ agent: The configured research agent
78
+ query: The research query to investigate
79
+ deps: Agent dependencies
80
+
81
+ Returns:
82
+ Summary of research findings
83
+ """
84
+ logger.debug("🔬 Starting research for query: %s", query)
85
+
86
+ message_history = await add_system_status_message(deps, message_history)
87
+
88
+ try:
89
+ # Create usage limits for responsible API usage
90
+ usage_limits = create_usage_limits()
91
+
92
+ result = await run_agent(
93
+ agent=agent,
94
+ prompt=query,
95
+ deps=deps,
96
+ message_history=message_history,
97
+ usage_limits=usage_limits,
98
+ )
99
+
100
+ logger.debug("✅ Research completed successfully")
101
+ return result
102
+
103
+ except Exception as e:
104
+ import traceback
105
+
106
+ logger.error("Full traceback:\n%s", traceback.format_exc())
107
+ logger.error("❌ Research failed: %s", str(e))
108
+ raise
@@ -0,0 +1,98 @@
1
+ """Specify agent factory and functions using Pydantic AI with file-based memory."""
2
+
3
+ from functools import partial
4
+
5
+ from pydantic_ai import (
6
+ Agent,
7
+ DeferredToolRequests,
8
+ )
9
+ from pydantic_ai.agent import AgentRunResult
10
+ from pydantic_ai.messages import ModelMessage
11
+
12
+ from shotgun.agents.config import ProviderType
13
+ from shotgun.logging_config import get_logger
14
+
15
+ from .common import (
16
+ add_system_status_message,
17
+ build_agent_system_prompt,
18
+ create_base_agent,
19
+ create_usage_limits,
20
+ run_agent,
21
+ )
22
+ from .models import AgentDeps, AgentRuntimeOptions, AgentType
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ def create_specify_agent(
28
+ agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
29
+ ) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
30
+ """Create a specify agent with artifact management capabilities.
31
+
32
+ Args:
33
+ agent_runtime_options: Agent runtime options for the agent
34
+ provider: Optional provider override. If None, uses configured default
35
+
36
+ Returns:
37
+ Tuple of (Configured Pydantic AI agent for specification tasks, Agent dependencies)
38
+ """
39
+ logger.debug("Initializing specify agent")
40
+ # Use partial to create system prompt function for specify agent
41
+ system_prompt_fn = partial(build_agent_system_prompt, "specify")
42
+
43
+ agent, deps = create_base_agent(
44
+ system_prompt_fn,
45
+ agent_runtime_options,
46
+ load_codebase_understanding_tools=True,
47
+ additional_tools=None,
48
+ provider=provider,
49
+ agent_mode=AgentType.SPECIFY,
50
+ )
51
+ return agent, deps
52
+
53
+
54
+ async def run_specify_agent(
55
+ agent: Agent[AgentDeps, str | DeferredToolRequests],
56
+ requirement: str,
57
+ deps: AgentDeps,
58
+ message_history: list[ModelMessage] | None = None,
59
+ ) -> AgentRunResult[str | DeferredToolRequests]:
60
+ """Create or update specifications based on the given requirement.
61
+
62
+ Args:
63
+ agent: The configured specify agent
64
+ requirement: The specification requirement or instruction
65
+ deps: Agent dependencies
66
+ message_history: Optional message history for conversation continuity
67
+
68
+ Returns:
69
+ AgentRunResult containing the specification process output
70
+ """
71
+ logger.debug("📋 Starting specification for requirement: %s", requirement)
72
+
73
+ # Simple prompt - the agent system prompt has all the artifact instructions
74
+ full_prompt = f"Create a comprehensive specification for: {requirement}"
75
+
76
+ try:
77
+ # Create usage limits for responsible API usage
78
+ usage_limits = create_usage_limits()
79
+
80
+ message_history = await add_system_status_message(deps, message_history)
81
+
82
+ result = await run_agent(
83
+ agent=agent,
84
+ prompt=full_prompt,
85
+ deps=deps,
86
+ message_history=message_history,
87
+ usage_limits=usage_limits,
88
+ )
89
+
90
+ logger.debug("✅ Specification completed successfully")
91
+ return result
92
+
93
+ except Exception as e:
94
+ import traceback
95
+
96
+ logger.error("Full traceback:\n%s", traceback.format_exc())
97
+ logger.error("❌ Specification failed: %s", str(e))
98
+ raise
@@ -0,0 +1,96 @@
1
+ """Tasks agent factory and functions using Pydantic AI with file-based memory."""
2
+
3
+ from functools import partial
4
+
5
+ from pydantic_ai import (
6
+ Agent,
7
+ DeferredToolRequests,
8
+ )
9
+ from pydantic_ai.agent import AgentRunResult
10
+ from pydantic_ai.messages import ModelMessage
11
+
12
+ from shotgun.agents.config import ProviderType
13
+ from shotgun.logging_config import get_logger
14
+
15
+ from .common import (
16
+ add_system_status_message,
17
+ build_agent_system_prompt,
18
+ create_base_agent,
19
+ create_usage_limits,
20
+ run_agent,
21
+ )
22
+ from .models import AgentDeps, AgentRuntimeOptions, AgentType
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ def create_tasks_agent(
28
+ agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
29
+ ) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
30
+ """Create a tasks agent with file management capabilities.
31
+
32
+ Args:
33
+ agent_runtime_options: Agent runtime options for the agent
34
+ provider: Optional provider override. If None, uses configured default
35
+
36
+ Returns:
37
+ Tuple of (Configured Pydantic AI agent for task management, Agent dependencies)
38
+ """
39
+ logger.debug("Initializing tasks agent")
40
+ # Use partial to create system prompt function for tasks agent
41
+ system_prompt_fn = partial(build_agent_system_prompt, "tasks")
42
+
43
+ agent, deps = create_base_agent(
44
+ system_prompt_fn,
45
+ agent_runtime_options,
46
+ provider=provider,
47
+ agent_mode=AgentType.TASKS,
48
+ )
49
+ return agent, deps
50
+
51
+
52
+ async def run_tasks_agent(
53
+ agent: Agent[AgentDeps, str | DeferredToolRequests],
54
+ instruction: str,
55
+ deps: AgentDeps,
56
+ message_history: list[ModelMessage] | None = None,
57
+ ) -> AgentRunResult[str | DeferredToolRequests]:
58
+ """Create or update tasks based on the given instruction.
59
+
60
+ Args:
61
+ agent: The configured tasks agent
62
+ instruction: The task creation/update instruction
63
+ deps: Agent dependencies
64
+ message_history: Optional message history for conversation continuity
65
+
66
+ Returns:
67
+ AgentRunResult containing the task creation process output
68
+ """
69
+ logger.debug("📋 Starting task creation for instruction: %s", instruction)
70
+
71
+ message_history = await add_system_status_message(deps, message_history)
72
+
73
+ # Let the agent use its tools to read existing tasks, plan, and research
74
+ full_prompt = f"Create or update tasks based on: {instruction}"
75
+
76
+ try:
77
+ # Create usage limits for responsible API usage
78
+ usage_limits = create_usage_limits()
79
+
80
+ result = await run_agent(
81
+ agent=agent,
82
+ prompt=full_prompt,
83
+ deps=deps,
84
+ message_history=message_history,
85
+ usage_limits=usage_limits,
86
+ )
87
+
88
+ logger.debug("✅ Task creation completed successfully")
89
+ return result
90
+
91
+ except Exception as e:
92
+ import traceback
93
+
94
+ logger.error("Full traceback:\n%s", traceback.format_exc())
95
+ logger.error("❌ Task creation failed: %s", str(e))
96
+ raise
@@ -0,0 +1,34 @@
1
+ """Tools package for Pydantic AI agents."""
2
+
3
+ from .codebase import (
4
+ codebase_shell,
5
+ directory_lister,
6
+ file_read,
7
+ query_graph,
8
+ retrieve_code,
9
+ )
10
+ from .file_management import append_file, read_file, write_file
11
+ from .user_interaction import ask_user
12
+ from .web_search import (
13
+ anthropic_web_search_tool,
14
+ gemini_web_search_tool,
15
+ get_available_web_search_tools,
16
+ openai_web_search_tool,
17
+ )
18
+
19
+ __all__ = [
20
+ "openai_web_search_tool",
21
+ "anthropic_web_search_tool",
22
+ "gemini_web_search_tool",
23
+ "get_available_web_search_tools",
24
+ "ask_user",
25
+ "read_file",
26
+ "write_file",
27
+ "append_file",
28
+ # Codebase understanding tools
29
+ "query_graph",
30
+ "retrieve_code",
31
+ "file_read",
32
+ "directory_lister",
33
+ "codebase_shell",
34
+ ]