@sayknow-cli/ai 0.2.4 → 0.2.6
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 +23 -0
- package/README.md +1 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -0
- package/dist/types/providers/anthropic.d.ts +0 -6
- package/dist/types/types.d.ts +11 -0
- package/dist/types/utils/schema/index.d.ts +1 -0
- package/dist/types/utils/schema/root-combinator.d.ts +12 -0
- package/package.json +2 -2
- package/src/model-thinking.ts +1 -1
- package/src/models.json +4974 -701
- package/src/models.ts +12 -5
- package/src/provider-models/openai-compat.ts +349 -60
- package/src/providers/amazon-bedrock.ts +2 -2
- package/src/providers/anthropic.ts +8 -123
- package/src/providers/azure-openai-responses.ts +2 -2
- package/src/providers/cursor.ts +2 -2
- package/src/providers/ollama.ts +2 -2
- package/src/providers/openai-codex-responses.ts +25 -5
- package/src/providers/openai-completions-compat.ts +2 -0
- package/src/providers/openai-completions.ts +12 -2
- package/src/providers/openai-responses-shared.ts +165 -85
- package/src/providers/openai-responses.ts +8 -2
- package/src/types.ts +11 -0
- package/src/utils/schema/index.ts +1 -0
- package/src/utils/schema/root-combinator.ts +143 -0
package/src/models.ts
CHANGED
|
@@ -33,14 +33,21 @@ function getProviderModels(provider: GeneratedProvider): Map<string, Model<Api>>
|
|
|
33
33
|
* Mythos rejecting forced tool use) without a full regeneration.
|
|
34
34
|
*/
|
|
35
35
|
function applyBundledCompatDefaults(model: Model<Api>): Model<Api> {
|
|
36
|
+
let normalized = model;
|
|
37
|
+
if (normalized.id === "minimax-m3" && normalized.name === "MiniMax M3") {
|
|
38
|
+
normalized = { ...normalized, name: "MiniMax-M3" };
|
|
39
|
+
}
|
|
36
40
|
if (
|
|
37
|
-
(
|
|
38
|
-
isClaudeForcedToolChoiceIncapableModelId(
|
|
39
|
-
(
|
|
41
|
+
(normalized.api === "anthropic-messages" || normalized.api === "bedrock-converse-stream") &&
|
|
42
|
+
isClaudeForcedToolChoiceIncapableModelId(normalized.id) &&
|
|
43
|
+
(normalized.compat as { toolChoiceSupport?: string } | undefined)?.toolChoiceSupport === undefined
|
|
40
44
|
) {
|
|
41
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
...normalized,
|
|
47
|
+
compat: { ...(normalized.compat ?? {}), toolChoiceSupport: "auto" } as Model<Api>["compat"],
|
|
48
|
+
};
|
|
42
49
|
}
|
|
43
|
-
return
|
|
50
|
+
return normalized;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export type GeneratedProvider = keyof typeof MODELS;
|
|
@@ -176,6 +176,57 @@ function mapWithBundledReference<TApi extends Api>(
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
function getNestedModelValue(entry: OpenAICompatibleModelRecord, path: readonly string[]): unknown {
|
|
180
|
+
let current: unknown = entry;
|
|
181
|
+
for (const segment of path) {
|
|
182
|
+
if (!isRecord(current)) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
current = current[segment];
|
|
186
|
+
}
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function firstPositiveModelNumber(fallback: number, ...candidates: readonly unknown[]): number {
|
|
191
|
+
for (const candidate of candidates) {
|
|
192
|
+
const value = toNumber(candidate);
|
|
193
|
+
if (value !== undefined && value > 0) {
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return fallback;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function mapLmStudioModel(
|
|
201
|
+
entry: OpenAICompatibleModelRecord,
|
|
202
|
+
defaults: Model<"openai-completions">,
|
|
203
|
+
reference: Model<"openai-completions"> | undefined,
|
|
204
|
+
): Model<"openai-completions"> {
|
|
205
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
206
|
+
return {
|
|
207
|
+
...model,
|
|
208
|
+
contextWindow: firstPositiveModelNumber(
|
|
209
|
+
model.contextWindow,
|
|
210
|
+
entry.context_length,
|
|
211
|
+
entry.max_context_length,
|
|
212
|
+
getNestedModelValue(entry, ["meta", "n_ctx"]),
|
|
213
|
+
getNestedModelValue(entry, ["details", "context_length"]),
|
|
214
|
+
getNestedModelValue(entry, ["details", "n_ctx"]),
|
|
215
|
+
getNestedModelValue(entry, ["meta", "n_ctx_train"]),
|
|
216
|
+
),
|
|
217
|
+
maxTokens: firstPositiveModelNumber(
|
|
218
|
+
model.maxTokens,
|
|
219
|
+
entry.max_completion_tokens,
|
|
220
|
+
entry.max_tokens,
|
|
221
|
+
entry.max_output_tokens,
|
|
222
|
+
getNestedModelValue(entry, ["details", "max_completion_tokens"]),
|
|
223
|
+
getNestedModelValue(entry, ["details", "max_tokens"]),
|
|
224
|
+
getNestedModelValue(entry, ["meta", "max_completion_tokens"]),
|
|
225
|
+
getNestedModelValue(entry, ["meta", "max_tokens"]),
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
179
230
|
function normalizeAnthropicBaseUrl(baseUrl: string | undefined, fallback: string): string {
|
|
180
231
|
const value = baseUrl?.trim();
|
|
181
232
|
if (!value) {
|
|
@@ -761,6 +812,8 @@ function openCodeModelManagerOptions(
|
|
|
761
812
|
): ModelManagerOptions<"openai-completions"> {
|
|
762
813
|
const apiKey = config?.apiKey;
|
|
763
814
|
const baseUrl = config?.baseUrl ?? defaultBaseUrl;
|
|
815
|
+
const references =
|
|
816
|
+
providerId === "opencode-go" ? createBundledReferenceMap<"openai-completions">(providerId) : undefined;
|
|
764
817
|
return {
|
|
765
818
|
providerId,
|
|
766
819
|
...(apiKey && {
|
|
@@ -770,6 +823,13 @@ function openCodeModelManagerOptions(
|
|
|
770
823
|
provider: providerId,
|
|
771
824
|
baseUrl,
|
|
772
825
|
apiKey,
|
|
826
|
+
...(providerId === "opencode-go" && {
|
|
827
|
+
mapModel: (entry, defaults) => {
|
|
828
|
+
const reference = references?.get(defaults.id);
|
|
829
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
830
|
+
return applyOpenCodeGoOfficialMetadata(model);
|
|
831
|
+
},
|
|
832
|
+
}),
|
|
773
833
|
}),
|
|
774
834
|
}),
|
|
775
835
|
};
|
|
@@ -1215,7 +1275,7 @@ export function lmStudioModelManagerOptions(
|
|
|
1215
1275
|
apiKey,
|
|
1216
1276
|
mapModel: (entry, defaults) => {
|
|
1217
1277
|
const reference = references.get(defaults.id);
|
|
1218
|
-
return
|
|
1278
|
+
return mapLmStudioModel(entry, defaults, reference);
|
|
1219
1279
|
},
|
|
1220
1280
|
}),
|
|
1221
1281
|
};
|
|
@@ -1870,6 +1930,8 @@ export interface ModelsDevProviderDescriptor {
|
|
|
1870
1930
|
* Can return null to skip the model, or an array to emit multiple models.
|
|
1871
1931
|
*/
|
|
1872
1932
|
transformModel?: (model: Model<Api>, modelId: string, raw: ModelsDevModel) => Model<Api> | Model<Api>[] | null;
|
|
1933
|
+
/** Optional static rows appended after mapped models. Used only for official provider catalogs missing from models.dev. */
|
|
1934
|
+
appendModels?: readonly Model<Api>[];
|
|
1873
1935
|
/**
|
|
1874
1936
|
* Optional: override the API type per-model.
|
|
1875
1937
|
* Called with (modelId, raw). Return the API type to use.
|
|
@@ -1886,56 +1948,59 @@ export function mapModelsDevToModels(
|
|
|
1886
1948
|
const models: Model<Api>[] = [];
|
|
1887
1949
|
for (const desc of descriptors) {
|
|
1888
1950
|
const providerData = (data as Record<string, Record<string, unknown>>)[desc.modelsDevKey];
|
|
1889
|
-
if (
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
}
|
|
1951
|
+
if (isRecord(providerData) && isRecord(providerData.models)) {
|
|
1952
|
+
for (const [modelId, rawModel] of Object.entries(providerData.models)) {
|
|
1953
|
+
if (!isRecord(rawModel)) continue;
|
|
1954
|
+
const m = rawModel as ModelsDevModel;
|
|
1955
|
+
|
|
1956
|
+
// Default filter: tool_call must be true
|
|
1957
|
+
if (desc.filterModel) {
|
|
1958
|
+
if (!desc.filterModel(modelId, m)) continue;
|
|
1959
|
+
} else {
|
|
1960
|
+
if (m.tool_call !== true) continue;
|
|
1961
|
+
}
|
|
1901
1962
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1963
|
+
// Resolve API and baseUrl (may be per-model for providers like OpenCode)
|
|
1964
|
+
const resolved = desc.resolveApi?.(modelId, m) ?? { api: desc.api, baseUrl: desc.baseUrl };
|
|
1965
|
+
if (!resolved) continue;
|
|
1966
|
+
|
|
1967
|
+
const mapped: Model<Api> = {
|
|
1968
|
+
id: modelId,
|
|
1969
|
+
name: toModelName(m.name, modelId),
|
|
1970
|
+
api: resolved.api,
|
|
1971
|
+
provider: desc.providerId as Model<Api>["provider"],
|
|
1972
|
+
baseUrl: resolved.baseUrl,
|
|
1973
|
+
reasoning: m.reasoning === true,
|
|
1974
|
+
input: toInputCapabilities(m.modalities?.input),
|
|
1975
|
+
cost: {
|
|
1976
|
+
input: toNumber(m.cost?.input) ?? 0,
|
|
1977
|
+
output: toNumber(m.cost?.output) ?? 0,
|
|
1978
|
+
cacheRead: toNumber(m.cost?.cache_read) ?? 0,
|
|
1979
|
+
cacheWrite: toNumber(m.cost?.cache_write) ?? 0,
|
|
1980
|
+
},
|
|
1981
|
+
contextWindow: toPositiveNumber(m.limit?.context, desc.defaultContextWindow ?? UNK_CONTEXT_WINDOW),
|
|
1982
|
+
maxTokens: toPositiveNumber(m.limit?.output, desc.defaultMaxTokens ?? UNK_MAX_TOKENS),
|
|
1983
|
+
...(desc.compat && { compat: desc.compat }),
|
|
1984
|
+
...(desc.headers && { headers: { ...desc.headers } }),
|
|
1985
|
+
};
|
|
1925
1986
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1987
|
+
// Apply per-model transform
|
|
1988
|
+
if (desc.transformModel) {
|
|
1989
|
+
const result = desc.transformModel(mapped, modelId, m);
|
|
1990
|
+
if (result === null) continue;
|
|
1991
|
+
if (Array.isArray(result)) {
|
|
1992
|
+
models.push(...result);
|
|
1993
|
+
} else {
|
|
1994
|
+
models.push(result);
|
|
1995
|
+
}
|
|
1932
1996
|
} else {
|
|
1933
|
-
models.push(
|
|
1997
|
+
models.push(mapped);
|
|
1934
1998
|
}
|
|
1935
|
-
} else {
|
|
1936
|
-
models.push(mapped);
|
|
1937
1999
|
}
|
|
1938
2000
|
}
|
|
2001
|
+
if (desc.appendModels) {
|
|
2002
|
+
models.push(...desc.appendModels);
|
|
2003
|
+
}
|
|
1939
2004
|
}
|
|
1940
2005
|
return models;
|
|
1941
2006
|
}
|
|
@@ -2025,22 +2090,35 @@ function createOpenCodeApiResolution(
|
|
|
2025
2090
|
};
|
|
2026
2091
|
}
|
|
2027
2092
|
|
|
2093
|
+
const OPENCODE_GO_BASE_PATH = "https://opencode.ai/zen/go";
|
|
2028
2094
|
const OPENCODE_ZEN_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen");
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
const
|
|
2040
|
-
"minimax-m2.
|
|
2041
|
-
"
|
|
2042
|
-
"
|
|
2043
|
-
|
|
2095
|
+
const OPENCODE_GO_CHAT_COMPLETIONS_MODEL_IDS = [
|
|
2096
|
+
"deepseek-v4-flash",
|
|
2097
|
+
"deepseek-v4-pro",
|
|
2098
|
+
"glm-5.1",
|
|
2099
|
+
"glm-5.2",
|
|
2100
|
+
"kimi-k2.6",
|
|
2101
|
+
"kimi-k2.7-code",
|
|
2102
|
+
"mimo-v2.5",
|
|
2103
|
+
"mimo-v2.5-pro",
|
|
2104
|
+
] as const;
|
|
2105
|
+
const OPENCODE_GO_MESSAGES_MODEL_IDS = [
|
|
2106
|
+
"minimax-m2.5",
|
|
2107
|
+
"minimax-m2.7",
|
|
2108
|
+
"minimax-m3",
|
|
2109
|
+
"qwen3.6-plus",
|
|
2110
|
+
"qwen3.7-max",
|
|
2111
|
+
"qwen3.7-plus",
|
|
2112
|
+
] as const;
|
|
2113
|
+
const OPENCODE_GO_API_OVERRIDES: Readonly<Record<string, Api>> = {
|
|
2114
|
+
...Object.fromEntries(OPENCODE_GO_CHAT_COMPLETIONS_MODEL_IDS.map(id => [id, "openai-completions"])),
|
|
2115
|
+
...Object.fromEntries(OPENCODE_GO_MESSAGES_MODEL_IDS.map(id => [id, "anthropic-messages"])),
|
|
2116
|
+
} as Record<string, Api>;
|
|
2117
|
+
// OpenCode Go has a provider-specific endpoint table at
|
|
2118
|
+
// https://opencode.ai/docs/go/#endpoints. Keep routing aligned with that table:
|
|
2119
|
+
// GLM/Kimi/DeepSeek/MiMo rows use /v1/chat/completions, while MiniMax and
|
|
2120
|
+
// current Qwen Plus/Max rows use /v1/messages via the Anthropic client.
|
|
2121
|
+
const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution(OPENCODE_GO_BASE_PATH, OPENCODE_GO_API_OVERRIDES);
|
|
2044
2122
|
|
|
2045
2123
|
const COPILOT_BASE_URL = "https://api.githubcopilot.com";
|
|
2046
2124
|
|
|
@@ -2269,6 +2347,215 @@ const filterActiveToolCallModels = (_id: string, m: ModelsDevModel): boolean =>
|
|
|
2269
2347
|
return true;
|
|
2270
2348
|
};
|
|
2271
2349
|
|
|
2350
|
+
interface OpenCodeGoOfficialModelMetadata {
|
|
2351
|
+
name: string;
|
|
2352
|
+
contextWindow: number;
|
|
2353
|
+
maxTokens: number;
|
|
2354
|
+
input: ("text" | "image")[];
|
|
2355
|
+
reasoning: boolean;
|
|
2356
|
+
cost: Model["cost"];
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
const OPENCODE_GO_OFFICIAL_MODELS: Readonly<Record<string, OpenCodeGoOfficialModelMetadata>> = {
|
|
2360
|
+
"deepseek-v4-flash": {
|
|
2361
|
+
name: "DeepSeek V4 Flash",
|
|
2362
|
+
contextWindow: 1_000_000,
|
|
2363
|
+
maxTokens: 384_000,
|
|
2364
|
+
input: ["text"],
|
|
2365
|
+
reasoning: true,
|
|
2366
|
+
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
|
2367
|
+
},
|
|
2368
|
+
"deepseek-v4-pro": {
|
|
2369
|
+
name: "DeepSeek V4 Pro",
|
|
2370
|
+
contextWindow: 1_000_000,
|
|
2371
|
+
maxTokens: 384_000,
|
|
2372
|
+
input: ["text"],
|
|
2373
|
+
reasoning: true,
|
|
2374
|
+
cost: { input: 1.74, output: 3.48, cacheRead: 0.0145, cacheWrite: 0 },
|
|
2375
|
+
},
|
|
2376
|
+
"glm-5": {
|
|
2377
|
+
name: "GLM-5",
|
|
2378
|
+
contextWindow: 204_800,
|
|
2379
|
+
maxTokens: 131_072,
|
|
2380
|
+
input: ["text"],
|
|
2381
|
+
reasoning: true,
|
|
2382
|
+
cost: { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
2383
|
+
},
|
|
2384
|
+
"glm-5.1": {
|
|
2385
|
+
name: "GLM-5.1",
|
|
2386
|
+
contextWindow: 200_000,
|
|
2387
|
+
maxTokens: 131_072,
|
|
2388
|
+
input: ["text"],
|
|
2389
|
+
reasoning: true,
|
|
2390
|
+
cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
2391
|
+
},
|
|
2392
|
+
"glm-5.2": {
|
|
2393
|
+
name: "GLM-5.2",
|
|
2394
|
+
contextWindow: 1_000_000,
|
|
2395
|
+
maxTokens: 131_072,
|
|
2396
|
+
input: ["text"],
|
|
2397
|
+
reasoning: true,
|
|
2398
|
+
cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
2399
|
+
},
|
|
2400
|
+
"kimi-k2.5": {
|
|
2401
|
+
name: "Kimi K2.5",
|
|
2402
|
+
contextWindow: 262_144,
|
|
2403
|
+
maxTokens: 262_144,
|
|
2404
|
+
input: ["text", "image"],
|
|
2405
|
+
reasoning: true,
|
|
2406
|
+
cost: { input: 0.3, output: 1.9, cacheRead: 0, cacheWrite: 0 },
|
|
2407
|
+
},
|
|
2408
|
+
"kimi-k2.6": {
|
|
2409
|
+
name: "Kimi K2.6",
|
|
2410
|
+
contextWindow: 262_144,
|
|
2411
|
+
maxTokens: 262_144,
|
|
2412
|
+
input: ["text", "image"],
|
|
2413
|
+
reasoning: true,
|
|
2414
|
+
cost: { input: 0.95, output: 4, cacheRead: 0.2, cacheWrite: 0 },
|
|
2415
|
+
},
|
|
2416
|
+
"kimi-k2.7-code": {
|
|
2417
|
+
name: "Kimi K2.7 Code",
|
|
2418
|
+
contextWindow: 262_144,
|
|
2419
|
+
maxTokens: 262_144,
|
|
2420
|
+
input: ["text", "image"],
|
|
2421
|
+
reasoning: true,
|
|
2422
|
+
cost: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
|
2423
|
+
},
|
|
2424
|
+
"minimax-m2.5": {
|
|
2425
|
+
name: "MiniMax M2.5",
|
|
2426
|
+
contextWindow: 204_800,
|
|
2427
|
+
maxTokens: 131_072,
|
|
2428
|
+
input: ["text"],
|
|
2429
|
+
reasoning: true,
|
|
2430
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
|
|
2431
|
+
},
|
|
2432
|
+
"minimax-m2.7": {
|
|
2433
|
+
name: "MiniMax M2.7",
|
|
2434
|
+
contextWindow: 204_800,
|
|
2435
|
+
maxTokens: 131_072,
|
|
2436
|
+
input: ["text"],
|
|
2437
|
+
reasoning: true,
|
|
2438
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
|
|
2439
|
+
},
|
|
2440
|
+
"minimax-m3": {
|
|
2441
|
+
name: "MiniMax M3",
|
|
2442
|
+
contextWindow: 512_000,
|
|
2443
|
+
maxTokens: 128_000,
|
|
2444
|
+
input: ["text", "image"],
|
|
2445
|
+
reasoning: true,
|
|
2446
|
+
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
2447
|
+
},
|
|
2448
|
+
"qwen3.5-plus": {
|
|
2449
|
+
name: "Qwen3.5 Plus",
|
|
2450
|
+
contextWindow: 1_000_000,
|
|
2451
|
+
maxTokens: 65_536,
|
|
2452
|
+
input: ["text", "image"],
|
|
2453
|
+
reasoning: true,
|
|
2454
|
+
cost: { input: 0.4, output: 2.4, cacheRead: 0, cacheWrite: 0 },
|
|
2455
|
+
},
|
|
2456
|
+
"qwen3.6-plus": {
|
|
2457
|
+
name: "Qwen3.6 Plus",
|
|
2458
|
+
contextWindow: 1_000_000,
|
|
2459
|
+
maxTokens: 65_536,
|
|
2460
|
+
input: ["text", "image"],
|
|
2461
|
+
reasoning: true,
|
|
2462
|
+
cost: { input: 2, output: 6, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
2463
|
+
},
|
|
2464
|
+
"qwen3.7-max": {
|
|
2465
|
+
name: "Qwen3.7 Max",
|
|
2466
|
+
contextWindow: 1_000_000,
|
|
2467
|
+
maxTokens: 65_536,
|
|
2468
|
+
input: ["text"],
|
|
2469
|
+
reasoning: true,
|
|
2470
|
+
cost: { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.125 },
|
|
2471
|
+
},
|
|
2472
|
+
"qwen3.7-plus": {
|
|
2473
|
+
name: "Qwen3.7 Plus",
|
|
2474
|
+
contextWindow: 1_000_000,
|
|
2475
|
+
maxTokens: 64_000,
|
|
2476
|
+
input: ["text", "image"],
|
|
2477
|
+
reasoning: true,
|
|
2478
|
+
cost: { input: 1.2, output: 4.8, cacheRead: 0.12, cacheWrite: 1.5 },
|
|
2479
|
+
},
|
|
2480
|
+
"mimo-v2-omni": {
|
|
2481
|
+
name: "MiMo-V2-Omni",
|
|
2482
|
+
contextWindow: 262_144,
|
|
2483
|
+
maxTokens: 131_072,
|
|
2484
|
+
input: ["text", "image"],
|
|
2485
|
+
reasoning: true,
|
|
2486
|
+
cost: { input: 0.4, output: 2, cacheRead: 0.08, cacheWrite: 0 },
|
|
2487
|
+
},
|
|
2488
|
+
"mimo-v2-pro": {
|
|
2489
|
+
name: "MiMo-V2-Pro",
|
|
2490
|
+
contextWindow: 1_048_576,
|
|
2491
|
+
maxTokens: 131_072,
|
|
2492
|
+
input: ["text"],
|
|
2493
|
+
reasoning: true,
|
|
2494
|
+
cost: { input: 1, output: 3, cacheRead: 0.2, cacheWrite: 0 },
|
|
2495
|
+
},
|
|
2496
|
+
"mimo-v2.5": {
|
|
2497
|
+
name: "MiMo-V2.5",
|
|
2498
|
+
contextWindow: 1_048_576,
|
|
2499
|
+
maxTokens: 131_072,
|
|
2500
|
+
input: ["text", "image"],
|
|
2501
|
+
reasoning: true,
|
|
2502
|
+
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
|
2503
|
+
},
|
|
2504
|
+
"mimo-v2.5-pro": {
|
|
2505
|
+
name: "MiMo-V2.5-Pro",
|
|
2506
|
+
contextWindow: 1_048_576,
|
|
2507
|
+
maxTokens: 131_072,
|
|
2508
|
+
input: ["text"],
|
|
2509
|
+
reasoning: true,
|
|
2510
|
+
cost: { input: 1.74, output: 3.48, cacheRead: 0.0145, cacheWrite: 0 },
|
|
2511
|
+
},
|
|
2512
|
+
"hy3-preview": {
|
|
2513
|
+
name: "Hy3 preview",
|
|
2514
|
+
contextWindow: 256_000,
|
|
2515
|
+
maxTokens: 64_000,
|
|
2516
|
+
input: ["text"],
|
|
2517
|
+
reasoning: true,
|
|
2518
|
+
cost: { input: 0.066, output: 0.26, cacheRead: 0.029, cacheWrite: 0 },
|
|
2519
|
+
},
|
|
2520
|
+
};
|
|
2521
|
+
|
|
2522
|
+
function applyOpenCodeGoOfficialMetadata<TApi extends Api>(model: Model<TApi>): Model<TApi> {
|
|
2523
|
+
const metadata = OPENCODE_GO_OFFICIAL_MODELS[model.id];
|
|
2524
|
+
if (!metadata) return model;
|
|
2525
|
+
return {
|
|
2526
|
+
...model,
|
|
2527
|
+
name: metadata.name,
|
|
2528
|
+
reasoning: metadata.reasoning,
|
|
2529
|
+
input: [...metadata.input],
|
|
2530
|
+
cost: { ...metadata.cost },
|
|
2531
|
+
contextWindow: metadata.contextWindow,
|
|
2532
|
+
maxTokens: metadata.maxTokens,
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
function createOpenCodeGoOfficialModels(): Model<Api>[] {
|
|
2537
|
+
return Object.entries(OPENCODE_GO_OFFICIAL_MODELS).map(([id, metadata]) => {
|
|
2538
|
+
const resolved = resolveApiByRules(
|
|
2539
|
+
id,
|
|
2540
|
+
{},
|
|
2541
|
+
OPENCODE_GO_API_RESOLUTION.rules,
|
|
2542
|
+
OPENCODE_GO_API_RESOLUTION.defaultResolution,
|
|
2543
|
+
);
|
|
2544
|
+
return {
|
|
2545
|
+
id,
|
|
2546
|
+
name: metadata.name,
|
|
2547
|
+
api: resolved.api,
|
|
2548
|
+
provider: "opencode-go",
|
|
2549
|
+
baseUrl: resolved.baseUrl,
|
|
2550
|
+
reasoning: metadata.reasoning,
|
|
2551
|
+
input: [...metadata.input],
|
|
2552
|
+
cost: { ...metadata.cost },
|
|
2553
|
+
contextWindow: metadata.contextWindow,
|
|
2554
|
+
maxTokens: metadata.maxTokens,
|
|
2555
|
+
};
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2272
2559
|
const MODELS_DEV_PROVIDER_DESCRIPTORS_SPECIALIZED: readonly ModelsDevProviderDescriptor[] = [
|
|
2273
2560
|
// --- Cloudflare AI Gateway ---
|
|
2274
2561
|
anthropicMessagesDescriptor(
|
|
@@ -2299,6 +2586,8 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_SPECIALIZED: readonly ModelsDevProviderDes
|
|
|
2299
2586
|
OPENCODE_GO_API_RESOLUTION.rules,
|
|
2300
2587
|
OPENCODE_GO_API_RESOLUTION.defaultResolution,
|
|
2301
2588
|
),
|
|
2589
|
+
transformModel: model => applyOpenCodeGoOfficialMetadata(model),
|
|
2590
|
+
appendModels: createOpenCodeGoOfficialModels(),
|
|
2302
2591
|
}),
|
|
2303
2592
|
// --- GitHub Copilot ---
|
|
2304
2593
|
openAiCompletionsDescriptor("github-copilot", "github-copilot", COPILOT_BASE_URL, {
|
|
@@ -33,7 +33,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
33
33
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
34
34
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
35
35
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
36
|
-
import { toolWireSchema } from "../utils/schema
|
|
36
|
+
import { flattenToolRootCombinators, toolWireSchema } from "../utils/schema";
|
|
37
37
|
import {
|
|
38
38
|
isForcedToolChoiceUnsupportedError,
|
|
39
39
|
markToolChoiceIncapability,
|
|
@@ -808,7 +808,7 @@ export function convertToolConfig(
|
|
|
808
808
|
toolSpec: {
|
|
809
809
|
name: tool.name,
|
|
810
810
|
description: tool.description || "",
|
|
811
|
-
inputSchema: { json: toolWireSchema(tool) },
|
|
811
|
+
inputSchema: { json: flattenToolRootCombinators(toolWireSchema(tool)) },
|
|
812
812
|
},
|
|
813
813
|
}));
|
|
814
814
|
|
|
@@ -62,7 +62,13 @@ import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
|
62
62
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
63
63
|
import { isCopilotTransientModelError } from "../utils/retry";
|
|
64
64
|
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
65
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
COMBINATOR_KEYS,
|
|
67
|
+
flattenToolRootCombinators,
|
|
68
|
+
isJsonSchemaObjectNode,
|
|
69
|
+
NO_STRICT,
|
|
70
|
+
toolWireSchema,
|
|
71
|
+
} from "../utils/schema";
|
|
66
72
|
import { spillToDescription } from "../utils/schema/spill";
|
|
67
73
|
import { notifyRawSseEvent, wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
68
74
|
import {
|
|
@@ -2406,22 +2412,6 @@ const MAX_ANTHROPIC_STRICT_TOOLS = 20;
|
|
|
2406
2412
|
const MAX_ANTHROPIC_STRICT_OPTIONAL_PARAMETERS = 24;
|
|
2407
2413
|
const MAX_ANTHROPIC_STRICT_UNION_PARAMETERS = 16;
|
|
2408
2414
|
|
|
2409
|
-
/** `minItems` / `maxItems` apply to arrays; Anthropic rejects them on `type: "object"` (including `minItems: 0`/`1`). */
|
|
2410
|
-
function isJsonSchemaArrayNode(schema: Record<string, unknown>): boolean {
|
|
2411
|
-
const t = schema.type;
|
|
2412
|
-
if (t === "array") return true;
|
|
2413
|
-
if (Array.isArray(t) && t.includes("array") && !t.includes("object")) return true;
|
|
2414
|
-
return false;
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
function isJsonSchemaObjectNode(schema: Record<string, unknown>): boolean {
|
|
2418
|
-
if (isJsonSchemaArrayNode(schema)) return false;
|
|
2419
|
-
if (schema.type === "object") return true;
|
|
2420
|
-
if (Array.isArray(schema.type) && schema.type.includes("object")) return true;
|
|
2421
|
-
if (isRecord(schema.properties)) return true;
|
|
2422
|
-
return false;
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
2415
|
/**
|
|
2426
2416
|
* Pick the principal non-null scalar type from a `type` keyword. Anthropic accepts
|
|
2427
2417
|
* `type` as either a single string or an array (e.g. `["number", "null"]` for a
|
|
@@ -2573,111 +2563,6 @@ export function normalizeAnthropicToolSchema(schema: unknown): unknown {
|
|
|
2573
2563
|
return result;
|
|
2574
2564
|
}
|
|
2575
2565
|
|
|
2576
|
-
function getRequiredNames(schema: Record<string, unknown>): Set<string> {
|
|
2577
|
-
return new Set(
|
|
2578
|
-
Array.isArray(schema.required)
|
|
2579
|
-
? schema.required.filter((entry): entry is string => typeof entry === "string")
|
|
2580
|
-
: [],
|
|
2581
|
-
);
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
function getSingleLiteralValue(schema: unknown): unknown | undefined {
|
|
2585
|
-
if (!isRecord(schema)) return undefined;
|
|
2586
|
-
if (Object.hasOwn(schema, "const")) return schema.const;
|
|
2587
|
-
if (Array.isArray(schema.enum) && schema.enum.length === 1) return schema.enum[0];
|
|
2588
|
-
return undefined;
|
|
2589
|
-
}
|
|
2590
|
-
|
|
2591
|
-
function describeAnthropicRootBranch(index: number, branch: Record<string, unknown>, action: unknown): string {
|
|
2592
|
-
const required = [...getRequiredNames(branch)];
|
|
2593
|
-
const parts = [`Branch ${index + 1}`];
|
|
2594
|
-
if (typeof action === "string" || typeof action === "number" || typeof action === "boolean") {
|
|
2595
|
-
parts.push(`action ${JSON.stringify(action)}`);
|
|
2596
|
-
}
|
|
2597
|
-
if (required.length > 0) parts.push(`branch-required fields: ${required.join(", ")}`);
|
|
2598
|
-
if (typeof branch.description === "string" && branch.description.length > 0) parts.push(branch.description);
|
|
2599
|
-
return parts.join("; ");
|
|
2600
|
-
}
|
|
2601
|
-
function collectAnthropicRootObjectBranches(schema: unknown): Record<string, unknown>[] | undefined {
|
|
2602
|
-
if (!isRecord(schema)) return undefined;
|
|
2603
|
-
if (isJsonSchemaObjectNode(schema)) return [schema];
|
|
2604
|
-
|
|
2605
|
-
const combinatorKeys = COMBINATOR_KEYS.filter(key => Array.isArray(schema[key]));
|
|
2606
|
-
if (combinatorKeys.length === 0) return undefined;
|
|
2607
|
-
|
|
2608
|
-
const branches: Record<string, unknown>[] = [];
|
|
2609
|
-
for (const key of combinatorKeys) {
|
|
2610
|
-
const variants = schema[key];
|
|
2611
|
-
if (!Array.isArray(variants) || variants.length === 0) return undefined;
|
|
2612
|
-
for (const variant of variants) {
|
|
2613
|
-
const nestedBranches = collectAnthropicRootObjectBranches(variant);
|
|
2614
|
-
if (nestedBranches === undefined) return undefined;
|
|
2615
|
-
branches.push(...nestedBranches);
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
return branches;
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
/**
|
|
2622
|
-
* Anthropic rejects tool `input_schema` roots containing top-level oneOf/anyOf/allOf.
|
|
2623
|
-
* Keep the generic normalizer schema-preserving, then flatten only provider-emitted
|
|
2624
|
-
* tool roots into one object while leaving nested combinators untouched.
|
|
2625
|
-
*/
|
|
2626
|
-
export function normalizeAnthropicToolRootInputSchema(schema: Record<string, unknown>): Record<string, unknown> {
|
|
2627
|
-
const result: Record<string, unknown> = { ...schema };
|
|
2628
|
-
const rootCombinators = COMBINATOR_KEYS.filter(key => Array.isArray(result[key]));
|
|
2629
|
-
if (rootCombinators.length === 0) return result;
|
|
2630
|
-
|
|
2631
|
-
const baseProperties = isRecord(result.properties) ? { ...result.properties } : {};
|
|
2632
|
-
const flattenedBranches = rootCombinators
|
|
2633
|
-
.map(key => ({ key, branches: collectAnthropicRootObjectBranches({ [key]: result[key] }) }))
|
|
2634
|
-
.find(entry => entry.branches !== undefined && entry.branches.length > 0);
|
|
2635
|
-
|
|
2636
|
-
result.type = "object";
|
|
2637
|
-
result.properties = baseProperties;
|
|
2638
|
-
result.additionalProperties = result.additionalProperties === undefined ? false : result.additionalProperties;
|
|
2639
|
-
|
|
2640
|
-
if (flattenedBranches?.branches !== undefined) {
|
|
2641
|
-
const variants = flattenedBranches.branches;
|
|
2642
|
-
const commonRequired = variants.map(variant => getRequiredNames(variant));
|
|
2643
|
-
const required = [...commonRequired[0]].filter(name => commonRequired.every(set => set.has(name)));
|
|
2644
|
-
const actionValues: unknown[] = [];
|
|
2645
|
-
const guidance: string[] = [];
|
|
2646
|
-
|
|
2647
|
-
for (const [index, branch] of variants.entries()) {
|
|
2648
|
-
if (isRecord(branch.properties)) {
|
|
2649
|
-
Object.assign(baseProperties, branch.properties);
|
|
2650
|
-
const actionValue = getSingleLiteralValue(branch.properties.action);
|
|
2651
|
-
if (actionValue !== undefined && !actionValues.includes(actionValue)) actionValues.push(actionValue);
|
|
2652
|
-
guidance.push(describeAnthropicRootBranch(index, branch, actionValue));
|
|
2653
|
-
} else {
|
|
2654
|
-
guidance.push(describeAnthropicRootBranch(index, branch, undefined));
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
if (actionValues.length > 0) {
|
|
2659
|
-
const existingAction = isRecord(baseProperties.action) ? { ...baseProperties.action } : {};
|
|
2660
|
-
delete existingAction.const;
|
|
2661
|
-
baseProperties.action = { ...existingAction, enum: actionValues };
|
|
2662
|
-
}
|
|
2663
|
-
result.required = required;
|
|
2664
|
-
spillToDescription(result, [
|
|
2665
|
-
["rootCombinatorGuidance", guidance],
|
|
2666
|
-
...rootCombinators
|
|
2667
|
-
.filter(key => key !== flattenedBranches.key)
|
|
2668
|
-
.map(key => [key, result[key]] as [string, unknown]),
|
|
2669
|
-
]);
|
|
2670
|
-
} else {
|
|
2671
|
-
spillToDescription(
|
|
2672
|
-
result,
|
|
2673
|
-
rootCombinators.map(key => [key, result[key]]),
|
|
2674
|
-
);
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
for (const key of COMBINATOR_KEYS) delete result[key];
|
|
2678
|
-
return result;
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
2566
|
type AnthropicToolInputSchema = Anthropic.Messages.Tool["input_schema"];
|
|
2682
2567
|
|
|
2683
2568
|
type AnthropicToolSchemaPlan = {
|
|
@@ -2845,7 +2730,7 @@ function normalizeAnthropicStrictSchema(
|
|
|
2845
2730
|
|
|
2846
2731
|
function buildAnthropicBaseToolInputSchema(tool: Tool): Record<string, unknown> {
|
|
2847
2732
|
const jsonSchema = toolWireSchema(tool);
|
|
2848
|
-
return
|
|
2733
|
+
return flattenToolRootCombinators(
|
|
2849
2734
|
normalizeAnthropicToolSchema({
|
|
2850
2735
|
...jsonSchema,
|
|
2851
2736
|
type: "object",
|