shotgun-sh 0.2.19__py3-none-any.whl → 0.2.23.dev1__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 +4 -1
- shotgun/agents/config/models.py +9 -0
- shotgun/agents/error/__init__.py +11 -0
- shotgun/agents/error/models.py +19 -0
- shotgun/agents/history/token_counting/anthropic.py +8 -0
- shotgun/agents/runner.py +230 -0
- shotgun/build_constants.py +2 -2
- shotgun/cli/context.py +43 -0
- shotgun/cli/error_handler.py +24 -0
- shotgun/cli/export.py +34 -34
- shotgun/cli/plan.py +34 -34
- shotgun/cli/research.py +17 -9
- shotgun/cli/specify.py +20 -19
- shotgun/cli/tasks.py +34 -34
- shotgun/exceptions.py +323 -0
- shotgun/llm_proxy/__init__.py +17 -0
- shotgun/llm_proxy/client.py +215 -0
- shotgun/llm_proxy/models.py +137 -0
- shotgun/sdk/codebase.py +14 -3
- shotgun/tui/app.py +28 -9
- shotgun/tui/screens/chat/chat_screen.py +126 -56
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +62 -11
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/chat_screen/history/chat_history.py +36 -0
- shotgun/tui/screens/directory_setup.py +35 -40
- {shotgun_sh-0.2.19.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/METADATA +8 -1
- {shotgun_sh-0.2.19.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/RECORD +30 -24
- {shotgun_sh-0.2.19.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.19.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.19.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/agent_manager.py
CHANGED
|
@@ -633,7 +633,10 @@ class AgentManager(Widget):
|
|
|
633
633
|
"Your OpenAI organization doesn't have streaming enabled for this model.\n\n"
|
|
634
634
|
"**Options:**\n"
|
|
635
635
|
"- Get a [Shotgun Account](https://shotgun.sh) - streaming works out of the box\n"
|
|
636
|
-
"- Complete [Biometric Verification](https://platform.openai.com/settings/organization/
|
|
636
|
+
"- Complete [Biometric Verification](https://platform.openai.com/settings/organization/general) with OpenAI, then:\n"
|
|
637
|
+
" 1. Press `Ctrl+P` → Open Provider Setup\n"
|
|
638
|
+
" 2. Select OpenAI → Clear key\n"
|
|
639
|
+
" 3. Re-add your OpenAI API key\n\n"
|
|
637
640
|
"Continuing without streaming (responses will appear all at once)."
|
|
638
641
|
)
|
|
639
642
|
)
|
shotgun/agents/config/models.py
CHANGED
|
@@ -86,6 +86,15 @@ class ModelConfig(BaseModel):
|
|
|
86
86
|
}
|
|
87
87
|
return f"{provider_prefix[self.provider]}:{self.name}"
|
|
88
88
|
|
|
89
|
+
@property
|
|
90
|
+
def is_shotgun_account(self) -> bool:
|
|
91
|
+
"""Check if this model is using Shotgun Account authentication.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if using Shotgun Account, False if BYOK
|
|
95
|
+
"""
|
|
96
|
+
return self.key_provider == KeyProvider.SHOTGUN
|
|
97
|
+
|
|
89
98
|
|
|
90
99
|
# Model specifications registry (static metadata)
|
|
91
100
|
MODEL_SPECS: dict[ModelName, ModelSpec] = {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Pydantic models for agent error handling."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AgentErrorContext(BaseModel):
|
|
9
|
+
"""Context information needed to classify and handle agent errors.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
exception: The exception that was raised
|
|
13
|
+
is_shotgun_account: Whether the user is using a Shotgun Account
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
17
|
+
|
|
18
|
+
exception: Any = Field(...)
|
|
19
|
+
is_shotgun_account: bool
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Anthropic token counting using official client."""
|
|
2
2
|
|
|
3
3
|
import logfire
|
|
4
|
+
from anthropic import APIStatusError
|
|
4
5
|
from pydantic_ai.messages import ModelMessage
|
|
5
6
|
|
|
6
7
|
from shotgun.agents.config.models import KeyProvider
|
|
@@ -103,6 +104,13 @@ class AnthropicTokenCounter(TokenCounter):
|
|
|
103
104
|
exception_type=type(e).__name__,
|
|
104
105
|
exception_message=str(e),
|
|
105
106
|
)
|
|
107
|
+
|
|
108
|
+
# Re-raise API errors directly so they can be classified by the runner
|
|
109
|
+
# This allows proper error classification for BYOK users (authentication, rate limits, etc.)
|
|
110
|
+
if isinstance(e, APIStatusError):
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
# Only wrap library-level errors in RuntimeError
|
|
106
114
|
raise RuntimeError(
|
|
107
115
|
f"Anthropic token counting API failed for {self.model_name}: {type(e).__name__}: {str(e)}"
|
|
108
116
|
) from e
|
shotgun/agents/runner.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Unified agent execution with consistent error handling.
|
|
2
|
+
|
|
3
|
+
This module provides a reusable agent runner that wraps agent execution exceptions
|
|
4
|
+
in user-friendly custom exceptions that can be caught and displayed by TUI or CLI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
10
|
+
|
|
11
|
+
from anthropic import APIStatusError as AnthropicAPIStatusError
|
|
12
|
+
from openai import APIStatusError as OpenAIAPIStatusError
|
|
13
|
+
from pydantic_ai.exceptions import ModelHTTPError, UnexpectedModelBehavior
|
|
14
|
+
|
|
15
|
+
from shotgun.agents.error.models import AgentErrorContext
|
|
16
|
+
from shotgun.exceptions import (
|
|
17
|
+
AgentCancelledException,
|
|
18
|
+
BudgetExceededException,
|
|
19
|
+
BYOKAuthenticationException,
|
|
20
|
+
BYOKGenericAPIException,
|
|
21
|
+
BYOKQuotaBillingException,
|
|
22
|
+
BYOKRateLimitException,
|
|
23
|
+
BYOKServiceOverloadException,
|
|
24
|
+
ContextSizeLimitExceeded,
|
|
25
|
+
GenericAPIStatusException,
|
|
26
|
+
ShotgunRateLimitException,
|
|
27
|
+
ShotgunServiceOverloadException,
|
|
28
|
+
UnknownAgentException,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from shotgun.agents.agent_manager import AgentManager
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentRunner:
|
|
38
|
+
"""Unified agent execution wrapper with consistent error handling.
|
|
39
|
+
|
|
40
|
+
This class wraps agent execution and converts any exceptions into
|
|
41
|
+
user-friendly custom exceptions that can be caught and displayed by the
|
|
42
|
+
calling interface (TUI or CLI).
|
|
43
|
+
|
|
44
|
+
The runner:
|
|
45
|
+
- Executes the agent
|
|
46
|
+
- Logs errors for debugging
|
|
47
|
+
- Wraps exceptions in custom exception types (AgentCancelledException,
|
|
48
|
+
BYOKRateLimitException, etc.)
|
|
49
|
+
- Lets exceptions propagate to caller for display
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> runner = AgentRunner(agent_manager)
|
|
53
|
+
>>> try:
|
|
54
|
+
>>> await runner.run("Write a hello world function")
|
|
55
|
+
>>> except ContextSizeLimitExceeded as e:
|
|
56
|
+
>>> print(e.to_markdown())
|
|
57
|
+
>>> except BYOKRateLimitException as e:
|
|
58
|
+
>>> print(e.to_plain_text())
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, agent_manager: "AgentManager"):
|
|
62
|
+
"""Initialize the agent runner.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
agent_manager: The agent manager to execute
|
|
66
|
+
"""
|
|
67
|
+
self.agent_manager = agent_manager
|
|
68
|
+
|
|
69
|
+
async def run(self, prompt: str) -> None:
|
|
70
|
+
"""Run the agent with the given prompt.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
prompt: The user's prompt/query
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
Custom exceptions for different error types:
|
|
77
|
+
- AgentCancelledException: User cancelled the operation
|
|
78
|
+
- ContextSizeLimitExceeded: Context too large for model
|
|
79
|
+
- BudgetExceededException: Shotgun Account budget exceeded
|
|
80
|
+
- BYOKRateLimitException: BYOK rate limit hit
|
|
81
|
+
- BYOKQuotaBillingException: BYOK quota/billing issue
|
|
82
|
+
- BYOKAuthenticationException: BYOK authentication failed
|
|
83
|
+
- BYOKServiceOverloadException: BYOK service overloaded
|
|
84
|
+
- BYOKGenericAPIException: Generic BYOK API error
|
|
85
|
+
- ShotgunServiceOverloadException: Shotgun service overloaded
|
|
86
|
+
- ShotgunRateLimitException: Shotgun rate limit hit
|
|
87
|
+
- GenericAPIStatusException: Generic API error
|
|
88
|
+
- UnknownAgentException: Unknown/unclassified error
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
await self.agent_manager.run(prompt=prompt)
|
|
92
|
+
|
|
93
|
+
except asyncio.CancelledError as e:
|
|
94
|
+
# User cancelled - wrap and re-raise as our custom exception
|
|
95
|
+
context = self._create_error_context(e)
|
|
96
|
+
self._classify_and_raise(context)
|
|
97
|
+
|
|
98
|
+
except ContextSizeLimitExceeded as e:
|
|
99
|
+
# Already a custom exception - log and re-raise
|
|
100
|
+
logger.info(
|
|
101
|
+
"Context size limit exceeded",
|
|
102
|
+
extra={
|
|
103
|
+
"max_tokens": e.max_tokens,
|
|
104
|
+
"model_name": e.model_name,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
raise
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
# Log with full stack trace to shotgun.log
|
|
111
|
+
logger.exception(
|
|
112
|
+
"Agent run failed",
|
|
113
|
+
extra={
|
|
114
|
+
"agent_mode": self.agent_manager._current_agent_type.value,
|
|
115
|
+
"error_type": type(e).__name__,
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Create error context and wrap/raise custom exception
|
|
120
|
+
context = self._create_error_context(e)
|
|
121
|
+
self._classify_and_raise(context)
|
|
122
|
+
|
|
123
|
+
def _create_error_context(self, exception: BaseException) -> AgentErrorContext:
|
|
124
|
+
"""Create error context from exception and agent state.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
exception: The exception that was raised
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
AgentErrorContext with all necessary information for classification
|
|
131
|
+
"""
|
|
132
|
+
return AgentErrorContext(
|
|
133
|
+
exception=exception,
|
|
134
|
+
is_shotgun_account=self.agent_manager.deps.llm_model.is_shotgun_account,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _classify_and_raise(self, context: AgentErrorContext) -> NoReturn:
|
|
138
|
+
"""Classify an exception and raise the appropriate custom exception.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
context: Context information about the error
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
Custom exception based on the error type
|
|
145
|
+
"""
|
|
146
|
+
exception = context.exception
|
|
147
|
+
error_name = type(exception).__name__
|
|
148
|
+
error_message = str(exception)
|
|
149
|
+
|
|
150
|
+
# Check for cancellation
|
|
151
|
+
if isinstance(exception, asyncio.CancelledError):
|
|
152
|
+
raise AgentCancelledException() from exception
|
|
153
|
+
|
|
154
|
+
# Check for context size limit exceeded
|
|
155
|
+
if isinstance(exception, ContextSizeLimitExceeded):
|
|
156
|
+
# Already the right exception type, re-raise it
|
|
157
|
+
raise exception
|
|
158
|
+
|
|
159
|
+
# Check for budget exceeded (Shotgun Account only)
|
|
160
|
+
if (
|
|
161
|
+
context.is_shotgun_account
|
|
162
|
+
and "apistatuserror" in error_name.lower()
|
|
163
|
+
and "budget" in error_message.lower()
|
|
164
|
+
and "exceeded" in error_message.lower()
|
|
165
|
+
):
|
|
166
|
+
raise BudgetExceededException(message=error_message) from exception
|
|
167
|
+
|
|
168
|
+
# Check for empty model response (e.g., model unavailable or misconfigured)
|
|
169
|
+
if isinstance(exception, UnexpectedModelBehavior):
|
|
170
|
+
raise GenericAPIStatusException(
|
|
171
|
+
"The model returned an empty response. This may indicate:\n"
|
|
172
|
+
"- The model is unavailable or misconfigured\n"
|
|
173
|
+
"- A temporary service issue\n\n"
|
|
174
|
+
"Try switching to a different model or try again later."
|
|
175
|
+
) from exception
|
|
176
|
+
|
|
177
|
+
# Detect API errors
|
|
178
|
+
is_api_error = False
|
|
179
|
+
if isinstance(exception, OpenAIAPIStatusError):
|
|
180
|
+
is_api_error = True
|
|
181
|
+
elif isinstance(exception, AnthropicAPIStatusError):
|
|
182
|
+
is_api_error = True
|
|
183
|
+
elif isinstance(exception, ModelHTTPError):
|
|
184
|
+
# pydantic_ai wraps API errors in ModelHTTPError
|
|
185
|
+
# Check for HTTP error status codes (4xx client errors)
|
|
186
|
+
if 400 <= exception.status_code < 500:
|
|
187
|
+
is_api_error = True
|
|
188
|
+
|
|
189
|
+
# BYOK user API errors
|
|
190
|
+
if not context.is_shotgun_account and is_api_error:
|
|
191
|
+
self._raise_byok_api_error(error_message, exception)
|
|
192
|
+
|
|
193
|
+
# Shotgun Account specific errors
|
|
194
|
+
if "APIStatusError" in error_name:
|
|
195
|
+
if "overload" in error_message.lower():
|
|
196
|
+
raise ShotgunServiceOverloadException(error_message) from exception
|
|
197
|
+
elif "rate" in error_message.lower():
|
|
198
|
+
raise ShotgunRateLimitException(error_message) from exception
|
|
199
|
+
else:
|
|
200
|
+
raise GenericAPIStatusException(error_message) from exception
|
|
201
|
+
|
|
202
|
+
# Unknown error - wrap in our custom exception
|
|
203
|
+
raise UnknownAgentException(exception) from exception
|
|
204
|
+
|
|
205
|
+
def _raise_byok_api_error(
|
|
206
|
+
self, error_message: str, original_exception: Exception
|
|
207
|
+
) -> NoReturn:
|
|
208
|
+
"""Classify and raise API errors for BYOK users into specific types.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
error_message: The error message from the API
|
|
212
|
+
original_exception: The original exception
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
Specific BYOK exception type
|
|
216
|
+
"""
|
|
217
|
+
error_lower = error_message.lower()
|
|
218
|
+
|
|
219
|
+
if "rate" in error_lower:
|
|
220
|
+
raise BYOKRateLimitException(error_message) from original_exception
|
|
221
|
+
elif "quota" in error_lower or "billing" in error_lower:
|
|
222
|
+
raise BYOKQuotaBillingException(error_message) from original_exception
|
|
223
|
+
elif "authentication" in error_lower or (
|
|
224
|
+
"invalid" in error_lower and "key" in error_lower
|
|
225
|
+
):
|
|
226
|
+
raise BYOKAuthenticationException(error_message) from original_exception
|
|
227
|
+
elif "overload" in error_lower:
|
|
228
|
+
raise BYOKServiceOverloadException(error_message) from original_exception
|
|
229
|
+
else:
|
|
230
|
+
raise BYOKGenericAPIException(error_message) from original_exception
|
shotgun/build_constants.py
CHANGED
|
@@ -12,8 +12,8 @@ POSTHOG_API_KEY = 'phc_KKnChzZUKeNqZDOTJ6soCBWNQSx3vjiULdwTR9H5Mcr'
|
|
|
12
12
|
POSTHOG_PROJECT_ID = '191396'
|
|
13
13
|
|
|
14
14
|
# Logfire configuration embedded at build time (only for dev builds)
|
|
15
|
-
LOGFIRE_ENABLED = ''
|
|
16
|
-
LOGFIRE_TOKEN = ''
|
|
15
|
+
LOGFIRE_ENABLED = 'true'
|
|
16
|
+
LOGFIRE_TOKEN = 'pylf_v1_us_RwZMlJm1tX6j0PL5RWWbmZpzK2hLBNtFWStNKlySfjh8'
|
|
17
17
|
|
|
18
18
|
# Build metadata
|
|
19
19
|
BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
|
shotgun/cli/context.py
CHANGED
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Annotated
|
|
7
7
|
|
|
8
|
+
import httpx
|
|
8
9
|
import typer
|
|
9
10
|
from rich.console import Console
|
|
10
11
|
|
|
@@ -16,6 +17,7 @@ from shotgun.agents.context_analyzer import (
|
|
|
16
17
|
)
|
|
17
18
|
from shotgun.agents.conversation_manager import ConversationManager
|
|
18
19
|
from shotgun.cli.models import OutputFormat
|
|
20
|
+
from shotgun.llm_proxy import BudgetInfo, LiteLLMProxyClient
|
|
19
21
|
from shotgun.logging_config import get_logger
|
|
20
22
|
|
|
21
23
|
app = typer.Typer(
|
|
@@ -108,4 +110,45 @@ async def analyze_context() -> ContextAnalysisOutput:
|
|
|
108
110
|
markdown = ContextFormatter.format_markdown(analysis)
|
|
109
111
|
json_data = ContextFormatter.format_json(analysis)
|
|
110
112
|
|
|
113
|
+
# Add budget info for Shotgun Account users
|
|
114
|
+
if model_config.is_shotgun_account:
|
|
115
|
+
try:
|
|
116
|
+
logger.debug("Fetching budget info for Shotgun Account")
|
|
117
|
+
client = LiteLLMProxyClient(model_config.api_key)
|
|
118
|
+
budget_info = await client.get_budget_info()
|
|
119
|
+
|
|
120
|
+
# Format budget section for markdown
|
|
121
|
+
budget_markdown = _format_budget_markdown(budget_info)
|
|
122
|
+
markdown = f"{markdown}\n\n{budget_markdown}"
|
|
123
|
+
|
|
124
|
+
# Add budget info to JSON using Pydantic model
|
|
125
|
+
json_data["budget"] = budget_info.model_dump()
|
|
126
|
+
logger.debug("Successfully added budget info to context output")
|
|
127
|
+
|
|
128
|
+
except httpx.HTTPError as e:
|
|
129
|
+
logger.warning(f"Failed to fetch budget info: {e}")
|
|
130
|
+
# Don't fail the entire command if budget fetch fails
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.warning(f"Unexpected error fetching budget info: {e}")
|
|
133
|
+
# Don't fail the entire command if budget fetch fails
|
|
134
|
+
|
|
111
135
|
return ContextAnalysisOutput(markdown=markdown, json_data=json_data)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _format_budget_markdown(budget_info: BudgetInfo) -> str:
|
|
139
|
+
"""Format budget information as markdown.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
budget_info: BudgetInfo instance
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Formatted markdown string
|
|
146
|
+
"""
|
|
147
|
+
source_label = "Key" if budget_info.source == "key" else "Team"
|
|
148
|
+
|
|
149
|
+
return f"""## Shotgun Account Budget
|
|
150
|
+
|
|
151
|
+
* Max Budget: ${budget_info.max_budget:.2f}
|
|
152
|
+
* Current Spend: ${budget_info.spend:.2f}
|
|
153
|
+
* Remaining: ${budget_info.remaining:.2f} ({100 - budget_info.percentage_used:.1f}%)
|
|
154
|
+
* Budget Source: {source_label}-level"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""CLI-specific error handling utilities.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for displaying agent errors in the CLI
|
|
4
|
+
by printing formatted messages to the console.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from shotgun.exceptions import ErrorNotPickedUpBySentry
|
|
10
|
+
|
|
11
|
+
console = Console(stderr=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_agent_error(exception: ErrorNotPickedUpBySentry) -> None:
|
|
15
|
+
"""Print an agent error to the console in yellow.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
exception: The error exception with formatting methods
|
|
19
|
+
"""
|
|
20
|
+
# Get plain text version for CLI
|
|
21
|
+
message = exception.to_plain_text()
|
|
22
|
+
|
|
23
|
+
# Print with yellow styling
|
|
24
|
+
console.print(message, style="yellow")
|
shotgun/cli/export.py
CHANGED
|
@@ -11,7 +11,10 @@ from shotgun.agents.export import (
|
|
|
11
11
|
run_export_agent,
|
|
12
12
|
)
|
|
13
13
|
from shotgun.agents.models import AgentRuntimeOptions
|
|
14
|
+
from shotgun.cli.error_handler import print_agent_error
|
|
15
|
+
from shotgun.exceptions import ErrorNotPickedUpBySentry
|
|
14
16
|
from shotgun.logging_config import get_logger
|
|
17
|
+
from shotgun.posthog_telemetry import track_event
|
|
15
18
|
|
|
16
19
|
app = typer.Typer(
|
|
17
20
|
name="export", help="Export artifacts to various formats with agentic approach"
|
|
@@ -45,37 +48,34 @@ def export(
|
|
|
45
48
|
|
|
46
49
|
logger.info("📤 Export Instruction: %s", instruction)
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
import traceback
|
|
80
|
-
|
|
81
|
-
logger.debug("Full traceback:\n%s", traceback.format_exc())
|
|
51
|
+
# Track export command usage
|
|
52
|
+
track_event(
|
|
53
|
+
"export_command",
|
|
54
|
+
{
|
|
55
|
+
"non_interactive": non_interactive,
|
|
56
|
+
"provider": provider.value if provider else "default",
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Create agent dependencies
|
|
61
|
+
agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
|
|
62
|
+
|
|
63
|
+
# Create the export agent with deps and provider
|
|
64
|
+
agent, deps = asyncio.run(create_export_agent(agent_runtime_options, provider))
|
|
65
|
+
|
|
66
|
+
# Start export process with error handling
|
|
67
|
+
logger.info("🎯 Starting export...")
|
|
68
|
+
|
|
69
|
+
async def async_export() -> None:
|
|
70
|
+
try:
|
|
71
|
+
result = await run_export_agent(agent, instruction, deps)
|
|
72
|
+
logger.info("✅ Export Complete!")
|
|
73
|
+
logger.info("📤 Results:")
|
|
74
|
+
logger.info("%s", result.output)
|
|
75
|
+
except ErrorNotPickedUpBySentry as e:
|
|
76
|
+
print_agent_error(e)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.exception("Unexpected error in export command")
|
|
79
|
+
print(f"⚠️ An unexpected error occurred: {str(e)}")
|
|
80
|
+
|
|
81
|
+
asyncio.run(async_export())
|
shotgun/cli/plan.py
CHANGED
|
@@ -8,7 +8,10 @@ import typer
|
|
|
8
8
|
from shotgun.agents.config import ProviderType
|
|
9
9
|
from shotgun.agents.models import AgentRuntimeOptions
|
|
10
10
|
from shotgun.agents.plan import create_plan_agent, run_plan_agent
|
|
11
|
+
from shotgun.cli.error_handler import print_agent_error
|
|
12
|
+
from shotgun.exceptions import ErrorNotPickedUpBySentry
|
|
11
13
|
from shotgun.logging_config import get_logger
|
|
14
|
+
from shotgun.posthog_telemetry import track_event
|
|
12
15
|
|
|
13
16
|
app = typer.Typer(name="plan", help="Generate structured plans", no_args_is_help=True)
|
|
14
17
|
logger = get_logger(__name__)
|
|
@@ -37,37 +40,34 @@ def plan(
|
|
|
37
40
|
|
|
38
41
|
logger.info("📋 Planning Goal: %s", goal)
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
import traceback
|
|
72
|
-
|
|
73
|
-
logger.debug("Full traceback:\n%s", traceback.format_exc())
|
|
43
|
+
# Track plan command usage
|
|
44
|
+
track_event(
|
|
45
|
+
"plan_command",
|
|
46
|
+
{
|
|
47
|
+
"non_interactive": non_interactive,
|
|
48
|
+
"provider": provider.value if provider else "default",
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Create agent dependencies
|
|
53
|
+
agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
|
|
54
|
+
|
|
55
|
+
# Create the plan agent with deps and provider
|
|
56
|
+
agent, deps = asyncio.run(create_plan_agent(agent_runtime_options, provider))
|
|
57
|
+
|
|
58
|
+
# Start planning process with error handling
|
|
59
|
+
logger.info("🎯 Starting planning...")
|
|
60
|
+
|
|
61
|
+
async def async_plan() -> None:
|
|
62
|
+
try:
|
|
63
|
+
result = await run_plan_agent(agent, goal, deps)
|
|
64
|
+
logger.info("✅ Planning Complete!")
|
|
65
|
+
logger.info("📋 Results:")
|
|
66
|
+
logger.info("%s", result.output)
|
|
67
|
+
except ErrorNotPickedUpBySentry as e:
|
|
68
|
+
print_agent_error(e)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.exception("Unexpected error in plan command")
|
|
71
|
+
print(f"⚠️ An unexpected error occurred: {str(e)}")
|
|
72
|
+
|
|
73
|
+
asyncio.run(async_plan())
|
shotgun/cli/research.py
CHANGED
|
@@ -11,7 +11,10 @@ from shotgun.agents.research import (
|
|
|
11
11
|
create_research_agent,
|
|
12
12
|
run_research_agent,
|
|
13
13
|
)
|
|
14
|
+
from shotgun.cli.error_handler import print_agent_error
|
|
15
|
+
from shotgun.exceptions import ErrorNotPickedUpBySentry
|
|
14
16
|
from shotgun.logging_config import get_logger
|
|
17
|
+
from shotgun.posthog_telemetry import track_event
|
|
15
18
|
|
|
16
19
|
app = typer.Typer(
|
|
17
20
|
name="research", help="Perform research with agentic loops", no_args_is_help=True
|
|
@@ -59,8 +62,6 @@ async def async_research(
|
|
|
59
62
|
) -> None:
|
|
60
63
|
"""Async wrapper for research process."""
|
|
61
64
|
# Track research command usage
|
|
62
|
-
from shotgun.posthog_telemetry import track_event
|
|
63
|
-
|
|
64
65
|
track_event(
|
|
65
66
|
"research_command",
|
|
66
67
|
{
|
|
@@ -75,11 +76,18 @@ async def async_research(
|
|
|
75
76
|
# Create the research agent with deps and provider
|
|
76
77
|
agent, deps = await create_research_agent(agent_runtime_options, provider)
|
|
77
78
|
|
|
78
|
-
# Start research process
|
|
79
|
+
# Start research process with error handling
|
|
79
80
|
logger.info("🔬 Starting research...")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
try:
|
|
82
|
+
result = await run_research_agent(agent, query, deps)
|
|
83
|
+
# Display results
|
|
84
|
+
print("✅ Research Complete!")
|
|
85
|
+
print("📋 Findings:")
|
|
86
|
+
print(result.output)
|
|
87
|
+
except ErrorNotPickedUpBySentry as e:
|
|
88
|
+
# All user-actionable errors - display with plain text
|
|
89
|
+
print_agent_error(e)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
# Unexpected errors that weren't wrapped (shouldn't happen)
|
|
92
|
+
logger.exception("Unexpected error in research command")
|
|
93
|
+
print(f"⚠️ An unexpected error occurred: {str(e)}")
|