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:
+96
-8
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -20,7 +20,7 @@ from src.result_types import Result
|
||||
from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
@pytest.fixture
|
||||
def caps() -> VendorCapabilities:
|
||||
|
||||
@@ -11,7 +11,7 @@ from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.result_types import Result
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
|
||||
return NormalizedResponse(
|
||||
|
||||
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch
|
||||
from src.openai_compatible import NormalizedResponse
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
|
||||
return NormalizedResponse(
|
||||
|
||||
@@ -214,7 +214,7 @@ def test_fr3_minimax_thinking_in_returned_text() -> None:
|
||||
from src import openai_compatible as oc
|
||||
from src import provider_state
|
||||
from src.provider_state import ProviderHistory
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor="minimax", model="MiniMax-M2.7", reasoning=True))
|
||||
ai_client._model = "MiniMax-M2.7"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_send_grok_uses_xai_endpoint(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert mock_client.chat.completions.create.called
|
||||
|
||||
def test_grok_2_vision_supports_image() -> None:
|
||||
from src.vendor_capabilities import get_capabilities
|
||||
from src.ai_client import get_capabilities
|
||||
caps = get_capabilities("grok", "grok-2-vision")
|
||||
assert caps.vision is True
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_llama_model_discovery_unions_ollama_and_openrouter() -> None:
|
||||
assert "llama-3.3-70b-specdec" in models
|
||||
|
||||
def test_llama_3_2_vision_vision_capability() -> None:
|
||||
from src.vendor_capabilities import get_capabilities
|
||||
from src.ai_client import get_capabilities
|
||||
caps = get_capabilities("llama", "llama-3.2-11b-vision-preview")
|
||||
assert caps.vision is True
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ def test_minimax_reasoning_extractor_used_when_caps_reasoning_true() -> None:
|
||||
def _fake_send(client, request, *, capabilities):
|
||||
captured_kwargs.append({"model": request.model})
|
||||
return MagicMock(text="ok", tool_calls=[], usage=UsageStats(input_tokens=0, output_tokens=0, cache_read_tokens=0, cache_creation_tokens=0), raw_response=None)
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.5', reasoning=True))
|
||||
with patch.object(oc, "send_openai_compatible", side_effect=_fake_send), \
|
||||
patch("src.ai_client._ensure_minimax_client", return_value=MagicMock()), \
|
||||
@@ -54,7 +54,7 @@ def test_minimax_reasoning_extractor_omitted_when_caps_reasoning_false() -> None
|
||||
"""caps.reasoning=False (M2/M2.1) should NOT pass the reasoning_extractor (avoid useless getattr)."""
|
||||
from src import openai_compatible as oc
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2', reasoning=False))
|
||||
captured_kwargs: list[dict] = []
|
||||
def _fake_send(client, request, *, capabilities):
|
||||
|
||||
@@ -6,7 +6,7 @@ from src.openai_compatible import (
|
||||
send_openai_compatible,
|
||||
)
|
||||
from src.openai_schemas import ChatMessage
|
||||
from src.vendor_capabilities import VendorCapabilities, register
|
||||
from src.ai_client import VendorCapabilities, register
|
||||
|
||||
@pytest.fixture
|
||||
def caps() -> VendorCapabilities:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
from src.vendor_capabilities import VendorCapabilities, get_capabilities, register
|
||||
from src.ai_client import VendorCapabilities, get_capabilities, register
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_registry():
|
||||
import src.vendor_capabilities
|
||||
snapshot = src.vendor_capabilities._REGISTRY.copy()
|
||||
import src.ai_client as _ai
|
||||
snapshot = _ai._VENDOR_REGISTRY.copy()
|
||||
yield
|
||||
src.vendor_capabilities._REGISTRY.clear()
|
||||
src.vendor_capabilities._REGISTRY.update(snapshot)
|
||||
_ai._VENDOR_REGISTRY.clear()
|
||||
_ai._VENDOR_REGISTRY.update(snapshot)
|
||||
|
||||
def test_registry_lookup_known_model():
|
||||
caps = VendorCapabilities(
|
||||
@@ -217,6 +217,6 @@ def test_v2_capability_badge_helper_skips_disabled_fields() -> None:
|
||||
a live context, but we can verify the helper is a no-op on
|
||||
the no-cap case.)"""
|
||||
from src.gui_2 import _render_v2_capability_badges
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
empty_caps = VendorCapabilities(vendor='test', model='empty')
|
||||
_render_v2_capability_badges(empty_caps)
|
||||
|
||||
Reference in New Issue
Block a user