Refactor: Data-driven model search + default presets
keyboardstaff committed
Mar 16, 2026 at 02:51 UTC
09a74381050b757f094458630a061aed6fbe5a4b
3 files changed
+123
-78
conf/model_providers.yaml
+56
@@ -15,11 +15,21 @@
15
# Optional fields:
16
# kwargs: A dictionary of extra parameters to pass to LiteLLM.
17
# This is useful for `api_base`, `extra_headers`, etc.
18
+#
19
+# Optional model search fields (used by the Model Configuration plugin):
20
+# models_endpoint: URL or path for the model listing API.
21
+# Absolute URL (https://...) is used directly.
22
+# Relative path (/path) is appended to api_base or default_base.
23
+# models_format: Response parsing format: "openai" (default), "google", "ollama".
24
+# models_params: Extra query parameters for the listing request.
25
+# static_models: Hardcoded model list for providers without a listing API.
26
+# default_base: Default base URL for local/self-hosted providers.
27
28
chat:
29
a0_venice:
30
name: Agent Zero API
31
litellm_provider: openai
32
+ models_endpoint: "https://api.venice.ai/api/v1/models"
33
kwargs:
34
api_base: https://llm.agent-zero.ai/v1
35
venice_parameters:
@@ -27,15 +37,24 @@ chat:
37
anthropic:
38
name: Anthropic
39
litellm_provider: anthropic
40
+ models_endpoint: "https://api.anthropic.com/v1/models"
41
+ models_params:
42
+ limit: "1000"
43
cometapi:
44
name: CometAPI
45
litellm_provider: cometapi
46
deepseek:
47
name: DeepSeek
48
litellm_provider: deepseek
49
+ models_endpoint: "https://api.deepseek.com/models"
50
github_copilot:
51
name: GitHub Copilot
52
litellm_provider: github_copilot
53
+ static_models:
54
+ - "gpt-4.1"
55
+ - "gpt-4o"
56
+ - "gpt-5-mini"
57
+ - "oswe-vscode-prime"
58
kwargs:
59
extra_headers:
60
"Editor-Version": "vscode/1.85.1"
@@ -44,36 +63,54 @@ chat:
63
google:
64
name: Google
65
litellm_provider: gemini
66
+ models_endpoint: "/v1beta/models"
67
+ models_format: "google"
68
+ models_params:
69
+ pageSize: "1000"
70
+ default_base: "https://generativelanguage.googleapis.com"
71
groq:
72
name: Groq
73
litellm_provider: groq
74
+ models_endpoint: "https://api.groq.com/openai/v1/models"
75
huggingface:
76
name: HuggingFace
77
litellm_provider: huggingface
78
lm_studio:
79
name: LM Studio
80
litellm_provider: lm_studio
81
+ models_endpoint: "/v1/models"
82
+ default_base: "http://host.docker.internal:1234"
83
mistral:
84
name: Mistral AI
85
litellm_provider: mistral
86
+ models_endpoint: "https://api.mistral.ai/v1/models"
87
moonshot:
88
name: Moonshot AI
89
litellm_provider: moonshot
90
+ models_endpoint: "https://api.moonshot.cn/v1/models"
91
ollama:
92
name: Ollama
93
litellm_provider: ollama
94
+ models_endpoint: "/api/tags"
95
+ models_format: "ollama"
96
+ default_base: "http://host.docker.internal:11434"
97
openai:
98
name: OpenAI
99
litellm_provider: openai
100
+ models_endpoint: "https://api.openai.com/v1/models"
101
azure:
102
name: OpenAI Azure
103
litellm_provider: azure
104
+ models_endpoint: "/openai/models"
105
+ models_params:
106
+ api-version: "2024-10-21"
107
bedrock:
108
name: AWS Bedrock
109
litellm_provider: bedrock
110
openrouter:
111
name: OpenRouter
112
litellm_provider: openrouter
113
+ models_endpoint: "https://openrouter.ai/api/v1/models"
114
kwargs:
115
extra_headers:
116
"HTTP-Referer": "https://agent-zero.ai/"
@@ -84,6 +121,7 @@ chat:
121
sambanova:
122
name: Sambanova
123
litellm_provider: sambanova
124
+ models_endpoint: "https://api.sambanova.ai/v1/models"
125
venice:
126
name: Venice.ai
127
litellm_provider: openai
@@ -94,14 +132,32 @@ chat:
132
xai:
133
name: xAI
134
litellm_provider: xai
135
+ models_endpoint: "https://api.x.ai/v1/models"
136
zai:
137
name: Z.AI
138
litellm_provider: openai
139
+ static_models:
140
+ - "glm-4-plus"
141
+ - "glm-4-air-250414"
142
+ - "glm-4-airx"
143
+ - "glm-4-long"
144
+ - "glm-4-flashx"
145
+ - "glm-4-flash-250414"
146
+ - "glm-4v-plus"
147
+ - "glm-4v"
148
+ - "glm-3-turbo"
149
kwargs:
150
api_base: https://api.z.ai/api/paas/v4
151
zai_coding:
152
name: Z.AI Coding
153
litellm_provider: openai
154
+ static_models:
155
+ - "codegeex-4"
156
+ - "glm-4-plus"
157
+ - "glm-4-air-250414"
158
+ - "glm-4-airx"
159
+ - "glm-4-flashx"
160
+ - "glm-4-flash-250414"
161
kwargs:
162
api_base: https://api.z.ai/api/coding/paas/v4
163
other:
plugins/_model_config/api/model_search.py
+44
-77
@@ -3,44 +3,6 @@ from helpers.api import ApiHandler, Request, Response
3
from helpers.providers import get_provider_config
4
import models
5
6
-_CLOUD_ENDPOINTS: dict[str, str] = {
7
- "openai": "https://api.openai.com/v1/models",
8
- "anthropic": "https://api.anthropic.com/v1/models",
9
- "groq": "https://api.groq.com/openai/v1/models",
10
- "deepseek": "https://api.deepseek.com/models",
11
- "mistral": "https://api.mistral.ai/v1/models",
12
- "openrouter": "https://openrouter.ai/api/v1/models",
13
- "xai": "https://api.x.ai/v1/models",
14
- "sambanova": "https://api.sambanova.ai/v1/models",
15
- "moonshot": "https://api.moonshot.cn/v1/models",
16
- "google": "https://generativelanguage.googleapis.com/v1beta/models",
17
- "a0_venice": "https://api.venice.ai/api/v1/models",
18
- "venice": "https://api.venice.ai/api/v1/models",
19
-}
20
-
21
-# Local providers with default base URLs (no auth required).
22
-_LOCAL_DEFAULTS: dict[str, str] = {
23
- "ollama": "http://host.docker.internal:11434",
24
- "lm_studio": "http://host.docker.internal:1234",
25
-}
26
-
27
-# Providers with hardcoded model lists (no listing API available).
28
-_STATIC_MODELS: dict[str, list[str]] = {
29
- "github_copilot": [
30
- "gpt-4.1", "gpt-4o", "gpt-5-mini", "oswe-vscode-prime",
31
- ],
32
- "zai": [
33
- "glm-4-plus", "glm-4-air-250414", "glm-4-airx",
34
- "glm-4-long", "glm-4-flashx", "glm-4-flash-250414",
35
- "glm-4v-plus", "glm-4v", "glm-3-turbo",
36
- ],
37
- "zai_coding": [
38
- "codegeex-4",
39
- "glm-4-plus", "glm-4-air-250414", "glm-4-airx",
40
- "glm-4-flashx", "glm-4-flash-250414",
41
- ],
42
-}
43
-
6
# Model name substrings to exclude from litellm fallback results
7
_LITELLM_EXCLUDE = frozenset({
8
"dall-e", "gpt-image", "tts", "whisper", "audio",
@@ -58,40 +20,52 @@ class ModelSearch(ApiHandler):
20
if not provider:
21
return {"models": []}
22
61
- if provider in _STATIC_MODELS:
62
- all_models = list(_STATIC_MODELS[provider])
23
+ cfg = self._get_search_config(model_type, provider)
24
+
25
+ static = cfg.get("static_models")
26
+ if static:
27
+ all_models = list(static)
28
else:
64
- provider_cfg = get_provider_config(model_type, provider)
65
- all_models = await self._fetch_models(provider, provider_cfg, user_api_base) or []
29
+ all_models = await self._fetch_models(provider, cfg, user_api_base) or []
30
31
if not all_models:
68
- litellm_provider = (provider_cfg or {}).get("litellm_provider", provider)
32
+ litellm_provider = (cfg or {}).get("litellm_provider", provider)
33
if litellm_provider == provider:
70
- all_models = self._litellm_fallback(provider, provider_cfg)
34
+ all_models = self._litellm_fallback(provider, cfg)
35
36
if query:
37
all_models = [m for m in all_models if query in m.lower()]
38
39
return {"models": sorted(all_models)[:50], "provider": provider}
40
77
- async def _fetch_models(self, provider: str, cfg: dict | None, user_api_base: str = "") -> list[str] | None:
78
- api_base = user_api_base or (cfg or {}).get("kwargs", {}).get("api_base", "")
41
+ @staticmethod
42
+ def _get_search_config(model_type: str, provider: str) -> dict:
43
+ """Get provider config with search metadata, falling back to chat config."""
44
+ cfg = get_provider_config(model_type, provider) or {}
45
+ if model_type != "chat" and not cfg.get("models_endpoint") and not cfg.get("static_models"):
46
+ chat_cfg = get_provider_config("chat", provider) or {}
47
+ merged = dict(cfg)
48
+ for field in ("models_endpoint", "models_format", "models_params",
49
+ "static_models", "default_base"):
50
+ if field in chat_cfg and field not in merged:
51
+ merged[field] = chat_cfg[field]
52
+ return merged
53
+ return cfg
54
+
55
+ async def _fetch_models(self, provider: str, cfg: dict, user_api_base: str = "") -> list[str] | None:
56
api_key = models.get_api_key(provider)
57
+ api_base = user_api_base or (cfg or {}).get("kwargs", {}).get("api_base", "")
58
81
- url, fmt = self._resolve_url(provider, api_base)
59
+ url, fmt = self._resolve_url(cfg, api_base)
60
if not url:
61
return None
62
63
headers = self._build_headers(provider, api_key, cfg)
86
- params: dict[str, str] = {}
87
- if provider == "google":
88
- if api_key and api_key != "None":
89
- params["key"] = api_key
90
- params["pageSize"] = "1000"
91
- elif provider == "anthropic":
92
- params["limit"] = "1000"
93
- elif provider == "azure":
94
- params["api-version"] = "2024-10-21"
64
+ params = dict(cfg.get("models_params", {}) or {})
65
+
66
+ # Google uses query-param auth
67
+ if provider == "google" and api_key and api_key != "None":
68
+ params.setdefault("key", api_key)
69
70
try:
71
async with httpx.AsyncClient(timeout=10.0) as client:
@@ -105,31 +79,24 @@ class ModelSearch(ApiHandler):
79
80
return None
81
108
- def _resolve_url(self, provider: str, api_base: str) -> tuple[str | None, str]:
109
- if provider == "ollama":
110
- base = api_base or _LOCAL_DEFAULTS.get("ollama", "")
111
- return (base.rstrip("/") + "/api/tags" if base else None), "ollama"
112
-
113
- if provider == "google":
114
- if api_base:
115
- return api_base.rstrip("/") + "/models", "google"
116
- return _CLOUD_ENDPOINTS["google"], "google"
117
-
118
- if provider == "azure":
119
- if not api_base:
120
- return None, "openai"
121
- return api_base.rstrip("/") + "/openai/models", "openai"
82
+ @staticmethod
83
+ def _resolve_url(cfg: dict, api_base: str) -> tuple[str | None, str]:
84
+ fmt = cfg.get("models_format", "openai")
85
+ endpoint = cfg.get("models_endpoint", "")
86
+ default_base = cfg.get("default_base", "")
87
123
- if provider in _CLOUD_ENDPOINTS:
124
- return _CLOUD_ENDPOINTS[provider], "openai"
88
+ if endpoint.startswith("http"):
89
+ return endpoint, fmt
90
126
- if api_base:
127
- return api_base.rstrip("/") + "/models", "openai"
91
+ base = api_base or default_base
92
+ if not base:
93
+ return None, fmt
94
129
- if provider in _LOCAL_DEFAULTS:
130
- return _LOCAL_DEFAULTS[provider] + "/v1/models", "openai"
95
+ if endpoint:
96
+ return base.rstrip("/") + endpoint, fmt
97
132
- return None, "openai"
98
+ # Generic fallback: base + /models
99
+ return base.rstrip("/") + "/models", fmt
100
101
def _build_headers(self, provider: str, api_key: str, cfg: dict | None) -> dict[str, str]:
102
headers: dict[str, str] = {}
plugins/_model_config/default_config.yaml
+23
-1
@@ -32,4 +32,26 @@ embedding_model:
32
33
browser_http_headers: {}
34
35
-model_presets: []
35
+model_presets:
36
+ - name: "Efficiency"
37
+ chat:
38
+ provider: "openrouter"
39
+ name: "openai/gpt-5.2-chat"
40
+ api_key: ""
41
+ api_base: ""
42
+ utility:
43
+ provider: "openrouter"
44
+ name: "openai/gpt-5-nano"
45
+ api_key: ""
46
+ api_base: ""
47
+ - name: "Intelligence"
48
+ chat:
49
+ provider: "openrouter"
50
+ name: "anthropic/claude-opus-4.6"
51
+ api_key: ""
52
+ api_base: ""
53
+ utility:
54
+ provider: "openrouter"
55
+ name: "google/gemini-3-flash-preview"
56
+ api_key: ""
57
+ api_base: ""