shotgun-sh 0.2.11.dev7__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 +25 -11
- shotgun/agents/config/README.md +89 -0
- shotgun/agents/config/__init__.py +10 -1
- shotgun/agents/config/manager.py +287 -32
- shotgun/agents/config/models.py +26 -1
- shotgun/agents/config/provider.py +27 -0
- shotgun/agents/config/streaming_test.py +119 -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 +1 -1
- 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/logging_config.py +42 -0
- shotgun/main.py +2 -0
- shotgun/posthog_telemetry.py +18 -25
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +3 -2
- shotgun/sdk/codebase.py +14 -3
- shotgun/sentry_telemetry.py +140 -2
- shotgun/settings.py +5 -0
- shotgun/tui/app.py +35 -10
- shotgun/tui/screens/chat/chat_screen.py +192 -91
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +62 -11
- shotgun/tui/screens/chat_screen/command_providers.py +3 -2
- shotgun/tui/screens/chat_screen/hint_message.py +76 -1
- shotgun/tui/screens/chat_screen/history/chat_history.py +37 -2
- shotgun/tui/screens/directory_setup.py +45 -41
- shotgun/tui/screens/feedback.py +10 -3
- shotgun/tui/screens/github_issue.py +11 -2
- shotgun/tui/screens/model_picker.py +8 -1
- shotgun/tui/screens/pipx_migration.py +12 -6
- shotgun/tui/screens/provider_config.py +25 -8
- shotgun/tui/screens/shotgun_auth.py +0 -10
- shotgun/tui/screens/welcome.py +32 -0
- shotgun/tui/widgets/widget_coordinator.py +3 -2
- shotgun_sh-0.2.23.dev1.dist-info/METADATA +472 -0
- {shotgun_sh-0.2.11.dev7.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/RECORD +50 -42
- shotgun_sh-0.2.11.dev7.dist-info/METADATA +0 -130
- {shotgun_sh-0.2.11.dev7.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.11.dev7.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.11.dev7.dist-info → shotgun_sh-0.2.23.dev1.dist-info}/licenses/LICENSE +0 -0
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)}")
|
shotgun/cli/specify.py
CHANGED
|
@@ -11,6 +11,8 @@ from shotgun.agents.specify import (
|
|
|
11
11
|
create_specify_agent,
|
|
12
12
|
run_specify_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
|
|
15
17
|
|
|
16
18
|
app = typer.Typer(
|
|
@@ -44,26 +46,25 @@ def specify(
|
|
|
44
46
|
|
|
45
47
|
logger.info("📝 Specification Requirement: %s", requirement)
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
agent_runtime_options = AgentRuntimeOptions(
|
|
50
|
-
interactive_mode=not non_interactive
|
|
51
|
-
)
|
|
49
|
+
# Create agent dependencies
|
|
50
|
+
agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
# Create the specify agent with deps and provider
|
|
53
|
+
agent, deps = asyncio.run(create_specify_agent(agent_runtime_options, provider))
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
result = asyncio.run(run_specify_agent(agent, requirement, deps))
|
|
55
|
+
# Start specification process with error handling
|
|
56
|
+
logger.info("📋 Starting specification generation...")
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
async def async_specify() -> None:
|
|
59
|
+
try:
|
|
60
|
+
result = await run_specify_agent(agent, requirement, deps)
|
|
61
|
+
logger.info("✅ Specification Complete!")
|
|
62
|
+
logger.info("📋 Results:")
|
|
63
|
+
logger.info("%s", result.output)
|
|
64
|
+
except ErrorNotPickedUpBySentry as e:
|
|
65
|
+
print_agent_error(e)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.exception("Unexpected error in specify command")
|
|
68
|
+
print(f"⚠️ An unexpected error occurred: {str(e)}")
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
logger.error("❌ Error during specification: %s", str(e))
|
|
67
|
-
import traceback
|
|
68
|
-
|
|
69
|
-
logger.debug("Full traceback:\n%s", traceback.format_exc())
|
|
70
|
+
asyncio.run(async_specify())
|
shotgun/cli/tasks.py
CHANGED
|
@@ -11,7 +11,10 @@ from shotgun.agents.tasks import (
|
|
|
11
11
|
create_tasks_agent,
|
|
12
12
|
run_tasks_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(name="tasks", help="Generate task lists with agentic approach")
|
|
17
20
|
logger = get_logger(__name__)
|
|
@@ -42,37 +45,34 @@ def tasks(
|
|
|
42
45
|
|
|
43
46
|
logger.info("📋 Task Creation Instruction: %s", instruction)
|
|
44
47
|
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
import traceback
|
|
77
|
-
|
|
78
|
-
logger.debug("Full traceback:\n%s", traceback.format_exc())
|
|
48
|
+
# Track tasks command usage
|
|
49
|
+
track_event(
|
|
50
|
+
"tasks_command",
|
|
51
|
+
{
|
|
52
|
+
"non_interactive": non_interactive,
|
|
53
|
+
"provider": provider.value if provider else "default",
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Create agent dependencies
|
|
58
|
+
agent_runtime_options = AgentRuntimeOptions(interactive_mode=not non_interactive)
|
|
59
|
+
|
|
60
|
+
# Create the tasks agent with deps and provider
|
|
61
|
+
agent, deps = asyncio.run(create_tasks_agent(agent_runtime_options, provider))
|
|
62
|
+
|
|
63
|
+
# Start task creation process with error handling
|
|
64
|
+
logger.info("🎯 Starting task creation...")
|
|
65
|
+
|
|
66
|
+
async def async_tasks() -> None:
|
|
67
|
+
try:
|
|
68
|
+
result = await run_tasks_agent(agent, instruction, deps)
|
|
69
|
+
logger.info("✅ Task Creation Complete!")
|
|
70
|
+
logger.info("📋 Results:")
|
|
71
|
+
logger.info("%s", result.output)
|
|
72
|
+
except ErrorNotPickedUpBySentry as e:
|
|
73
|
+
print_agent_error(e)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.exception("Unexpected error in tasks command")
|
|
76
|
+
print(f"⚠️ An unexpected error occurred: {str(e)}")
|
|
77
|
+
|
|
78
|
+
asyncio.run(async_tasks())
|
shotgun/exceptions.py
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
1
1
|
"""General exceptions for Shotgun application."""
|
|
2
2
|
|
|
3
|
+
from shotgun.utils import get_shotgun_home
|
|
4
|
+
|
|
5
|
+
# Shotgun Account signup URL for BYOK users
|
|
6
|
+
SHOTGUN_SIGNUP_URL = "https://shotgun.sh"
|
|
7
|
+
SHOTGUN_CONTACT_EMAIL = "contact@shotgun.sh"
|
|
8
|
+
|
|
3
9
|
|
|
4
10
|
class ErrorNotPickedUpBySentry(Exception): # noqa: N818
|
|
5
11
|
"""Base for user-actionable errors that shouldn't be sent to Sentry.
|
|
6
12
|
|
|
7
13
|
These errors represent expected user conditions requiring action
|
|
8
14
|
rather than bugs that need tracking.
|
|
15
|
+
|
|
16
|
+
All subclasses should implement to_markdown() and to_plain_text() methods
|
|
17
|
+
for consistent error message formatting.
|
|
9
18
|
"""
|
|
10
19
|
|
|
20
|
+
def to_markdown(self) -> str:
|
|
21
|
+
"""Generate markdown-formatted error message for TUI.
|
|
22
|
+
|
|
23
|
+
Subclasses should override this method.
|
|
24
|
+
"""
|
|
25
|
+
return f"⚠️ {str(self)}"
|
|
26
|
+
|
|
27
|
+
def to_plain_text(self) -> str:
|
|
28
|
+
"""Generate plain text error message for CLI.
|
|
29
|
+
|
|
30
|
+
Subclasses should override this method.
|
|
31
|
+
"""
|
|
32
|
+
return f"⚠️ {str(self)}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# User Action Required Errors
|
|
37
|
+
# ============================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AgentCancelledException(ErrorNotPickedUpBySentry):
|
|
41
|
+
"""Raised when user cancels an agent operation."""
|
|
42
|
+
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
"""Initialize the exception."""
|
|
45
|
+
super().__init__("Operation cancelled by user")
|
|
46
|
+
|
|
47
|
+
def to_markdown(self) -> str:
|
|
48
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
49
|
+
return "⚠️ Operation cancelled by user"
|
|
50
|
+
|
|
51
|
+
def to_plain_text(self) -> str:
|
|
52
|
+
"""Generate plain text error message for CLI."""
|
|
53
|
+
return "⚠️ Operation cancelled by user"
|
|
54
|
+
|
|
11
55
|
|
|
12
56
|
class ContextSizeLimitExceeded(ErrorNotPickedUpBySentry):
|
|
13
57
|
"""Raised when conversation context exceeds the model's limits.
|
|
@@ -30,3 +74,282 @@ class ContextSizeLimitExceeded(ErrorNotPickedUpBySentry):
|
|
|
30
74
|
super().__init__(
|
|
31
75
|
f"Context too large for {model_name} (limit: {max_tokens:,} tokens)"
|
|
32
76
|
)
|
|
77
|
+
|
|
78
|
+
def to_markdown(self) -> str:
|
|
79
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
80
|
+
return (
|
|
81
|
+
f"⚠️ **Context too large for {self.model_name}**\n\n"
|
|
82
|
+
f"Your conversation history exceeds this model's limit ({self.max_tokens:,} tokens).\n\n"
|
|
83
|
+
f"**Choose an action:**\n\n"
|
|
84
|
+
f"1. Switch to a larger model (`Ctrl+P` → Change Model)\n"
|
|
85
|
+
f"2. Switch to a larger model, compact (`/compact`), then switch back to {self.model_name}\n"
|
|
86
|
+
f"3. Clear conversation (`/clear`)\n"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def to_plain_text(self) -> str:
|
|
90
|
+
"""Generate plain text error message for CLI."""
|
|
91
|
+
return (
|
|
92
|
+
f"⚠️ Context too large for {self.model_name}\n\n"
|
|
93
|
+
f"Your conversation history exceeds this model's limit ({self.max_tokens:,} tokens).\n\n"
|
|
94
|
+
f"Choose an action:\n"
|
|
95
|
+
f"1. Switch to a larger model\n"
|
|
96
|
+
f"2. Switch to a larger model, compact, then switch back to {self.model_name}\n"
|
|
97
|
+
f"3. Clear conversation\n"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ============================================================================
|
|
102
|
+
# Shotgun Account Errors (show contact email in TUI)
|
|
103
|
+
# ============================================================================
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ShotgunAccountException(ErrorNotPickedUpBySentry):
|
|
107
|
+
"""Base class for Shotgun Account service errors.
|
|
108
|
+
|
|
109
|
+
TUI will check isinstance() of this class to show contact email UI.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BudgetExceededException(ShotgunAccountException):
|
|
114
|
+
"""Raised when Shotgun Account budget has been exceeded.
|
|
115
|
+
|
|
116
|
+
This is a user-actionable error - they need to contact support
|
|
117
|
+
to increase their budget limit. This is a temporary exception
|
|
118
|
+
until self-service budget increases are implemented.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
current_cost: float | None = None,
|
|
124
|
+
max_budget: float | None = None,
|
|
125
|
+
message: str | None = None,
|
|
126
|
+
):
|
|
127
|
+
"""Initialize the exception.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
current_cost: Current total spend/cost (optional)
|
|
131
|
+
max_budget: Maximum budget limit (optional)
|
|
132
|
+
message: Optional custom error message from API
|
|
133
|
+
"""
|
|
134
|
+
self.current_cost = current_cost
|
|
135
|
+
self.max_budget = max_budget
|
|
136
|
+
self.api_message = message
|
|
137
|
+
|
|
138
|
+
if message:
|
|
139
|
+
error_msg = message
|
|
140
|
+
elif current_cost is not None and max_budget is not None:
|
|
141
|
+
error_msg = f"Budget exceeded: ${current_cost:.2f} / ${max_budget:.2f}"
|
|
142
|
+
else:
|
|
143
|
+
error_msg = "Budget exceeded"
|
|
144
|
+
|
|
145
|
+
super().__init__(error_msg)
|
|
146
|
+
|
|
147
|
+
def to_markdown(self) -> str:
|
|
148
|
+
"""Generate markdown-formatted error message for TUI.
|
|
149
|
+
|
|
150
|
+
Note: TUI will detect ShotgunAccountException and automatically
|
|
151
|
+
show email contact UI component.
|
|
152
|
+
"""
|
|
153
|
+
return (
|
|
154
|
+
"⚠️ **Your Shotgun Account budget has been exceeded!**\n\n"
|
|
155
|
+
"Your account has reached its spending limit and cannot process more requests.\n\n"
|
|
156
|
+
"**Need help?** Contact us for budget increase.\n\n"
|
|
157
|
+
"• Self-service budget increases are coming soon!\n\n"
|
|
158
|
+
f"_Error details: {str(self)}_"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def to_plain_text(self) -> str:
|
|
162
|
+
"""Generate plain text error message for CLI."""
|
|
163
|
+
return (
|
|
164
|
+
"⚠️ Your Shotgun Account budget has been exceeded!\n\n"
|
|
165
|
+
"Your account has reached its spending limit and cannot process more requests.\n\n"
|
|
166
|
+
f"Need help? Contact: {SHOTGUN_CONTACT_EMAIL}\n\n"
|
|
167
|
+
"• Self-service budget increases are coming soon!\n\n"
|
|
168
|
+
f"Error details: {str(self)}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ShotgunServiceOverloadException(ShotgunAccountException):
|
|
173
|
+
"""Raised when Shotgun Account AI service is overloaded."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, message: str | None = None):
|
|
176
|
+
"""Initialize the exception.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
message: Optional custom error message from API
|
|
180
|
+
"""
|
|
181
|
+
super().__init__(message or "Service temporarily overloaded")
|
|
182
|
+
|
|
183
|
+
def to_markdown(self) -> str:
|
|
184
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
185
|
+
return "⚠️ The AI service is temporarily overloaded. Please wait a moment and try again."
|
|
186
|
+
|
|
187
|
+
def to_plain_text(self) -> str:
|
|
188
|
+
"""Generate plain text error message for CLI."""
|
|
189
|
+
return "⚠️ The AI service is temporarily overloaded. Please wait a moment and try again."
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class ShotgunRateLimitException(ShotgunAccountException):
|
|
193
|
+
"""Raised when Shotgun Account rate limit is reached."""
|
|
194
|
+
|
|
195
|
+
def __init__(self, message: str | None = None):
|
|
196
|
+
"""Initialize the exception.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
message: Optional custom error message from API
|
|
200
|
+
"""
|
|
201
|
+
super().__init__(message or "Rate limit reached")
|
|
202
|
+
|
|
203
|
+
def to_markdown(self) -> str:
|
|
204
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
205
|
+
return "⚠️ Rate limit reached. Please wait before trying again."
|
|
206
|
+
|
|
207
|
+
def to_plain_text(self) -> str:
|
|
208
|
+
"""Generate plain text error message for CLI."""
|
|
209
|
+
return "⚠️ Rate limit reached. Please wait before trying again."
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ============================================================================
|
|
213
|
+
# BYOK (Bring Your Own Key) API Errors
|
|
214
|
+
# ============================================================================
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class BYOKAPIException(ErrorNotPickedUpBySentry):
|
|
218
|
+
"""Base class for BYOK API errors.
|
|
219
|
+
|
|
220
|
+
All BYOK errors suggest using Shotgun Account to avoid the issue.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def __init__(self, message: str, specific_error: str = "API error"):
|
|
224
|
+
"""Initialize the exception.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
message: The error message from the API
|
|
228
|
+
specific_error: Human-readable error type label
|
|
229
|
+
"""
|
|
230
|
+
self.api_message = message
|
|
231
|
+
self.specific_error = specific_error
|
|
232
|
+
super().__init__(message)
|
|
233
|
+
|
|
234
|
+
def to_markdown(self) -> str:
|
|
235
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
236
|
+
return (
|
|
237
|
+
f"⚠️ **{self.specific_error}**: {self.api_message}\n\n"
|
|
238
|
+
f"_This could be avoided with a [Shotgun Account]({SHOTGUN_SIGNUP_URL})._"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def to_plain_text(self) -> str:
|
|
242
|
+
"""Generate plain text error message for CLI."""
|
|
243
|
+
return (
|
|
244
|
+
f"⚠️ {self.specific_error}: {self.api_message}\n\n"
|
|
245
|
+
f"This could be avoided with a Shotgun Account: {SHOTGUN_SIGNUP_URL}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class BYOKRateLimitException(BYOKAPIException):
|
|
250
|
+
"""Raised when BYOK user hits rate limit."""
|
|
251
|
+
|
|
252
|
+
def __init__(self, message: str):
|
|
253
|
+
"""Initialize the exception.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
message: The error message from the API
|
|
257
|
+
"""
|
|
258
|
+
super().__init__(message, specific_error="Rate limit reached")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class BYOKQuotaBillingException(BYOKAPIException):
|
|
262
|
+
"""Raised when BYOK user has quota or billing issues."""
|
|
263
|
+
|
|
264
|
+
def __init__(self, message: str):
|
|
265
|
+
"""Initialize the exception.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
message: The error message from the API
|
|
269
|
+
"""
|
|
270
|
+
super().__init__(message, specific_error="Quota or billing issue")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class BYOKAuthenticationException(BYOKAPIException):
|
|
274
|
+
"""Raised when BYOK authentication fails."""
|
|
275
|
+
|
|
276
|
+
def __init__(self, message: str):
|
|
277
|
+
"""Initialize the exception.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
message: The error message from the API
|
|
281
|
+
"""
|
|
282
|
+
super().__init__(message, specific_error="Authentication error")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class BYOKServiceOverloadException(BYOKAPIException):
|
|
286
|
+
"""Raised when BYOK service is overloaded."""
|
|
287
|
+
|
|
288
|
+
def __init__(self, message: str):
|
|
289
|
+
"""Initialize the exception.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
message: The error message from the API
|
|
293
|
+
"""
|
|
294
|
+
super().__init__(message, specific_error="Service overloaded")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class BYOKGenericAPIException(BYOKAPIException):
|
|
298
|
+
"""Raised for generic BYOK API errors."""
|
|
299
|
+
|
|
300
|
+
def __init__(self, message: str):
|
|
301
|
+
"""Initialize the exception.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
message: The error message from the API
|
|
305
|
+
"""
|
|
306
|
+
super().__init__(message, specific_error="API error")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ============================================================================
|
|
310
|
+
# Generic Errors
|
|
311
|
+
# ============================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class GenericAPIStatusException(ErrorNotPickedUpBySentry):
|
|
315
|
+
"""Raised for generic API status errors that don't fit other categories."""
|
|
316
|
+
|
|
317
|
+
def __init__(self, message: str):
|
|
318
|
+
"""Initialize the exception.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
message: The error message from the API
|
|
322
|
+
"""
|
|
323
|
+
self.api_message = message
|
|
324
|
+
super().__init__(message)
|
|
325
|
+
|
|
326
|
+
def to_markdown(self) -> str:
|
|
327
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
328
|
+
return f"⚠️ AI service error: {self.api_message}"
|
|
329
|
+
|
|
330
|
+
def to_plain_text(self) -> str:
|
|
331
|
+
"""Generate plain text error message for CLI."""
|
|
332
|
+
return f"⚠️ AI service error: {self.api_message}"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class UnknownAgentException(ErrorNotPickedUpBySentry):
|
|
336
|
+
"""Raised for unknown/unclassified agent errors."""
|
|
337
|
+
|
|
338
|
+
def __init__(self, original_exception: Exception):
|
|
339
|
+
"""Initialize the exception.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
original_exception: The original exception that was caught
|
|
343
|
+
"""
|
|
344
|
+
self.original_exception = original_exception
|
|
345
|
+
super().__init__(str(original_exception))
|
|
346
|
+
|
|
347
|
+
def to_markdown(self) -> str:
|
|
348
|
+
"""Generate markdown-formatted error message for TUI."""
|
|
349
|
+
log_path = get_shotgun_home() / "logs" / "shotgun.log"
|
|
350
|
+
return f"⚠️ An error occurred: {str(self.original_exception)}\n\nCheck logs at {log_path}"
|
|
351
|
+
|
|
352
|
+
def to_plain_text(self) -> str:
|
|
353
|
+
"""Generate plain text error message for CLI."""
|
|
354
|
+
log_path = get_shotgun_home() / "logs" / "shotgun.log"
|
|
355
|
+
return f"⚠️ An error occurred: {str(self.original_exception)}\n\nCheck logs at {log_path}"
|
shotgun/llm_proxy/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""LiteLLM proxy client utilities and configuration."""
|
|
2
2
|
|
|
3
|
+
from .client import LiteLLMProxyClient, get_budget_info
|
|
3
4
|
from .clients import (
|
|
4
5
|
create_anthropic_proxy_provider,
|
|
5
6
|
create_litellm_provider,
|
|
@@ -9,6 +10,14 @@ from .constants import (
|
|
|
9
10
|
LITELLM_PROXY_BASE_URL,
|
|
10
11
|
LITELLM_PROXY_OPENAI_BASE,
|
|
11
12
|
)
|
|
13
|
+
from .models import (
|
|
14
|
+
BudgetInfo,
|
|
15
|
+
BudgetSource,
|
|
16
|
+
KeyInfoData,
|
|
17
|
+
KeyInfoResponse,
|
|
18
|
+
TeamInfoData,
|
|
19
|
+
TeamInfoResponse,
|
|
20
|
+
)
|
|
12
21
|
|
|
13
22
|
__all__ = [
|
|
14
23
|
"LITELLM_PROXY_BASE_URL",
|
|
@@ -16,4 +25,12 @@ __all__ = [
|
|
|
16
25
|
"LITELLM_PROXY_OPENAI_BASE",
|
|
17
26
|
"create_litellm_provider",
|
|
18
27
|
"create_anthropic_proxy_provider",
|
|
28
|
+
"LiteLLMProxyClient",
|
|
29
|
+
"get_budget_info",
|
|
30
|
+
"BudgetInfo",
|
|
31
|
+
"BudgetSource",
|
|
32
|
+
"KeyInfoData",
|
|
33
|
+
"KeyInfoResponse",
|
|
34
|
+
"TeamInfoData",
|
|
35
|
+
"TeamInfoResponse",
|
|
19
36
|
]
|