@f5-sales-demo/pi-ai 19.51.2
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.
- package/CHANGELOG.md +1997 -0
- package/README.md +1160 -0
- package/package.json +135 -0
- package/src/api-registry.ts +95 -0
- package/src/auth-storage.ts +2694 -0
- package/src/cli.ts +493 -0
- package/src/index.ts +42 -0
- package/src/model-cache.ts +97 -0
- package/src/model-manager.ts +349 -0
- package/src/model-thinking.ts +561 -0
- package/src/models.json +49439 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +81 -0
- package/src/provider-models/descriptors.ts +285 -0
- package/src/provider-models/google.ts +90 -0
- package/src/provider-models/index.ts +4 -0
- package/src/provider-models/openai-compat.ts +2074 -0
- package/src/provider-models/special.ts +106 -0
- package/src/providers/amazon-bedrock.ts +706 -0
- package/src/providers/anthropic.ts +1682 -0
- package/src/providers/azure-openai-responses.ts +391 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2218 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +381 -0
- package/src/providers/google-gemini-cli.ts +1133 -0
- package/src/providers/google-shared.ts +354 -0
- package/src/providers/google-vertex.ts +436 -0
- package/src/providers/google.ts +381 -0
- package/src/providers/kimi.ts +151 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +158 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2345 -0
- package/src/providers/openai-completions-compat.ts +159 -0
- package/src/providers/openai-completions.ts +1290 -0
- package/src/providers/openai-responses-shared.ts +452 -0
- package/src/providers/openai-responses.ts +519 -0
- package/src/providers/register-builtins.ts +329 -0
- package/src/providers/synthetic.ts +154 -0
- package/src/providers/transform-messages.ts +234 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +728 -0
- package/src/types.ts +546 -0
- package/src/usage/claude.ts +337 -0
- package/src/usage/gemini.ts +248 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +200 -0
- package/src/usage/kimi.ts +286 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +387 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +130 -0
- package/src/utils/abort.ts +36 -0
- package/src/utils/anthropic-auth.ts +293 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +5 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +209 -0
- package/src/utils/http-inspector.ts +165 -0
- package/src/utils/idle-iterator.ts +176 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +134 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +59 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/github-copilot.ts +358 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +275 -0
- package/src/utils/oauth/google-gemini-cli.ts +334 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +512 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +251 -0
- package/src/utils/oauth/litellm.ts +81 -0
- package/src/utils/oauth/lm-studio.ts +40 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +59 -0
- package/src/utils/oauth/nanogpt.ts +51 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +190 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +200 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +60 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +59 -0
- package/src/utils/oauth/types.ts +89 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +88 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +51 -0
- package/src/utils/overflow.ts +134 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +93 -0
- package/src/utils/schema/CONSTRAINTS.md +160 -0
- package/src/utils/schema/adapt.ts +20 -0
- package/src/utils/schema/compatibility.ts +397 -0
- package/src/utils/schema/dereference.ts +93 -0
- package/src/utils/schema/equality.ts +93 -0
- package/src/utils/schema/fields.ts +147 -0
- package/src/utils/schema/index.ts +9 -0
- package/src/utils/schema/normalize-cca.ts +479 -0
- package/src/utils/schema/sanitize-google.ts +212 -0
- package/src/utils/schema/strict-mode.ts +385 -0
- package/src/utils/schema/types.ts +5 -0
- package/src/utils/tool-choice.ts +81 -0
- package/src/utils/validation.ts +664 -0
- package/src/utils.ts +147 -0
|
@@ -0,0 +1,2074 @@
|
|
|
1
|
+
import type { ModelManagerOptions } from "../model-manager";
|
|
2
|
+
import { getBundledModels, getBundledProviders } from "../models";
|
|
3
|
+
import type { Api, Model } from "../types";
|
|
4
|
+
import { isAnthropicOAuthToken, isRecord, toNumber, toPositiveNumber } from "../utils";
|
|
5
|
+
import {
|
|
6
|
+
fetchOpenAICompatibleModels,
|
|
7
|
+
type OpenAICompatibleModelMapperContext,
|
|
8
|
+
type OpenAICompatibleModelRecord,
|
|
9
|
+
} from "../utils/discovery/openai-compatible";
|
|
10
|
+
import { getGitHubCopilotBaseUrl, OPENCODE_HEADERS, parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
11
|
+
|
|
12
|
+
const MODELS_DEV_URL = "https://models.dev/api.json";
|
|
13
|
+
const ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
14
|
+
const ANTHROPIC_OAUTH_BETA =
|
|
15
|
+
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05";
|
|
16
|
+
|
|
17
|
+
export interface ModelsDevModel {
|
|
18
|
+
id?: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
tool_call?: boolean;
|
|
21
|
+
reasoning?: boolean;
|
|
22
|
+
limit?: {
|
|
23
|
+
context?: number;
|
|
24
|
+
output?: number;
|
|
25
|
+
};
|
|
26
|
+
cost?: {
|
|
27
|
+
input?: number;
|
|
28
|
+
output?: number;
|
|
29
|
+
cache_read?: number;
|
|
30
|
+
cache_write?: number;
|
|
31
|
+
};
|
|
32
|
+
modalities?: {
|
|
33
|
+
input?: string[];
|
|
34
|
+
};
|
|
35
|
+
status?: string;
|
|
36
|
+
provider?: { npm?: string };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toModelName(value: unknown, fallback: string): string {
|
|
40
|
+
if (typeof value !== "string") {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toInputCapabilities(value: unknown): ("text" | "image")[] {
|
|
48
|
+
if (!Array.isArray(value)) {
|
|
49
|
+
return ["text"];
|
|
50
|
+
}
|
|
51
|
+
const supportsImage = value.some(item => item === "image");
|
|
52
|
+
return supportsImage ? ["text", "image"] : ["text"];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function fetchModelsDevPayload(fetchImpl: typeof fetch = fetch): Promise<unknown> {
|
|
56
|
+
const response = await fetchImpl(MODELS_DEV_URL, {
|
|
57
|
+
method: "GET",
|
|
58
|
+
headers: { Accept: "application/json" },
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`models.dev fetch failed: ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mapAnthropicModelsDev(payload: unknown, baseUrl: string): Model<"anthropic-messages">[] {
|
|
67
|
+
if (!isRecord(payload)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const anthropicPayload = payload.anthropic;
|
|
71
|
+
if (!isRecord(anthropicPayload)) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const modelsValue = anthropicPayload.models;
|
|
75
|
+
if (!isRecord(modelsValue)) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const models: Model<"anthropic-messages">[] = [];
|
|
80
|
+
for (const [modelId, rawModel] of Object.entries(modelsValue)) {
|
|
81
|
+
if (!isRecord(rawModel)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const model = rawModel as ModelsDevModel;
|
|
85
|
+
if (model.tool_call !== true) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
models.push({
|
|
89
|
+
id: modelId,
|
|
90
|
+
name: toModelName(model.name, modelId),
|
|
91
|
+
api: "anthropic-messages",
|
|
92
|
+
provider: "anthropic",
|
|
93
|
+
baseUrl,
|
|
94
|
+
reasoning: model.reasoning === true,
|
|
95
|
+
input: toInputCapabilities(model.modalities?.input),
|
|
96
|
+
cost: {
|
|
97
|
+
input: toNumber(model.cost?.input) ?? 0,
|
|
98
|
+
output: toNumber(model.cost?.output) ?? 0,
|
|
99
|
+
cacheRead: toNumber(model.cost?.cache_read) ?? 0,
|
|
100
|
+
cacheWrite: toNumber(model.cost?.cache_write) ?? 0,
|
|
101
|
+
},
|
|
102
|
+
contextWindow: toPositiveNumber(model.limit?.context, UNK_CONTEXT_WINDOW),
|
|
103
|
+
maxTokens: toPositiveNumber(model.limit?.output, UNK_MAX_TOKENS),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
models.sort((left, right) => left.id.localeCompare(right.id));
|
|
108
|
+
return models;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildAnthropicDiscoveryHeaders(apiKey: string): Record<string, string> {
|
|
112
|
+
const oauthToken = isAnthropicOAuthToken(apiKey);
|
|
113
|
+
const headers: Record<string, string> = {
|
|
114
|
+
"anthropic-version": "2023-06-01",
|
|
115
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
116
|
+
"anthropic-beta": ANTHROPIC_OAUTH_BETA,
|
|
117
|
+
};
|
|
118
|
+
if (oauthToken) {
|
|
119
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
120
|
+
} else {
|
|
121
|
+
headers["x-api-key"] = apiKey;
|
|
122
|
+
}
|
|
123
|
+
return headers;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildAnthropicReferenceMap(
|
|
127
|
+
modelsDevModels: readonly Model<"anthropic-messages">[],
|
|
128
|
+
): Map<string, Model<"anthropic-messages">> {
|
|
129
|
+
const merged = new Map<string, Model<"anthropic-messages">>();
|
|
130
|
+
for (const model of modelsDevModels) {
|
|
131
|
+
merged.set(model.id, model);
|
|
132
|
+
}
|
|
133
|
+
// Anthropic /v1/models does not carry token limits, so bundled metadata stays canonical
|
|
134
|
+
// for known models while models.dev only fills gaps for newly discovered ids.
|
|
135
|
+
const bundledModels = getBundledModels("anthropic").filter(
|
|
136
|
+
(model): model is Model<"anthropic-messages"> => model.api === "anthropic-messages",
|
|
137
|
+
);
|
|
138
|
+
for (const model of bundledModels) {
|
|
139
|
+
merged.set(model.id, model);
|
|
140
|
+
}
|
|
141
|
+
return merged;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function mapWithBundledReference<TApi extends Api>(
|
|
145
|
+
entry: OpenAICompatibleModelRecord,
|
|
146
|
+
defaults: Model<TApi>,
|
|
147
|
+
reference: Model<TApi> | undefined,
|
|
148
|
+
): Model<TApi> {
|
|
149
|
+
const name = toModelName(entry.name, reference?.name ?? defaults.name);
|
|
150
|
+
if (!reference) {
|
|
151
|
+
return {
|
|
152
|
+
...defaults,
|
|
153
|
+
name,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
...reference,
|
|
158
|
+
id: defaults.id,
|
|
159
|
+
name,
|
|
160
|
+
baseUrl: defaults.baseUrl,
|
|
161
|
+
contextWindow: toPositiveNumber(entry.context_length, reference.contextWindow),
|
|
162
|
+
maxTokens: toPositiveNumber(entry.max_completion_tokens, reference.maxTokens),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createBundledReferenceMap<TApi extends Api>(
|
|
167
|
+
provider: Parameters<typeof getBundledModels>[0],
|
|
168
|
+
): Map<string, Model<TApi>> {
|
|
169
|
+
const references = new Map<string, Model<TApi>>();
|
|
170
|
+
for (const model of getBundledModels(provider)) {
|
|
171
|
+
references.set(model.id, model as Model<TApi>);
|
|
172
|
+
}
|
|
173
|
+
return references;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns a lookup that resolves a model ID to a bundled reference, preferring
|
|
178
|
+
* the provider-specific entry over a cross-provider fallback. The global fallback
|
|
179
|
+
* picks the best entry across all providers (largest contextWindow, then maxTokens,
|
|
180
|
+
* then canonical OpenAI), but proxy providers (Copilot, nanogpt, etc.) impose their
|
|
181
|
+
* own limits that are typically lower than native provider limits, so the
|
|
182
|
+
* provider-specific entry must win.
|
|
183
|
+
*/
|
|
184
|
+
function createReferenceResolver<TApi extends Api>(
|
|
185
|
+
providerRefs: Map<string, Model<TApi>>,
|
|
186
|
+
): (modelId: string) => Model<TApi> | undefined {
|
|
187
|
+
const globalRefs = new Map<string, Model<Api>>();
|
|
188
|
+
for (const provider of getBundledProviders()) {
|
|
189
|
+
for (const model of getBundledModels(provider as Parameters<typeof getBundledModels>[0])) {
|
|
190
|
+
const candidate = model as Model<Api>;
|
|
191
|
+
const existing = globalRefs.get(candidate.id);
|
|
192
|
+
if (!existing) {
|
|
193
|
+
globalRefs.set(candidate.id, candidate);
|
|
194
|
+
} else if (candidate.contextWindow !== existing.contextWindow) {
|
|
195
|
+
if (candidate.contextWindow > existing.contextWindow) globalRefs.set(candidate.id, candidate);
|
|
196
|
+
} else if (candidate.maxTokens !== existing.maxTokens) {
|
|
197
|
+
if (candidate.maxTokens > existing.maxTokens) globalRefs.set(candidate.id, candidate);
|
|
198
|
+
} else if (existing.provider !== "openai" && candidate.provider === "openai") {
|
|
199
|
+
// When limits tie, prefer OpenAI as canonical so generic OpenAI-family
|
|
200
|
+
// providers inherit OpenAI pricing/capabilities instead of proxy metadata.
|
|
201
|
+
globalRefs.set(candidate.id, candidate);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return (modelId: string) => providerRefs.get(modelId) ?? (globalRefs.get(modelId) as Model<TApi> | undefined);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeAnthropicBaseUrl(baseUrl: string | undefined, fallback: string): string {
|
|
209
|
+
const value = baseUrl?.trim();
|
|
210
|
+
if (!value) {
|
|
211
|
+
return fallback;
|
|
212
|
+
}
|
|
213
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function toAnthropicDiscoveryBaseUrl(baseUrl: string): string {
|
|
217
|
+
return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function normalizeOllamaBaseUrl(baseUrl?: string): string {
|
|
221
|
+
const value = baseUrl?.trim();
|
|
222
|
+
if (!value) {
|
|
223
|
+
return "http://127.0.0.1:11434/v1";
|
|
224
|
+
}
|
|
225
|
+
const trimmed = value.endsWith("/") ? value.slice(0, -1) : value;
|
|
226
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function toOllamaNativeBaseUrl(baseUrl: string): string {
|
|
230
|
+
return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function fetchOllamaNativeModels(baseUrl: string): Promise<Model<"openai-responses">[] | null> {
|
|
234
|
+
const nativeBaseUrl = toOllamaNativeBaseUrl(baseUrl);
|
|
235
|
+
let response: Response;
|
|
236
|
+
try {
|
|
237
|
+
response = await fetch(`${nativeBaseUrl}/api/tags`, {
|
|
238
|
+
method: "GET",
|
|
239
|
+
headers: { Accept: "application/json" },
|
|
240
|
+
});
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
|
|
248
|
+
const entries = payload.models ?? [];
|
|
249
|
+
const models: Model<"openai-responses">[] = [];
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const id = entry.model ?? entry.name;
|
|
252
|
+
if (!id) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
models.push({
|
|
256
|
+
id,
|
|
257
|
+
name: entry.name ?? id,
|
|
258
|
+
api: "openai-responses",
|
|
259
|
+
provider: "ollama",
|
|
260
|
+
baseUrl,
|
|
261
|
+
reasoning: false,
|
|
262
|
+
input: ["text"],
|
|
263
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
264
|
+
contextWindow: 128000,
|
|
265
|
+
maxTokens: 8192,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return models.sort((left, right) => left.id.localeCompare(right.id));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const OPENAI_NON_RESPONSES_PREFIXES = [
|
|
272
|
+
"text-embedding",
|
|
273
|
+
"whisper-",
|
|
274
|
+
"tts-",
|
|
275
|
+
"omni-moderation",
|
|
276
|
+
"omni-transcribe",
|
|
277
|
+
"omni-speech",
|
|
278
|
+
"gpt-image-",
|
|
279
|
+
"gpt-realtime",
|
|
280
|
+
] as const;
|
|
281
|
+
|
|
282
|
+
function isLikelyOpenAIResponsesModelId(id: string, references: Map<string, Model<"openai-responses">>): boolean {
|
|
283
|
+
const trimmed = id.trim();
|
|
284
|
+
if (!trimmed) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
if (references.has(trimmed)) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
const normalized = trimmed.toLowerCase();
|
|
291
|
+
if (OPENAI_NON_RESPONSES_PREFIXES.some(prefix => normalized.startsWith(prefix))) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (normalized.includes("embedding")) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
return (
|
|
298
|
+
normalized.startsWith("gpt-") ||
|
|
299
|
+
normalized.startsWith("o1") ||
|
|
300
|
+
normalized.startsWith("o3") ||
|
|
301
|
+
normalized.startsWith("o4") ||
|
|
302
|
+
normalized.startsWith("chatgpt")
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const NANO_GPT_NON_TEXT_MODEL_TOKENS = [
|
|
307
|
+
"embedding",
|
|
308
|
+
"image",
|
|
309
|
+
"vision",
|
|
310
|
+
"audio",
|
|
311
|
+
"speech",
|
|
312
|
+
"transcribe",
|
|
313
|
+
"moderation",
|
|
314
|
+
"realtime",
|
|
315
|
+
"whisper",
|
|
316
|
+
"tts",
|
|
317
|
+
] as const;
|
|
318
|
+
|
|
319
|
+
/** Regex matching NanoGPT `:thinking` suffixed model IDs (with or without a level). */
|
|
320
|
+
const NANO_GPT_THINKING_SUFFIX_RE = /:thinking(:[^:]+)?$/;
|
|
321
|
+
|
|
322
|
+
function isLikelyNanoGptTextModelId(id: string): boolean {
|
|
323
|
+
const normalized = id.trim().toLowerCase();
|
|
324
|
+
if (!normalized) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
if (NANO_GPT_THINKING_SUFFIX_RE.test(normalized)) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return !NANO_GPT_NON_TEXT_MODEL_TOKENS.some(token => normalized.includes(token));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// 1. OpenAI
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
export interface OpenAIModelManagerConfig {
|
|
338
|
+
apiKey?: string;
|
|
339
|
+
baseUrl?: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): ModelManagerOptions<"openai-responses"> {
|
|
343
|
+
const apiKey = config?.apiKey;
|
|
344
|
+
const baseUrl = config?.baseUrl ?? "https://api.openai.com/v1";
|
|
345
|
+
const references = createBundledReferenceMap<"openai-responses">("openai");
|
|
346
|
+
return {
|
|
347
|
+
providerId: "openai",
|
|
348
|
+
...(apiKey && {
|
|
349
|
+
fetchDynamicModels: () =>
|
|
350
|
+
fetchOpenAICompatibleModels({
|
|
351
|
+
api: "openai-responses",
|
|
352
|
+
provider: "openai",
|
|
353
|
+
baseUrl,
|
|
354
|
+
apiKey,
|
|
355
|
+
filterModel: (_entry, model) => isLikelyOpenAIResponsesModelId(model.id, references),
|
|
356
|
+
mapModel: (entry, defaults) => {
|
|
357
|
+
const reference = references.get(defaults.id);
|
|
358
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
359
|
+
},
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// 2. Groq
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
export interface GroqModelManagerConfig {
|
|
370
|
+
apiKey?: string;
|
|
371
|
+
baseUrl?: string;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function groqModelManagerOptions(config?: GroqModelManagerConfig): ModelManagerOptions<"openai-completions"> {
|
|
375
|
+
const apiKey = config?.apiKey;
|
|
376
|
+
const baseUrl = config?.baseUrl ?? "https://api.groq.com/openai/v1";
|
|
377
|
+
const references = createBundledReferenceMap<"openai-completions">("groq");
|
|
378
|
+
return {
|
|
379
|
+
providerId: "groq",
|
|
380
|
+
...(apiKey && {
|
|
381
|
+
fetchDynamicModels: () =>
|
|
382
|
+
fetchOpenAICompatibleModels({
|
|
383
|
+
api: "openai-completions",
|
|
384
|
+
provider: "groq",
|
|
385
|
+
baseUrl,
|
|
386
|
+
apiKey,
|
|
387
|
+
mapModel: (entry, defaults) => {
|
|
388
|
+
const reference = references.get(defaults.id);
|
|
389
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
390
|
+
},
|
|
391
|
+
}),
|
|
392
|
+
}),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// 3. Cerebras
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
export interface CerebrasModelManagerConfig {
|
|
401
|
+
apiKey?: string;
|
|
402
|
+
baseUrl?: string;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function cerebrasModelManagerOptions(
|
|
406
|
+
config?: CerebrasModelManagerConfig,
|
|
407
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
408
|
+
const apiKey = config?.apiKey;
|
|
409
|
+
const baseUrl = config?.baseUrl ?? "https://api.cerebras.ai/v1";
|
|
410
|
+
const references = createBundledReferenceMap<"openai-completions">("cerebras");
|
|
411
|
+
return {
|
|
412
|
+
providerId: "cerebras",
|
|
413
|
+
...(apiKey && {
|
|
414
|
+
fetchDynamicModels: () =>
|
|
415
|
+
fetchOpenAICompatibleModels({
|
|
416
|
+
api: "openai-completions",
|
|
417
|
+
provider: "cerebras",
|
|
418
|
+
baseUrl,
|
|
419
|
+
apiKey,
|
|
420
|
+
mapModel: (entry, defaults) => {
|
|
421
|
+
const reference = references.get(defaults.id);
|
|
422
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
423
|
+
},
|
|
424
|
+
}),
|
|
425
|
+
}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// 4. Hugging Face
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
export interface HuggingfaceModelManagerConfig {
|
|
434
|
+
apiKey?: string;
|
|
435
|
+
baseUrl?: string;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function huggingfaceModelManagerOptions(
|
|
439
|
+
config?: HuggingfaceModelManagerConfig,
|
|
440
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
441
|
+
const apiKey = config?.apiKey;
|
|
442
|
+
const baseUrl = config?.baseUrl ?? "https://router.huggingface.co/v1";
|
|
443
|
+
const references = createBundledReferenceMap<"openai-completions">("huggingface");
|
|
444
|
+
return {
|
|
445
|
+
providerId: "huggingface",
|
|
446
|
+
...(apiKey && {
|
|
447
|
+
fetchDynamicModels: () =>
|
|
448
|
+
fetchOpenAICompatibleModels({
|
|
449
|
+
api: "openai-completions",
|
|
450
|
+
provider: "huggingface",
|
|
451
|
+
baseUrl,
|
|
452
|
+
apiKey,
|
|
453
|
+
mapModel: (entry, defaults) => {
|
|
454
|
+
const reference = references.get(defaults.id);
|
|
455
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
456
|
+
},
|
|
457
|
+
}),
|
|
458
|
+
}),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
// 5. NVIDIA
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
|
|
466
|
+
export interface NvidiaModelManagerConfig {
|
|
467
|
+
apiKey?: string;
|
|
468
|
+
baseUrl?: string;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function nvidiaModelManagerOptions(
|
|
472
|
+
config?: NvidiaModelManagerConfig,
|
|
473
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
474
|
+
const apiKey = config?.apiKey;
|
|
475
|
+
const baseUrl = config?.baseUrl ?? "https://integrate.api.nvidia.com/v1";
|
|
476
|
+
const references = createBundledReferenceMap<"openai-completions">("nvidia");
|
|
477
|
+
return {
|
|
478
|
+
providerId: "nvidia",
|
|
479
|
+
...(apiKey && {
|
|
480
|
+
fetchDynamicModels: () =>
|
|
481
|
+
fetchOpenAICompatibleModels({
|
|
482
|
+
api: "openai-completions",
|
|
483
|
+
provider: "nvidia",
|
|
484
|
+
baseUrl,
|
|
485
|
+
apiKey,
|
|
486
|
+
mapModel: (entry, defaults) => {
|
|
487
|
+
const reference = references.get(defaults.id);
|
|
488
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
489
|
+
},
|
|
490
|
+
}),
|
|
491
|
+
}),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
// 6. xAI
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
export interface XaiModelManagerConfig {
|
|
500
|
+
apiKey?: string;
|
|
501
|
+
baseUrl?: string;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function xaiModelManagerOptions(config?: XaiModelManagerConfig): ModelManagerOptions<"openai-completions"> {
|
|
505
|
+
const apiKey = config?.apiKey;
|
|
506
|
+
const baseUrl = config?.baseUrl ?? "https://api.x.ai/v1";
|
|
507
|
+
const references = createBundledReferenceMap<"openai-completions">("xai");
|
|
508
|
+
return {
|
|
509
|
+
providerId: "xai",
|
|
510
|
+
...(apiKey && {
|
|
511
|
+
fetchDynamicModels: () =>
|
|
512
|
+
fetchOpenAICompatibleModels({
|
|
513
|
+
api: "openai-completions",
|
|
514
|
+
provider: "xai",
|
|
515
|
+
baseUrl,
|
|
516
|
+
apiKey,
|
|
517
|
+
mapModel: (entry, defaults) => {
|
|
518
|
+
const reference = references.get(defaults.id);
|
|
519
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
520
|
+
},
|
|
521
|
+
}),
|
|
522
|
+
}),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// 7. Mistral
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
|
|
530
|
+
export interface MistralModelManagerConfig {
|
|
531
|
+
apiKey?: string;
|
|
532
|
+
baseUrl?: string;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function mistralModelManagerOptions(
|
|
536
|
+
config?: MistralModelManagerConfig,
|
|
537
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
538
|
+
const apiKey = config?.apiKey;
|
|
539
|
+
const baseUrl = config?.baseUrl ?? "https://api.mistral.ai/v1";
|
|
540
|
+
const references = createBundledReferenceMap<"openai-completions">("mistral");
|
|
541
|
+
return {
|
|
542
|
+
providerId: "mistral",
|
|
543
|
+
...(apiKey && {
|
|
544
|
+
fetchDynamicModels: () =>
|
|
545
|
+
fetchOpenAICompatibleModels({
|
|
546
|
+
api: "openai-completions",
|
|
547
|
+
provider: "mistral",
|
|
548
|
+
baseUrl,
|
|
549
|
+
apiKey,
|
|
550
|
+
mapModel: (entry, defaults) => {
|
|
551
|
+
const reference = references.get(defaults.id);
|
|
552
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
553
|
+
},
|
|
554
|
+
}),
|
|
555
|
+
}),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// 8. OpenCode
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
|
|
563
|
+
export interface OpenCodeModelManagerConfig {
|
|
564
|
+
apiKey?: string;
|
|
565
|
+
baseUrl?: string;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function openCodeModelManagerOptions(
|
|
569
|
+
providerId: "opencode-go" | "opencode-zen",
|
|
570
|
+
defaultBaseUrl: string,
|
|
571
|
+
config?: OpenCodeModelManagerConfig,
|
|
572
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
573
|
+
const apiKey = config?.apiKey;
|
|
574
|
+
const baseUrl = config?.baseUrl ?? defaultBaseUrl;
|
|
575
|
+
return {
|
|
576
|
+
providerId,
|
|
577
|
+
...(apiKey && {
|
|
578
|
+
fetchDynamicModels: () =>
|
|
579
|
+
fetchOpenAICompatibleModels({
|
|
580
|
+
api: "openai-completions",
|
|
581
|
+
provider: providerId,
|
|
582
|
+
baseUrl,
|
|
583
|
+
apiKey,
|
|
584
|
+
}),
|
|
585
|
+
}),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function opencodeZenModelManagerOptions(
|
|
590
|
+
config?: OpenCodeModelManagerConfig,
|
|
591
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
592
|
+
return openCodeModelManagerOptions("opencode-zen", "https://opencode.ai/zen/v1", config);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function opencodeGoModelManagerOptions(
|
|
596
|
+
config?: OpenCodeModelManagerConfig,
|
|
597
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
598
|
+
return openCodeModelManagerOptions("opencode-go", "https://opencode.ai/zen/go/v1", config);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ---------------------------------------------------------------------------
|
|
602
|
+
// 9. Ollama
|
|
603
|
+
// ---------------------------------------------------------------------------
|
|
604
|
+
|
|
605
|
+
export interface OllamaModelManagerConfig {
|
|
606
|
+
apiKey?: string;
|
|
607
|
+
baseUrl?: string;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export function ollamaModelManagerOptions(config?: OllamaModelManagerConfig): ModelManagerOptions<"openai-responses"> {
|
|
611
|
+
const apiKey = config?.apiKey;
|
|
612
|
+
const baseUrl = normalizeOllamaBaseUrl(config?.baseUrl);
|
|
613
|
+
const references = createBundledReferenceMap<"openai-responses">("ollama" as Parameters<typeof getBundledModels>[0]);
|
|
614
|
+
return {
|
|
615
|
+
providerId: "ollama",
|
|
616
|
+
fetchDynamicModels: async () => {
|
|
617
|
+
const openAiCompatible = await fetchOpenAICompatibleModels({
|
|
618
|
+
api: "openai-responses",
|
|
619
|
+
provider: "ollama",
|
|
620
|
+
baseUrl,
|
|
621
|
+
apiKey,
|
|
622
|
+
mapModel: (entry, defaults) => {
|
|
623
|
+
const reference = references.get(defaults.id);
|
|
624
|
+
if (!reference) {
|
|
625
|
+
return {
|
|
626
|
+
...defaults,
|
|
627
|
+
name: toModelName(entry.name, defaults.name),
|
|
628
|
+
contextWindow: 128000,
|
|
629
|
+
maxTokens: 8192,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
if (openAiCompatible && openAiCompatible.length > 0) {
|
|
636
|
+
return openAiCompatible;
|
|
637
|
+
}
|
|
638
|
+
const nativeFallback = await fetchOllamaNativeModels(baseUrl);
|
|
639
|
+
if (nativeFallback && nativeFallback.length > 0) {
|
|
640
|
+
return nativeFallback;
|
|
641
|
+
}
|
|
642
|
+
return openAiCompatible;
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ---------------------------------------------------------------------------
|
|
648
|
+
// 10. OpenRouter
|
|
649
|
+
// ---------------------------------------------------------------------------
|
|
650
|
+
|
|
651
|
+
export interface OpenRouterModelManagerConfig {
|
|
652
|
+
apiKey?: string;
|
|
653
|
+
baseUrl?: string;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function openrouterModelManagerOptions(
|
|
657
|
+
config?: OpenRouterModelManagerConfig,
|
|
658
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
659
|
+
const apiKey = config?.apiKey;
|
|
660
|
+
const baseUrl = config?.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
661
|
+
return {
|
|
662
|
+
providerId: "openrouter",
|
|
663
|
+
fetchDynamicModels: () =>
|
|
664
|
+
fetchOpenAICompatibleModels({
|
|
665
|
+
api: "openai-completions",
|
|
666
|
+
provider: "openrouter",
|
|
667
|
+
baseUrl,
|
|
668
|
+
apiKey,
|
|
669
|
+
headers: {
|
|
670
|
+
"X-Title": "xcsh",
|
|
671
|
+
},
|
|
672
|
+
filterModel: (entry: OpenAICompatibleModelRecord) => {
|
|
673
|
+
const params = entry.supported_parameters;
|
|
674
|
+
return Array.isArray(params) && params.includes("tools");
|
|
675
|
+
},
|
|
676
|
+
mapModel: (
|
|
677
|
+
entry: OpenAICompatibleModelRecord,
|
|
678
|
+
defaults: Model<"openai-completions">,
|
|
679
|
+
_context: OpenAICompatibleModelMapperContext<"openai-completions">,
|
|
680
|
+
): Model<"openai-completions"> => {
|
|
681
|
+
const pricing = entry.pricing as Record<string, unknown> | undefined;
|
|
682
|
+
const params = Array.isArray(entry.supported_parameters) ? (entry.supported_parameters as string[]) : [];
|
|
683
|
+
const modality = String((entry.architecture as Record<string, unknown> | undefined)?.modality ?? "");
|
|
684
|
+
const topProvider = entry.top_provider as Record<string, unknown> | undefined;
|
|
685
|
+
|
|
686
|
+
const supportsToolChoice = params.includes("tool_choice");
|
|
687
|
+
|
|
688
|
+
return {
|
|
689
|
+
...defaults,
|
|
690
|
+
reasoning: params.includes("reasoning"),
|
|
691
|
+
input: modality.includes("image") ? ["text", "image"] : ["text"],
|
|
692
|
+
cost: {
|
|
693
|
+
input: parseFloat(String(pricing?.prompt ?? "0")) * 1_000_000,
|
|
694
|
+
output: parseFloat(String(pricing?.completion ?? "0")) * 1_000_000,
|
|
695
|
+
cacheRead: parseFloat(String(pricing?.input_cache_read ?? "0")) * 1_000_000,
|
|
696
|
+
cacheWrite: parseFloat(String(pricing?.input_cache_write ?? "0")) * 1_000_000,
|
|
697
|
+
},
|
|
698
|
+
contextWindow:
|
|
699
|
+
typeof entry.context_length === "number" ? entry.context_length : defaults.contextWindow,
|
|
700
|
+
maxTokens:
|
|
701
|
+
typeof topProvider?.max_completion_tokens === "number"
|
|
702
|
+
? topProvider.max_completion_tokens
|
|
703
|
+
: defaults.maxTokens,
|
|
704
|
+
...(!supportsToolChoice && {
|
|
705
|
+
compat: { supportsToolChoice: false },
|
|
706
|
+
}),
|
|
707
|
+
};
|
|
708
|
+
},
|
|
709
|
+
}),
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const ZENMUX_OPENAI_BASE_URL = "https://zenmux.ai/api/v1";
|
|
714
|
+
const ZENMUX_ANTHROPIC_BASE_URL = "https://zenmux.ai/api/anthropic";
|
|
715
|
+
|
|
716
|
+
function normalizeZenMuxOpenAiBaseUrl(baseUrl?: string): string {
|
|
717
|
+
const value = baseUrl?.trim();
|
|
718
|
+
if (!value) {
|
|
719
|
+
return ZENMUX_OPENAI_BASE_URL;
|
|
720
|
+
}
|
|
721
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function toZenMuxAnthropicBaseUrl(openAiBaseUrl: string): string {
|
|
725
|
+
try {
|
|
726
|
+
const parsed = new URL(openAiBaseUrl);
|
|
727
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
728
|
+
parsed.pathname = trimmedPath.endsWith("/api/v1")
|
|
729
|
+
? `${trimmedPath.slice(0, -"/api/v1".length)}/api/anthropic`
|
|
730
|
+
: "/api/anthropic";
|
|
731
|
+
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
732
|
+
} catch {
|
|
733
|
+
return ZENMUX_ANTHROPIC_BASE_URL;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function isZenMuxAnthropicModel(entry: OpenAICompatibleModelRecord, modelId: string): boolean {
|
|
738
|
+
if (typeof entry.owned_by === "string" && entry.owned_by.toLowerCase() === "anthropic") {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
return modelId.toLowerCase().startsWith("anthropic/");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function getZenMuxPricingValue(pricings: Record<string, unknown> | undefined, key: string): number {
|
|
745
|
+
const bucket = pricings?.[key];
|
|
746
|
+
if (!Array.isArray(bucket)) {
|
|
747
|
+
return 0;
|
|
748
|
+
}
|
|
749
|
+
for (const item of bucket) {
|
|
750
|
+
if (!isRecord(item)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const value = toNumber(item.value);
|
|
754
|
+
if (value !== undefined) {
|
|
755
|
+
return value;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function getZenMuxCacheWritePrice(pricings: Record<string, unknown> | undefined): number {
|
|
762
|
+
const oneHour = getZenMuxPricingValue(pricings, "input_cache_write_1_h");
|
|
763
|
+
if (oneHour > 0) {
|
|
764
|
+
return oneHour;
|
|
765
|
+
}
|
|
766
|
+
const fiveMinute = getZenMuxPricingValue(pricings, "input_cache_write_5_min");
|
|
767
|
+
if (fiveMinute > 0) {
|
|
768
|
+
return fiveMinute;
|
|
769
|
+
}
|
|
770
|
+
return getZenMuxPricingValue(pricings, "input_cache_write");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ---------------------------------------------------------------------------
|
|
774
|
+
// 10.5 ZenMux
|
|
775
|
+
// ---------------------------------------------------------------------------
|
|
776
|
+
|
|
777
|
+
export interface ZenMuxModelManagerConfig {
|
|
778
|
+
apiKey?: string;
|
|
779
|
+
baseUrl?: string;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export function zenmuxModelManagerOptions(config?: ZenMuxModelManagerConfig): ModelManagerOptions<Api> {
|
|
783
|
+
const apiKey = config?.apiKey;
|
|
784
|
+
const openAiBaseUrl = normalizeZenMuxOpenAiBaseUrl(config?.baseUrl);
|
|
785
|
+
const anthropicBaseUrl = toZenMuxAnthropicBaseUrl(openAiBaseUrl);
|
|
786
|
+
return {
|
|
787
|
+
providerId: "zenmux",
|
|
788
|
+
...(apiKey && {
|
|
789
|
+
fetchDynamicModels: () =>
|
|
790
|
+
fetchOpenAICompatibleModels<Api>({
|
|
791
|
+
api: "openai-completions",
|
|
792
|
+
provider: "zenmux",
|
|
793
|
+
baseUrl: openAiBaseUrl,
|
|
794
|
+
apiKey,
|
|
795
|
+
mapModel: (entry, defaults) => {
|
|
796
|
+
const pricings = isRecord(entry.pricings) ? entry.pricings : undefined;
|
|
797
|
+
const capabilities = isRecord(entry.capabilities) ? entry.capabilities : undefined;
|
|
798
|
+
const isAnthropicModel = isZenMuxAnthropicModel(entry, defaults.id);
|
|
799
|
+
return {
|
|
800
|
+
...defaults,
|
|
801
|
+
name: toModelName(entry.display_name, defaults.name),
|
|
802
|
+
api: isAnthropicModel ? "anthropic-messages" : "openai-completions",
|
|
803
|
+
baseUrl: isAnthropicModel ? anthropicBaseUrl : openAiBaseUrl,
|
|
804
|
+
reasoning: capabilities?.reasoning === true || defaults.reasoning,
|
|
805
|
+
input: toInputCapabilities(entry.input_modalities),
|
|
806
|
+
cost: {
|
|
807
|
+
input: getZenMuxPricingValue(pricings, "prompt"),
|
|
808
|
+
output: getZenMuxPricingValue(pricings, "completion"),
|
|
809
|
+
cacheRead: getZenMuxPricingValue(pricings, "input_cache_read"),
|
|
810
|
+
cacheWrite: getZenMuxCacheWritePrice(pricings),
|
|
811
|
+
},
|
|
812
|
+
contextWindow: toPositiveNumber(entry.context_length, defaults.contextWindow),
|
|
813
|
+
maxTokens: toPositiveNumber(entry.max_completion_tokens, defaults.maxTokens),
|
|
814
|
+
};
|
|
815
|
+
},
|
|
816
|
+
}),
|
|
817
|
+
}),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// ---------------------------------------------------------------------------
|
|
822
|
+
// 10.6 Kilo Gateway
|
|
823
|
+
// ---------------------------------------------------------------------------
|
|
824
|
+
|
|
825
|
+
export interface KiloModelManagerConfig {
|
|
826
|
+
apiKey?: string;
|
|
827
|
+
baseUrl?: string;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export function kiloModelManagerOptions(config?: KiloModelManagerConfig): ModelManagerOptions<"openai-completions"> {
|
|
831
|
+
const apiKey = config?.apiKey;
|
|
832
|
+
const baseUrl = config?.baseUrl ?? "https://api.kilo.ai/api/gateway";
|
|
833
|
+
return {
|
|
834
|
+
providerId: "kilo",
|
|
835
|
+
fetchDynamicModels: () =>
|
|
836
|
+
fetchOpenAICompatibleModels({
|
|
837
|
+
api: "openai-completions",
|
|
838
|
+
provider: "kilo",
|
|
839
|
+
baseUrl,
|
|
840
|
+
apiKey,
|
|
841
|
+
}),
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ---------------------------------------------------------------------------
|
|
846
|
+
// Alibaba Coding Plan
|
|
847
|
+
// ---------------------------------------------------------------------------
|
|
848
|
+
|
|
849
|
+
export interface AlibabaCodingPlanModelManagerConfig {
|
|
850
|
+
apiKey?: string;
|
|
851
|
+
baseUrl?: string;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export function alibabaCodingPlanModelManagerOptions(
|
|
855
|
+
config?: AlibabaCodingPlanModelManagerConfig,
|
|
856
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
857
|
+
const apiKey = config?.apiKey;
|
|
858
|
+
const baseUrl = config?.baseUrl ?? "https://coding-intl.dashscope.aliyuncs.com/v1";
|
|
859
|
+
const references = createBundledReferenceMap<"openai-completions">("alibaba-coding-plan");
|
|
860
|
+
return {
|
|
861
|
+
providerId: "alibaba-coding-plan",
|
|
862
|
+
fetchDynamicModels: () =>
|
|
863
|
+
fetchOpenAICompatibleModels({
|
|
864
|
+
api: "openai-completions",
|
|
865
|
+
provider: "alibaba-coding-plan",
|
|
866
|
+
baseUrl,
|
|
867
|
+
apiKey,
|
|
868
|
+
mapModel: (entry, defaults) => {
|
|
869
|
+
const reference = references.get(defaults.id);
|
|
870
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
871
|
+
},
|
|
872
|
+
}),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// ---------------------------------------------------------------------------
|
|
877
|
+
// 11. Vercel AI Gateway
|
|
878
|
+
// ---------------------------------------------------------------------------
|
|
879
|
+
|
|
880
|
+
export interface VercelAiGatewayModelManagerConfig {
|
|
881
|
+
apiKey?: string;
|
|
882
|
+
baseUrl?: string;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export function vercelAiGatewayModelManagerOptions(
|
|
886
|
+
config?: VercelAiGatewayModelManagerConfig,
|
|
887
|
+
): ModelManagerOptions<"anthropic-messages"> {
|
|
888
|
+
const apiKey = config?.apiKey;
|
|
889
|
+
const baseUrl = config?.baseUrl ?? "https://ai-gateway.vercel.sh";
|
|
890
|
+
return {
|
|
891
|
+
providerId: "vercel-ai-gateway",
|
|
892
|
+
fetchDynamicModels: () =>
|
|
893
|
+
fetchOpenAICompatibleModels({
|
|
894
|
+
api: "anthropic-messages",
|
|
895
|
+
provider: "vercel-ai-gateway",
|
|
896
|
+
baseUrl,
|
|
897
|
+
apiKey,
|
|
898
|
+
filterModel: (entry: OpenAICompatibleModelRecord) => {
|
|
899
|
+
const tags = entry.tags;
|
|
900
|
+
return Array.isArray(tags) && tags.includes("tool-use");
|
|
901
|
+
},
|
|
902
|
+
mapModel: (
|
|
903
|
+
entry: OpenAICompatibleModelRecord,
|
|
904
|
+
defaults: Model<"anthropic-messages">,
|
|
905
|
+
_context: OpenAICompatibleModelMapperContext<"anthropic-messages">,
|
|
906
|
+
): Model<"anthropic-messages"> => {
|
|
907
|
+
const pricing = entry.pricing as Record<string, unknown> | undefined;
|
|
908
|
+
const tags = Array.isArray(entry.tags) ? (entry.tags as string[]) : [];
|
|
909
|
+
|
|
910
|
+
return {
|
|
911
|
+
...defaults,
|
|
912
|
+
reasoning: tags.includes("reasoning"),
|
|
913
|
+
input: tags.includes("vision") ? ["text", "image"] : ["text"],
|
|
914
|
+
cost: {
|
|
915
|
+
input: (toNumber(pricing?.input) ?? 0) * 1_000_000,
|
|
916
|
+
output: (toNumber(pricing?.output) ?? 0) * 1_000_000,
|
|
917
|
+
cacheRead: (toNumber(pricing?.input_cache_read) ?? 0) * 1_000_000,
|
|
918
|
+
cacheWrite: (toNumber(pricing?.input_cache_write) ?? 0) * 1_000_000,
|
|
919
|
+
},
|
|
920
|
+
contextWindow:
|
|
921
|
+
typeof entry.context_window === "number" ? entry.context_window : defaults.contextWindow,
|
|
922
|
+
maxTokens: typeof entry.max_tokens === "number" ? entry.max_tokens : defaults.maxTokens,
|
|
923
|
+
};
|
|
924
|
+
},
|
|
925
|
+
}),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// ---------------------------------------------------------------------------
|
|
930
|
+
// 12. Kimi Code
|
|
931
|
+
// ---------------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
export interface KimiCodeModelManagerConfig {
|
|
934
|
+
apiKey?: string;
|
|
935
|
+
baseUrl?: string;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
export function kimiCodeModelManagerOptions(
|
|
939
|
+
config?: KimiCodeModelManagerConfig,
|
|
940
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
941
|
+
const apiKey = config?.apiKey;
|
|
942
|
+
const baseUrl = config?.baseUrl ?? "https://api.kimi.com/coding/v1";
|
|
943
|
+
return {
|
|
944
|
+
providerId: "kimi-code",
|
|
945
|
+
...(apiKey && {
|
|
946
|
+
fetchDynamicModels: () =>
|
|
947
|
+
fetchOpenAICompatibleModels({
|
|
948
|
+
api: "openai-completions",
|
|
949
|
+
provider: "kimi-code",
|
|
950
|
+
baseUrl,
|
|
951
|
+
apiKey,
|
|
952
|
+
headers: {
|
|
953
|
+
"User-Agent": "KimiCLI/1.0",
|
|
954
|
+
"X-Msh-Platform": "kimi_cli",
|
|
955
|
+
},
|
|
956
|
+
mapModel: (
|
|
957
|
+
entry: OpenAICompatibleModelRecord,
|
|
958
|
+
defaults: Model<"openai-completions">,
|
|
959
|
+
_context: OpenAICompatibleModelMapperContext<"openai-completions">,
|
|
960
|
+
): Model<"openai-completions"> => {
|
|
961
|
+
const id = defaults.id;
|
|
962
|
+
return {
|
|
963
|
+
...defaults,
|
|
964
|
+
name: typeof entry.display_name === "string" ? entry.display_name : defaults.name,
|
|
965
|
+
reasoning: entry.supports_reasoning === true || id.includes("thinking"),
|
|
966
|
+
input: entry.supports_image_in === true || id.includes("k2.5") ? ["text", "image"] : ["text"],
|
|
967
|
+
contextWindow: typeof entry.context_length === "number" ? entry.context_length : 262144,
|
|
968
|
+
maxTokens: 32000,
|
|
969
|
+
compat: {
|
|
970
|
+
thinkingFormat: "zai",
|
|
971
|
+
reasoningContentField: "reasoning_content",
|
|
972
|
+
supportsDeveloperRole: false,
|
|
973
|
+
},
|
|
974
|
+
};
|
|
975
|
+
},
|
|
976
|
+
}),
|
|
977
|
+
}),
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// ---------------------------------------------------------------------------
|
|
982
|
+
// 12.5. LM Studio
|
|
983
|
+
// ---------------------------------------------------------------------------
|
|
984
|
+
|
|
985
|
+
export interface LmStudioModelManagerConfig {
|
|
986
|
+
apiKey?: string;
|
|
987
|
+
baseUrl?: string;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
export function lmStudioModelManagerOptions(
|
|
991
|
+
config?: LmStudioModelManagerConfig,
|
|
992
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
993
|
+
const apiKey = config?.apiKey;
|
|
994
|
+
const baseUrl = config?.baseUrl ?? Bun.env.LM_STUDIO_BASE_URL ?? "http://127.0.0.1:1234/v1";
|
|
995
|
+
const references = createBundledReferenceMap<"openai-completions">("lm-studio" as any);
|
|
996
|
+
return {
|
|
997
|
+
providerId: "lm-studio",
|
|
998
|
+
fetchDynamicModels: () =>
|
|
999
|
+
fetchOpenAICompatibleModels({
|
|
1000
|
+
api: "openai-completions",
|
|
1001
|
+
provider: "lm-studio",
|
|
1002
|
+
baseUrl,
|
|
1003
|
+
apiKey,
|
|
1004
|
+
mapModel: (entry, defaults) => {
|
|
1005
|
+
const reference = references.get(defaults.id);
|
|
1006
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1007
|
+
},
|
|
1008
|
+
}),
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// ---------------------------------------------------------------------------
|
|
1013
|
+
// 13. Synthetic
|
|
1014
|
+
// ---------------------------------------------------------------------------
|
|
1015
|
+
|
|
1016
|
+
export interface SyntheticModelManagerConfig {
|
|
1017
|
+
apiKey?: string;
|
|
1018
|
+
baseUrl?: string;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
export function syntheticModelManagerOptions(
|
|
1022
|
+
config?: SyntheticModelManagerConfig,
|
|
1023
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1024
|
+
const apiKey = config?.apiKey;
|
|
1025
|
+
const baseUrl = config?.baseUrl ?? "https://api.synthetic.new/openai/v1";
|
|
1026
|
+
const references = new Map(
|
|
1027
|
+
(getBundledModels("synthetic") as Model<"openai-completions">[]).map(model => [model.id, model]),
|
|
1028
|
+
);
|
|
1029
|
+
return {
|
|
1030
|
+
providerId: "synthetic",
|
|
1031
|
+
...(apiKey && {
|
|
1032
|
+
fetchDynamicModels: () =>
|
|
1033
|
+
fetchOpenAICompatibleModels({
|
|
1034
|
+
api: "openai-completions",
|
|
1035
|
+
provider: "synthetic",
|
|
1036
|
+
baseUrl,
|
|
1037
|
+
apiKey,
|
|
1038
|
+
mapModel: (
|
|
1039
|
+
entry: OpenAICompatibleModelRecord,
|
|
1040
|
+
defaults: Model<"openai-completions">,
|
|
1041
|
+
_context: OpenAICompatibleModelMapperContext<"openai-completions">,
|
|
1042
|
+
): Model<"openai-completions"> => {
|
|
1043
|
+
const reference = references.get(defaults.id);
|
|
1044
|
+
const referenceSupportsImage = reference?.input.includes("image") ?? false;
|
|
1045
|
+
return {
|
|
1046
|
+
...(reference ? { ...reference, id: defaults.id, baseUrl } : defaults),
|
|
1047
|
+
name: toModelName(entry.name, reference?.name ?? defaults.name),
|
|
1048
|
+
reasoning: entry.supports_reasoning === true || (reference?.reasoning ?? false),
|
|
1049
|
+
input: entry.supports_vision === true || referenceSupportsImage ? ["text", "image"] : ["text"],
|
|
1050
|
+
contextWindow: toPositiveNumber(
|
|
1051
|
+
entry.context_length,
|
|
1052
|
+
reference?.contextWindow ?? defaults.contextWindow,
|
|
1053
|
+
),
|
|
1054
|
+
maxTokens: toPositiveNumber(entry.max_tokens, reference?.maxTokens ?? 8192),
|
|
1055
|
+
};
|
|
1056
|
+
},
|
|
1057
|
+
}),
|
|
1058
|
+
}),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// ---------------------------------------------------------------------------
|
|
1063
|
+
// 14. Venice
|
|
1064
|
+
// ---------------------------------------------------------------------------
|
|
1065
|
+
|
|
1066
|
+
export interface VeniceModelManagerConfig {
|
|
1067
|
+
apiKey?: string;
|
|
1068
|
+
baseUrl?: string;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
export function veniceModelManagerOptions(
|
|
1072
|
+
config?: VeniceModelManagerConfig,
|
|
1073
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1074
|
+
const apiKey = config?.apiKey;
|
|
1075
|
+
const baseUrl = config?.baseUrl ?? "https://api.venice.ai/api/v1";
|
|
1076
|
+
const references = createBundledReferenceMap<"openai-completions">("venice");
|
|
1077
|
+
return {
|
|
1078
|
+
providerId: "venice",
|
|
1079
|
+
fetchDynamicModels: () =>
|
|
1080
|
+
fetchOpenAICompatibleModels({
|
|
1081
|
+
api: "openai-completions",
|
|
1082
|
+
provider: "venice",
|
|
1083
|
+
baseUrl,
|
|
1084
|
+
apiKey,
|
|
1085
|
+
mapModel: (entry, defaults) => {
|
|
1086
|
+
const reference = references.get(defaults.id);
|
|
1087
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
1088
|
+
return {
|
|
1089
|
+
...model,
|
|
1090
|
+
compat: { ...model.compat, supportsUsageInStreaming: false },
|
|
1091
|
+
};
|
|
1092
|
+
},
|
|
1093
|
+
}),
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// ---------------------------------------------------------------------------
|
|
1098
|
+
// 15. Together
|
|
1099
|
+
// ---------------------------------------------------------------------------
|
|
1100
|
+
|
|
1101
|
+
export interface TogetherModelManagerConfig {
|
|
1102
|
+
apiKey?: string;
|
|
1103
|
+
baseUrl?: string;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export function togetherModelManagerOptions(
|
|
1107
|
+
config?: TogetherModelManagerConfig,
|
|
1108
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1109
|
+
const apiKey = config?.apiKey;
|
|
1110
|
+
const baseUrl = config?.baseUrl ?? "https://api.together.xyz/v1";
|
|
1111
|
+
const references = createBundledReferenceMap<"openai-completions">("together");
|
|
1112
|
+
return {
|
|
1113
|
+
providerId: "together",
|
|
1114
|
+
...(apiKey && {
|
|
1115
|
+
fetchDynamicModels: () =>
|
|
1116
|
+
fetchOpenAICompatibleModels({
|
|
1117
|
+
api: "openai-completions",
|
|
1118
|
+
provider: "together",
|
|
1119
|
+
baseUrl,
|
|
1120
|
+
apiKey,
|
|
1121
|
+
mapModel: (entry, defaults) => {
|
|
1122
|
+
const reference = references.get(defaults.id);
|
|
1123
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1124
|
+
},
|
|
1125
|
+
}),
|
|
1126
|
+
}),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// ---------------------------------------------------------------------------
|
|
1131
|
+
// 16. Moonshot
|
|
1132
|
+
// ---------------------------------------------------------------------------
|
|
1133
|
+
|
|
1134
|
+
export interface MoonshotModelManagerConfig {
|
|
1135
|
+
apiKey?: string;
|
|
1136
|
+
baseUrl?: string;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
export function moonshotModelManagerOptions(
|
|
1140
|
+
config?: MoonshotModelManagerConfig,
|
|
1141
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1142
|
+
const apiKey = config?.apiKey;
|
|
1143
|
+
const baseUrl = config?.baseUrl ?? "https://api.moonshot.ai/v1";
|
|
1144
|
+
const references = createBundledReferenceMap<"openai-completions">("moonshot");
|
|
1145
|
+
return {
|
|
1146
|
+
providerId: "moonshot",
|
|
1147
|
+
...(apiKey && {
|
|
1148
|
+
fetchDynamicModels: () =>
|
|
1149
|
+
fetchOpenAICompatibleModels({
|
|
1150
|
+
api: "openai-completions",
|
|
1151
|
+
provider: "moonshot",
|
|
1152
|
+
baseUrl,
|
|
1153
|
+
apiKey,
|
|
1154
|
+
mapModel: (entry, defaults) => {
|
|
1155
|
+
const reference = references.get(defaults.id);
|
|
1156
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
1157
|
+
const id = model.id.toLowerCase();
|
|
1158
|
+
const isThinking = id.includes("thinking");
|
|
1159
|
+
const isVision = id.includes("vision") || id.includes("vl") || id.includes("k2.5");
|
|
1160
|
+
return {
|
|
1161
|
+
...model,
|
|
1162
|
+
reasoning: isThinking || model.reasoning,
|
|
1163
|
+
input: isVision ? ["text", "image"] : model.input,
|
|
1164
|
+
};
|
|
1165
|
+
},
|
|
1166
|
+
}),
|
|
1167
|
+
}),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// ---------------------------------------------------------------------------
|
|
1172
|
+
// 17. Qwen Portal
|
|
1173
|
+
// ---------------------------------------------------------------------------
|
|
1174
|
+
|
|
1175
|
+
export interface QwenPortalModelManagerConfig {
|
|
1176
|
+
apiKey?: string;
|
|
1177
|
+
baseUrl?: string;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
export function qwenPortalModelManagerOptions(
|
|
1181
|
+
config?: QwenPortalModelManagerConfig,
|
|
1182
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1183
|
+
const apiKey = config?.apiKey;
|
|
1184
|
+
const baseUrl = config?.baseUrl ?? "https://portal.qwen.ai/v1";
|
|
1185
|
+
const references = createBundledReferenceMap<"openai-completions">("qwen-portal");
|
|
1186
|
+
return {
|
|
1187
|
+
providerId: "qwen-portal",
|
|
1188
|
+
...(apiKey && {
|
|
1189
|
+
fetchDynamicModels: () =>
|
|
1190
|
+
fetchOpenAICompatibleModels({
|
|
1191
|
+
api: "openai-completions",
|
|
1192
|
+
provider: "qwen-portal",
|
|
1193
|
+
baseUrl,
|
|
1194
|
+
apiKey,
|
|
1195
|
+
mapModel: (entry, defaults) => {
|
|
1196
|
+
const reference = references.get(defaults.id);
|
|
1197
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1198
|
+
},
|
|
1199
|
+
}),
|
|
1200
|
+
}),
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// ---------------------------------------------------------------------------
|
|
1205
|
+
// 18. Qianfan
|
|
1206
|
+
// ---------------------------------------------------------------------------
|
|
1207
|
+
|
|
1208
|
+
export interface QianfanModelManagerConfig {
|
|
1209
|
+
apiKey?: string;
|
|
1210
|
+
baseUrl?: string;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
export function qianfanModelManagerOptions(
|
|
1214
|
+
config?: QianfanModelManagerConfig,
|
|
1215
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1216
|
+
const apiKey = config?.apiKey;
|
|
1217
|
+
const baseUrl = config?.baseUrl ?? "https://qianfan.baidubce.com/v2";
|
|
1218
|
+
const references = createBundledReferenceMap<"openai-completions">("qianfan");
|
|
1219
|
+
return {
|
|
1220
|
+
providerId: "qianfan",
|
|
1221
|
+
...(apiKey && {
|
|
1222
|
+
fetchDynamicModels: () =>
|
|
1223
|
+
fetchOpenAICompatibleModels({
|
|
1224
|
+
api: "openai-completions",
|
|
1225
|
+
provider: "qianfan",
|
|
1226
|
+
baseUrl,
|
|
1227
|
+
apiKey,
|
|
1228
|
+
mapModel: (entry, defaults) => {
|
|
1229
|
+
const reference = references.get(defaults.id);
|
|
1230
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1231
|
+
},
|
|
1232
|
+
}),
|
|
1233
|
+
}),
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// ---------------------------------------------------------------------------
|
|
1238
|
+
// 19. Cloudflare AI Gateway
|
|
1239
|
+
// ---------------------------------------------------------------------------
|
|
1240
|
+
|
|
1241
|
+
export interface CloudflareAiGatewayModelManagerConfig {
|
|
1242
|
+
apiKey?: string;
|
|
1243
|
+
baseUrl?: string;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
export function cloudflareAiGatewayModelManagerOptions(
|
|
1247
|
+
config?: CloudflareAiGatewayModelManagerConfig,
|
|
1248
|
+
): ModelManagerOptions<"anthropic-messages"> {
|
|
1249
|
+
const apiKey = config?.apiKey;
|
|
1250
|
+
const baseUrl = normalizeAnthropicBaseUrl(
|
|
1251
|
+
config?.baseUrl,
|
|
1252
|
+
"https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic",
|
|
1253
|
+
);
|
|
1254
|
+
const discoveryBaseUrl = toAnthropicDiscoveryBaseUrl(baseUrl);
|
|
1255
|
+
const references = createBundledReferenceMap<"anthropic-messages">("cloudflare-ai-gateway");
|
|
1256
|
+
return {
|
|
1257
|
+
providerId: "cloudflare-ai-gateway",
|
|
1258
|
+
...(apiKey && {
|
|
1259
|
+
fetchDynamicModels: () =>
|
|
1260
|
+
fetchOpenAICompatibleModels({
|
|
1261
|
+
api: "anthropic-messages",
|
|
1262
|
+
provider: "cloudflare-ai-gateway",
|
|
1263
|
+
baseUrl: discoveryBaseUrl,
|
|
1264
|
+
headers: buildAnthropicDiscoveryHeaders(apiKey),
|
|
1265
|
+
mapModel: (entry, defaults) => {
|
|
1266
|
+
const reference = references.get(defaults.id);
|
|
1267
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
1268
|
+
return {
|
|
1269
|
+
...model,
|
|
1270
|
+
name: toModelName(entry.display_name, model.name),
|
|
1271
|
+
baseUrl,
|
|
1272
|
+
};
|
|
1273
|
+
},
|
|
1274
|
+
}),
|
|
1275
|
+
}),
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// ---------------------------------------------------------------------------
|
|
1280
|
+
// 20. Xiaomi
|
|
1281
|
+
// ---------------------------------------------------------------------------
|
|
1282
|
+
|
|
1283
|
+
export interface XiaomiModelManagerConfig {
|
|
1284
|
+
apiKey?: string;
|
|
1285
|
+
baseUrl?: string;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
export function xiaomiModelManagerOptions(
|
|
1289
|
+
config?: XiaomiModelManagerConfig,
|
|
1290
|
+
): ModelManagerOptions<"anthropic-messages"> {
|
|
1291
|
+
const apiKey = config?.apiKey;
|
|
1292
|
+
const baseUrl = normalizeAnthropicBaseUrl(config?.baseUrl, "https://api.xiaomimimo.com/anthropic");
|
|
1293
|
+
const discoveryBaseUrl = toAnthropicDiscoveryBaseUrl(baseUrl);
|
|
1294
|
+
const references = createBundledReferenceMap<"anthropic-messages">("xiaomi");
|
|
1295
|
+
return {
|
|
1296
|
+
providerId: "xiaomi",
|
|
1297
|
+
...(apiKey && {
|
|
1298
|
+
fetchDynamicModels: () =>
|
|
1299
|
+
fetchOpenAICompatibleModels({
|
|
1300
|
+
api: "anthropic-messages",
|
|
1301
|
+
provider: "xiaomi",
|
|
1302
|
+
baseUrl: discoveryBaseUrl,
|
|
1303
|
+
headers: buildAnthropicDiscoveryHeaders(apiKey),
|
|
1304
|
+
mapModel: (entry, defaults) => {
|
|
1305
|
+
const reference = references.get(defaults.id);
|
|
1306
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
1307
|
+
return {
|
|
1308
|
+
...model,
|
|
1309
|
+
name: toModelName(entry.display_name, model.name),
|
|
1310
|
+
baseUrl,
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
}),
|
|
1314
|
+
}),
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// ---------------------------------------------------------------------------
|
|
1319
|
+
// 21. LiteLLM
|
|
1320
|
+
// ---------------------------------------------------------------------------
|
|
1321
|
+
|
|
1322
|
+
export interface LiteLLMModelManagerConfig {
|
|
1323
|
+
apiKey?: string;
|
|
1324
|
+
baseUrl?: string;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
export function litellmModelManagerOptions(
|
|
1328
|
+
config?: LiteLLMModelManagerConfig,
|
|
1329
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1330
|
+
const apiKey = config?.apiKey;
|
|
1331
|
+
const baseUrl = config?.baseUrl ?? "http://localhost:4000/v1";
|
|
1332
|
+
const references = createBundledReferenceMap<"openai-completions">("litellm");
|
|
1333
|
+
return {
|
|
1334
|
+
providerId: "litellm",
|
|
1335
|
+
fetchDynamicModels: () =>
|
|
1336
|
+
fetchOpenAICompatibleModels({
|
|
1337
|
+
api: "openai-completions",
|
|
1338
|
+
provider: "litellm",
|
|
1339
|
+
baseUrl,
|
|
1340
|
+
apiKey,
|
|
1341
|
+
mapModel: (entry, defaults) => {
|
|
1342
|
+
const reference = references.get(defaults.id);
|
|
1343
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1344
|
+
},
|
|
1345
|
+
}),
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// ---------------------------------------------------------------------------
|
|
1350
|
+
// 22. vLLM
|
|
1351
|
+
// ---------------------------------------------------------------------------
|
|
1352
|
+
|
|
1353
|
+
export interface VllmModelManagerConfig {
|
|
1354
|
+
apiKey?: string;
|
|
1355
|
+
baseUrl?: string;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
export function vllmModelManagerOptions(config?: VllmModelManagerConfig): ModelManagerOptions<"openai-completions"> {
|
|
1359
|
+
const apiKey = config?.apiKey;
|
|
1360
|
+
const baseUrl = config?.baseUrl ?? "http://127.0.0.1:8000/v1";
|
|
1361
|
+
const references = createBundledReferenceMap<"openai-completions">("vllm" as Parameters<typeof getBundledModels>[0]);
|
|
1362
|
+
return {
|
|
1363
|
+
providerId: "vllm",
|
|
1364
|
+
fetchDynamicModels: () =>
|
|
1365
|
+
fetchOpenAICompatibleModels({
|
|
1366
|
+
api: "openai-completions",
|
|
1367
|
+
provider: "vllm",
|
|
1368
|
+
baseUrl,
|
|
1369
|
+
apiKey,
|
|
1370
|
+
mapModel: (entry, defaults) => {
|
|
1371
|
+
const reference = references.get(defaults.id);
|
|
1372
|
+
return mapWithBundledReference(entry, defaults, reference);
|
|
1373
|
+
},
|
|
1374
|
+
}),
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// ---------------------------------------------------------------------------
|
|
1379
|
+
// 23. NanoGPT
|
|
1380
|
+
// ---------------------------------------------------------------------------
|
|
1381
|
+
|
|
1382
|
+
export interface NanoGptModelManagerConfig {
|
|
1383
|
+
apiKey?: string;
|
|
1384
|
+
baseUrl?: string;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
export function nanoGptModelManagerOptions(
|
|
1388
|
+
config?: NanoGptModelManagerConfig,
|
|
1389
|
+
): ModelManagerOptions<"openai-completions"> {
|
|
1390
|
+
const apiKey = config?.apiKey;
|
|
1391
|
+
const baseUrl = config?.baseUrl ?? "https://nano-gpt.com/api/v1";
|
|
1392
|
+
const resolveReference = createReferenceResolver(
|
|
1393
|
+
createBundledReferenceMap<"openai-completions">("nanogpt" as Parameters<typeof getBundledModels>[0]),
|
|
1394
|
+
);
|
|
1395
|
+
return {
|
|
1396
|
+
providerId: "nanogpt",
|
|
1397
|
+
...(apiKey && {
|
|
1398
|
+
fetchDynamicModels: async () => {
|
|
1399
|
+
// Track base IDs that have :thinking variants so we can mark them reasoning-capable.
|
|
1400
|
+
const thinkingBaseIds = new Set<string>();
|
|
1401
|
+
const models = await fetchOpenAICompatibleModels({
|
|
1402
|
+
api: "openai-completions",
|
|
1403
|
+
provider: "nanogpt",
|
|
1404
|
+
baseUrl,
|
|
1405
|
+
apiKey,
|
|
1406
|
+
mapModel: (entry, defaults) => {
|
|
1407
|
+
const reference = resolveReference(defaults.id);
|
|
1408
|
+
const mapped = mapWithBundledReference(entry, defaults, reference);
|
|
1409
|
+
return { ...mapped, api: "openai-completions", provider: "nanogpt" };
|
|
1410
|
+
},
|
|
1411
|
+
filterModel: (_entry, model) => {
|
|
1412
|
+
const match = NANO_GPT_THINKING_SUFFIX_RE.exec(model.id);
|
|
1413
|
+
if (match) {
|
|
1414
|
+
thinkingBaseIds.add(model.id.slice(0, match.index));
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
return isLikelyNanoGptTextModelId(model.id);
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
if (!models) return null;
|
|
1421
|
+
// Mark base models as reasoning-capable when a :thinking variant existed.
|
|
1422
|
+
for (const model of models) {
|
|
1423
|
+
if (!model.reasoning && thinkingBaseIds.has(model.id)) {
|
|
1424
|
+
(model as { reasoning: boolean }).reasoning = true;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
return models;
|
|
1428
|
+
},
|
|
1429
|
+
}),
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// ---------------------------------------------------------------------------
|
|
1434
|
+
// 24. GitHub Copilot
|
|
1435
|
+
// ---------------------------------------------------------------------------
|
|
1436
|
+
|
|
1437
|
+
export interface GithubCopilotModelManagerConfig {
|
|
1438
|
+
apiKey?: string;
|
|
1439
|
+
baseUrl?: string;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function inferCopilotApi(modelId: string): Api {
|
|
1443
|
+
if (/^claude-(haiku|sonnet|opus)-4([.-]|$)/.test(modelId)) {
|
|
1444
|
+
return "anthropic-messages";
|
|
1445
|
+
}
|
|
1446
|
+
if (modelId.startsWith("gpt-5") || modelId.startsWith("oswe")) {
|
|
1447
|
+
return "openai-responses";
|
|
1448
|
+
}
|
|
1449
|
+
return "openai-completions";
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function extractCopilotLimits(entry: OpenAICompatibleModelRecord): {
|
|
1453
|
+
maxPromptTokens?: number;
|
|
1454
|
+
maxContextWindowTokens?: number;
|
|
1455
|
+
maxOutputTokens?: number;
|
|
1456
|
+
maxNonStreamingOutputTokens?: number;
|
|
1457
|
+
} {
|
|
1458
|
+
if (!isRecord(entry.capabilities)) {
|
|
1459
|
+
return {};
|
|
1460
|
+
}
|
|
1461
|
+
const limitsValue = entry.capabilities.limits;
|
|
1462
|
+
if (!isRecord(limitsValue)) {
|
|
1463
|
+
return {};
|
|
1464
|
+
}
|
|
1465
|
+
return {
|
|
1466
|
+
maxPromptTokens: toNumber(limitsValue.max_prompt_tokens),
|
|
1467
|
+
maxContextWindowTokens: toNumber(limitsValue.max_context_window_tokens),
|
|
1468
|
+
maxOutputTokens: toNumber(limitsValue.max_output_tokens),
|
|
1469
|
+
maxNonStreamingOutputTokens: toNumber(limitsValue.max_non_streaming_output_tokens),
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
export function githubCopilotModelManagerOptions(config?: GithubCopilotModelManagerConfig): ModelManagerOptions<Api> {
|
|
1474
|
+
const rawApiKey = config?.apiKey;
|
|
1475
|
+
const configuredBaseUrl = config?.baseUrl ?? "https://api.githubcopilot.com";
|
|
1476
|
+
const parsedApiKey = rawApiKey ? parseGitHubCopilotApiKey(rawApiKey) : undefined;
|
|
1477
|
+
const apiKey = parsedApiKey?.accessToken;
|
|
1478
|
+
const baseUrl =
|
|
1479
|
+
parsedApiKey?.enterpriseUrl && configuredBaseUrl.includes("githubcopilot.com")
|
|
1480
|
+
? getGitHubCopilotBaseUrl(parsedApiKey.enterpriseUrl)
|
|
1481
|
+
: configuredBaseUrl;
|
|
1482
|
+
const providerRefs = createBundledReferenceMap<Api>("github-copilot");
|
|
1483
|
+
const resolveReference = createReferenceResolver(providerRefs);
|
|
1484
|
+
return {
|
|
1485
|
+
providerId: "github-copilot",
|
|
1486
|
+
...(apiKey && {
|
|
1487
|
+
fetchDynamicModels: () =>
|
|
1488
|
+
fetchOpenAICompatibleModels<Api>({
|
|
1489
|
+
api: "openai-completions",
|
|
1490
|
+
provider: "github-copilot",
|
|
1491
|
+
baseUrl,
|
|
1492
|
+
apiKey,
|
|
1493
|
+
headers: OPENCODE_HEADERS,
|
|
1494
|
+
mapModel: (
|
|
1495
|
+
entry: OpenAICompatibleModelRecord,
|
|
1496
|
+
defaults: Model<Api>,
|
|
1497
|
+
_context: OpenAICompatibleModelMapperContext<Api>,
|
|
1498
|
+
): Model<Api> => {
|
|
1499
|
+
const reference = resolveReference(defaults.id);
|
|
1500
|
+
const copilotLimits = extractCopilotLimits(entry);
|
|
1501
|
+
// Copilot exposes token limits under capabilities.limits.*.
|
|
1502
|
+
// max_prompt_tokens is the prompt capacity (what OMP calls contextWindow).
|
|
1503
|
+
// max_context_window_tokens is the total window (prompt + output budget)
|
|
1504
|
+
// and must NOT be used for contextWindow — it inflates the limit and
|
|
1505
|
+
// breaks compaction thresholds, overflow detection, and promotion.
|
|
1506
|
+
const contextWindow = toPositiveNumber(
|
|
1507
|
+
entry.context_length,
|
|
1508
|
+
toPositiveNumber(
|
|
1509
|
+
copilotLimits.maxPromptTokens,
|
|
1510
|
+
reference?.contextWindow ?? defaults.contextWindow,
|
|
1511
|
+
),
|
|
1512
|
+
);
|
|
1513
|
+
const maxTokens = toPositiveNumber(
|
|
1514
|
+
entry.max_completion_tokens,
|
|
1515
|
+
toPositiveNumber(
|
|
1516
|
+
copilotLimits.maxOutputTokens,
|
|
1517
|
+
toPositiveNumber(
|
|
1518
|
+
copilotLimits.maxNonStreamingOutputTokens,
|
|
1519
|
+
reference?.maxTokens ?? defaults.maxTokens,
|
|
1520
|
+
),
|
|
1521
|
+
),
|
|
1522
|
+
);
|
|
1523
|
+
const name =
|
|
1524
|
+
typeof entry.name === "string" && entry.name.trim().length > 0
|
|
1525
|
+
? entry.name
|
|
1526
|
+
: (reference?.name ?? defaults.name);
|
|
1527
|
+
const api = inferCopilotApi(defaults.id);
|
|
1528
|
+
if (reference) {
|
|
1529
|
+
return {
|
|
1530
|
+
...reference,
|
|
1531
|
+
api,
|
|
1532
|
+
provider: "github-copilot",
|
|
1533
|
+
baseUrl,
|
|
1534
|
+
name,
|
|
1535
|
+
contextWindow,
|
|
1536
|
+
maxTokens,
|
|
1537
|
+
headers: { ...OPENCODE_HEADERS, ...(providerRefs.get(defaults.id)?.headers ?? {}) },
|
|
1538
|
+
...(api === "openai-completions"
|
|
1539
|
+
? {
|
|
1540
|
+
compat: {
|
|
1541
|
+
supportsStore: false,
|
|
1542
|
+
supportsDeveloperRole: false,
|
|
1543
|
+
supportsReasoningEffort: false,
|
|
1544
|
+
},
|
|
1545
|
+
}
|
|
1546
|
+
: {}),
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
...defaults,
|
|
1551
|
+
api,
|
|
1552
|
+
baseUrl,
|
|
1553
|
+
name,
|
|
1554
|
+
contextWindow,
|
|
1555
|
+
maxTokens,
|
|
1556
|
+
headers: { ...OPENCODE_HEADERS },
|
|
1557
|
+
...(api === "openai-completions"
|
|
1558
|
+
? {
|
|
1559
|
+
compat: {
|
|
1560
|
+
supportsStore: false,
|
|
1561
|
+
supportsDeveloperRole: false,
|
|
1562
|
+
supportsReasoningEffort: false,
|
|
1563
|
+
},
|
|
1564
|
+
}
|
|
1565
|
+
: {}),
|
|
1566
|
+
};
|
|
1567
|
+
},
|
|
1568
|
+
}),
|
|
1569
|
+
}),
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// ---------------------------------------------------------------------------
|
|
1574
|
+
// 24. Anthropic
|
|
1575
|
+
// ---------------------------------------------------------------------------
|
|
1576
|
+
|
|
1577
|
+
export interface AnthropicModelManagerConfig {
|
|
1578
|
+
apiKey?: string;
|
|
1579
|
+
baseUrl?: string;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
export function anthropicModelManagerOptions(
|
|
1583
|
+
config?: AnthropicModelManagerConfig,
|
|
1584
|
+
): ModelManagerOptions<"anthropic-messages"> {
|
|
1585
|
+
const apiKey = config?.apiKey;
|
|
1586
|
+
const baseUrl = config?.baseUrl ?? ANTHROPIC_BASE_URL;
|
|
1587
|
+
return {
|
|
1588
|
+
providerId: "anthropic",
|
|
1589
|
+
modelsDev: {
|
|
1590
|
+
fetch: fetchModelsDevPayload,
|
|
1591
|
+
map: payload => mapAnthropicModelsDev(payload, baseUrl),
|
|
1592
|
+
},
|
|
1593
|
+
...(apiKey && {
|
|
1594
|
+
fetchDynamicModels: async () => {
|
|
1595
|
+
const modelsDevModels = await fetchModelsDevPayload()
|
|
1596
|
+
.then(payload => mapAnthropicModelsDev(payload, baseUrl))
|
|
1597
|
+
.catch(() => []);
|
|
1598
|
+
const references = buildAnthropicReferenceMap(modelsDevModels);
|
|
1599
|
+
return (
|
|
1600
|
+
fetchOpenAICompatibleModels({
|
|
1601
|
+
api: "anthropic-messages",
|
|
1602
|
+
provider: "anthropic",
|
|
1603
|
+
baseUrl,
|
|
1604
|
+
headers: buildAnthropicDiscoveryHeaders(apiKey),
|
|
1605
|
+
mapModel: (
|
|
1606
|
+
entry: OpenAICompatibleModelRecord,
|
|
1607
|
+
defaults: Model<"anthropic-messages">,
|
|
1608
|
+
_context: OpenAICompatibleModelMapperContext<"anthropic-messages">,
|
|
1609
|
+
): Model<"anthropic-messages"> => {
|
|
1610
|
+
const discoveredName = typeof entry.display_name === "string" ? entry.display_name : defaults.name;
|
|
1611
|
+
const reference = references.get(defaults.id);
|
|
1612
|
+
if (!reference) {
|
|
1613
|
+
return {
|
|
1614
|
+
...defaults,
|
|
1615
|
+
name: discoveredName,
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
return {
|
|
1619
|
+
...reference,
|
|
1620
|
+
id: defaults.id,
|
|
1621
|
+
name: discoveredName,
|
|
1622
|
+
api: "anthropic-messages",
|
|
1623
|
+
provider: "anthropic",
|
|
1624
|
+
baseUrl,
|
|
1625
|
+
};
|
|
1626
|
+
},
|
|
1627
|
+
}) ?? null
|
|
1628
|
+
);
|
|
1629
|
+
},
|
|
1630
|
+
}),
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// ---------------------------------------------------------------------------
|
|
1635
|
+
// Models.dev provider descriptors for generate-models.ts
|
|
1636
|
+
// ---------------------------------------------------------------------------
|
|
1637
|
+
|
|
1638
|
+
export const UNK_CONTEXT_WINDOW = 222_222;
|
|
1639
|
+
export const UNK_MAX_TOKENS = 8_888;
|
|
1640
|
+
|
|
1641
|
+
/** Describes how to map models.dev API data for a single provider. */
|
|
1642
|
+
export interface ModelsDevProviderDescriptor {
|
|
1643
|
+
/** Key in the models.dev API response JSON (e.g., "anthropic", "amazon-bedrock") */
|
|
1644
|
+
modelsDevKey: string;
|
|
1645
|
+
/** Provider ID in our system */
|
|
1646
|
+
providerId: string;
|
|
1647
|
+
/** Default API type for this provider's models */
|
|
1648
|
+
api: Api;
|
|
1649
|
+
/** Default base URL */
|
|
1650
|
+
baseUrl: string;
|
|
1651
|
+
/** Default context window fallback (default: UNKNNOWN_CONTEXT_WINDOW) */
|
|
1652
|
+
defaultContextWindow?: number;
|
|
1653
|
+
/** Default max tokens fallback (default: UNKNNOWN_MAX_TOKENS) */
|
|
1654
|
+
defaultMaxTokens?: number;
|
|
1655
|
+
/** Optional compat overrides applied to every model from this provider */
|
|
1656
|
+
compat?: Model<Api>["compat"];
|
|
1657
|
+
/** Optional static headers applied to every model */
|
|
1658
|
+
headers?: Record<string, string>;
|
|
1659
|
+
/**
|
|
1660
|
+
* Optional filter: return false to skip a model.
|
|
1661
|
+
* Called with (modelId, rawModel). Default: skip if tool_call !== true.
|
|
1662
|
+
*/
|
|
1663
|
+
filterModel?: (modelId: string, model: ModelsDevModel) => boolean;
|
|
1664
|
+
/**
|
|
1665
|
+
* Optional transform: modify the mapped model before it's added.
|
|
1666
|
+
* Can return null to skip the model, or an array to emit multiple models.
|
|
1667
|
+
*/
|
|
1668
|
+
transformModel?: (model: Model<Api>, modelId: string, raw: ModelsDevModel) => Model<Api> | Model<Api>[] | null;
|
|
1669
|
+
/**
|
|
1670
|
+
* Optional: override the API type per-model.
|
|
1671
|
+
* Called with (modelId, raw). Return the API type to use.
|
|
1672
|
+
* If not provided, uses the `api` field.
|
|
1673
|
+
*/
|
|
1674
|
+
resolveApi?: (modelId: string, raw: ModelsDevModel) => { api: Api; baseUrl: string } | null;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/** Generic mapper that converts models.dev data using provider descriptors. */
|
|
1678
|
+
export function mapModelsDevToModels(
|
|
1679
|
+
data: Record<string, unknown>,
|
|
1680
|
+
descriptors: readonly ModelsDevProviderDescriptor[],
|
|
1681
|
+
): Model<Api>[] {
|
|
1682
|
+
const models: Model<Api>[] = [];
|
|
1683
|
+
for (const desc of descriptors) {
|
|
1684
|
+
const providerData = (data as Record<string, Record<string, unknown>>)[desc.modelsDevKey];
|
|
1685
|
+
if (!isRecord(providerData) || !isRecord(providerData.models)) continue;
|
|
1686
|
+
|
|
1687
|
+
for (const [modelId, rawModel] of Object.entries(providerData.models)) {
|
|
1688
|
+
if (!isRecord(rawModel)) continue;
|
|
1689
|
+
const m = rawModel as ModelsDevModel;
|
|
1690
|
+
|
|
1691
|
+
// Default filter: tool_call must be true
|
|
1692
|
+
if (desc.filterModel) {
|
|
1693
|
+
if (!desc.filterModel(modelId, m)) continue;
|
|
1694
|
+
} else {
|
|
1695
|
+
if (m.tool_call !== true) continue;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// Resolve API and baseUrl (may be per-model for providers like OpenCode)
|
|
1699
|
+
const resolved = desc.resolveApi?.(modelId, m) ?? { api: desc.api, baseUrl: desc.baseUrl };
|
|
1700
|
+
if (!resolved) continue;
|
|
1701
|
+
|
|
1702
|
+
const mapped: Model<Api> = {
|
|
1703
|
+
id: modelId,
|
|
1704
|
+
name: toModelName(m.name, modelId),
|
|
1705
|
+
api: resolved.api,
|
|
1706
|
+
provider: desc.providerId as Model<Api>["provider"],
|
|
1707
|
+
baseUrl: resolved.baseUrl,
|
|
1708
|
+
reasoning: m.reasoning === true,
|
|
1709
|
+
input: toInputCapabilities(m.modalities?.input),
|
|
1710
|
+
cost: {
|
|
1711
|
+
input: toNumber(m.cost?.input) ?? 0,
|
|
1712
|
+
output: toNumber(m.cost?.output) ?? 0,
|
|
1713
|
+
cacheRead: toNumber(m.cost?.cache_read) ?? 0,
|
|
1714
|
+
cacheWrite: toNumber(m.cost?.cache_write) ?? 0,
|
|
1715
|
+
},
|
|
1716
|
+
contextWindow: toPositiveNumber(m.limit?.context, desc.defaultContextWindow ?? UNK_CONTEXT_WINDOW),
|
|
1717
|
+
maxTokens: toPositiveNumber(m.limit?.output, desc.defaultMaxTokens ?? UNK_MAX_TOKENS),
|
|
1718
|
+
...(desc.compat && { compat: desc.compat }),
|
|
1719
|
+
...(desc.headers && { headers: { ...desc.headers } }),
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
// Apply per-model transform
|
|
1723
|
+
if (desc.transformModel) {
|
|
1724
|
+
const result = desc.transformModel(mapped, modelId, m);
|
|
1725
|
+
if (result === null) continue;
|
|
1726
|
+
if (Array.isArray(result)) {
|
|
1727
|
+
models.push(...result);
|
|
1728
|
+
} else {
|
|
1729
|
+
models.push(result);
|
|
1730
|
+
}
|
|
1731
|
+
} else {
|
|
1732
|
+
models.push(mapped);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return models;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// Bedrock cross-region prefix helpers
|
|
1740
|
+
const BEDROCK_GLOBAL_PREFIXES = [
|
|
1741
|
+
"anthropic.claude-haiku-4-5",
|
|
1742
|
+
"anthropic.claude-sonnet-4",
|
|
1743
|
+
"anthropic.claude-opus-4-5",
|
|
1744
|
+
"amazon.nova-2-lite",
|
|
1745
|
+
"cohere.embed-v4",
|
|
1746
|
+
"twelvelabs.pegasus-1-2",
|
|
1747
|
+
];
|
|
1748
|
+
|
|
1749
|
+
const BEDROCK_US_PREFIXES = [
|
|
1750
|
+
"amazon.nova-lite",
|
|
1751
|
+
"amazon.nova-micro",
|
|
1752
|
+
"amazon.nova-premier",
|
|
1753
|
+
"amazon.nova-pro",
|
|
1754
|
+
"anthropic.claude-3-7-sonnet",
|
|
1755
|
+
"anthropic.claude-opus-4-1",
|
|
1756
|
+
"anthropic.claude-opus-4-20250514",
|
|
1757
|
+
"deepseek.r1",
|
|
1758
|
+
"meta.llama3-2",
|
|
1759
|
+
"meta.llama3-3",
|
|
1760
|
+
"meta.llama4",
|
|
1761
|
+
];
|
|
1762
|
+
|
|
1763
|
+
function bedrockCrossRegionId(id: string): string {
|
|
1764
|
+
if (BEDROCK_GLOBAL_PREFIXES.some(p => id.startsWith(p))) return `global.${id}`;
|
|
1765
|
+
if (BEDROCK_US_PREFIXES.some(p => id.startsWith(p))) return `us.${id}`;
|
|
1766
|
+
return id;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
interface ApiResolutionRule {
|
|
1770
|
+
matches: (modelId: string, raw: ModelsDevModel) => boolean;
|
|
1771
|
+
resolved: { api: Api; baseUrl: string };
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function resolveApiByRules(
|
|
1775
|
+
modelId: string,
|
|
1776
|
+
raw: ModelsDevModel,
|
|
1777
|
+
rules: readonly ApiResolutionRule[],
|
|
1778
|
+
fallback: { api: Api; baseUrl: string },
|
|
1779
|
+
): { api: Api; baseUrl: string } {
|
|
1780
|
+
for (const rule of rules) {
|
|
1781
|
+
if (rule.matches(modelId, raw)) return rule.resolved;
|
|
1782
|
+
}
|
|
1783
|
+
return fallback;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function createOpenCodeApiResolution(basePath: string): {
|
|
1787
|
+
defaultResolution: { api: Api; baseUrl: string };
|
|
1788
|
+
rules: ApiResolutionRule[];
|
|
1789
|
+
} {
|
|
1790
|
+
const completionsBaseUrl = `${basePath}/v1`;
|
|
1791
|
+
return {
|
|
1792
|
+
defaultResolution: { api: "openai-completions", baseUrl: completionsBaseUrl },
|
|
1793
|
+
rules: [
|
|
1794
|
+
{
|
|
1795
|
+
matches: (_modelId, raw) => raw.provider?.npm === "@ai-sdk/openai",
|
|
1796
|
+
resolved: { api: "openai-responses", baseUrl: completionsBaseUrl },
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
matches: (_modelId, raw) => raw.provider?.npm === "@ai-sdk/anthropic",
|
|
1800
|
+
resolved: { api: "anthropic-messages", baseUrl: basePath },
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
matches: (_modelId, raw) => raw.provider?.npm === "@ai-sdk/google",
|
|
1804
|
+
resolved: { api: "google-generative-ai", baseUrl: completionsBaseUrl },
|
|
1805
|
+
},
|
|
1806
|
+
],
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
const OPENCODE_ZEN_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen");
|
|
1811
|
+
const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen/go");
|
|
1812
|
+
|
|
1813
|
+
const COPILOT_BASE_URL = "https://api.githubcopilot.com";
|
|
1814
|
+
|
|
1815
|
+
const COPILOT_DEFAULT_RESOLUTION = {
|
|
1816
|
+
api: "openai-completions",
|
|
1817
|
+
baseUrl: COPILOT_BASE_URL,
|
|
1818
|
+
} as const satisfies { api: Api; baseUrl: string };
|
|
1819
|
+
|
|
1820
|
+
const COPILOT_API_RESOLUTION_RULES: readonly ApiResolutionRule[] = [
|
|
1821
|
+
{
|
|
1822
|
+
matches: modelId => /^claude-(haiku|sonnet|opus)-4([.-]|$)/.test(modelId),
|
|
1823
|
+
resolved: { api: "anthropic-messages", baseUrl: COPILOT_BASE_URL },
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
matches: modelId => modelId.startsWith("gpt-5") || modelId.startsWith("oswe"),
|
|
1827
|
+
resolved: { api: "openai-responses", baseUrl: COPILOT_BASE_URL },
|
|
1828
|
+
},
|
|
1829
|
+
];
|
|
1830
|
+
|
|
1831
|
+
function simpleModelsDevDescriptor(
|
|
1832
|
+
modelsDevKey: string,
|
|
1833
|
+
providerId: string,
|
|
1834
|
+
api: Api,
|
|
1835
|
+
baseUrl: string,
|
|
1836
|
+
options: Omit<ModelsDevProviderDescriptor, "modelsDevKey" | "providerId" | "api" | "baseUrl"> = {},
|
|
1837
|
+
): ModelsDevProviderDescriptor {
|
|
1838
|
+
return {
|
|
1839
|
+
modelsDevKey,
|
|
1840
|
+
providerId,
|
|
1841
|
+
api,
|
|
1842
|
+
baseUrl,
|
|
1843
|
+
...options,
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function openAiCompletionsDescriptor(
|
|
1848
|
+
modelsDevKey: string,
|
|
1849
|
+
providerId: string,
|
|
1850
|
+
baseUrl: string,
|
|
1851
|
+
options: Omit<ModelsDevProviderDescriptor, "modelsDevKey" | "providerId" | "api" | "baseUrl"> = {},
|
|
1852
|
+
): ModelsDevProviderDescriptor {
|
|
1853
|
+
return simpleModelsDevDescriptor(modelsDevKey, providerId, "openai-completions", baseUrl, options);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
function anthropicMessagesDescriptor(
|
|
1857
|
+
modelsDevKey: string,
|
|
1858
|
+
providerId: string,
|
|
1859
|
+
baseUrl: string,
|
|
1860
|
+
options: Omit<ModelsDevProviderDescriptor, "modelsDevKey" | "providerId" | "api" | "baseUrl"> = {},
|
|
1861
|
+
): ModelsDevProviderDescriptor {
|
|
1862
|
+
return simpleModelsDevDescriptor(modelsDevKey, providerId, "anthropic-messages", baseUrl, options);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
const MODELS_DEV_PROVIDER_DESCRIPTORS_BEDROCK: readonly ModelsDevProviderDescriptor[] = [
|
|
1866
|
+
// --- Amazon Bedrock ---
|
|
1867
|
+
{
|
|
1868
|
+
modelsDevKey: "amazon-bedrock",
|
|
1869
|
+
providerId: "amazon-bedrock",
|
|
1870
|
+
api: "bedrock-converse-stream",
|
|
1871
|
+
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
1872
|
+
filterModel: (id, m) => {
|
|
1873
|
+
if (m.tool_call !== true) return false;
|
|
1874
|
+
if (id.startsWith("ai21.jamba")) return false;
|
|
1875
|
+
if (id.startsWith("amazon.titan-text-express") || id.startsWith("mistral.mistral-7b-instruct-v0"))
|
|
1876
|
+
return false;
|
|
1877
|
+
return true;
|
|
1878
|
+
},
|
|
1879
|
+
transformModel: (model, modelId, m) => {
|
|
1880
|
+
const crossRegionId = bedrockCrossRegionId(modelId);
|
|
1881
|
+
const bedrockModel: Model<Api> = {
|
|
1882
|
+
...model,
|
|
1883
|
+
id: crossRegionId,
|
|
1884
|
+
name: toModelName(m.name, crossRegionId),
|
|
1885
|
+
};
|
|
1886
|
+
// Also emit EU variants for Claude models
|
|
1887
|
+
if (modelId.startsWith("anthropic.claude-")) {
|
|
1888
|
+
return [
|
|
1889
|
+
bedrockModel,
|
|
1890
|
+
{
|
|
1891
|
+
...bedrockModel,
|
|
1892
|
+
id: `eu.${modelId}`,
|
|
1893
|
+
name: `${toModelName(m.name, modelId)} (EU)`,
|
|
1894
|
+
},
|
|
1895
|
+
];
|
|
1896
|
+
}
|
|
1897
|
+
return bedrockModel;
|
|
1898
|
+
},
|
|
1899
|
+
},
|
|
1900
|
+
];
|
|
1901
|
+
|
|
1902
|
+
const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor[] = [
|
|
1903
|
+
// --- Anthropic ---
|
|
1904
|
+
anthropicMessagesDescriptor("anthropic", "anthropic", "https://api.anthropic.com", {
|
|
1905
|
+
filterModel: (id, m) => {
|
|
1906
|
+
if (m.tool_call !== true) return false;
|
|
1907
|
+
if (
|
|
1908
|
+
id.startsWith("claude-3-5-haiku") ||
|
|
1909
|
+
id.startsWith("claude-3-7-sonnet") ||
|
|
1910
|
+
id === "claude-3-opus-20240229" ||
|
|
1911
|
+
id === "claude-3-sonnet-20240229"
|
|
1912
|
+
)
|
|
1913
|
+
return false;
|
|
1914
|
+
return true;
|
|
1915
|
+
},
|
|
1916
|
+
}),
|
|
1917
|
+
// --- Google ---
|
|
1918
|
+
simpleModelsDevDescriptor(
|
|
1919
|
+
"google",
|
|
1920
|
+
"google",
|
|
1921
|
+
"google-generative-ai",
|
|
1922
|
+
"https://generativelanguage.googleapis.com/v1beta",
|
|
1923
|
+
),
|
|
1924
|
+
// --- OpenAI ---
|
|
1925
|
+
simpleModelsDevDescriptor("openai", "openai", "openai-responses", "https://api.openai.com/v1"),
|
|
1926
|
+
// --- Groq ---
|
|
1927
|
+
openAiCompletionsDescriptor("groq", "groq", "https://api.groq.com/openai/v1"),
|
|
1928
|
+
// --- Cerebras ---
|
|
1929
|
+
openAiCompletionsDescriptor("cerebras", "cerebras", "https://api.cerebras.ai/v1"),
|
|
1930
|
+
// --- Together ---
|
|
1931
|
+
openAiCompletionsDescriptor("together", "together", "https://api.together.xyz/v1"),
|
|
1932
|
+
// --- NVIDIA ---
|
|
1933
|
+
openAiCompletionsDescriptor("nvidia", "nvidia", "https://integrate.api.nvidia.com/v1", {
|
|
1934
|
+
defaultContextWindow: 131072,
|
|
1935
|
+
}),
|
|
1936
|
+
// --- xAI ---
|
|
1937
|
+
openAiCompletionsDescriptor("xai", "xai", "https://api.x.ai/v1"),
|
|
1938
|
+
];
|
|
1939
|
+
|
|
1940
|
+
const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDescriptor[] = [
|
|
1941
|
+
// --- zAI ---
|
|
1942
|
+
anthropicMessagesDescriptor("zai-coding-plan", "zai", "https://api.z.ai/api/anthropic"),
|
|
1943
|
+
// --- Xiaomi ---
|
|
1944
|
+
anthropicMessagesDescriptor("xiaomi", "xiaomi", "https://api.xiaomimimo.com/anthropic", {
|
|
1945
|
+
defaultContextWindow: 262144,
|
|
1946
|
+
defaultMaxTokens: 8192,
|
|
1947
|
+
}),
|
|
1948
|
+
// --- MiniMax Coding Plan ---
|
|
1949
|
+
openAiCompletionsDescriptor("minimax-coding-plan", "minimax-code", "https://api.minimax.io/v1", {
|
|
1950
|
+
compat: {
|
|
1951
|
+
supportsStore: false,
|
|
1952
|
+
supportsDeveloperRole: false,
|
|
1953
|
+
thinkingFormat: "zai",
|
|
1954
|
+
reasoningContentField: "reasoning_content",
|
|
1955
|
+
},
|
|
1956
|
+
}),
|
|
1957
|
+
openAiCompletionsDescriptor("minimax-cn-coding-plan", "minimax-code-cn", "https://api.minimaxi.com/v1", {
|
|
1958
|
+
compat: {
|
|
1959
|
+
supportsStore: false,
|
|
1960
|
+
supportsDeveloperRole: false,
|
|
1961
|
+
thinkingFormat: "zai",
|
|
1962
|
+
reasoningContentField: "reasoning_content",
|
|
1963
|
+
},
|
|
1964
|
+
}),
|
|
1965
|
+
// --- Alibaba Coding Plan ---
|
|
1966
|
+
openAiCompletionsDescriptor(
|
|
1967
|
+
"alibaba-coding-plan",
|
|
1968
|
+
"alibaba-coding-plan",
|
|
1969
|
+
"https://coding-intl.dashscope.aliyuncs.com/v1",
|
|
1970
|
+
{
|
|
1971
|
+
compat: {
|
|
1972
|
+
supportsDeveloperRole: false,
|
|
1973
|
+
},
|
|
1974
|
+
},
|
|
1975
|
+
),
|
|
1976
|
+
];
|
|
1977
|
+
|
|
1978
|
+
const MODELS_DEV_PROVIDER_DESCRIPTORS_SPECIALIZED: readonly ModelsDevProviderDescriptor[] = [
|
|
1979
|
+
// --- Cloudflare AI Gateway ---
|
|
1980
|
+
anthropicMessagesDescriptor(
|
|
1981
|
+
"cloudflare-ai-gateway",
|
|
1982
|
+
"cloudflare-ai-gateway",
|
|
1983
|
+
"https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic",
|
|
1984
|
+
),
|
|
1985
|
+
// --- Mistral ---
|
|
1986
|
+
openAiCompletionsDescriptor("mistral", "mistral", "https://api.mistral.ai/v1"),
|
|
1987
|
+
// --- OpenCode Zen ---
|
|
1988
|
+
openAiCompletionsDescriptor("opencode", "opencode-zen", "https://opencode.ai/zen/v1", {
|
|
1989
|
+
filterModel: (_id, m) => {
|
|
1990
|
+
if (m.tool_call !== true) return false;
|
|
1991
|
+
if (m.status === "deprecated") return false;
|
|
1992
|
+
return true;
|
|
1993
|
+
},
|
|
1994
|
+
resolveApi: (modelId, raw) =>
|
|
1995
|
+
resolveApiByRules(
|
|
1996
|
+
modelId,
|
|
1997
|
+
raw,
|
|
1998
|
+
OPENCODE_ZEN_API_RESOLUTION.rules,
|
|
1999
|
+
OPENCODE_ZEN_API_RESOLUTION.defaultResolution,
|
|
2000
|
+
),
|
|
2001
|
+
}),
|
|
2002
|
+
// --- OpenCode Go ---
|
|
2003
|
+
openAiCompletionsDescriptor("opencode-go", "opencode-go", "https://opencode.ai/zen/go/v1", {
|
|
2004
|
+
filterModel: (_id, m) => {
|
|
2005
|
+
if (m.tool_call !== true) return false;
|
|
2006
|
+
if (m.status === "deprecated") return false;
|
|
2007
|
+
return true;
|
|
2008
|
+
},
|
|
2009
|
+
resolveApi: (modelId, raw) =>
|
|
2010
|
+
resolveApiByRules(
|
|
2011
|
+
modelId,
|
|
2012
|
+
raw,
|
|
2013
|
+
OPENCODE_GO_API_RESOLUTION.rules,
|
|
2014
|
+
OPENCODE_GO_API_RESOLUTION.defaultResolution,
|
|
2015
|
+
),
|
|
2016
|
+
}),
|
|
2017
|
+
// --- GitHub Copilot ---
|
|
2018
|
+
openAiCompletionsDescriptor("github-copilot", "github-copilot", COPILOT_BASE_URL, {
|
|
2019
|
+
defaultContextWindow: 128000,
|
|
2020
|
+
defaultMaxTokens: 8192,
|
|
2021
|
+
headers: { ...OPENCODE_HEADERS },
|
|
2022
|
+
filterModel: (_id, m) => {
|
|
2023
|
+
if (m.tool_call !== true) return false;
|
|
2024
|
+
if (m.status === "deprecated") return false;
|
|
2025
|
+
return true;
|
|
2026
|
+
},
|
|
2027
|
+
resolveApi: (modelId, raw) =>
|
|
2028
|
+
resolveApiByRules(modelId, raw, COPILOT_API_RESOLUTION_RULES, COPILOT_DEFAULT_RESOLUTION),
|
|
2029
|
+
transformModel: model => {
|
|
2030
|
+
// compat only applies to openai-completions models
|
|
2031
|
+
if (model.api === "openai-completions") {
|
|
2032
|
+
return {
|
|
2033
|
+
...model,
|
|
2034
|
+
compat: {
|
|
2035
|
+
supportsStore: false,
|
|
2036
|
+
supportsDeveloperRole: false,
|
|
2037
|
+
supportsReasoningEffort: false,
|
|
2038
|
+
},
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
return model;
|
|
2042
|
+
},
|
|
2043
|
+
}),
|
|
2044
|
+
// --- MiniMax (Anthropic) ---
|
|
2045
|
+
anthropicMessagesDescriptor("minimax", "minimax", "https://api.minimax.io/anthropic"),
|
|
2046
|
+
anthropicMessagesDescriptor("minimax-cn", "minimax-cn", "https://api.minimaxi.com/anthropic"),
|
|
2047
|
+
// --- Qwen Portal ---
|
|
2048
|
+
openAiCompletionsDescriptor("qwen-portal", "qwen-portal", "https://portal.qwen.ai/v1", {
|
|
2049
|
+
defaultContextWindow: 128000,
|
|
2050
|
+
defaultMaxTokens: 8192,
|
|
2051
|
+
}),
|
|
2052
|
+
|
|
2053
|
+
// --- ZenMux ---
|
|
2054
|
+
openAiCompletionsDescriptor("zenmux", "zenmux", ZENMUX_OPENAI_BASE_URL, {
|
|
2055
|
+
filterModel: (_id, m) => {
|
|
2056
|
+
if (m.tool_call !== true) return false;
|
|
2057
|
+
if (m.status === "deprecated") return false;
|
|
2058
|
+
return true;
|
|
2059
|
+
},
|
|
2060
|
+
resolveApi: modelId => {
|
|
2061
|
+
if (modelId.startsWith("anthropic/")) {
|
|
2062
|
+
return { api: "anthropic-messages" as const, baseUrl: ZENMUX_ANTHROPIC_BASE_URL };
|
|
2063
|
+
}
|
|
2064
|
+
return { api: "openai-completions" as const, baseUrl: ZENMUX_OPENAI_BASE_URL };
|
|
2065
|
+
},
|
|
2066
|
+
}),
|
|
2067
|
+
];
|
|
2068
|
+
/** All provider descriptors for models.dev data mapping in generate-models.ts. */
|
|
2069
|
+
export const MODELS_DEV_PROVIDER_DESCRIPTORS: readonly ModelsDevProviderDescriptor[] = [
|
|
2070
|
+
...MODELS_DEV_PROVIDER_DESCRIPTORS_BEDROCK,
|
|
2071
|
+
...MODELS_DEV_PROVIDER_DESCRIPTORS_CORE,
|
|
2072
|
+
...MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS,
|
|
2073
|
+
...MODELS_DEV_PROVIDER_DESCRIPTORS_SPECIALIZED,
|
|
2074
|
+
];
|