refactor(ai_client): merge vendor_capabilities into ai_client; git rm src/vendor_capabilities.py

Per spec FR2 + Phase 2.1: VendorCapabilities + register + get_capabilities +
list_models_for_vendor + the ~40 vendor registrations move into ai_client.py
as a region block. Renamed internal _REGISTRY to _VENDOR_REGISTRY to avoid
collision with mcp_tool_specs._REGISTRY.

Importers (in src/) updated:
- src/ai_client.py: removed top-level import; removed 4 local imports of
  list_models_for_vendor/get_capabilities (symbol now in module namespace)
- src/app_controller.py: 2 sites updated to 'from src.ai_client import get_capabilities'
- src/gui_2.py: 1 site updated to 'from src.ai_client import VendorCapabilities, get_capabilities'

Tests updated:
- 8 test_*.py files: changed 'from src.vendor_capabilities import' to
  'from src.ai_client import'
- tests/test_vendor_capabilities.py: _clean_registry fixture updated to
  reference src.ai_client._VENDOR_REGISTRY (was src.vendor_capabilities._REGISTRY)

Verification: 157 tests pass across the affected files (vendor_capabilities,
ai_client_tool_loop variants, openai_compatible, command_palette,
diff_viewer, patch_modal, app_controller_result, app_controller_sigint,
handle_reset_session, ai_loop_regressions, grok/llama/minimax provider tests).
This commit is contained in:
ed
2026-06-26 07:07:12 -04:00
parent ac2a5ac3bd
commit 81d8bce419
13 changed files with 114 additions and 117 deletions
+96 -8
View File
@@ -29,6 +29,7 @@ import sys
import threading
import time
import tomllib
from dataclasses import dataclass
# TODO(Ed): Eliminate These?
from collections import deque
@@ -44,16 +45,17 @@ from src import mma_prompts
from src import performance_monitor
from src import project_manager
from src import provider_state
from src.vendor_capabilities import VendorCapabilities, get_capabilities
# TODO(Ed): Eliminate these?
from src.events import EventEmitter
from src.gemini_cli_adapter import GeminiCliAdapter
from src.models import FileItem, ToolPreset, BiasProfile, Tool
from src.paths import get_credentials_path
from src.tool_bias import ToolBiasEngine
from src.tool_presets import ToolPresetManager
from src.tool_presets import ToolPresetManager
# VendorCapabilities, get_capabilities, list_models_for_vendor, register
# are defined in this file (see '#region: Vendor Capabilities'). Previously
# imported from src/vendor_capabilities.py (deleted in
# module_taxonomy_refactor_20260627 Phase 2.1).
PROVIDERS: List[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax", "qwen", "grok", "llama"]
@@ -2595,7 +2597,6 @@ def _send_grok(md_content: str, user_message: str, base_dir: str,
return Result(data="", errors=[_classify_openai_compatible_error(exc, source="ai_client.grok")])
def _list_grok_models() -> list[str]:
from src.vendor_capabilities import list_models_for_vendor
return list_models_for_vendor("grok")
def _send_minimax(md_content: str, user_message: str, base_dir: str,
@@ -2753,7 +2754,6 @@ def _extract_dashscope_tool_calls(resp: Any) -> list[Metadata]:
return out
def _list_qwen_models() -> list[str]:
from src.vendor_capabilities import list_models_for_vendor
return list_models_for_vendor("qwen")
def _send_qwen(md_content: str, user_message: str, base_dir: str,
@@ -3015,13 +3015,11 @@ def _send_llama_native(md_content: str, user_message: str, base_dir: str,
return Result(data="", errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(exc), source="ai_client.llama_native", original=exc)])
def _list_llama_models() -> list[str]:
from src.vendor_capabilities import list_models_for_vendor
return list_models_for_vendor("llama")
def _get_llama_cost_tracking() -> bool:
if "localhost" in _llama_base_url or "127.0.0.1" in _llama_base_url:
return False
from src.vendor_capabilities import get_capabilities
try:
caps = get_capabilities("llama", _model)
return caps.cost_tracking
@@ -3449,3 +3447,93 @@ def run_discussion_compression(discussion_text: str) -> str:
return f"ERROR: Unsupported provider for discussion compression: '{p}'"
#endregion: Subagent Summarization
#region: Vendor Capabilities (moved from src/vendor_capabilities.py)
@dataclass(frozen=True)
class VendorCapabilities:
vendor: str
model: str
vision: bool = False
tool_calling: bool = True
caching: bool = False
streaming: bool = True
model_discovery: bool = True
context_window: int = 8192
cost_tracking: bool = True
cost_input_per_mtok: float = 0.0
cost_output_per_mtok: float = 0.0
notes: str = ''
local: bool = False
reasoning: bool = False
structured_output: bool = False
code_execution: bool = False
web_search: bool = False
x_search: bool = False
file_search: bool = False
mcp_support: bool = False
audio: bool = False
video: bool = False
grounding: bool = False
computer_use: bool = False
_VENDOR_REGISTRY: dict[tuple[str, str], "VendorCapabilities"] = {}
def register(cap: "VendorCapabilities") -> None:
_VENDOR_REGISTRY[(cap.vendor, cap.model)] = cap
def get_capabilities(vendor: str, model: str) -> "VendorCapabilities":
if (vendor, model) in _VENDOR_REGISTRY: return _VENDOR_REGISTRY[(vendor, model)]
if (vendor, '*') in _VENDOR_REGISTRY: return _VENDOR_REGISTRY[(vendor, '*')]
raise KeyError(f'No capabilities registered for vendor={vendor!r} model={model!r}')
def list_models_for_vendor(vendor: str) -> list[str]:
return sorted({m for v, m in _VENDOR_REGISTRY if v == vendor and m != '*'})
register(VendorCapabilities(vendor='minimax', model='*', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.7', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20, reasoning=True))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.5', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20, reasoning=True))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.1', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='grok', model='*', context_window=131072, cost_input_per_mtok=2.00, cost_output_per_mtok=10.00, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-2', context_window=131072, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-2-vision', vision=True, context_window=32768, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-beta', context_window=131072, cost_input_per_mtok=5.00, cost_output_per_mtok=15.00, web_search=True, x_search=True))
register(VendorCapabilities(vendor='llama', model='*', context_window=131072))
register(VendorCapabilities(vendor='llama', model='llama-3.1-8b-instant', context_window=131072, cost_input_per_mtok=0.05, cost_output_per_mtok=0.08))
register(VendorCapabilities(vendor='llama', model='llama-3.1-70b-versatile', context_window=131072, cost_input_per_mtok=0.59, cost_output_per_mtok=0.79))
register(VendorCapabilities(vendor='llama', model='llama-3.1-405b-reasoning', context_window=131072, cost_input_per_mtok=3.00, cost_output_per_mtok=3.00, reasoning=True))
register(VendorCapabilities(vendor='llama', model='llama-3.2-1b-preview', context_window=131072, cost_input_per_mtok=0.04, cost_output_per_mtok=0.04))
register(VendorCapabilities(vendor='llama', model='llama-3.2-3b-preview', context_window=131072, cost_input_per_mtok=0.06, cost_output_per_mtok=0.06))
register(VendorCapabilities(vendor='llama', model='llama-3.2-11b-vision-preview', vision=True, context_window=131072, cost_input_per_mtok=0.18, cost_output_per_mtok=0.18))
register(VendorCapabilities(vendor='llama', model='llama-3.2-90b-vision-preview', vision=True, context_window=131072, cost_input_per_mtok=0.90, cost_output_per_mtok=0.90))
register(VendorCapabilities(vendor='llama', model='llama-3.3-70b-specdec', context_window=131072, cost_input_per_mtok=0.59, cost_output_per_mtok=0.79))
register(VendorCapabilities(vendor='qwen', model='*', context_window=32768))
register(VendorCapabilities(vendor='qwen', model='qwen-turbo', context_window=1000000, cost_input_per_mtok=0.05, cost_output_per_mtok=0.10))
register(VendorCapabilities(vendor='qwen', model='qwen-plus', context_window=131072, cost_input_per_mtok=0.40, cost_output_per_mtok=1.20))
register(VendorCapabilities(vendor='qwen', model='qwen-max', context_window=32768, cost_input_per_mtok=2.00, cost_output_per_mtok=6.00))
register(VendorCapabilities(vendor='qwen', model='qwen-long', context_window=1000000, cost_input_per_mtok=0.07, cost_output_per_mtok=0.28, caching=True, notes='qwen-long supports custom chunked long-context caching'))
register(VendorCapabilities(vendor='qwen', model='qwen-vl-plus', vision=True, context_window=131072, cost_input_per_mtok=0.21, cost_output_per_mtok=0.63))
register(VendorCapabilities(vendor='qwen', model='qwen-vl-max', vision=True, context_window=32768, cost_input_per_mtok=0.50, cost_output_per_mtok=1.50))
register(VendorCapabilities(vendor='qwen', model='qwen-audio', context_window=32768, cost_input_per_mtok=0.10, cost_output_per_mtok=0.30, audio=True, notes='Audio input support added 2026-06-11 (v2 matrix)'))
register(VendorCapabilities(vendor='anthropic', model='*', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True, notes='Anthropic wildcard: Sonnet defaults. Per-model variations below.'))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-5-20250929', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-20250514', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-6', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-1-20250805', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-20250514', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-5-20251101', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-6', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-7', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-8', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-haiku-4-5-20251001', context_window=200000, cost_input_per_mtok=1.00, cost_output_per_mtok=5.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-fable-5', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='gemini', model='*', context_window=1000000, cost_input_per_mtok=1.25, cost_output_per_mtok=5.00, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True, notes='Gemini wildcard: 1M+ context window. Per-model variations below.'))
register(VendorCapabilities(vendor='gemini', model='gemini-3.1-pro-preview', context_window=1000000, cost_input_per_mtok=3.50, cost_output_per_mtok=10.50, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-3-flash-preview', context_window=1000000, cost_input_per_mtok=0.15, cost_output_per_mtok=0.60, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-2.5-flash', context_window=1000000, cost_input_per_mtok=0.15, cost_output_per_mtok=0.60, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-2.5-flash-lite', context_window=1000000, cost_input_per_mtok=0.075, cost_output_per_mtok=0.30, caching=True, vision=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='*', context_window=32768, cost_input_per_mtok=0.27, cost_output_per_mtok=1.10, reasoning=True, structured_output=True, notes='DeepSeek wildcard: V3 defaults. R1/reasoner variants below.'))
register(VendorCapabilities(vendor='deepseek', model='deepseek-v3', context_window=32768, cost_input_per_mtok=0.27, cost_output_per_mtok=1.10, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='deepseek-reasoner', context_window=32768, cost_input_per_mtok=0.55, cost_output_per_mtok=2.19, reasoning=True, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='deepseek-r1', context_window=32768, cost_input_per_mtok=0.55, cost_output_per_mtok=2.19, reasoning=True, structured_output=True))
#endregion: Vendor Capabilities
+2 -2
View File
@@ -2054,7 +2054,7 @@ class AppController:
from src.personas import PersonaManager
self.persona_manager = PersonaManager(Path(self.active_project_path).parent if self.active_project_path else None)
from src.vendor_capabilities import get_capabilities
from src.ai_client import get_capabilities
try:
caps = get_capabilities(self.current_provider, self.current_model)
except KeyError:
@@ -4281,7 +4281,7 @@ class AppController:
def _on_ai_stream(self, text: str) -> None:
"""Handles streaming text from the AI."""
self.event_queue.put("response", {"text": text, "status": "streaming...", "role": "AI"})
from src.vendor_capabilities import get_capabilities
from src.ai_client import get_capabilities
try:
caps = get_capabilities(self.current_provider, self.current_model)
except KeyError:
+1 -1
View File
@@ -785,7 +785,7 @@ class App:
#TODO(Ed): Remove Exception based errors.
def _get_active_capabilities(self) -> "VendorCapabilities":
from src.vendor_capabilities import VendorCapabilities, get_capabilities
from src.ai_client import VendorCapabilities, get_capabilities
#TODO(Ed): Remove Exception based errors.
try:
caps = get_capabilities(self.current_provider, self.current_model)
-91
View File
@@ -1,91 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class VendorCapabilities:
vendor: str
model: str
vision: bool = False
tool_calling: bool = True
caching: bool = False
streaming: bool = True
model_discovery: bool = True
context_window: int = 8192
cost_tracking: bool = True
cost_input_per_mtok: float = 0.0
cost_output_per_mtok: float = 0.0
notes: str = ''
# v2 fields (added 2026-06-11)
local: bool = False
reasoning: bool = False
structured_output: bool = False
code_execution: bool = False
web_search: bool = False
x_search: bool = False
file_search: bool = False
mcp_support: bool = False
audio: bool = False
video: bool = False
grounding: bool = False
computer_use: bool = False
_REGISTRY: dict[tuple[str, str], VendorCapabilities] = {}
def register(cap: VendorCapabilities) -> None:
_REGISTRY[(cap.vendor, cap.model)] = cap
def get_capabilities(vendor: str, model: str) -> VendorCapabilities:
if (vendor, model) in _REGISTRY: return _REGISTRY[(vendor, model)]
if (vendor, '*') in _REGISTRY: return _REGISTRY[(vendor, '*')]
raise KeyError(f'No capabilities registered for vendor={vendor!r} model={model!r}')
def list_models_for_vendor(vendor: str) -> list[str]:
return sorted({m for v, m in _REGISTRY if v == vendor and m != '*'})
register(VendorCapabilities(vendor='minimax', model='*', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.7', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20, reasoning=True))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.5', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20, reasoning=True))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.1', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2', context_window=131072, cost_input_per_mtok=0.20, cost_output_per_mtok=0.20))
register(VendorCapabilities(vendor='grok', model='*', context_window=131072, cost_input_per_mtok=2.00, cost_output_per_mtok=10.00, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-2', context_window=131072, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-2-vision', vision=True, context_window=32768, web_search=True, x_search=True))
register(VendorCapabilities(vendor='grok', model='grok-beta', context_window=131072, cost_input_per_mtok=5.00, cost_output_per_mtok=15.00, web_search=True, x_search=True))
register(VendorCapabilities(vendor='llama', model='*', context_window=131072))
register(VendorCapabilities(vendor='llama', model='llama-3.1-8b-instant', context_window=131072, cost_input_per_mtok=0.05, cost_output_per_mtok=0.08))
register(VendorCapabilities(vendor='llama', model='llama-3.1-70b-versatile', context_window=131072, cost_input_per_mtok=0.59, cost_output_per_mtok=0.79))
register(VendorCapabilities(vendor='llama', model='llama-3.1-405b-reasoning', context_window=131072, cost_input_per_mtok=3.00, cost_output_per_mtok=3.00, reasoning=True))
register(VendorCapabilities(vendor='llama', model='llama-3.2-1b-preview', context_window=131072, cost_input_per_mtok=0.04, cost_output_per_mtok=0.04))
register(VendorCapabilities(vendor='llama', model='llama-3.2-3b-preview', context_window=131072, cost_input_per_mtok=0.06, cost_output_per_mtok=0.06))
register(VendorCapabilities(vendor='llama', model='llama-3.2-11b-vision-preview', vision=True, context_window=131072, cost_input_per_mtok=0.18, cost_output_per_mtok=0.18))
register(VendorCapabilities(vendor='llama', model='llama-3.2-90b-vision-preview', vision=True, context_window=131072, cost_input_per_mtok=0.90, cost_output_per_mtok=0.90))
register(VendorCapabilities(vendor='llama', model='llama-3.3-70b-specdec', context_window=131072, cost_input_per_mtok=0.59, cost_output_per_mtok=0.79))
register(VendorCapabilities(vendor='qwen', model='*', context_window=32768))
register(VendorCapabilities(vendor='qwen', model='qwen-turbo', context_window=1000000, cost_input_per_mtok=0.05, cost_output_per_mtok=0.10))
register(VendorCapabilities(vendor='qwen', model='qwen-plus', context_window=131072, cost_input_per_mtok=0.40, cost_output_per_mtok=1.20))
register(VendorCapabilities(vendor='qwen', model='qwen-max', context_window=32768, cost_input_per_mtok=2.00, cost_output_per_mtok=6.00))
register(VendorCapabilities(vendor='qwen', model='qwen-long', context_window=1000000, cost_input_per_mtok=0.07, cost_output_per_mtok=0.28, caching=True, notes='qwen-long supports custom chunked long-context caching'))
register(VendorCapabilities(vendor='qwen', model='qwen-vl-plus', vision=True, context_window=131072, cost_input_per_mtok=0.21, cost_output_per_mtok=0.63))
register(VendorCapabilities(vendor='qwen', model='qwen-vl-max', vision=True, context_window=32768, cost_input_per_mtok=0.50, cost_output_per_mtok=1.50))
register(VendorCapabilities(vendor='qwen', model='qwen-audio', context_window=32768, cost_input_per_mtok=0.10, cost_output_per_mtok=0.30, audio=True, notes='Audio input support added 2026-06-11 (v2 matrix)'))
register(VendorCapabilities(vendor='anthropic', model='*', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True, notes='Anthropic wildcard: Sonnet defaults. Per-model variations below.'))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-5-20250929', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-20250514', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-sonnet-4-6', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-1-20250805', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-20250514', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-5-20251101', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-6', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-7', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-opus-4-8', context_window=200000, cost_input_per_mtok=15.00, cost_output_per_mtok=75.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-haiku-4-5-20251001', context_window=200000, cost_input_per_mtok=1.00, cost_output_per_mtok=5.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='anthropic', model='claude-fable-5', context_window=200000, cost_input_per_mtok=3.00, cost_output_per_mtok=15.00, caching=True, structured_output=True, file_search=True, mcp_support=True, computer_use=True))
register(VendorCapabilities(vendor='gemini', model='*', context_window=1000000, cost_input_per_mtok=1.25, cost_output_per_mtok=5.00, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True, notes='Gemini wildcard: 1M+ context window. Per-model variations below.'))
register(VendorCapabilities(vendor='gemini', model='gemini-3.1-pro-preview', context_window=1000000, cost_input_per_mtok=3.50, cost_output_per_mtok=10.50, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-3-flash-preview', context_window=1000000, cost_input_per_mtok=0.15, cost_output_per_mtok=0.60, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-2.5-flash', context_window=1000000, cost_input_per_mtok=0.15, cost_output_per_mtok=0.60, caching=True, vision=True, video=True, audio=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='gemini', model='gemini-2.5-flash-lite', context_window=1000000, cost_input_per_mtok=0.075, cost_output_per_mtok=0.30, caching=True, vision=True, grounding=True, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='*', context_window=32768, cost_input_per_mtok=0.27, cost_output_per_mtok=1.10, reasoning=True, structured_output=True, notes='DeepSeek wildcard: V3 defaults. R1/reasoner variants below.'))
register(VendorCapabilities(vendor='deepseek', model='deepseek-v3', context_window=32768, cost_input_per_mtok=0.27, cost_output_per_mtok=1.10, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='deepseek-reasoner', context_window=32768, cost_input_per_mtok=0.55, cost_output_per_mtok=2.19, reasoning=True, structured_output=True))
register(VendorCapabilities(vendor='deepseek', model='deepseek-r1', context_window=32768, cost_input_per_mtok=0.55, cost_output_per_mtok=2.19, reasoning=True, structured_output=True))