gdmcode 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdmcode-0.1.0.dist-info/METADATA +240 -0
- gdmcode-0.1.0.dist-info/RECORD +131 -0
- gdmcode-0.1.0.dist-info/WHEEL +4 -0
- gdmcode-0.1.0.dist-info/entry_points.txt +2 -0
- src/__init__.py +1 -0
- src/_internal/__init__.py +0 -0
- src/_internal/constants.py +244 -0
- src/_internal/domain_skills.py +339 -0
- src/agent/__init__.py +0 -0
- src/agent/commit_classifier.py +91 -0
- src/agent/context_budget.py +391 -0
- src/agent/daemon.py +681 -0
- src/agent/dag_validator.py +153 -0
- src/agent/debug_loop.py +473 -0
- src/agent/impact_analyzer.py +149 -0
- src/agent/impact_graph.py +117 -0
- src/agent/loop.py +1410 -0
- src/agent/orchestrator.py +141 -0
- src/agent/regression_guard.py +251 -0
- src/agent/review_gate.py +648 -0
- src/agent/risk_scorer.py +169 -0
- src/agent/self_healing.py +145 -0
- src/agent/smart_test_selector.py +89 -0
- src/agent/system_prompt.py +226 -0
- src/agent/task_tracker.py +320 -0
- src/agent/test_validator.py +210 -0
- src/agent/tool_orchestrator.py +402 -0
- src/agent/transcript.py +230 -0
- src/agent/verification_loop.py +133 -0
- src/agent/work_director.py +136 -0
- src/agent/worktree_manager.py +53 -0
- src/artifacts/__init__.py +16 -0
- src/artifacts/artifact_store.py +456 -0
- src/artifacts/verification_graph.py +75 -0
- src/auth.py +411 -0
- src/cli.py +1290 -0
- src/commands.py +1398 -0
- src/config.py +762 -0
- src/cost_tracker.py +348 -0
- src/db/__init__.py +4 -0
- src/db/migrations.py +337 -0
- src/enterprise/__init__.py +3 -0
- src/enterprise/audit_log.py +182 -0
- src/enterprise/identity.py +90 -0
- src/enterprise/rbac.py +100 -0
- src/enterprise/team_config.py +125 -0
- src/enterprise/usage_analytics.py +261 -0
- src/exceptions.py +207 -0
- src/git_workflow.py +651 -0
- src/integrations/__init__.py +6 -0
- src/integrations/github_actions.py +106 -0
- src/integrations/mcp_server.py +333 -0
- src/integrations/sentry_integration.py +100 -0
- src/integrations/sentry_server.py +82 -0
- src/integrations/webhook_security.py +19 -0
- src/main.py +27 -0
- src/memory/__init__.py +0 -0
- src/memory/code_index.py +376 -0
- src/memory/compressor.py +378 -0
- src/memory/context_memory.py +135 -0
- src/memory/continuous_memory.py +234 -0
- src/memory/conventions.py +495 -0
- src/memory/db.py +1119 -0
- src/memory/document_index.py +205 -0
- src/memory/file_cache.py +128 -0
- src/memory/project_scanner.py +178 -0
- src/memory/session_store.py +201 -0
- src/models/__init__.py +0 -0
- src/models/client.py +715 -0
- src/models/definitions.py +459 -0
- src/models/router.py +418 -0
- src/models/schemas.py +389 -0
- src/permissions.py +294 -0
- src/remote/__init__.py +5 -0
- src/remote/command_filter.py +33 -0
- src/remote/models.py +31 -0
- src/remote/permission_handler.py +79 -0
- src/remote/phone_ui.py +48 -0
- src/remote/protocol.py +59 -0
- src/remote/qr.py +65 -0
- src/remote/server.py +586 -0
- src/remote/token_manager.py +61 -0
- src/remote/tunnel.py +212 -0
- src/repl.py +475 -0
- src/runtime/__init__.py +1 -0
- src/runtime/branch_farm.py +372 -0
- src/runtime/replay.py +351 -0
- src/sandbox/__init__.py +2 -0
- src/sandbox/hermetic.py +214 -0
- src/sandbox/policy.py +44 -0
- src/sdk/__init__.py +3 -0
- src/sdk/plugin_base.py +39 -0
- src/sdk/plugin_host.py +100 -0
- src/sdk/plugin_loader.py +101 -0
- src/security.py +409 -0
- src/server/__init__.py +7 -0
- src/server/bridge.py +427 -0
- src/server/bridge_cli.py +103 -0
- src/server/bridge_client.py +170 -0
- src/server/protocol_version.py +103 -0
- src/session/__init__.py +10 -0
- src/session/event_fanout.py +46 -0
- src/session/input_broker.py +38 -0
- src/session/permission_bridge.py +100 -0
- src/tools/__init__.py +160 -0
- src/tools/_atomic.py +72 -0
- src/tools/agent_tools.py +423 -0
- src/tools/ask_user_tool.py +83 -0
- src/tools/bash_tool.py +384 -0
- src/tools/browser_tool.py +352 -0
- src/tools/browser_tools.py +179 -0
- src/tools/dep_tools.py +210 -0
- src/tools/document_reader.py +167 -0
- src/tools/document_tool.py +240 -0
- src/tools/document_writer.py +171 -0
- src/tools/impact_tools.py +240 -0
- src/tools/playwright_tool.py +172 -0
- src/tools/quality_tools.py +366 -0
- src/tools/read_tools.py +318 -0
- src/tools/result_cache.py +157 -0
- src/tools/search_tools.py +310 -0
- src/tools/shell_tools.py +311 -0
- src/tools/write_tools.py +337 -0
- src/voice/__init__.py +25 -0
- src/voice/audio_capture.py +92 -0
- src/voice/audio_playback.py +68 -0
- src/voice/errors.py +14 -0
- src/voice/models.py +35 -0
- src/voice/providers.py +143 -0
- src/voice/vad.py +55 -0
- src/voice/voice_loop.py +156 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""Model definitions — IDs, pricing, context windows for all supported providers.
|
|
2
|
+
|
|
3
|
+
All values are named constants. No magic strings or numbers anywhere else in
|
|
4
|
+
the codebase should reference model IDs or pricing directly.
|
|
5
|
+
|
|
6
|
+
Pricing as of April 2026 (USD per 1M tokens unless noted).
|
|
7
|
+
Providers:
|
|
8
|
+
- xAI Grok — primary, best models, native web_search tool
|
|
9
|
+
- Google Gemini — fallback, free tier (Flash), easy signup for everyone
|
|
10
|
+
- OpenAI Codex — optional, o3/o4-mini for reasoning, GPT-4.1 for cheap bulk
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass, replace
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Provider",
|
|
19
|
+
"ModelTier",
|
|
20
|
+
"PROXY_DEFAULT_URL",
|
|
21
|
+
"ModelDef",
|
|
22
|
+
"MODELS",
|
|
23
|
+
"SCOUT",
|
|
24
|
+
"CODER",
|
|
25
|
+
"THINKER",
|
|
26
|
+
"REASONER",
|
|
27
|
+
"DEBATE",
|
|
28
|
+
"STANDARD",
|
|
29
|
+
"GEMINI_SCOUT",
|
|
30
|
+
"GEMINI_CODER",
|
|
31
|
+
"GEMINI_THINKER",
|
|
32
|
+
"GEMINI_REASONER",
|
|
33
|
+
"CODEX_SCOUT",
|
|
34
|
+
"CODEX_CODER",
|
|
35
|
+
"CODEX_THINKER",
|
|
36
|
+
"CODEX_REASONER",
|
|
37
|
+
"CODEX_DEBATE",
|
|
38
|
+
"PROVIDER_BASE_URLS",
|
|
39
|
+
"TOOL_CALL_COSTS",
|
|
40
|
+
"get_model",
|
|
41
|
+
"get_capability",
|
|
42
|
+
"models_for_provider",
|
|
43
|
+
"apply_model_id_overrides",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Provider:
|
|
48
|
+
"""Supported API providers (string constants for serialisation safety)."""
|
|
49
|
+
GROK = "grok"
|
|
50
|
+
GEMINI = "gemini"
|
|
51
|
+
CODEX = "codex"
|
|
52
|
+
OLLAMA = "ollama"
|
|
53
|
+
VLLM = "vllm"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Default proxy relay URL — used when no custom URL is configured.
|
|
57
|
+
# The proxy implements the OpenAI-compatible /v1 API and forwards requests
|
|
58
|
+
# to the real providers, allowing access from geo-restricted regions.
|
|
59
|
+
PROXY_DEFAULT_URL: str = "https://proxy.gdm.dev/v1"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ModelTier:
|
|
63
|
+
"""Human-readable tier names used in routing logic."""
|
|
64
|
+
SCOUT = "scout"
|
|
65
|
+
CODER = "coder"
|
|
66
|
+
THINKER = "thinker"
|
|
67
|
+
REASONER = "reasoner"
|
|
68
|
+
DEBATE = "debate"
|
|
69
|
+
STANDARD = "standard"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
PROVIDER_BASE_URLS: dict[str, str] = {
|
|
73
|
+
Provider.GROK: "https://api.x.ai/v1",
|
|
74
|
+
Provider.GEMINI: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
75
|
+
Provider.CODEX: "https://api.openai.com/v1",
|
|
76
|
+
Provider.OLLAMA: "http://localhost:11434",
|
|
77
|
+
Provider.VLLM: "http://localhost:8000",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class ModelDef:
|
|
83
|
+
"""Complete definition of one model variant."""
|
|
84
|
+
|
|
85
|
+
id: str
|
|
86
|
+
tier: str
|
|
87
|
+
provider: str
|
|
88
|
+
input_per_m: float # USD per 1M input tokens
|
|
89
|
+
output_per_m: float # USD per 1M output tokens
|
|
90
|
+
cached_per_m: float # USD per 1M cached input tokens (0 if unsupported)
|
|
91
|
+
context_window: int # max tokens
|
|
92
|
+
supports_reasoning: bool = False
|
|
93
|
+
supports_tools: bool = True
|
|
94
|
+
supports_web_search: bool = False # native web_search tool (Grok only)
|
|
95
|
+
notes: str = ""
|
|
96
|
+
|
|
97
|
+
def input_cost(self, tokens: int) -> float:
|
|
98
|
+
return (tokens / 1_000_000) * self.input_per_m
|
|
99
|
+
|
|
100
|
+
def output_cost(self, tokens: int) -> float:
|
|
101
|
+
return (tokens / 1_000_000) * self.output_per_m
|
|
102
|
+
|
|
103
|
+
def cached_cost(self, tokens: int) -> float:
|
|
104
|
+
return (tokens / 1_000_000) * self.cached_per_m
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# xAI Grok models
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
SCOUT = ModelDef(
|
|
112
|
+
id="grok-4-1-fast-non-reasoning",
|
|
113
|
+
tier=ModelTier.SCOUT,
|
|
114
|
+
provider=Provider.GROK,
|
|
115
|
+
input_per_m=0.20,
|
|
116
|
+
output_per_m=0.50,
|
|
117
|
+
cached_per_m=0.05,
|
|
118
|
+
context_window=2_000_000,
|
|
119
|
+
supports_web_search=True,
|
|
120
|
+
notes="File reads, grep, summarization, compression, Tree-sitter indexing, commit messages",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
CODER = ModelDef(
|
|
124
|
+
id="grok-4-1-fast-reasoning",
|
|
125
|
+
tier=ModelTier.CODER,
|
|
126
|
+
provider=Provider.GROK,
|
|
127
|
+
input_per_m=0.20,
|
|
128
|
+
output_per_m=0.50,
|
|
129
|
+
cached_per_m=0.05,
|
|
130
|
+
context_window=2_000_000,
|
|
131
|
+
supports_reasoning=True,
|
|
132
|
+
supports_web_search=True,
|
|
133
|
+
notes="Quick code edits, tests, boilerplate, ensemble patching strategies",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
THINKER = ModelDef(
|
|
137
|
+
id="grok-4.20-0309-non-reasoning",
|
|
138
|
+
tier=ModelTier.THINKER,
|
|
139
|
+
provider=Provider.GROK,
|
|
140
|
+
input_per_m=2.00,
|
|
141
|
+
output_per_m=6.00,
|
|
142
|
+
cached_per_m=0.20,
|
|
143
|
+
context_window=2_000_000,
|
|
144
|
+
supports_web_search=True,
|
|
145
|
+
notes="Standard coding tasks, refactoring, multi-file edits",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
REASONER = ModelDef(
|
|
149
|
+
id="grok-4.20-0309-reasoning",
|
|
150
|
+
tier=ModelTier.REASONER,
|
|
151
|
+
provider=Provider.GROK,
|
|
152
|
+
input_per_m=2.00,
|
|
153
|
+
output_per_m=6.00,
|
|
154
|
+
cached_per_m=0.20,
|
|
155
|
+
context_window=2_000_000,
|
|
156
|
+
supports_reasoning=True,
|
|
157
|
+
supports_web_search=True,
|
|
158
|
+
notes="Architecture decisions, hard bugs, cross-file design, security reviews",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
DEBATE = ModelDef(
|
|
162
|
+
id="grok-4.20-multi-agent-0309",
|
|
163
|
+
tier=ModelTier.DEBATE,
|
|
164
|
+
provider=Provider.GROK,
|
|
165
|
+
input_per_m=2.00,
|
|
166
|
+
output_per_m=6.00,
|
|
167
|
+
cached_per_m=0.20,
|
|
168
|
+
context_window=2_000_000,
|
|
169
|
+
supports_reasoning=True,
|
|
170
|
+
supports_web_search=True,
|
|
171
|
+
notes="4-agent internal debate. Requires SuperGrok Heavy. Falls back to REASONER.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
STANDARD = ModelDef(
|
|
175
|
+
id="grok-4-0709",
|
|
176
|
+
tier=ModelTier.STANDARD,
|
|
177
|
+
provider=Provider.GROK,
|
|
178
|
+
input_per_m=3.00,
|
|
179
|
+
output_per_m=15.00,
|
|
180
|
+
cached_per_m=0.20,
|
|
181
|
+
context_window=256_000,
|
|
182
|
+
supports_reasoning=True,
|
|
183
|
+
supports_web_search=True,
|
|
184
|
+
notes="Classic Grok 4. Fallback when 2M context not needed.",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Google Gemini models — OpenAI-compatible endpoint, no extra SDK
|
|
189
|
+
# Flash-8B for Coder tier: ultra-cheap bulk edits and indexing
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
GEMINI_SCOUT = ModelDef(
|
|
193
|
+
id="gemini-2.5-flash",
|
|
194
|
+
tier=ModelTier.SCOUT,
|
|
195
|
+
provider=Provider.GEMINI,
|
|
196
|
+
input_per_m=0.15,
|
|
197
|
+
output_per_m=0.60,
|
|
198
|
+
cached_per_m=0.0,
|
|
199
|
+
context_window=1_000_000,
|
|
200
|
+
notes="Free tier: 1K req/day. File reads, summarization, quick answers.",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
GEMINI_CODER = ModelDef(
|
|
204
|
+
id="gemini-2.5-flash-8b",
|
|
205
|
+
tier=ModelTier.CODER,
|
|
206
|
+
provider=Provider.GEMINI,
|
|
207
|
+
input_per_m=0.075,
|
|
208
|
+
output_per_m=0.30,
|
|
209
|
+
cached_per_m=0.0,
|
|
210
|
+
context_window=1_000_000,
|
|
211
|
+
notes="Ultra-cheap bulk: file indexing, convention extraction, boilerplate edits.",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
GEMINI_THINKER = ModelDef(
|
|
215
|
+
id="gemini-2.5-pro",
|
|
216
|
+
tier=ModelTier.THINKER,
|
|
217
|
+
provider=Provider.GEMINI,
|
|
218
|
+
input_per_m=1.25,
|
|
219
|
+
output_per_m=10.00,
|
|
220
|
+
cached_per_m=0.0,
|
|
221
|
+
context_window=1_000_000,
|
|
222
|
+
supports_reasoning=True,
|
|
223
|
+
notes="≤200K input tier. Architecture, refactor, hard bugs.",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
GEMINI_REASONER = ModelDef(
|
|
227
|
+
id="gemini-2.5-pro",
|
|
228
|
+
tier=ModelTier.REASONER,
|
|
229
|
+
provider=Provider.GEMINI,
|
|
230
|
+
input_per_m=2.50,
|
|
231
|
+
output_per_m=10.00,
|
|
232
|
+
cached_per_m=0.0,
|
|
233
|
+
context_window=1_000_000,
|
|
234
|
+
supports_reasoning=True,
|
|
235
|
+
notes=">200K input tier (long context premium). Also handles DEBATE fallback.",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# OpenAI Codex models — o3/o4-mini for reasoning, GPT-4.1 for bulk
|
|
240
|
+
# o4-mini: best cost/performance reasoning model globally for algorithmic bugs
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
CODEX_SCOUT = ModelDef(
|
|
244
|
+
id="gpt-4.1-mini",
|
|
245
|
+
tier=ModelTier.SCOUT,
|
|
246
|
+
provider=Provider.CODEX,
|
|
247
|
+
input_per_m=0.40,
|
|
248
|
+
output_per_m=1.60,
|
|
249
|
+
cached_per_m=0.10,
|
|
250
|
+
context_window=1_047_576,
|
|
251
|
+
notes="Fast, cheap bulk. File reads, summarization, grep-class tasks.",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
CODEX_CODER = ModelDef(
|
|
255
|
+
id="gpt-4.1",
|
|
256
|
+
tier=ModelTier.CODER,
|
|
257
|
+
provider=Provider.CODEX,
|
|
258
|
+
input_per_m=2.00,
|
|
259
|
+
output_per_m=8.00,
|
|
260
|
+
cached_per_m=0.50,
|
|
261
|
+
context_window=1_047_576,
|
|
262
|
+
supports_tools=True,
|
|
263
|
+
notes="Strong code generation, multi-file edits, test writing.",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
CODEX_THINKER = ModelDef(
|
|
267
|
+
id="o4-mini",
|
|
268
|
+
tier=ModelTier.THINKER,
|
|
269
|
+
provider=Provider.CODEX,
|
|
270
|
+
input_per_m=1.10,
|
|
271
|
+
output_per_m=4.40,
|
|
272
|
+
cached_per_m=0.275,
|
|
273
|
+
context_window=200_000,
|
|
274
|
+
supports_reasoning=True,
|
|
275
|
+
notes="Best cost/perf reasoning. Exceptional for algorithmic bugs and hard logic.",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
CODEX_REASONER = ModelDef(
|
|
279
|
+
id="o3",
|
|
280
|
+
tier=ModelTier.REASONER,
|
|
281
|
+
provider=Provider.CODEX,
|
|
282
|
+
input_per_m=2.00,
|
|
283
|
+
output_per_m=8.00,
|
|
284
|
+
cached_per_m=0.50,
|
|
285
|
+
context_window=200_000,
|
|
286
|
+
supports_reasoning=True,
|
|
287
|
+
notes="Deep multi-step reasoning. Architecture decisions, security audits.",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
CODEX_DEBATE = ModelDef(
|
|
291
|
+
id="o3",
|
|
292
|
+
tier=ModelTier.DEBATE,
|
|
293
|
+
provider=Provider.CODEX,
|
|
294
|
+
input_per_m=2.00,
|
|
295
|
+
output_per_m=8.00,
|
|
296
|
+
cached_per_m=0.50,
|
|
297
|
+
context_window=200_000,
|
|
298
|
+
supports_reasoning=True,
|
|
299
|
+
notes="Simulated debate: 4 parallel o3 calls with adversarial prompts, merged by CODER.",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
# Catalogue and routing
|
|
304
|
+
# ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
_GROK_MODELS: list[ModelDef] = [SCOUT, CODER, THINKER, REASONER, DEBATE, STANDARD]
|
|
307
|
+
_GEMINI_MODELS: list[ModelDef] = [GEMINI_SCOUT, GEMINI_CODER, GEMINI_THINKER, GEMINI_REASONER]
|
|
308
|
+
_CODEX_MODELS: list[ModelDef] = [CODEX_SCOUT, CODEX_CODER, CODEX_THINKER, CODEX_REASONER, CODEX_DEBATE]
|
|
309
|
+
MODELS: list[ModelDef] = _GROK_MODELS + _GEMINI_MODELS + _CODEX_MODELS
|
|
310
|
+
|
|
311
|
+
_GROK_BY_TIER: dict[str, ModelDef] = {m.tier: m for m in _GROK_MODELS}
|
|
312
|
+
_GEMINI_BY_TIER: dict[str, ModelDef] = {m.tier: m for m in _GEMINI_MODELS}
|
|
313
|
+
_CODEX_BY_TIER: dict[str, ModelDef] = {m.tier: m for m in _CODEX_MODELS}
|
|
314
|
+
|
|
315
|
+
_TIER_MAPS: dict[str, dict[str, ModelDef]] = {
|
|
316
|
+
Provider.GROK: _GROK_BY_TIER,
|
|
317
|
+
Provider.GEMINI: _GEMINI_BY_TIER,
|
|
318
|
+
Provider.CODEX: _CODEX_BY_TIER,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# Fallback chain when a tier is missing for a provider
|
|
322
|
+
_FALLBACKS: dict[tuple[str, str], str] = {
|
|
323
|
+
(Provider.GEMINI, ModelTier.DEBATE): ModelTier.REASONER,
|
|
324
|
+
(Provider.GEMINI, ModelTier.STANDARD): ModelTier.REASONER,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def get_model(id_or_tier: str, provider: str = Provider.GROK) -> ModelDef:
|
|
329
|
+
"""Look up a ModelDef by (tier, provider). Falls back gracefully."""
|
|
330
|
+
tier_map = _TIER_MAPS.get(provider, _GROK_BY_TIER)
|
|
331
|
+
if id_or_tier in tier_map:
|
|
332
|
+
return tier_map[id_or_tier]
|
|
333
|
+
# Check provider-specific fallbacks
|
|
334
|
+
fallback_tier = _FALLBACKS.get((provider, id_or_tier))
|
|
335
|
+
if fallback_tier and fallback_tier in tier_map:
|
|
336
|
+
return tier_map[fallback_tier]
|
|
337
|
+
raise KeyError(f"Unknown model tier '{id_or_tier}' for provider '{provider}'.")
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def models_for_provider(provider: str) -> list[ModelDef]:
|
|
341
|
+
"""Return all models for a given provider in escalation order."""
|
|
342
|
+
match provider:
|
|
343
|
+
case Provider.GEMINI:
|
|
344
|
+
return list(_GEMINI_MODELS)
|
|
345
|
+
case Provider.CODEX:
|
|
346
|
+
return list(_CODEX_MODELS)
|
|
347
|
+
case _:
|
|
348
|
+
return list(_GROK_MODELS)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# ---------------------------------------------------------------------------
|
|
352
|
+
# Tool call costs (USD per 1K calls) — Grok-native tools only
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
@dataclass(frozen=True)
|
|
356
|
+
class ToolCallCost:
|
|
357
|
+
name: str
|
|
358
|
+
per_1k_calls: float
|
|
359
|
+
|
|
360
|
+
def cost(self, calls: int) -> float:
|
|
361
|
+
return (calls / 1000) * self.per_1k_calls
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
TOOL_CALL_COSTS: dict[str, ToolCallCost] = {
|
|
365
|
+
"web_search": ToolCallCost("web_search", 5.00),
|
|
366
|
+
"x_search": ToolCallCost("x_search", 5.00),
|
|
367
|
+
"document_query": ToolCallCost("document_query", 2.50),
|
|
368
|
+
"file_attachment_search": ToolCallCost("file_attachment_search", 10.00),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
# Constant name → module attribute map (for apply_model_id_overrides)
|
|
373
|
+
# ---------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
_CONST_MAP: dict[str, str] = {
|
|
376
|
+
"grok.scout": "SCOUT",
|
|
377
|
+
"grok.coder": "CODER",
|
|
378
|
+
"grok.thinker": "THINKER",
|
|
379
|
+
"grok.reasoner": "REASONER",
|
|
380
|
+
"grok.debate": "DEBATE",
|
|
381
|
+
"grok.standard": "STANDARD",
|
|
382
|
+
"gemini.scout": "GEMINI_SCOUT",
|
|
383
|
+
"gemini.coder": "GEMINI_CODER",
|
|
384
|
+
"gemini.thinker": "GEMINI_THINKER",
|
|
385
|
+
"gemini.reasoner": "GEMINI_REASONER",
|
|
386
|
+
"codex.scout": "CODEX_SCOUT",
|
|
387
|
+
"codex.coder": "CODEX_CODER",
|
|
388
|
+
"codex.thinker": "CODEX_THINKER",
|
|
389
|
+
"codex.reasoner": "CODEX_REASONER",
|
|
390
|
+
"codex.debate": "CODEX_DEBATE",
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def apply_model_id_overrides(overrides: dict[str, str]) -> None:
|
|
395
|
+
"""Patch module-level ModelDef constants with configured IDs.
|
|
396
|
+
|
|
397
|
+
Resolution order: env var > config.toml > hardcoded default.
|
|
398
|
+
Must be called once during ``load_config()`` before any model is used.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
overrides: mapping of ``"{provider}.{tier}"`` → new model ID string.
|
|
402
|
+
"""
|
|
403
|
+
module = sys.modules[__name__]
|
|
404
|
+
patched: list[str] = []
|
|
405
|
+
for key, new_id in overrides.items():
|
|
406
|
+
const_name = _CONST_MAP.get(key.lower())
|
|
407
|
+
if const_name is None:
|
|
408
|
+
continue
|
|
409
|
+
old_def: ModelDef = getattr(module, const_name)
|
|
410
|
+
if old_def.id != new_id:
|
|
411
|
+
setattr(module, const_name, replace(old_def, id=new_id))
|
|
412
|
+
patched.append(f"{const_name}: {old_def.id!r} → {new_id!r}")
|
|
413
|
+
if patched:
|
|
414
|
+
_rebuild_lookup_dicts()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _rebuild_lookup_dicts() -> None:
|
|
418
|
+
"""Rebuild tier-lookup dicts after ID overrides."""
|
|
419
|
+
module = sys.modules[__name__]
|
|
420
|
+
grok_models = [
|
|
421
|
+
module.SCOUT, module.CODER, module.THINKER,
|
|
422
|
+
module.REASONER, module.DEBATE, module.STANDARD,
|
|
423
|
+
]
|
|
424
|
+
gemini_models = [
|
|
425
|
+
module.GEMINI_SCOUT, module.GEMINI_CODER,
|
|
426
|
+
module.GEMINI_THINKER, module.GEMINI_REASONER,
|
|
427
|
+
]
|
|
428
|
+
codex_models = [
|
|
429
|
+
module.CODEX_SCOUT, module.CODEX_CODER, module.CODEX_THINKER,
|
|
430
|
+
module.CODEX_REASONER, module.CODEX_DEBATE,
|
|
431
|
+
]
|
|
432
|
+
module.MODELS = grok_models + gemini_models + codex_models # type: ignore[attr-defined]
|
|
433
|
+
module._GROK_BY_TIER = {m.tier: m for m in grok_models} # type: ignore[attr-defined]
|
|
434
|
+
module._GEMINI_BY_TIER = {m.tier: m for m in gemini_models} # type: ignore[attr-defined]
|
|
435
|
+
module._CODEX_BY_TIER = {m.tier: m for m in codex_models} # type: ignore[attr-defined]
|
|
436
|
+
module._TIER_MAPS = { # type: ignore[attr-defined]
|
|
437
|
+
Provider.GROK: module._GROK_BY_TIER,
|
|
438
|
+
Provider.GEMINI: module._GEMINI_BY_TIER,
|
|
439
|
+
Provider.CODEX: module._CODEX_BY_TIER,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def get_capability(tier: str, provider: str, capability: str) -> bool:
|
|
444
|
+
"""Return a boolean capability flag for a given model tier + provider.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
tier: ModelTier constant (e.g. ``ModelTier.CODER``).
|
|
448
|
+
provider: Provider constant (e.g. ``Provider.GROK``).
|
|
449
|
+
capability: Attribute name on ModelDef (e.g. ``"supports_tools"``).
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
``False`` if the tier/provider is unknown or capability is unrecognised.
|
|
453
|
+
"""
|
|
454
|
+
try:
|
|
455
|
+
m = get_model(tier, provider)
|
|
456
|
+
return bool(getattr(m, capability, False))
|
|
457
|
+
except KeyError:
|
|
458
|
+
return False
|
|
459
|
+
|