vtx-coding-agent 0.1.1__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.
- vtx/__init__.py +63 -0
- vtx/async_utils.py +40 -0
- vtx/builtin_skills/github/SKILL.md +139 -0
- vtx/builtin_skills/init/SKILL.md +74 -0
- vtx/builtin_skills/review/SKILL.md +73 -0
- vtx/builtin_skills/skill-builder/SKILL.md +133 -0
- vtx/cli.py +90 -0
- vtx/config.py +741 -0
- vtx/context/__init__.py +15 -0
- vtx/context/_xml.py +8 -0
- vtx/context/agent_mds.py +128 -0
- vtx/context/git.py +64 -0
- vtx/context/loader.py +41 -0
- vtx/context/skills.py +423 -0
- vtx/core/__init__.py +47 -0
- vtx/core/compaction.py +89 -0
- vtx/core/errors.py +17 -0
- vtx/core/handoff.py +51 -0
- vtx/core/scratchpad.py +54 -0
- vtx/core/types.py +197 -0
- vtx/defaults/__init__.py +0 -0
- vtx/defaults/config.yml +53 -0
- vtx/diff_display.py +12 -0
- vtx/events.py +224 -0
- vtx/gh_cli.py +82 -0
- vtx/git_branch.py +90 -0
- vtx/headless.py +127 -0
- vtx/llm/__init__.py +93 -0
- vtx/llm/base.py +217 -0
- vtx/llm/context_length.py +150 -0
- vtx/llm/dynamic_models.py +735 -0
- vtx/llm/model_fetcher.py +279 -0
- vtx/llm/models.py +78 -0
- vtx/llm/oauth/__init__.py +59 -0
- vtx/llm/oauth/copilot.py +358 -0
- vtx/llm/oauth/dynamic.py +236 -0
- vtx/llm/oauth/openai.py +400 -0
- vtx/llm/phase_parser.py +270 -0
- vtx/llm/provider.yaml +280 -0
- vtx/llm/provider_catalog.py +230 -0
- vtx/llm/providers/__init__.py +45 -0
- vtx/llm/providers/anthropic_sdk.py +256 -0
- vtx/llm/providers/mock.py +249 -0
- vtx/llm/providers/openai_sdk.py +246 -0
- vtx/llm/providers/sanitize.py +14 -0
- vtx/llm/sdk/__init__.py +13 -0
- vtx/llm/sdk/anthropic.py +382 -0
- vtx/llm/sdk/base.py +82 -0
- vtx/llm/sdk/openai.py +344 -0
- vtx/llm/tool_parser.py +161 -0
- vtx/loop.py +272 -0
- vtx/notify.py +109 -0
- vtx/permissions.py +114 -0
- vtx/prompts/__init__.py +45 -0
- vtx/prompts/builder.py +86 -0
- vtx/prompts/env.py +58 -0
- vtx/prompts/identity.py +166 -0
- vtx/prompts/tooling.py +36 -0
- vtx/py.typed +0 -0
- vtx/runtime.py +580 -0
- vtx/session.py +868 -0
- vtx/sounds/completion.wav +0 -0
- vtx/sounds/error.wav +0 -0
- vtx/sounds/permission.wav +0 -0
- vtx/themes.py +1104 -0
- vtx/tools/__init__.py +68 -0
- vtx/tools/_read_image.py +106 -0
- vtx/tools/_tool_utils.py +90 -0
- vtx/tools/base.py +36 -0
- vtx/tools/bash.py +371 -0
- vtx/tools/edit.py +261 -0
- vtx/tools/find.py +132 -0
- vtx/tools/read.py +238 -0
- vtx/tools/skill.py +278 -0
- vtx/tools/web.py +238 -0
- vtx/tools/write.py +88 -0
- vtx/tools_manager.py +216 -0
- vtx/turn.py +789 -0
- vtx/ui/__init__.py +0 -0
- vtx/ui/agent_runner.py +417 -0
- vtx/ui/app.py +665 -0
- vtx/ui/app_protocol.py +29 -0
- vtx/ui/autocomplete.py +440 -0
- vtx/ui/blocks.py +735 -0
- vtx/ui/chat.py +613 -0
- vtx/ui/clipboard.py +59 -0
- vtx/ui/commands/__init__.py +100 -0
- vtx/ui/commands/auth.py +306 -0
- vtx/ui/commands/base.py +122 -0
- vtx/ui/commands/models.py +144 -0
- vtx/ui/commands/sessions.py +388 -0
- vtx/ui/commands/settings.py +286 -0
- vtx/ui/completion_ui.py +313 -0
- vtx/ui/export.py +703 -0
- vtx/ui/floating_list.py +370 -0
- vtx/ui/formatting.py +287 -0
- vtx/ui/input.py +760 -0
- vtx/ui/latex.py +349 -0
- vtx/ui/launch.py +108 -0
- vtx/ui/path_complete.py +228 -0
- vtx/ui/prompt_history.py +102 -0
- vtx/ui/queue_ui.py +141 -0
- vtx/ui/selection_mode.py +18 -0
- vtx/ui/session_ui.py +235 -0
- vtx/ui/startup.py +124 -0
- vtx/ui/styles.py +327 -0
- vtx/ui/tool_output.py +34 -0
- vtx/ui/tree.py +437 -0
- vtx/ui/welcome.py +51 -0
- vtx/ui/widgets.py +558 -0
- vtx/update_check.py +49 -0
- vtx/version.py +22 -0
- vtx_coding_agent-0.1.1.dist-info/METADATA +259 -0
- vtx_coding_agent-0.1.1.dist-info/RECORD +117 -0
- vtx_coding_agent-0.1.1.dist-info/WHEEL +4 -0
- vtx_coding_agent-0.1.1.dist-info/entry_points.txt +2 -0
- vtx_coding_agent-0.1.1.dist-info/licenses/LICENSE +201 -0
vtx/llm/provider.yaml
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# LLM Provider Catalog
|
|
2
|
+
# Add new providers here. Each entry defines how to connect to a provider.
|
|
3
|
+
#
|
|
4
|
+
# Fields:
|
|
5
|
+
# slug: Unique identifier
|
|
6
|
+
# display_name: Human-readable name
|
|
7
|
+
# description: One-line description
|
|
8
|
+
# family: API family - "openai_compat" or "anthropic"
|
|
9
|
+
# base_url: API endpoint URL
|
|
10
|
+
# api_key_env: Environment variable for API key (null for keyless)
|
|
11
|
+
# known_models: Fallback list (used when fetch fails or fetch_models=false)
|
|
12
|
+
# supports_tools: Tool calling support (default: true)
|
|
13
|
+
# supports_vision: Image input support (default: false)
|
|
14
|
+
# api_key_optional: Works without API key (default: false)
|
|
15
|
+
# is_local: Local provider (default: false)
|
|
16
|
+
# max_tokens: Default max output tokens (default: 8192)
|
|
17
|
+
# supports_thinking: Reasoning/thinking support (default: false)
|
|
18
|
+
# headers: Extra request headers, e.g. {"X-Foo": "bar"} (default: {})
|
|
19
|
+
# openmodelendpoint: Provider's /models catalog is public (no key needed for
|
|
20
|
+
# model discovery). Inference still requires a real key
|
|
21
|
+
# unless api_key_optional is also set. (default: false)
|
|
22
|
+
#
|
|
23
|
+
# Auto-fetch fields:
|
|
24
|
+
# fetch_models: Fetch model list from /models endpoint (default: false)
|
|
25
|
+
# models_endpoint: Path to append to base_url (default: "/models")
|
|
26
|
+
# model_parser:
|
|
27
|
+
# array_path: JSON key containing model array (default: "data")
|
|
28
|
+
# id_field: Field for model ID (default: "id")
|
|
29
|
+
# name_field: Field for model name (default: "name")
|
|
30
|
+
# context_field: Field for context length (default: "context_length")
|
|
31
|
+
# output_field: Field for max output tokens (default: "max_completion_tokens")
|
|
32
|
+
# cooldown_minutes: Cache TTL in minutes (default: 60)
|
|
33
|
+
|
|
34
|
+
providers:
|
|
35
|
+
- slug: openai
|
|
36
|
+
display_name: OpenAI
|
|
37
|
+
description: "Official OpenAI API (gpt-4o, gpt-4o-mini, ...)."
|
|
38
|
+
family: openai_compat
|
|
39
|
+
base_url: https://api.openai.com/v1
|
|
40
|
+
api_key_env: OPENAI_API_KEY
|
|
41
|
+
known_models:
|
|
42
|
+
- gpt-4o
|
|
43
|
+
- gpt-4o-mini
|
|
44
|
+
- gpt-4-turbo
|
|
45
|
+
supports_vision: true
|
|
46
|
+
max_tokens: 16384
|
|
47
|
+
fetch_models: true
|
|
48
|
+
models_endpoint: /models
|
|
49
|
+
model_parser:
|
|
50
|
+
cooldown_minutes: 60
|
|
51
|
+
|
|
52
|
+
- slug: anthropic
|
|
53
|
+
display_name: Anthropic
|
|
54
|
+
description: Direct Anthropic API.
|
|
55
|
+
family: anthropic
|
|
56
|
+
base_url: https://api.anthropic.com
|
|
57
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
58
|
+
known_models:
|
|
59
|
+
- claude-sonnet-4-20250514
|
|
60
|
+
- claude-3-5-sonnet-20241022
|
|
61
|
+
- claude-3-5-haiku-20241022
|
|
62
|
+
supports_vision: true
|
|
63
|
+
supports_thinking: true
|
|
64
|
+
max_tokens: 8192
|
|
65
|
+
|
|
66
|
+
- slug: deepseek
|
|
67
|
+
display_name: DeepSeek
|
|
68
|
+
description: DeepSeek Chat + Reasoner.
|
|
69
|
+
family: openai_compat
|
|
70
|
+
base_url: https://api.deepseek.com/v1
|
|
71
|
+
api_key_env: DEEPSEEK_API_KEY
|
|
72
|
+
known_models:
|
|
73
|
+
- deepseek-chat
|
|
74
|
+
- deepseek-reasoner
|
|
75
|
+
supports_thinking: true
|
|
76
|
+
fetch_models: true
|
|
77
|
+
models_endpoint: /models
|
|
78
|
+
model_parser:
|
|
79
|
+
cooldown_minutes: 60
|
|
80
|
+
|
|
81
|
+
- slug: zhipu
|
|
82
|
+
display_name: ZhiPu
|
|
83
|
+
description: ZhiPu GLM models.
|
|
84
|
+
family: openai_compat
|
|
85
|
+
base_url: https://api.z.ai/api/coding/paas/v4
|
|
86
|
+
api_key_env: ZAI_API_KEY
|
|
87
|
+
known_models:
|
|
88
|
+
- glm-5.1
|
|
89
|
+
- glm-5.2
|
|
90
|
+
supports_thinking: true
|
|
91
|
+
fetch_models: true
|
|
92
|
+
models_endpoint: /models
|
|
93
|
+
model_parser:
|
|
94
|
+
cooldown_minutes: 60
|
|
95
|
+
|
|
96
|
+
- slug: openrouter
|
|
97
|
+
display_name: OpenRouter
|
|
98
|
+
description: "200+ models from every provider, one key."
|
|
99
|
+
family: openai_compat
|
|
100
|
+
base_url: https://openrouter.ai/api/v1
|
|
101
|
+
api_key_env: OPENROUTER_API_KEY
|
|
102
|
+
known_models:
|
|
103
|
+
- anthropic/claude-3.5-sonnet
|
|
104
|
+
- openai/gpt-4o
|
|
105
|
+
- google/gemini-2.0-flash-exp:free
|
|
106
|
+
supports_vision: true
|
|
107
|
+
fetch_models: true
|
|
108
|
+
models_endpoint: /models
|
|
109
|
+
model_parser:
|
|
110
|
+
cooldown_minutes: 60
|
|
111
|
+
|
|
112
|
+
- slug: groq
|
|
113
|
+
display_name: Groq
|
|
114
|
+
description: Ultra-fast LPU inference.
|
|
115
|
+
family: openai_compat
|
|
116
|
+
base_url: https://api.groq.com/openai/v1
|
|
117
|
+
api_key_env: GROQ_API_KEY
|
|
118
|
+
known_models:
|
|
119
|
+
- llama-3.3-70b-versatile
|
|
120
|
+
- mixtral-8x7b-32768
|
|
121
|
+
fetch_models: true
|
|
122
|
+
models_endpoint: /models
|
|
123
|
+
model_parser:
|
|
124
|
+
cooldown_minutes: 60
|
|
125
|
+
|
|
126
|
+
- slug: together
|
|
127
|
+
display_name: Together AI
|
|
128
|
+
description: Open-source models hosted by Together.
|
|
129
|
+
family: openai_compat
|
|
130
|
+
base_url: https://api.together.xyz/v1
|
|
131
|
+
api_key_env: TOGETHER_API_KEY
|
|
132
|
+
known_models:
|
|
133
|
+
- meta-llama/Llama-3.3-70B-Instruct-Turbo
|
|
134
|
+
fetch_models: true
|
|
135
|
+
models_endpoint: /models
|
|
136
|
+
model_parser:
|
|
137
|
+
cooldown_minutes: 60
|
|
138
|
+
|
|
139
|
+
- slug: fireworks
|
|
140
|
+
display_name: Fireworks AI
|
|
141
|
+
description: Fast open-source inference.
|
|
142
|
+
family: openai_compat
|
|
143
|
+
base_url: https://api.fireworks.ai/inference/v1
|
|
144
|
+
api_key_env: FIREWORKS_API_KEY
|
|
145
|
+
known_models: []
|
|
146
|
+
fetch_models: true
|
|
147
|
+
models_endpoint: /models
|
|
148
|
+
model_parser:
|
|
149
|
+
cooldown_minutes: 60
|
|
150
|
+
|
|
151
|
+
- slug: mistral
|
|
152
|
+
display_name: Mistral AI
|
|
153
|
+
description: Mistral's OpenAI-compatible endpoint.
|
|
154
|
+
family: openai_compat
|
|
155
|
+
base_url: https://api.mistral.ai/v1
|
|
156
|
+
api_key_env: MISTRAL_API_KEY
|
|
157
|
+
known_models: []
|
|
158
|
+
fetch_models: true
|
|
159
|
+
models_endpoint: /models
|
|
160
|
+
model_parser:
|
|
161
|
+
cooldown_minutes: 60
|
|
162
|
+
|
|
163
|
+
- slug: nvidia
|
|
164
|
+
display_name: NVIDIA NIM
|
|
165
|
+
description: NVIDIA inference endpoints.
|
|
166
|
+
family: openai_compat
|
|
167
|
+
base_url: https://integrate.api.nvidia.com/v1
|
|
168
|
+
api_key_env: NVIDIA_API_KEY
|
|
169
|
+
known_models: []
|
|
170
|
+
fetch_models: true
|
|
171
|
+
models_endpoint: /models
|
|
172
|
+
model_parser:
|
|
173
|
+
cooldown_minutes: 60
|
|
174
|
+
|
|
175
|
+
- slug: deepinfra
|
|
176
|
+
display_name: DeepInfra
|
|
177
|
+
description: Serverless GPU inference.
|
|
178
|
+
family: openai_compat
|
|
179
|
+
base_url: https://api.deepinfra.com/v1/openai
|
|
180
|
+
api_key_env: DEEPINFRA_API_KEY
|
|
181
|
+
known_models: []
|
|
182
|
+
fetch_models: true
|
|
183
|
+
models_endpoint: /models
|
|
184
|
+
model_parser:
|
|
185
|
+
cooldown_minutes: 60
|
|
186
|
+
|
|
187
|
+
- slug: huggingface
|
|
188
|
+
display_name: Hugging Face
|
|
189
|
+
description: Hugging Face inference API.
|
|
190
|
+
family: openai_compat
|
|
191
|
+
base_url: https://router.huggingface.co/v1
|
|
192
|
+
api_key_env: HF_TOKEN
|
|
193
|
+
known_models: []
|
|
194
|
+
fetch_models: true
|
|
195
|
+
models_endpoint: /models
|
|
196
|
+
model_parser:
|
|
197
|
+
cooldown_minutes: 60
|
|
198
|
+
|
|
199
|
+
- slug: ollama
|
|
200
|
+
display_name: "Ollama (local)"
|
|
201
|
+
description: Local models served by Ollama.
|
|
202
|
+
family: openai_compat
|
|
203
|
+
base_url: http://localhost:11434/v1
|
|
204
|
+
api_key_env: null
|
|
205
|
+
known_models: []
|
|
206
|
+
api_key_optional: true
|
|
207
|
+
is_local: true
|
|
208
|
+
openmodelendpoint: true
|
|
209
|
+
fetch_models: true
|
|
210
|
+
models_endpoint: /models
|
|
211
|
+
model_parser:
|
|
212
|
+
cooldown_minutes: 5
|
|
213
|
+
|
|
214
|
+
- slug: aerolink
|
|
215
|
+
display_name: Aerolink
|
|
216
|
+
description: Aerolink Anthropic-compatible gateway with auto-fetched model catalog.
|
|
217
|
+
family: anthropic
|
|
218
|
+
base_url: https://capi.aerolink.lat
|
|
219
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
220
|
+
known_models: []
|
|
221
|
+
supports_vision: true
|
|
222
|
+
supports_thinking: true
|
|
223
|
+
max_tokens: 8192
|
|
224
|
+
fetch_models: true
|
|
225
|
+
models_endpoint: /v1/models
|
|
226
|
+
model_parser:
|
|
227
|
+
cooldown_minutes: 60
|
|
228
|
+
|
|
229
|
+
- slug: airouter
|
|
230
|
+
display_name: Airouter
|
|
231
|
+
description: Airouter gateway (free tier + paid models).
|
|
232
|
+
family: openai_compat
|
|
233
|
+
base_url: https://api.airouter.in/v1
|
|
234
|
+
api_key_env: AIROUTER_API_KEY
|
|
235
|
+
known_models: []
|
|
236
|
+
fetch_models: true
|
|
237
|
+
models_endpoint: /models
|
|
238
|
+
model_parser:
|
|
239
|
+
cooldown_minutes: 60
|
|
240
|
+
|
|
241
|
+
- slug: opencode
|
|
242
|
+
display_name: OpenCode Zen
|
|
243
|
+
description: Curated models via OpenCode Zen.
|
|
244
|
+
family: openai_compat
|
|
245
|
+
base_url: https://opencode.ai/zen/v1
|
|
246
|
+
api_key_env: OPENCODE_API_KEY
|
|
247
|
+
known_models: []
|
|
248
|
+
openmodelendpoint: true
|
|
249
|
+
fetch_models: true
|
|
250
|
+
models_endpoint: /models
|
|
251
|
+
model_parser:
|
|
252
|
+
cooldown_minutes: 60
|
|
253
|
+
|
|
254
|
+
- slug: kilo
|
|
255
|
+
display_name: Kilo Gateway
|
|
256
|
+
description: Kilo AI gateway (free tier + paid models).
|
|
257
|
+
family: openai_compat
|
|
258
|
+
base_url: https://api.kilo.ai/api/gateway
|
|
259
|
+
api_key_env: KILO_API_KEY
|
|
260
|
+
known_models: []
|
|
261
|
+
headers:
|
|
262
|
+
X-KILOCODE-EDITORNAME: vtx
|
|
263
|
+
User-Agent: vtx
|
|
264
|
+
openmodelendpoint: true
|
|
265
|
+
fetch_models: true
|
|
266
|
+
models_endpoint: /models
|
|
267
|
+
model_parser:
|
|
268
|
+
cooldown_minutes: 60
|
|
269
|
+
|
|
270
|
+
- slug: tokenrouter
|
|
271
|
+
display_name: TokenRouter
|
|
272
|
+
description: TokenRouter gateway.
|
|
273
|
+
family: openai_compat
|
|
274
|
+
base_url: https://api.tokenrouter.com/v1
|
|
275
|
+
api_key_env: TOKENROUTER_API_KEY
|
|
276
|
+
known_models: []
|
|
277
|
+
fetch_models: true
|
|
278
|
+
models_endpoint: /models
|
|
279
|
+
model_parser:
|
|
280
|
+
cooldown_minutes: 60
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Load LLM provider catalog from provider.yaml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from .models import ApiType, Model
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ModelParserConfig:
|
|
16
|
+
array_path: str = "data"
|
|
17
|
+
id_field: str = "id"
|
|
18
|
+
name_field: str = "name"
|
|
19
|
+
context_field: str = "context_length"
|
|
20
|
+
output_field: str = "max_completion_tokens"
|
|
21
|
+
cooldown_minutes: int = 60
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ProviderInfo:
|
|
26
|
+
slug: str
|
|
27
|
+
display_name: str
|
|
28
|
+
description: str
|
|
29
|
+
family: str
|
|
30
|
+
base_url: str | None = None
|
|
31
|
+
api_key_env: str | None = None
|
|
32
|
+
known_models: tuple[str, ...] = ()
|
|
33
|
+
supports_tools: bool = True
|
|
34
|
+
supports_vision: bool = False
|
|
35
|
+
api_key_optional: bool = False
|
|
36
|
+
is_local: bool = False
|
|
37
|
+
max_tokens: int = 8192
|
|
38
|
+
supports_thinking: bool = False
|
|
39
|
+
fetch_models: bool = False
|
|
40
|
+
models_endpoint: str = "/models"
|
|
41
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
42
|
+
openmodelendpoint: bool = False
|
|
43
|
+
model_parser: ModelParserConfig = field(default_factory=ModelParserConfig)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_YAML_PATH = Path(__file__).parent / "provider.yaml"
|
|
47
|
+
_cache: dict[str, ProviderInfo] | None = None
|
|
48
|
+
_order_cache: list[str] | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _load() -> dict[str, ProviderInfo]:
|
|
52
|
+
global _cache
|
|
53
|
+
if _cache is not None:
|
|
54
|
+
return _cache
|
|
55
|
+
|
|
56
|
+
with open(_YAML_PATH, encoding="utf-8") as f:
|
|
57
|
+
data = yaml.safe_load(f)
|
|
58
|
+
|
|
59
|
+
providers: dict[str, ProviderInfo] = {}
|
|
60
|
+
for entry in data.get("providers", []):
|
|
61
|
+
parser_data = entry.get("model_parser") or {}
|
|
62
|
+
parser = ModelParserConfig(
|
|
63
|
+
array_path=parser_data.get("array_path", "data"),
|
|
64
|
+
id_field=parser_data.get("id_field", "id"),
|
|
65
|
+
name_field=parser_data.get("name_field", "name"),
|
|
66
|
+
context_field=parser_data.get("context_field", "context_length"),
|
|
67
|
+
output_field=parser_data.get("output_field", "max_completion_tokens"),
|
|
68
|
+
cooldown_minutes=parser_data.get("cooldown_minutes", 60),
|
|
69
|
+
)
|
|
70
|
+
p = ProviderInfo(
|
|
71
|
+
slug=entry["slug"],
|
|
72
|
+
display_name=entry["display_name"],
|
|
73
|
+
description=entry["description"],
|
|
74
|
+
family=entry["family"],
|
|
75
|
+
base_url=entry.get("base_url"),
|
|
76
|
+
api_key_env=entry.get("api_key_env"),
|
|
77
|
+
known_models=tuple(entry.get("known_models", [])),
|
|
78
|
+
supports_tools=entry.get("supports_tools", True),
|
|
79
|
+
supports_vision=entry.get("supports_vision", False),
|
|
80
|
+
api_key_optional=entry.get("api_key_optional", False),
|
|
81
|
+
is_local=entry.get("is_local", False),
|
|
82
|
+
max_tokens=entry.get("max_tokens", 8192),
|
|
83
|
+
supports_thinking=entry.get("supports_thinking", False),
|
|
84
|
+
fetch_models=entry.get("fetch_models", False),
|
|
85
|
+
models_endpoint=entry.get("models_endpoint", "/models"),
|
|
86
|
+
headers=entry.get("headers") or {},
|
|
87
|
+
openmodelendpoint=entry.get("openmodelendpoint", False),
|
|
88
|
+
model_parser=parser,
|
|
89
|
+
)
|
|
90
|
+
providers[p.slug] = p
|
|
91
|
+
|
|
92
|
+
_cache = providers
|
|
93
|
+
return _cache
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _get_order() -> list[str]:
|
|
97
|
+
global _order_cache
|
|
98
|
+
if _order_cache is not None:
|
|
99
|
+
return _order_cache
|
|
100
|
+
with open(_YAML_PATH, encoding="utf-8") as f:
|
|
101
|
+
data = yaml.safe_load(f)
|
|
102
|
+
_order_cache = [e["slug"] for e in data.get("providers", [])]
|
|
103
|
+
return _order_cache
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
DEFAULT_PROVIDER_SLUG = "openai"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get(slug: str) -> ProviderInfo | None:
|
|
110
|
+
return _load().get(slug)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def list_providers() -> list[ProviderInfo]:
|
|
114
|
+
order = _get_order()
|
|
115
|
+
providers = _load()
|
|
116
|
+
return [providers[s] for s in order if s in providers]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def detect_provider_from_env() -> ProviderInfo:
|
|
120
|
+
providers = _load()
|
|
121
|
+
order = _get_order()
|
|
122
|
+
|
|
123
|
+
explicit = os.getenv("VTX_PROVIDER", "").strip().lower()
|
|
124
|
+
if explicit and explicit in providers:
|
|
125
|
+
return providers[explicit]
|
|
126
|
+
|
|
127
|
+
for slug in order:
|
|
128
|
+
p = providers[slug]
|
|
129
|
+
if p.is_local:
|
|
130
|
+
continue
|
|
131
|
+
if p.api_key_env and os.getenv(p.api_key_env):
|
|
132
|
+
return p
|
|
133
|
+
|
|
134
|
+
for slug in order:
|
|
135
|
+
p = providers[slug]
|
|
136
|
+
if p.is_local and p.api_key_optional:
|
|
137
|
+
return p
|
|
138
|
+
|
|
139
|
+
return providers[DEFAULT_PROVIDER_SLUG]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_provider_configured(p: ProviderInfo) -> bool:
|
|
143
|
+
if p.api_key_optional:
|
|
144
|
+
return True
|
|
145
|
+
if p.api_key_env is None:
|
|
146
|
+
return True
|
|
147
|
+
return bool(os.getenv(p.api_key_env))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _provider_info_to_model(p: ProviderInfo, model_id: str) -> Model:
|
|
151
|
+
family_to_api = {
|
|
152
|
+
"openai_compat": ApiType(ApiType.OPENAI_SDK),
|
|
153
|
+
"anthropic": ApiType(ApiType.ANTHROPIC),
|
|
154
|
+
}
|
|
155
|
+
from .context_length import context_length_manager
|
|
156
|
+
|
|
157
|
+
limits = context_length_manager.get_limits(model_id)
|
|
158
|
+
is_matched = model_id in context_length_manager._limits or any(
|
|
159
|
+
model_id.lower() in k.lower() or k.lower() in model_id.lower()
|
|
160
|
+
for k in context_length_manager._limits
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if is_matched:
|
|
164
|
+
max_tokens = limits.output
|
|
165
|
+
supports_images = limits.supports_vision
|
|
166
|
+
supports_thinking = limits.supports_reasoning
|
|
167
|
+
context_window = limits.context
|
|
168
|
+
supports_tools = limits.supports_tools
|
|
169
|
+
supports_audio = limits.supports_audio
|
|
170
|
+
else:
|
|
171
|
+
max_tokens = p.max_tokens
|
|
172
|
+
supports_images = p.supports_vision
|
|
173
|
+
supports_thinking = p.supports_thinking
|
|
174
|
+
context_window = limits.context
|
|
175
|
+
supports_tools = p.supports_tools
|
|
176
|
+
supports_audio = False
|
|
177
|
+
|
|
178
|
+
return Model(
|
|
179
|
+
id=model_id,
|
|
180
|
+
provider=p.slug,
|
|
181
|
+
api=family_to_api[p.family],
|
|
182
|
+
base_url=p.base_url or "",
|
|
183
|
+
max_tokens=max_tokens,
|
|
184
|
+
supports_images=supports_images,
|
|
185
|
+
supports_thinking=supports_thinking,
|
|
186
|
+
context_window=context_window,
|
|
187
|
+
supports_tools=supports_tools,
|
|
188
|
+
supports_audio=supports_audio,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_all_catalog_models() -> list[Model]:
|
|
193
|
+
from .model_fetcher import get_fetched_models
|
|
194
|
+
|
|
195
|
+
models: list[Model] = []
|
|
196
|
+
for p in _load().values():
|
|
197
|
+
fetched = get_fetched_models(p)
|
|
198
|
+
if fetched:
|
|
199
|
+
models.extend(fetched)
|
|
200
|
+
else:
|
|
201
|
+
for model_id in p.known_models:
|
|
202
|
+
models.append(_provider_info_to_model(p, model_id))
|
|
203
|
+
return models
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def find_model(model_id: str, provider: str | None = None) -> Model | None:
|
|
207
|
+
from .model_fetcher import get_fetched_models
|
|
208
|
+
|
|
209
|
+
providers = _load()
|
|
210
|
+
if provider:
|
|
211
|
+
p = providers.get(provider)
|
|
212
|
+
if p:
|
|
213
|
+
fetched = get_fetched_models(p)
|
|
214
|
+
if fetched:
|
|
215
|
+
for m in fetched:
|
|
216
|
+
if m.id == model_id:
|
|
217
|
+
return m
|
|
218
|
+
if model_id in p.known_models:
|
|
219
|
+
return _provider_info_to_model(p, model_id)
|
|
220
|
+
|
|
221
|
+
for p in providers.values():
|
|
222
|
+
fetched = get_fetched_models(p)
|
|
223
|
+
if fetched:
|
|
224
|
+
for m in fetched:
|
|
225
|
+
if m.id == model_id:
|
|
226
|
+
return m
|
|
227
|
+
if model_id in p.known_models:
|
|
228
|
+
return _provider_info_to_model(p, model_id)
|
|
229
|
+
|
|
230
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from ..base import BaseProvider
|
|
2
|
+
from ..models import ApiType
|
|
3
|
+
|
|
4
|
+
PROVIDER_API_BY_NAME: dict[str, ApiType] = {
|
|
5
|
+
"openai": ApiType(ApiType.OPENAI_SDK),
|
|
6
|
+
"anthropic": ApiType(ApiType.ANTHROPIC),
|
|
7
|
+
"zhipu": ApiType(ApiType.OPENAI_SDK),
|
|
8
|
+
"deepseek": ApiType(ApiType.OPENAI_SDK),
|
|
9
|
+
"airouter": ApiType(ApiType.OPENAI_SDK),
|
|
10
|
+
"opencode": ApiType(ApiType.OPENAI_SDK),
|
|
11
|
+
"kilo": ApiType(ApiType.OPENAI_SDK),
|
|
12
|
+
"tokenrouter": ApiType(ApiType.OPENAI_SDK),
|
|
13
|
+
"openrouter": ApiType(ApiType.OPENAI_SDK),
|
|
14
|
+
"ollama": ApiType(ApiType.OPENAI_SDK),
|
|
15
|
+
"aerolink": ApiType(ApiType.ANTHROPIC),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def resolve_provider_api_type(provider: str | None) -> ApiType:
|
|
20
|
+
if provider is None:
|
|
21
|
+
return ApiType(ApiType.OPENAI_SDK)
|
|
22
|
+
api_type = PROVIDER_API_BY_NAME.get(provider)
|
|
23
|
+
if api_type is None:
|
|
24
|
+
return ApiType(ApiType.OPENAI_SDK)
|
|
25
|
+
return api_type
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_provider_class(api_type: ApiType) -> type[BaseProvider]:
|
|
29
|
+
match api_type.value:
|
|
30
|
+
case ApiType.OPENAI_SDK:
|
|
31
|
+
from .openai_sdk import OpenAISDKProvider
|
|
32
|
+
|
|
33
|
+
return OpenAISDKProvider
|
|
34
|
+
case ApiType.ANTHROPIC:
|
|
35
|
+
from .anthropic_sdk import AnthropicSDKProvider
|
|
36
|
+
|
|
37
|
+
return AnthropicSDKProvider
|
|
38
|
+
case ApiType.OPENAI_COMPLETIONS:
|
|
39
|
+
from .openai_sdk import OpenAISDKProvider
|
|
40
|
+
|
|
41
|
+
return OpenAISDKProvider
|
|
42
|
+
raise ValueError(f"Unsupported API type: {api_type.value}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["PROVIDER_API_BY_NAME", "get_provider_class", "resolve_provider_api_type"]
|