Files
next-ai-draw-io/scripts/update-model-catalog.mjs
T
dayuan.jiang 6236124338 feat(settings): fetch the model list from any provider and flag models that cannot draw
The "Fetch models" button asks the provider for its models (OpenAI-style
/models, Anthropic, Google, Ollama, OpenRouter, Vercel Gateway, AIHubMix)
and shows them in a searchable picker. This replaces the route that only
worked for AIHubMix.

A snapshot of models.dev (MIT) says which models support tool calls.
Models without them get a "no tool calls" badge in the picker and a hint
in the model list, since drawing needs tool calls. Refresh the snapshot
with scripts/update-model-catalog.mjs.
2026-10-04 14:02:54 +09:00

79 lines
2.7 KiB
JavaScript

// Refresh lib/model-catalog.json from models.dev (MIT licensed model data):
// whether each model can call tools and read images, and its limits. The
// app only uses it for hints in model settings. Run before a release:
// node scripts/update-model-catalog.mjs
import { writeFileSync } from "node:fs"
// Our provider id -> models.dev provider id
const PROVIDERS = {
openai: "openai",
anthropic: "anthropic",
google: "google",
vertexai: "google-vertex",
azure: "azure",
bedrock: "amazon-bedrock",
ollama: "ollama-cloud",
openrouter: "openrouter",
aihubmix: "aihubmix",
deepseek: "deepseek",
siliconflow: "siliconflow-cn",
gateway: "vercel",
doubao: "volcengine",
modelscope: "modelscope",
glm: "zhipuai",
qwen: "alibaba-cn",
qiniu: "qiniu-ai",
kimi: "moonshotai-cn",
minimax: "minimax-cn",
novita: "novita-ai",
mimo: "xiaomi",
}
const response = await fetch("https://models.dev/api.json")
if (!response.ok) throw new Error(`models.dev answered ${response.status}`)
const data = await response.json()
const catalog = {}
for (const [provider, source] of Object.entries(PROVIDERS)) {
const models = data[source]?.models
if (!models) {
console.warn(`models.dev has no provider ${source}`)
continue
}
catalog[provider] = {}
for (const [id, m] of Object.entries(models).sort(([a], [b]) =>
a.localeCompare(b),
)) {
// Chat models only, and none that are on their way out
if (!m.modalities?.output?.includes("text")) continue
if (m.status === "deprecated") continue
catalog[provider][id] = {
tools: m.tool_call === true,
images: m.modalities?.input?.includes("image") === true,
reasoning: m.reasoning === true,
...(m.limit?.context && { context: m.limit.context }),
...(m.limit?.output && { output: m.limit.output }),
}
}
}
// One model per line, so a refresh shows up as a readable diff
const lines = ["{"]
const providers = Object.entries(catalog)
providers.forEach(([provider, models], p) => {
lines.push(` ${JSON.stringify(provider)}: {`)
const entries = Object.entries(models)
entries.forEach(([id, info], i) => {
const comma = i < entries.length - 1 ? "," : ""
lines.push(` ${JSON.stringify(id)}: ${JSON.stringify(info)}${comma}`)
})
lines.push(` }${p < providers.length - 1 ? "," : ""}`)
})
lines.push("}")
writeFileSync(
new URL("../lib/model-catalog.json", import.meta.url),
`${lines.join("\n")}\n`,
)
const count = providers.reduce((n, [, m]) => n + Object.keys(m).length, 0)
console.log(`Wrote ${count} models for ${providers.length} providers`)