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
shotgun/agents/models.py
ADDED
|
@@ -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
|
shotgun/agents/tasks.py
ADDED
|
@@ -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
|
+
]
|