shotgun-sh 0.1.0.dev12__py3-none-any.whl → 0.1.0.dev14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +16 -3
- shotgun/agents/artifact_state.py +58 -0
- shotgun/agents/common.py +137 -88
- shotgun/agents/config/constants.py +18 -0
- shotgun/agents/config/manager.py +68 -16
- shotgun/agents/config/models.py +61 -0
- shotgun/agents/config/provider.py +11 -6
- shotgun/agents/history/compaction.py +85 -0
- shotgun/agents/history/constants.py +19 -0
- shotgun/agents/history/context_extraction.py +108 -0
- shotgun/agents/history/history_building.py +104 -0
- shotgun/agents/history/history_processors.py +354 -157
- shotgun/agents/history/message_utils.py +46 -0
- shotgun/agents/history/token_counting.py +429 -0
- shotgun/agents/history/token_estimation.py +138 -0
- shotgun/agents/models.py +131 -1
- shotgun/agents/plan.py +15 -37
- shotgun/agents/research.py +10 -45
- shotgun/agents/specify.py +97 -0
- shotgun/agents/tasks.py +7 -36
- shotgun/agents/tools/artifact_management.py +482 -0
- shotgun/agents/tools/file_management.py +31 -12
- shotgun/agents/tools/web_search/anthropic.py +78 -17
- shotgun/agents/tools/web_search/gemini.py +1 -1
- shotgun/agents/tools/web_search/openai.py +16 -2
- shotgun/artifacts/__init__.py +17 -0
- shotgun/artifacts/exceptions.py +89 -0
- shotgun/artifacts/manager.py +530 -0
- shotgun/artifacts/models.py +334 -0
- shotgun/artifacts/service.py +463 -0
- shotgun/artifacts/templates/__init__.py +10 -0
- shotgun/artifacts/templates/loader.py +252 -0
- shotgun/artifacts/templates/models.py +136 -0
- shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +66 -0
- shotgun/artifacts/templates/research/market_research.yaml +585 -0
- shotgun/artifacts/templates/research/sdk_comparison.yaml +257 -0
- shotgun/artifacts/templates/specify/prd.yaml +331 -0
- shotgun/artifacts/templates/specify/product_spec.yaml +301 -0
- shotgun/artifacts/utils.py +76 -0
- shotgun/cli/plan.py +1 -4
- shotgun/cli/specify.py +69 -0
- shotgun/cli/tasks.py +0 -4
- shotgun/codebase/core/nl_query.py +4 -4
- shotgun/logging_config.py +23 -7
- shotgun/main.py +7 -6
- shotgun/prompts/agents/partials/artifact_system.j2 +35 -0
- shotgun/prompts/agents/partials/codebase_understanding.j2 +1 -2
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +28 -2
- shotgun/prompts/agents/partials/content_formatting.j2 +65 -0
- shotgun/prompts/agents/partials/interactive_mode.j2 +10 -2
- shotgun/prompts/agents/plan.j2 +33 -32
- shotgun/prompts/agents/research.j2 +39 -29
- shotgun/prompts/agents/specify.j2 +32 -0
- shotgun/prompts/agents/state/artifact_templates_available.j2 +18 -0
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +3 -1
- shotgun/prompts/agents/state/existing_artifacts_available.j2 +23 -0
- shotgun/prompts/agents/state/system_state.j2 +9 -1
- shotgun/prompts/agents/tasks.j2 +27 -12
- shotgun/prompts/history/incremental_summarization.j2 +53 -0
- shotgun/sdk/artifact_models.py +186 -0
- shotgun/sdk/artifacts.py +448 -0
- shotgun/sdk/services.py +14 -0
- shotgun/tui/app.py +26 -7
- shotgun/tui/screens/chat.py +32 -5
- shotgun/tui/screens/directory_setup.py +113 -0
- shotgun/utils/file_system_utils.py +6 -1
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/METADATA +3 -2
- shotgun_sh-0.1.0.dev14.dist-info/RECORD +138 -0
- shotgun/prompts/user/research.j2 +0 -5
- shotgun_sh-0.1.0.dev12.dist-info/RECORD +0 -104
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.1.0.dev12.dist-info → shotgun_sh-0.1.0.dev14.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/models.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""Pydantic models for agent dependencies and configuration."""
|
|
2
2
|
|
|
3
3
|
from asyncio import Future, Queue
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
4
7
|
from pathlib import Path
|
|
5
8
|
from typing import TYPE_CHECKING
|
|
6
9
|
|
|
7
10
|
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
from pydantic_ai import RunContext
|
|
8
12
|
|
|
9
13
|
from .config.models import ModelConfig
|
|
10
14
|
|
|
11
15
|
if TYPE_CHECKING:
|
|
16
|
+
from shotgun.artifacts.service import ArtifactService
|
|
12
17
|
from shotgun.codebase.service import CodebaseService
|
|
13
18
|
|
|
14
19
|
|
|
@@ -72,6 +77,117 @@ class AgentRuntimeOptions(BaseModel):
|
|
|
72
77
|
)
|
|
73
78
|
|
|
74
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
|
+
|
|
75
191
|
class AgentDeps(AgentRuntimeOptions):
|
|
76
192
|
"""Dependencies passed to all agents for configuration and runtime behavior."""
|
|
77
193
|
|
|
@@ -83,12 +199,26 @@ class AgentDeps(AgentRuntimeOptions):
|
|
|
83
199
|
description="Codebase service for code analysis tools",
|
|
84
200
|
)
|
|
85
201
|
|
|
202
|
+
artifact_service: "ArtifactService" = Field(
|
|
203
|
+
description="Artifact service for managing structured artifacts",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
system_prompt_fn: Callable[[RunContext["AgentDeps"]], str] = Field(
|
|
207
|
+
description="Function that generates the system prompt for this agent",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
file_tracker: FileOperationTracker = Field(
|
|
211
|
+
default_factory=FileOperationTracker,
|
|
212
|
+
description="Tracker for file operations during agent run",
|
|
213
|
+
)
|
|
214
|
+
|
|
86
215
|
|
|
87
216
|
# Rebuild model to resolve forward references after imports are available
|
|
88
217
|
try:
|
|
218
|
+
from shotgun.artifacts.service import ArtifactService
|
|
89
219
|
from shotgun.codebase.service import CodebaseService
|
|
90
220
|
|
|
91
221
|
AgentDeps.model_rebuild()
|
|
92
222
|
except ImportError:
|
|
93
|
-
#
|
|
223
|
+
# Services may not be available in all contexts
|
|
94
224
|
pass
|
shotgun/agents/plan.py
CHANGED
|
@@ -1,51 +1,33 @@
|
|
|
1
1
|
"""Plan agent factory and functions using Pydantic AI with file-based memory."""
|
|
2
2
|
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
3
5
|
from pydantic_ai import (
|
|
4
6
|
Agent,
|
|
5
7
|
DeferredToolRequests,
|
|
6
|
-
RunContext,
|
|
7
8
|
)
|
|
8
9
|
from pydantic_ai.agent import AgentRunResult
|
|
9
10
|
from pydantic_ai.messages import ModelMessage
|
|
10
11
|
|
|
11
12
|
from shotgun.agents.config import ProviderType
|
|
12
13
|
from shotgun.logging_config import get_logger
|
|
13
|
-
from shotgun.prompts import PromptLoader
|
|
14
14
|
|
|
15
15
|
from .common import (
|
|
16
16
|
add_system_status_message,
|
|
17
|
+
build_agent_system_prompt,
|
|
17
18
|
create_base_agent,
|
|
18
19
|
create_usage_limits,
|
|
19
|
-
ensure_file_exists,
|
|
20
|
-
get_file_history,
|
|
21
20
|
run_agent,
|
|
22
21
|
)
|
|
23
22
|
from .models import AgentDeps, AgentRuntimeOptions
|
|
24
23
|
|
|
25
24
|
logger = get_logger(__name__)
|
|
26
25
|
|
|
27
|
-
# Global prompt loader instance
|
|
28
|
-
prompt_loader = PromptLoader()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _build_plan_agent_system_prompt(ctx: RunContext[AgentDeps]) -> str:
|
|
32
|
-
"""Build the system prompt for the plan agent.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
ctx: RunContext containing AgentDeps with interactive_mode and other settings
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
The complete system prompt string for the plan agent
|
|
39
|
-
"""
|
|
40
|
-
return prompt_loader.render(
|
|
41
|
-
"agents/plan.j2", interactive_mode=ctx.deps.interactive_mode, context="plans"
|
|
42
|
-
)
|
|
43
|
-
|
|
44
26
|
|
|
45
27
|
def create_plan_agent(
|
|
46
28
|
agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
|
|
47
29
|
) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
|
|
48
|
-
"""Create a plan agent with
|
|
30
|
+
"""Create a plan agent with artifact management capabilities.
|
|
49
31
|
|
|
50
32
|
Args:
|
|
51
33
|
agent_runtime_options: Agent runtime options for the agent
|
|
@@ -55,8 +37,15 @@ def create_plan_agent(
|
|
|
55
37
|
Tuple of (Configured Pydantic AI agent for planning tasks, Agent dependencies)
|
|
56
38
|
"""
|
|
57
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
|
+
|
|
58
43
|
agent, deps = create_base_agent(
|
|
59
|
-
|
|
44
|
+
system_prompt_fn,
|
|
45
|
+
agent_runtime_options,
|
|
46
|
+
load_codebase_understanding_tools=True,
|
|
47
|
+
additional_tools=None,
|
|
48
|
+
provider=provider,
|
|
60
49
|
)
|
|
61
50
|
return agent, deps
|
|
62
51
|
|
|
@@ -67,7 +56,7 @@ async def run_plan_agent(
|
|
|
67
56
|
deps: AgentDeps,
|
|
68
57
|
message_history: list[ModelMessage] | None = None,
|
|
69
58
|
) -> AgentRunResult[str | DeferredToolRequests]:
|
|
70
|
-
"""Create or update a plan based on the given goal.
|
|
59
|
+
"""Create or update a plan based on the given goal using artifacts.
|
|
71
60
|
|
|
72
61
|
Args:
|
|
73
62
|
agent: The configured plan agent
|
|
@@ -80,11 +69,9 @@ async def run_plan_agent(
|
|
|
80
69
|
"""
|
|
81
70
|
logger.debug("📋 Starting planning for goal: %s", goal)
|
|
82
71
|
|
|
83
|
-
#
|
|
84
|
-
ensure_file_exists("plan.md", "# Plan")
|
|
85
|
-
|
|
86
|
-
# Let the agent use its tools to read existing plan and research
|
|
72
|
+
# Simple prompt - the agent system prompt has all the artifact instructions
|
|
87
73
|
full_prompt = f"Create a comprehensive plan for: {goal}"
|
|
74
|
+
|
|
88
75
|
try:
|
|
89
76
|
# Create usage limits for responsible API usage
|
|
90
77
|
usage_limits = create_usage_limits()
|
|
@@ -108,12 +95,3 @@ async def run_plan_agent(
|
|
|
108
95
|
logger.error("Full traceback:\n%s", traceback.format_exc())
|
|
109
96
|
logger.error("❌ Planning failed: %s", str(e))
|
|
110
97
|
raise
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def get_plan_history() -> str:
|
|
114
|
-
"""Get the full plan history from the file.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Plan history content or fallback message
|
|
118
|
-
"""
|
|
119
|
-
return get_file_history("plan.md")
|
shotgun/agents/research.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Research agent factory and functions using Pydantic AI with file-based memory."""
|
|
2
2
|
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
3
5
|
from pydantic_ai import (
|
|
4
6
|
Agent,
|
|
5
7
|
DeferredToolRequests,
|
|
6
|
-
RunContext,
|
|
7
8
|
)
|
|
8
9
|
from pydantic_ai.agent import AgentRunResult
|
|
9
10
|
from pydantic_ai.messages import (
|
|
@@ -12,14 +13,12 @@ from pydantic_ai.messages import (
|
|
|
12
13
|
|
|
13
14
|
from shotgun.agents.config import ProviderType
|
|
14
15
|
from shotgun.logging_config import get_logger
|
|
15
|
-
from shotgun.prompts import PromptLoader
|
|
16
16
|
|
|
17
17
|
from .common import (
|
|
18
18
|
add_system_status_message,
|
|
19
|
+
build_agent_system_prompt,
|
|
19
20
|
create_base_agent,
|
|
20
21
|
create_usage_limits,
|
|
21
|
-
ensure_file_exists,
|
|
22
|
-
get_file_history,
|
|
23
22
|
run_agent,
|
|
24
23
|
)
|
|
25
24
|
from .models import AgentDeps, AgentRuntimeOptions
|
|
@@ -27,30 +26,11 @@ from .tools import get_available_web_search_tools
|
|
|
27
26
|
|
|
28
27
|
logger = get_logger(__name__)
|
|
29
28
|
|
|
30
|
-
# Global prompt loader instance
|
|
31
|
-
prompt_loader = PromptLoader()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _build_research_agent_system_prompt(ctx: RunContext[AgentDeps]) -> str:
|
|
35
|
-
"""Build the system prompt for the research agent.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
ctx: RunContext containing AgentDeps with interactive_mode and other settings
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
The complete system prompt string for the research agent
|
|
42
|
-
"""
|
|
43
|
-
return prompt_loader.render(
|
|
44
|
-
"agents/research.j2",
|
|
45
|
-
interactive_mode=ctx.deps.interactive_mode,
|
|
46
|
-
context="research output",
|
|
47
|
-
)
|
|
48
|
-
|
|
49
29
|
|
|
50
30
|
def create_research_agent(
|
|
51
31
|
agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
|
|
52
32
|
) -> tuple[Agent[AgentDeps, str | DeferredToolRequests], AgentDeps]:
|
|
53
|
-
"""Create a research agent with web search capabilities.
|
|
33
|
+
"""Create a research agent with web search and artifact management capabilities.
|
|
54
34
|
|
|
55
35
|
Args:
|
|
56
36
|
agent_runtime_options: Agent runtime options for the agent
|
|
@@ -71,8 +51,11 @@ def create_research_agent(
|
|
|
71
51
|
else:
|
|
72
52
|
logger.warning("Research agent configured without web search tools")
|
|
73
53
|
|
|
54
|
+
# Use partial to create system prompt function for research agent
|
|
55
|
+
system_prompt_fn = partial(build_agent_system_prompt, "research")
|
|
56
|
+
|
|
74
57
|
agent, deps = create_base_agent(
|
|
75
|
-
|
|
58
|
+
system_prompt_fn,
|
|
76
59
|
agent_runtime_options,
|
|
77
60
|
load_codebase_understanding_tools=True,
|
|
78
61
|
additional_tools=web_search_tools,
|
|
@@ -87,7 +70,7 @@ async def run_research_agent(
|
|
|
87
70
|
deps: AgentDeps,
|
|
88
71
|
message_history: list[ModelMessage] | None = None,
|
|
89
72
|
) -> AgentRunResult[str | DeferredToolRequests]:
|
|
90
|
-
"""Perform research on the given query and update
|
|
73
|
+
"""Perform research on the given query and update research artifacts.
|
|
91
74
|
|
|
92
75
|
Args:
|
|
93
76
|
agent: The configured research agent
|
|
@@ -99,24 +82,15 @@ async def run_research_agent(
|
|
|
99
82
|
"""
|
|
100
83
|
logger.debug("🔬 Starting research for query: %s", query)
|
|
101
84
|
|
|
102
|
-
# Ensure research.md exists
|
|
103
|
-
ensure_file_exists("research.md", "# Research")
|
|
104
|
-
|
|
105
85
|
message_history = await add_system_status_message(deps, message_history)
|
|
106
86
|
|
|
107
|
-
user_prompt = prompt_loader.render(
|
|
108
|
-
"user/research.j2",
|
|
109
|
-
user_query=query,
|
|
110
|
-
context="research output",
|
|
111
|
-
)
|
|
112
|
-
|
|
113
87
|
try:
|
|
114
88
|
# Create usage limits for responsible API usage
|
|
115
89
|
usage_limits = create_usage_limits()
|
|
116
90
|
|
|
117
91
|
result = await run_agent(
|
|
118
92
|
agent=agent,
|
|
119
|
-
prompt=
|
|
93
|
+
prompt=query,
|
|
120
94
|
deps=deps,
|
|
121
95
|
message_history=message_history,
|
|
122
96
|
usage_limits=usage_limits,
|
|
@@ -131,12 +105,3 @@ async def run_research_agent(
|
|
|
131
105
|
logger.error("Full traceback:\n%s", traceback.format_exc())
|
|
132
106
|
logger.error("❌ Research failed: %s", str(e))
|
|
133
107
|
raise
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def get_research_history() -> str:
|
|
137
|
-
"""Get the full research history from the file.
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
Research history content or fallback message
|
|
141
|
-
"""
|
|
142
|
-
return get_file_history("research.md")
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
|
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
|
+
)
|
|
50
|
+
return agent, deps
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def run_specify_agent(
|
|
54
|
+
agent: Agent[AgentDeps, str | DeferredToolRequests],
|
|
55
|
+
requirement: str,
|
|
56
|
+
deps: AgentDeps,
|
|
57
|
+
message_history: list[ModelMessage] | None = None,
|
|
58
|
+
) -> AgentRunResult[str | DeferredToolRequests]:
|
|
59
|
+
"""Create or update specifications based on the given requirement.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
agent: The configured specify agent
|
|
63
|
+
requirement: The specification requirement or instruction
|
|
64
|
+
deps: Agent dependencies
|
|
65
|
+
message_history: Optional message history for conversation continuity
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
AgentRunResult containing the specification process output
|
|
69
|
+
"""
|
|
70
|
+
logger.debug("📋 Starting specification for requirement: %s", requirement)
|
|
71
|
+
|
|
72
|
+
# Simple prompt - the agent system prompt has all the artifact instructions
|
|
73
|
+
full_prompt = f"Create a comprehensive specification for: {requirement}"
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
# Create usage limits for responsible API usage
|
|
77
|
+
usage_limits = create_usage_limits()
|
|
78
|
+
|
|
79
|
+
message_history = await add_system_status_message(deps, message_history)
|
|
80
|
+
|
|
81
|
+
result = await run_agent(
|
|
82
|
+
agent=agent,
|
|
83
|
+
prompt=full_prompt,
|
|
84
|
+
deps=deps,
|
|
85
|
+
message_history=message_history,
|
|
86
|
+
usage_limits=usage_limits,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
logger.debug("✅ Specification completed successfully")
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
import traceback
|
|
94
|
+
|
|
95
|
+
logger.error("Full traceback:\n%s", traceback.format_exc())
|
|
96
|
+
logger.error("❌ Specification failed: %s", str(e))
|
|
97
|
+
raise
|
shotgun/agents/tasks.py
CHANGED
|
@@ -1,48 +1,28 @@
|
|
|
1
1
|
"""Tasks agent factory and functions using Pydantic AI with file-based memory."""
|
|
2
2
|
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
3
5
|
from pydantic_ai import (
|
|
4
6
|
Agent,
|
|
5
7
|
DeferredToolRequests,
|
|
6
|
-
RunContext,
|
|
7
8
|
)
|
|
8
9
|
from pydantic_ai.agent import AgentRunResult
|
|
9
10
|
from pydantic_ai.messages import ModelMessage
|
|
10
11
|
|
|
11
12
|
from shotgun.agents.config import ProviderType
|
|
12
13
|
from shotgun.logging_config import get_logger
|
|
13
|
-
from shotgun.prompts import PromptLoader
|
|
14
14
|
|
|
15
15
|
from .common import (
|
|
16
16
|
add_system_status_message,
|
|
17
|
+
build_agent_system_prompt,
|
|
17
18
|
create_base_agent,
|
|
18
19
|
create_usage_limits,
|
|
19
|
-
ensure_file_exists,
|
|
20
|
-
get_file_history,
|
|
21
20
|
run_agent,
|
|
22
21
|
)
|
|
23
22
|
from .models import AgentDeps, AgentRuntimeOptions
|
|
24
23
|
|
|
25
24
|
logger = get_logger(__name__)
|
|
26
25
|
|
|
27
|
-
# Global prompt loader instance
|
|
28
|
-
prompt_loader = PromptLoader()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _build_tasks_agent_system_prompt(ctx: RunContext[AgentDeps]) -> str:
|
|
32
|
-
"""Build the system prompt for the tasks agent.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
ctx: RunContext containing AgentDeps with interactive_mode and other settings
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
The complete system prompt string for the tasks agent
|
|
39
|
-
"""
|
|
40
|
-
return prompt_loader.render(
|
|
41
|
-
"agents/tasks.j2",
|
|
42
|
-
interactive_mode=ctx.deps.interactive_mode,
|
|
43
|
-
context="task lists",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
26
|
|
|
47
27
|
def create_tasks_agent(
|
|
48
28
|
agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
|
|
@@ -57,8 +37,11 @@ def create_tasks_agent(
|
|
|
57
37
|
Tuple of (Configured Pydantic AI agent for task management, Agent dependencies)
|
|
58
38
|
"""
|
|
59
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
|
+
|
|
60
43
|
agent, deps = create_base_agent(
|
|
61
|
-
|
|
44
|
+
system_prompt_fn, agent_runtime_options, provider=provider
|
|
62
45
|
)
|
|
63
46
|
return agent, deps
|
|
64
47
|
|
|
@@ -82,9 +65,6 @@ async def run_tasks_agent(
|
|
|
82
65
|
"""
|
|
83
66
|
logger.debug("📋 Starting task creation for instruction: %s", instruction)
|
|
84
67
|
|
|
85
|
-
# Ensure tasks.md exists
|
|
86
|
-
ensure_file_exists("tasks.md", "# Tasks")
|
|
87
|
-
|
|
88
68
|
message_history = await add_system_status_message(deps, message_history)
|
|
89
69
|
|
|
90
70
|
# Let the agent use its tools to read existing tasks, plan, and research
|
|
@@ -111,12 +91,3 @@ async def run_tasks_agent(
|
|
|
111
91
|
logger.error("Full traceback:\n%s", traceback.format_exc())
|
|
112
92
|
logger.error("❌ Task creation failed: %s", str(e))
|
|
113
93
|
raise
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def get_tasks_history() -> str:
|
|
117
|
-
"""Get the full tasks history from the file.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Tasks history content or fallback message
|
|
121
|
-
"""
|
|
122
|
-
return get_file_history("tasks.md")
|