Merge remote-tracking branch 'origin/main' into add-kimi-deferred-tools
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import {
|
||||
CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL,
|
||||
@@ -22,6 +22,40 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageRoot = join(__dirname, "..");
|
||||
|
||||
function readGeneratorOptions(args: string[]): {
|
||||
strict: boolean;
|
||||
jsonOnly: boolean;
|
||||
jsonOutputDir: string | undefined;
|
||||
} {
|
||||
let strict = false;
|
||||
let jsonOnly = false;
|
||||
let jsonOutputDir: string | undefined;
|
||||
|
||||
for (let index = 0; index < args.length; index++) {
|
||||
const arg = args[index];
|
||||
if (arg === "--strict") {
|
||||
strict = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--json-only") {
|
||||
jsonOnly = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--json-output") {
|
||||
const value = args[++index];
|
||||
if (!value) throw new Error("--json-output requires a directory");
|
||||
jsonOutputDir = resolve(value);
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
if (jsonOnly && !jsonOutputDir) throw new Error("--json-only requires --json-output");
|
||||
return { strict, jsonOnly, jsonOutputDir };
|
||||
}
|
||||
|
||||
const generatorOptions = readGeneratorOptions(process.argv.slice(2));
|
||||
|
||||
interface ModelsDevModel {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -191,6 +225,16 @@ const DEEPSEEK_V4_THINKING_LEVEL_MAP = {
|
||||
max: "max",
|
||||
} as const;
|
||||
|
||||
const KIMI_K3_THINKING_LEVEL_MAP = {
|
||||
off: null,
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: null,
|
||||
xhigh: null,
|
||||
max: "max",
|
||||
} as const;
|
||||
|
||||
const ANT_LING_RING_THINKING_LEVEL_MAP = {
|
||||
off: null,
|
||||
minimal: null,
|
||||
@@ -255,6 +299,14 @@ const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
]);
|
||||
const XAI_RESPONSES_MODEL_ID = "grok-4.5";
|
||||
const XAI_RESPONSES_EFFORT_LEVEL_MAP = {
|
||||
off: null,
|
||||
minimal: null,
|
||||
} as const;
|
||||
const XAI_RESPONSES_COMPAT: OpenAIResponsesCompat = {
|
||||
supportsLongCacheRetention: false,
|
||||
};
|
||||
|
||||
const OPENCODE_OPENAI_COMPLETIONS_LONG_CACHE_RETENTION_UNSUPPORTED_MODELS = new Set([
|
||||
"opencode:deepseek-v4-flash",
|
||||
@@ -539,6 +591,9 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
|
||||
) {
|
||||
mergeThinkingLevelMap(model, { off: "none" });
|
||||
}
|
||||
if (model.provider === "xai" && model.api === "openai-responses" && model.id === XAI_RESPONSES_MODEL_ID) {
|
||||
mergeThinkingLevelMap(model, XAI_RESPONSES_EFFORT_LEVEL_MAP);
|
||||
}
|
||||
if (supportsOpenAiXhigh(model.id)) {
|
||||
mergeThinkingLevelMap(model, { xhigh: "xhigh" });
|
||||
}
|
||||
@@ -678,6 +733,7 @@ async function fetchNvidiaNimModelIds(): Promise<Map<string, string>> {
|
||||
try {
|
||||
console.log("Fetching models from NVIDIA NIM API...");
|
||||
const response = await fetch(`${NVIDIA_BASE_URL}/models`);
|
||||
if (!response.ok) throw new Error(`NVIDIA NIM API returned ${response.status}`);
|
||||
const data = (await response.json()) as { data?: NvidiaNimModelListItem[] };
|
||||
const modelIds = new Map<string, string>();
|
||||
|
||||
@@ -690,6 +746,7 @@ async function fetchNvidiaNimModelIds(): Promise<Map<string, string>> {
|
||||
return modelIds;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch NVIDIA NIM models:", error);
|
||||
if (generatorOptions.strict) throw error;
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
@@ -698,6 +755,7 @@ async function fetchOpenRouterModels(): Promise<Model<any>[]> {
|
||||
try {
|
||||
console.log("Fetching models from OpenRouter API...");
|
||||
const response = await fetch("https://openrouter.ai/api/v1/models");
|
||||
if (!response.ok) throw new Error(`OpenRouter API returned ${response.status}`);
|
||||
const data = await response.json();
|
||||
|
||||
const models: Model<any>[] = [];
|
||||
@@ -750,6 +808,7 @@ async function fetchOpenRouterModels(): Promise<Model<any>[]> {
|
||||
return models;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch OpenRouter models:", error);
|
||||
if (generatorOptions.strict) throw error;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -758,6 +817,7 @@ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
|
||||
try {
|
||||
console.log("Fetching models from Vercel AI Gateway API...");
|
||||
const response = await fetch(`${AI_GATEWAY_MODELS_URL}/models`);
|
||||
if (!response.ok) throw new Error(`Vercel AI Gateway API returned ${response.status}`);
|
||||
const data = await response.json();
|
||||
const models: Model<any>[] = [];
|
||||
|
||||
@@ -808,6 +868,7 @@ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
|
||||
return models;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch Vercel AI Gateway models:", error);
|
||||
if (generatorOptions.strict) throw error;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -816,6 +877,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
try {
|
||||
console.log("Fetching models from models.dev API...");
|
||||
const response = await fetch("https://models.dev/api.json");
|
||||
if (!response.ok) throw new Error(`models.dev API returned ${response.status}`);
|
||||
const data = await response.json();
|
||||
|
||||
const models: Model<any>[] = [];
|
||||
@@ -1127,13 +1189,15 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
for (const [modelId, model] of Object.entries(data.xai.models)) {
|
||||
const m = model as ModelsDevModel;
|
||||
if (m.tool_call !== true) continue;
|
||||
const useResponsesApi = modelId === XAI_RESPONSES_MODEL_ID;
|
||||
|
||||
models.push({
|
||||
id: modelId,
|
||||
name: m.name || modelId,
|
||||
api: "openai-completions",
|
||||
api: useResponsesApi ? "openai-responses" : "openai-completions",
|
||||
provider: "xai",
|
||||
baseUrl: "https://api.x.ai/v1",
|
||||
...(useResponsesApi ? { compat: { ...XAI_RESPONSES_COMPAT } } : {}),
|
||||
reasoning: m.reasoning === true,
|
||||
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||
cost: {
|
||||
@@ -1610,13 +1674,15 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
for (const [modelId, m] of Object.entries(moonshotModels[key])) {
|
||||
if (m.tool_call !== true) continue;
|
||||
|
||||
const isKimiK3 = modelId === "kimi-k3";
|
||||
models.push({
|
||||
id: modelId,
|
||||
name: m.name || modelId,
|
||||
api: "openai-completions",
|
||||
provider,
|
||||
baseUrl,
|
||||
reasoning: m.reasoning === true,
|
||||
reasoning: isKimiK3 || m.reasoning === true,
|
||||
...(isKimiK3 ? { thinkingLevelMap: KIMI_K3_THINKING_LEVEL_MAP } : {}),
|
||||
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||
cost: {
|
||||
input: m.cost?.input || 0,
|
||||
@@ -1626,7 +1692,9 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
},
|
||||
contextWindow: m.limit?.context || 4096,
|
||||
maxTokens: m.limit?.output || 4096,
|
||||
compat: moonshotCompat,
|
||||
compat: isKimiK3
|
||||
? { ...moonshotCompat, requiresReasoningContentOnAssistantMessages: true }
|
||||
: moonshotCompat,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1691,6 +1759,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
return models;
|
||||
} catch (error) {
|
||||
console.error("Failed to load models.dev data:", error);
|
||||
if (generatorOptions.strict) throw error;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -2224,85 +2293,110 @@ async function generateModels() {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate TypeScript files: one catalog per provider plus an aggregator
|
||||
const generatedHeader = `// This file is auto-generated by scripts/generate-models.ts
|
||||
const sortedProviderIds = Object.keys(providers).sort();
|
||||
|
||||
if (!generatorOptions.jsonOnly) {
|
||||
// Generate TypeScript files: one catalog per provider plus an aggregator
|
||||
const generatedHeader = `// This file is auto-generated by scripts/generate-models.ts
|
||||
// Do not edit manually - run 'npm run generate-models' to update
|
||||
|
||||
`;
|
||||
const catalogConstName = (providerId: string) => `${providerId.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_MODELS`;
|
||||
const catalogConstName = (providerId: string) =>
|
||||
`${providerId.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_MODELS`;
|
||||
|
||||
function emitModel(model: Model<any>, indent: string): string {
|
||||
let output = `${indent}"${model.id}": {\n`;
|
||||
output += `${indent}\tid: "${model.id}",\n`;
|
||||
output += `${indent}\tname: "${model.name}",\n`;
|
||||
output += `${indent}\tapi: "${model.api}",\n`;
|
||||
output += `${indent}\tprovider: "${model.provider}",\n`;
|
||||
if (model.baseUrl !== undefined) {
|
||||
output += `${indent}\tbaseUrl: "${model.baseUrl}",\n`;
|
||||
function emitModel(model: Model<any>, indent: string): string {
|
||||
let output = `${indent}"${model.id}": {\n`;
|
||||
output += `${indent}\tid: "${model.id}",\n`;
|
||||
output += `${indent}\tname: "${model.name}",\n`;
|
||||
output += `${indent}\tapi: "${model.api}",\n`;
|
||||
output += `${indent}\tprovider: "${model.provider}",\n`;
|
||||
if (model.baseUrl !== undefined) {
|
||||
output += `${indent}\tbaseUrl: "${model.baseUrl}",\n`;
|
||||
}
|
||||
if (model.headers) {
|
||||
output += `${indent}\theaders: ${JSON.stringify(model.headers)},\n`;
|
||||
}
|
||||
if (model.compat) {
|
||||
output += `${indent}\tcompat: ${JSON.stringify(model.compat)},\n`;
|
||||
}
|
||||
output += `${indent}\treasoning: ${model.reasoning},\n`;
|
||||
if (model.thinkingLevelMap) {
|
||||
output += `${indent}\tthinkingLevelMap: ${JSON.stringify(model.thinkingLevelMap)},\n`;
|
||||
}
|
||||
output += `${indent}\tinput: [${model.input.map(i => `"${i}"`).join(", ")}],\n`;
|
||||
output += `${indent}\tcost: {\n`;
|
||||
output += `${indent}\t\tinput: ${model.cost.input},\n`;
|
||||
output += `${indent}\t\toutput: ${model.cost.output},\n`;
|
||||
output += `${indent}\t\tcacheRead: ${model.cost.cacheRead},\n`;
|
||||
output += `${indent}\t\tcacheWrite: ${model.cost.cacheWrite},\n`;
|
||||
if (model.cost.tiers) {
|
||||
output += `${indent}\t\ttiers: ${JSON.stringify(model.cost.tiers)},\n`;
|
||||
}
|
||||
output += `${indent}\t},\n`;
|
||||
output += `${indent}\tcontextWindow: ${model.contextWindow},\n`;
|
||||
output += `${indent}\tmaxTokens: ${model.maxTokens},\n`;
|
||||
output += `${indent}} satisfies Model<"${model.api}">,\n`;
|
||||
return output;
|
||||
}
|
||||
if (model.headers) {
|
||||
output += `${indent}\theaders: ${JSON.stringify(model.headers)},\n`;
|
||||
}
|
||||
if (model.compat) {
|
||||
output += `${indent}\tcompat: ${JSON.stringify(model.compat)},\n`;
|
||||
}
|
||||
output += `${indent}\treasoning: ${model.reasoning},\n`;
|
||||
if (model.thinkingLevelMap) {
|
||||
output += `${indent}\tthinkingLevelMap: ${JSON.stringify(model.thinkingLevelMap)},\n`;
|
||||
}
|
||||
output += `${indent}\tinput: [${model.input.map(i => `"${i}"`).join(", ")}],\n`;
|
||||
output += `${indent}\tcost: {\n`;
|
||||
output += `${indent}\t\tinput: ${model.cost.input},\n`;
|
||||
output += `${indent}\t\toutput: ${model.cost.output},\n`;
|
||||
output += `${indent}\t\tcacheRead: ${model.cost.cacheRead},\n`;
|
||||
output += `${indent}\t\tcacheWrite: ${model.cost.cacheWrite},\n`;
|
||||
if (model.cost.tiers) {
|
||||
output += `${indent}\t\ttiers: ${JSON.stringify(model.cost.tiers)},\n`;
|
||||
}
|
||||
output += `${indent}\t},\n`;
|
||||
output += `${indent}\tcontextWindow: ${model.contextWindow},\n`;
|
||||
output += `${indent}\tmaxTokens: ${model.maxTokens},\n`;
|
||||
output += `${indent}} satisfies Model<"${model.api}">,\n`;
|
||||
return output;
|
||||
}
|
||||
|
||||
const sortedProviderIds = Object.keys(providers).sort();
|
||||
const providersDir = join(packageRoot, "src/providers");
|
||||
const providersDir = join(packageRoot, "src/providers");
|
||||
|
||||
// Remove stale per-provider catalogs
|
||||
for (const entry of readdirSync(providersDir)) {
|
||||
if (entry.endsWith(".models.ts")) {
|
||||
rmSync(join(providersDir, entry));
|
||||
// Remove stale per-provider catalogs
|
||||
for (const entry of readdirSync(providersDir)) {
|
||||
if (entry.endsWith(".models.ts")) {
|
||||
rmSync(join(providersDir, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-provider catalogs (sorted for deterministic output)
|
||||
for (const providerId of sortedProviderIds) {
|
||||
const models = providers[providerId];
|
||||
// Per-provider catalogs (sorted for deterministic output)
|
||||
for (const providerId of sortedProviderIds) {
|
||||
const models = providers[providerId];
|
||||
let output = generatedHeader;
|
||||
output += `import type { Model } from "../types.ts";\n\n`;
|
||||
output += `export const ${catalogConstName(providerId)} = {\n`;
|
||||
const sortedModelIds = Object.keys(models).sort();
|
||||
for (const modelId of sortedModelIds) {
|
||||
output += emitModel(models[modelId], "\t");
|
||||
}
|
||||
output += `} as const;\n`;
|
||||
writeFileSync(join(providersDir, `${providerId}.models.ts`), output);
|
||||
}
|
||||
console.log(`Generated ${sortedProviderIds.length} catalogs under src/providers/`);
|
||||
|
||||
// Aggregator
|
||||
let output = generatedHeader;
|
||||
output += `import type { Model } from "../types.ts";\n\n`;
|
||||
output += `export const ${catalogConstName(providerId)} = {\n`;
|
||||
const sortedModelIds = Object.keys(models).sort();
|
||||
for (const modelId of sortedModelIds) {
|
||||
output += emitModel(models[modelId], "\t");
|
||||
for (const providerId of sortedProviderIds) {
|
||||
output += `import { ${catalogConstName(providerId)} } from "./providers/${providerId}.models.ts";\n`;
|
||||
}
|
||||
output += `\nexport const MODELS = {\n`;
|
||||
for (const providerId of sortedProviderIds) {
|
||||
output += `\t${JSON.stringify(providerId)}: ${catalogConstName(providerId)},\n`;
|
||||
}
|
||||
output += `} as const;\n`;
|
||||
writeFileSync(join(providersDir, `${providerId}.models.ts`), output);
|
||||
writeFileSync(join(packageRoot, "src/models.generated.ts"), output);
|
||||
console.log("Generated src/models.generated.ts");
|
||||
}
|
||||
console.log(`Generated ${sortedProviderIds.length} catalogs under src/providers/`);
|
||||
|
||||
// Aggregator
|
||||
let output = generatedHeader;
|
||||
for (const providerId of sortedProviderIds) {
|
||||
output += `import { ${catalogConstName(providerId)} } from "./providers/${providerId}.models.ts";\n`;
|
||||
if (generatorOptions.jsonOutputDir) {
|
||||
const jsonProviders: Record<string, Record<string, Model<any>>> = {};
|
||||
for (const providerId of sortedProviderIds) {
|
||||
jsonProviders[providerId] = {};
|
||||
for (const modelId of Object.keys(providers[providerId]).sort()) {
|
||||
jsonProviders[providerId][modelId] = providers[providerId][modelId];
|
||||
}
|
||||
}
|
||||
|
||||
const providerOutputDir = join(generatorOptions.jsonOutputDir, "providers");
|
||||
rmSync(generatorOptions.jsonOutputDir, { recursive: true, force: true });
|
||||
mkdirSync(providerOutputDir, { recursive: true });
|
||||
const writeJson = (path: string, value: unknown) => writeFileSync(path, `${JSON.stringify(value)}\n`);
|
||||
writeJson(join(generatorOptions.jsonOutputDir, "models.json"), jsonProviders);
|
||||
writeJson(join(generatorOptions.jsonOutputDir, "providers.json"), sortedProviderIds);
|
||||
for (const providerId of sortedProviderIds) {
|
||||
writeJson(join(providerOutputDir, `${providerId}.json`), jsonProviders[providerId]);
|
||||
}
|
||||
console.log(`Generated JSON model catalog under ${generatorOptions.jsonOutputDir}`);
|
||||
}
|
||||
output += `\nexport const MODELS = {\n`;
|
||||
for (const providerId of sortedProviderIds) {
|
||||
output += `\t${JSON.stringify(providerId)}: ${catalogConstName(providerId)},\n`;
|
||||
}
|
||||
output += `} as const;\n`;
|
||||
writeFileSync(join(packageRoot, "src/models.generated.ts"), output);
|
||||
console.log("Generated src/models.generated.ts");
|
||||
|
||||
// Print statistics
|
||||
const totalModels = allModels.length;
|
||||
@@ -2318,4 +2412,7 @@ async function generateModels() {
|
||||
}
|
||||
|
||||
// Run the generator
|
||||
generateModels().catch(console.error);
|
||||
generateModels().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user