foundry-mcp 0.8.22__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 foundry-mcp might be problematic. Click here for more details.
- foundry_mcp/__init__.py +13 -0
- foundry_mcp/cli/__init__.py +67 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +640 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +667 -0
- foundry_mcp/cli/commands/session.py +472 -0
- foundry_mcp/cli/commands/specs.py +686 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +298 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +1454 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1773 -0
- foundry_mcp/core/batch_operations.py +1202 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/journal.py +700 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1376 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +146 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +387 -0
- foundry_mcp/core/prometheus.py +564 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +691 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
- foundry_mcp/core/prompts/plan_review.py +627 -0
- foundry_mcp/core/providers/__init__.py +237 -0
- foundry_mcp/core/providers/base.py +515 -0
- foundry_mcp/core/providers/claude.py +472 -0
- foundry_mcp/core/providers/codex.py +637 -0
- foundry_mcp/core/providers/cursor_agent.py +630 -0
- foundry_mcp/core/providers/detectors.py +515 -0
- foundry_mcp/core/providers/gemini.py +426 -0
- foundry_mcp/core/providers/opencode.py +718 -0
- foundry_mcp/core/providers/opencode_wrapper.js +308 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +857 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1234 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4142 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +1624 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +4119 -0
- foundry_mcp/core/task.py +2463 -0
- foundry_mcp/core/testing.py +839 -0
- foundry_mcp/core/validation.py +2357 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +177 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +300 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +164 -0
- foundry_mcp/dashboard/views/overview.py +96 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +414 -0
- foundry_mcp/server.py +150 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +92 -0
- foundry_mcp/tools/unified/authoring.py +3620 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +268 -0
- foundry_mcp/tools/unified/environment.py +1341 -0
- foundry_mcp/tools/unified/error.py +479 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +640 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +876 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +589 -0
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +1042 -0
- foundry_mcp/tools/unified/review_helpers.py +314 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +565 -0
- foundry_mcp/tools/unified/spec.py +1283 -0
- foundry_mcp/tools/unified/task.py +3846 -0
- foundry_mcp/tools/unified/test.py +431 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.8.22.dist-info/METADATA +344 -0
- foundry_mcp-0.8.22.dist-info/RECORD +153 -0
- foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
- foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base provider abstractions for foundry-mcp.
|
|
3
|
+
|
|
4
|
+
This module provides the core provider contracts adapted from sdd-toolkit,
|
|
5
|
+
enabling pluggable LLM backends for CLI operations. The abstractions support
|
|
6
|
+
capability negotiation, request/response normalization, and lifecycle hooks.
|
|
7
|
+
|
|
8
|
+
Design principles:
|
|
9
|
+
- Frozen dataclasses for immutability
|
|
10
|
+
- Enum-based capabilities for type-safe routing
|
|
11
|
+
- Status codes aligned with existing ProviderStatus patterns
|
|
12
|
+
- Error hierarchy for granular exception handling
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence, Set
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from foundry_mcp.core.responses import ErrorType
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProviderCapability(Enum):
|
|
27
|
+
"""
|
|
28
|
+
Feature flags a provider can expose to routing heuristics.
|
|
29
|
+
|
|
30
|
+
These capabilities enable callers to select providers based on
|
|
31
|
+
required features (vision, streaming, etc.) and allow registries
|
|
32
|
+
to route requests to appropriate backends.
|
|
33
|
+
|
|
34
|
+
Values:
|
|
35
|
+
TEXT: Basic text generation capability
|
|
36
|
+
VISION: Image/vision input processing
|
|
37
|
+
FUNCTION_CALLING: Tool/function invocation support
|
|
38
|
+
STREAMING: Incremental response streaming
|
|
39
|
+
THINKING: Extended reasoning/chain-of-thought support
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
TEXT = "text_generation"
|
|
43
|
+
VISION = "vision"
|
|
44
|
+
FUNCTION_CALLING = "function_calling"
|
|
45
|
+
STREAMING = "streaming"
|
|
46
|
+
THINKING = "thinking"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ProviderStatus(Enum):
|
|
50
|
+
"""
|
|
51
|
+
Normalized execution outcomes emitted by providers.
|
|
52
|
+
|
|
53
|
+
These status codes provide a consistent interface for handling provider
|
|
54
|
+
responses across different backends, enabling uniform error handling
|
|
55
|
+
and retry logic.
|
|
56
|
+
|
|
57
|
+
Values:
|
|
58
|
+
SUCCESS: Operation completed successfully
|
|
59
|
+
TIMEOUT: Operation exceeded time limit (retryable)
|
|
60
|
+
NOT_FOUND: Provider/resource not available (not retryable)
|
|
61
|
+
INVALID_OUTPUT: Provider returned malformed response (not retryable)
|
|
62
|
+
ERROR: Generic error during execution (retryable)
|
|
63
|
+
CANCELED: Operation was explicitly canceled (not retryable)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
SUCCESS = "success"
|
|
67
|
+
TIMEOUT = "timeout"
|
|
68
|
+
NOT_FOUND = "not_found"
|
|
69
|
+
INVALID_OUTPUT = "invalid_output"
|
|
70
|
+
ERROR = "error"
|
|
71
|
+
CANCELED = "canceled"
|
|
72
|
+
|
|
73
|
+
def is_retryable(self) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Check if this status represents a transient failure that may succeed on retry.
|
|
76
|
+
|
|
77
|
+
Retryable statuses:
|
|
78
|
+
- TIMEOUT: May succeed with more time or if temporary resource contention resolves
|
|
79
|
+
- ERROR: Generic errors may be transient (network issues, rate limits, etc.)
|
|
80
|
+
|
|
81
|
+
Non-retryable statuses:
|
|
82
|
+
- SUCCESS: Already succeeded
|
|
83
|
+
- NOT_FOUND: Provider/tool not available (won't change on retry)
|
|
84
|
+
- INVALID_OUTPUT: Provider responded but output was malformed
|
|
85
|
+
- CANCELED: Explicitly canceled (shouldn't retry)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
True if the status is retryable, False otherwise
|
|
89
|
+
"""
|
|
90
|
+
return self in (ProviderStatus.TIMEOUT, ProviderStatus.ERROR)
|
|
91
|
+
|
|
92
|
+
def to_error_type(self) -> "ErrorType":
|
|
93
|
+
"""
|
|
94
|
+
Map provider status to foundry-mcp ErrorType for response envelopes.
|
|
95
|
+
|
|
96
|
+
This enables consistent error categorization across MCP and CLI surfaces,
|
|
97
|
+
allowing callers to handle provider errors using the standard error taxonomy.
|
|
98
|
+
|
|
99
|
+
Mapping:
|
|
100
|
+
- SUCCESS: raises ValueError (not an error state)
|
|
101
|
+
- TIMEOUT: UNAVAILABLE (503 analog, retryable)
|
|
102
|
+
- NOT_FOUND: NOT_FOUND (404 analog, not retryable)
|
|
103
|
+
- INVALID_OUTPUT: VALIDATION (400 analog, not retryable)
|
|
104
|
+
- ERROR: INTERNAL (500 analog, retryable)
|
|
105
|
+
- CANCELED: INTERNAL (operation aborted)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
ErrorType enum value corresponding to this status
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: If called on SUCCESS status (not an error)
|
|
112
|
+
"""
|
|
113
|
+
from foundry_mcp.core.responses import ErrorType
|
|
114
|
+
|
|
115
|
+
if self == ProviderStatus.SUCCESS:
|
|
116
|
+
raise ValueError("SUCCESS status cannot be mapped to an error type")
|
|
117
|
+
|
|
118
|
+
mapping = {
|
|
119
|
+
ProviderStatus.TIMEOUT: ErrorType.UNAVAILABLE,
|
|
120
|
+
ProviderStatus.NOT_FOUND: ErrorType.NOT_FOUND,
|
|
121
|
+
ProviderStatus.INVALID_OUTPUT: ErrorType.VALIDATION,
|
|
122
|
+
ProviderStatus.ERROR: ErrorType.INTERNAL,
|
|
123
|
+
ProviderStatus.CANCELED: ErrorType.INTERNAL,
|
|
124
|
+
}
|
|
125
|
+
return mapping[self]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass(frozen=True)
|
|
129
|
+
class ProviderRequest:
|
|
130
|
+
"""
|
|
131
|
+
Normalized request payload for provider execution.
|
|
132
|
+
|
|
133
|
+
This dataclass encapsulates all parameters needed to make a generation
|
|
134
|
+
request to any provider backend. Fields follow common LLM API conventions
|
|
135
|
+
to ensure portability across different providers.
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
prompt: The user's input prompt/message
|
|
139
|
+
system_prompt: Optional system/instruction prompt
|
|
140
|
+
model: Model identifier (provider-specific, e.g., "pro", "flash")
|
|
141
|
+
timeout: Request timeout in seconds (None = provider default)
|
|
142
|
+
temperature: Sampling temperature (0.0-2.0, None = provider default)
|
|
143
|
+
max_tokens: Maximum output tokens (None = provider default)
|
|
144
|
+
metadata: Arbitrary request metadata (tracing IDs, feature flags, etc.)
|
|
145
|
+
stream: Whether to request streaming response
|
|
146
|
+
attachments: File paths or URIs for multimodal inputs
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
prompt: str
|
|
150
|
+
system_prompt: Optional[str] = None
|
|
151
|
+
model: Optional[str] = None
|
|
152
|
+
timeout: Optional[float] = None
|
|
153
|
+
temperature: Optional[float] = None
|
|
154
|
+
max_tokens: Optional[int] = None
|
|
155
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
156
|
+
stream: bool = False
|
|
157
|
+
attachments: Sequence[str] = field(default_factory=list)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True)
|
|
161
|
+
class TokenUsage:
|
|
162
|
+
"""
|
|
163
|
+
Token accounting information reported by providers.
|
|
164
|
+
|
|
165
|
+
Tracks input, output, and cached tokens for cost estimation
|
|
166
|
+
and usage monitoring.
|
|
167
|
+
|
|
168
|
+
Attributes:
|
|
169
|
+
input_tokens: Tokens consumed by the prompt
|
|
170
|
+
output_tokens: Tokens generated in the response
|
|
171
|
+
cached_input_tokens: Tokens served from cache (if supported)
|
|
172
|
+
total_tokens: Sum of all token counts
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
input_tokens: int = 0
|
|
176
|
+
output_tokens: int = 0
|
|
177
|
+
cached_input_tokens: int = 0
|
|
178
|
+
total_tokens: int = 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass(frozen=True)
|
|
182
|
+
class ProviderResult:
|
|
183
|
+
"""
|
|
184
|
+
Normalized provider response.
|
|
185
|
+
|
|
186
|
+
This dataclass encapsulates all data returned from a provider execution,
|
|
187
|
+
providing a consistent interface regardless of the backend used.
|
|
188
|
+
|
|
189
|
+
Attributes:
|
|
190
|
+
content: Final text output (aggregated if streaming was used)
|
|
191
|
+
provider_id: Canonical provider identifier (e.g., "gemini", "codex")
|
|
192
|
+
model_used: Fully-qualified model identifier (e.g., "gemini:pro")
|
|
193
|
+
status: ProviderStatus describing execution outcome
|
|
194
|
+
tokens: Token usage data (if reported by provider)
|
|
195
|
+
duration_ms: Execution duration in milliseconds
|
|
196
|
+
stderr: Captured stderr/log output for debugging
|
|
197
|
+
raw_payload: Provider-specific metadata (traces, debug info, etc.)
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
content: str
|
|
201
|
+
provider_id: str
|
|
202
|
+
model_used: str
|
|
203
|
+
status: ProviderStatus
|
|
204
|
+
tokens: TokenUsage = field(default_factory=TokenUsage)
|
|
205
|
+
duration_ms: Optional[float] = None
|
|
206
|
+
stderr: Optional[str] = None
|
|
207
|
+
raw_payload: Dict[str, Any] = field(default_factory=dict)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True)
|
|
211
|
+
class ModelDescriptor:
|
|
212
|
+
"""
|
|
213
|
+
Describes a model supported by a provider.
|
|
214
|
+
|
|
215
|
+
Attributes:
|
|
216
|
+
id: Provider-specific model identifier (e.g., "pro", "flash")
|
|
217
|
+
display_name: Human-friendly name for UIs/logs
|
|
218
|
+
capabilities: Feature flags supported by this model
|
|
219
|
+
routing_hints: Optional metadata for routing (cost, latency, etc.)
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
id: str
|
|
223
|
+
display_name: Optional[str] = None
|
|
224
|
+
capabilities: Set[ProviderCapability] = field(default_factory=set)
|
|
225
|
+
routing_hints: Dict[str, Any] = field(default_factory=dict)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass(frozen=True)
|
|
229
|
+
class ProviderMetadata:
|
|
230
|
+
"""
|
|
231
|
+
Provider-level metadata shared with registries and consumers.
|
|
232
|
+
|
|
233
|
+
This dataclass describes a provider's capabilities, supported models,
|
|
234
|
+
and configuration, enabling informed routing decisions.
|
|
235
|
+
|
|
236
|
+
Attributes:
|
|
237
|
+
provider_id: Canonical provider identifier (e.g., "gemini", "codex")
|
|
238
|
+
display_name: Human-friendly provider name
|
|
239
|
+
models: Supported model descriptors
|
|
240
|
+
default_model: Model ID used when no override supplied
|
|
241
|
+
capabilities: Aggregate capabilities across all models
|
|
242
|
+
security_flags: Provider-specific sandbox/safety configuration
|
|
243
|
+
extra: Arbitrary metadata (version info, auth requirements, etc.)
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
provider_id: str
|
|
247
|
+
display_name: Optional[str] = None
|
|
248
|
+
models: Sequence[ModelDescriptor] = field(default_factory=list)
|
|
249
|
+
default_model: Optional[str] = None
|
|
250
|
+
capabilities: Set[ProviderCapability] = field(default_factory=set)
|
|
251
|
+
security_flags: Dict[str, Any] = field(default_factory=dict)
|
|
252
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# =============================================================================
|
|
256
|
+
# Error Hierarchy
|
|
257
|
+
# =============================================================================
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class ProviderError(RuntimeError):
|
|
261
|
+
"""Base exception for provider orchestration errors."""
|
|
262
|
+
|
|
263
|
+
def __init__(self, message: str, *, provider: Optional[str] = None):
|
|
264
|
+
self.provider = provider
|
|
265
|
+
super().__init__(message)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ProviderUnavailableError(ProviderError):
|
|
269
|
+
"""Raised when a provider cannot be instantiated (binary missing, auth issues)."""
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class ProviderExecutionError(ProviderError):
|
|
273
|
+
"""Raised when a provider command returns a non-retryable error."""
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class ProviderTimeoutError(ProviderError):
|
|
277
|
+
"""Raised when a provider exceeds its allotted execution time."""
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class ContextWindowError(ProviderExecutionError):
|
|
281
|
+
"""Raised when prompt exceeds the model's context window limit.
|
|
282
|
+
|
|
283
|
+
This error indicates the prompt/context size exceeded what the model
|
|
284
|
+
can process. It includes token counts to help with debugging and
|
|
285
|
+
provides actionable guidance for resolution.
|
|
286
|
+
|
|
287
|
+
Attributes:
|
|
288
|
+
prompt_tokens: Estimated tokens in the prompt (if known)
|
|
289
|
+
max_tokens: Maximum context window size (if known)
|
|
290
|
+
provider: Provider that raised the error
|
|
291
|
+
truncation_needed: How many tokens need to be removed
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
def __init__(
|
|
295
|
+
self,
|
|
296
|
+
message: str,
|
|
297
|
+
*,
|
|
298
|
+
provider: Optional[str] = None,
|
|
299
|
+
prompt_tokens: Optional[int] = None,
|
|
300
|
+
max_tokens: Optional[int] = None,
|
|
301
|
+
):
|
|
302
|
+
"""Initialize context window error.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
message: Error message describing the issue
|
|
306
|
+
provider: Provider ID that raised the error
|
|
307
|
+
prompt_tokens: Number of tokens in the prompt (if known)
|
|
308
|
+
max_tokens: Maximum tokens allowed (if known)
|
|
309
|
+
"""
|
|
310
|
+
super().__init__(message, provider=provider)
|
|
311
|
+
self.prompt_tokens = prompt_tokens
|
|
312
|
+
self.max_tokens = max_tokens
|
|
313
|
+
self.truncation_needed = (
|
|
314
|
+
(prompt_tokens - max_tokens) if prompt_tokens and max_tokens else None
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# =============================================================================
|
|
319
|
+
# Lifecycle Hooks
|
|
320
|
+
# =============================================================================
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# Type aliases for hook callables
|
|
324
|
+
StreamChunkCallback = Callable[["StreamChunk", ProviderMetadata], None]
|
|
325
|
+
BeforeExecuteHook = Callable[[ProviderRequest, ProviderMetadata], None]
|
|
326
|
+
AfterResultHook = Callable[[ProviderResult, ProviderMetadata], None]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@dataclass(frozen=True)
|
|
330
|
+
class StreamChunk:
|
|
331
|
+
"""Represents a streamed fragment emitted by the provider."""
|
|
332
|
+
|
|
333
|
+
content: str
|
|
334
|
+
index: int
|
|
335
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass
|
|
339
|
+
class ProviderHooks:
|
|
340
|
+
"""
|
|
341
|
+
Optional lifecycle hooks wired by the registry.
|
|
342
|
+
|
|
343
|
+
Hooks default to None (no-ops) so providers can invoke them unconditionally.
|
|
344
|
+
Registries can wire hooks for observability, logging, or streaming.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
before_execute: Optional[BeforeExecuteHook] = None
|
|
348
|
+
on_stream_chunk: Optional[StreamChunkCallback] = None
|
|
349
|
+
after_result: Optional[AfterResultHook] = None
|
|
350
|
+
|
|
351
|
+
def emit_before(self, request: ProviderRequest, metadata: ProviderMetadata) -> None:
|
|
352
|
+
"""Emit before-execution hook if registered."""
|
|
353
|
+
if self.before_execute:
|
|
354
|
+
self.before_execute(request, metadata)
|
|
355
|
+
|
|
356
|
+
def emit_stream(self, chunk: StreamChunk, metadata: ProviderMetadata) -> None:
|
|
357
|
+
"""Emit stream chunk hook if registered."""
|
|
358
|
+
if self.on_stream_chunk:
|
|
359
|
+
self.on_stream_chunk(chunk, metadata)
|
|
360
|
+
|
|
361
|
+
def emit_after(self, result: ProviderResult, metadata: ProviderMetadata) -> None:
|
|
362
|
+
"""Emit after-result hook if registered."""
|
|
363
|
+
if self.after_result:
|
|
364
|
+
self.after_result(result, metadata)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# =============================================================================
|
|
368
|
+
# Abstract Base Class
|
|
369
|
+
# =============================================================================
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class ProviderContext(ABC):
|
|
373
|
+
"""
|
|
374
|
+
Base class for provider implementations.
|
|
375
|
+
|
|
376
|
+
Subclasses should:
|
|
377
|
+
* Resolve CLI/environment dependencies during initialization
|
|
378
|
+
* Implement `_execute()` to run the underlying provider
|
|
379
|
+
* Return a populated `ProviderResult` from `_execute()`
|
|
380
|
+
* Emit streaming chunks via `self._emit_stream_chunk()` when
|
|
381
|
+
`request.stream` is True and the provider supports streaming
|
|
382
|
+
|
|
383
|
+
The `generate()` method is a template method that:
|
|
384
|
+
1. Calls `_prepare_request()` for any request modifications
|
|
385
|
+
2. Emits the `before_execute` hook
|
|
386
|
+
3. Calls the abstract `_execute()` method
|
|
387
|
+
4. Normalizes exceptions into typed ProviderErrors
|
|
388
|
+
5. Emits the `after_result` hook
|
|
389
|
+
6. Returns the result
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
def __init__(
|
|
393
|
+
self,
|
|
394
|
+
metadata: ProviderMetadata,
|
|
395
|
+
hooks: Optional[ProviderHooks] = None,
|
|
396
|
+
):
|
|
397
|
+
self._metadata = metadata
|
|
398
|
+
self._hooks = hooks or ProviderHooks()
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def metadata(self) -> ProviderMetadata:
|
|
402
|
+
"""Return provider metadata."""
|
|
403
|
+
return self._metadata
|
|
404
|
+
|
|
405
|
+
def supports(self, capability: ProviderCapability) -> bool:
|
|
406
|
+
"""Return True if any registered model advertises the capability."""
|
|
407
|
+
# Check provider-level capabilities first
|
|
408
|
+
if capability in self._metadata.capabilities:
|
|
409
|
+
return True
|
|
410
|
+
# Then check model-level capabilities
|
|
411
|
+
return any(capability in model.capabilities for model in self._metadata.models)
|
|
412
|
+
|
|
413
|
+
def generate(self, request: ProviderRequest) -> ProviderResult:
|
|
414
|
+
"""
|
|
415
|
+
Execute the provider with the supplied request (template method).
|
|
416
|
+
|
|
417
|
+
Applies lifecycle hooks, normalizes errors, and ensures ProviderStatus
|
|
418
|
+
is consistent across implementations.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
request: The generation request
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
ProviderResult with the generation output
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
ProviderUnavailableError: If provider binary/auth unavailable
|
|
428
|
+
ProviderTimeoutError: If request exceeds timeout
|
|
429
|
+
ProviderExecutionError: For other execution errors
|
|
430
|
+
"""
|
|
431
|
+
normalized_request = self._prepare_request(request)
|
|
432
|
+
self._hooks.emit_before(normalized_request, self._metadata)
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
result = self._execute(normalized_request)
|
|
436
|
+
except ProviderTimeoutError:
|
|
437
|
+
raise
|
|
438
|
+
except ProviderUnavailableError:
|
|
439
|
+
raise
|
|
440
|
+
except ProviderError:
|
|
441
|
+
raise
|
|
442
|
+
except FileNotFoundError as exc:
|
|
443
|
+
raise ProviderUnavailableError(
|
|
444
|
+
str(exc), provider=self._metadata.provider_id
|
|
445
|
+
) from exc
|
|
446
|
+
except TimeoutError as exc:
|
|
447
|
+
raise ProviderTimeoutError(
|
|
448
|
+
str(exc), provider=self._metadata.provider_id
|
|
449
|
+
) from exc
|
|
450
|
+
except Exception as exc: # noqa: BLE001 - intentionally wrap all provider exceptions
|
|
451
|
+
raise ProviderExecutionError(
|
|
452
|
+
str(exc), provider=self._metadata.provider_id
|
|
453
|
+
) from exc
|
|
454
|
+
|
|
455
|
+
self._hooks.emit_after(result, self._metadata)
|
|
456
|
+
return result
|
|
457
|
+
|
|
458
|
+
def _prepare_request(self, request: ProviderRequest) -> ProviderRequest:
|
|
459
|
+
"""
|
|
460
|
+
Allow subclasses to adjust request metadata before execution.
|
|
461
|
+
|
|
462
|
+
The default implementation simply returns the request unchanged.
|
|
463
|
+
Subclasses can override to inject defaults, normalize parameters, etc.
|
|
464
|
+
"""
|
|
465
|
+
return request
|
|
466
|
+
|
|
467
|
+
def _emit_stream_chunk(self, chunk: StreamChunk) -> None:
|
|
468
|
+
"""Helper for subclasses to publish streaming output through hooks."""
|
|
469
|
+
self._hooks.emit_stream(chunk, self._metadata)
|
|
470
|
+
|
|
471
|
+
@abstractmethod
|
|
472
|
+
def _execute(self, request: ProviderRequest) -> ProviderResult:
|
|
473
|
+
"""
|
|
474
|
+
Subclasses must implement the actual provider invocation.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
request: The (possibly modified) generation request
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
ProviderResult with generated content and metadata
|
|
481
|
+
"""
|
|
482
|
+
raise NotImplementedError
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# =============================================================================
|
|
486
|
+
# Module Exports
|
|
487
|
+
# =============================================================================
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
__all__ = [
|
|
491
|
+
# Enums
|
|
492
|
+
"ProviderCapability",
|
|
493
|
+
"ProviderStatus",
|
|
494
|
+
# Request/Response dataclasses
|
|
495
|
+
"ProviderRequest",
|
|
496
|
+
"ProviderResult",
|
|
497
|
+
"TokenUsage",
|
|
498
|
+
"StreamChunk",
|
|
499
|
+
# Metadata dataclasses
|
|
500
|
+
"ModelDescriptor",
|
|
501
|
+
"ProviderMetadata",
|
|
502
|
+
# Hooks
|
|
503
|
+
"ProviderHooks",
|
|
504
|
+
"StreamChunkCallback",
|
|
505
|
+
"BeforeExecuteHook",
|
|
506
|
+
"AfterResultHook",
|
|
507
|
+
# Errors
|
|
508
|
+
"ProviderError",
|
|
509
|
+
"ProviderUnavailableError",
|
|
510
|
+
"ProviderExecutionError",
|
|
511
|
+
"ProviderTimeoutError",
|
|
512
|
+
"ContextWindowError",
|
|
513
|
+
# ABC
|
|
514
|
+
"ProviderContext",
|
|
515
|
+
]
|