fix: call models.dev once instead of twice on start (#20765)

This commit is contained in:
Luke Parker 2026-04-03 11:26:53 +10:00 committed by GitHub
commit eb6f1dada8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 15 deletions

View file

@ -28,7 +28,7 @@ export const ModelsCommand = cmd({
}, },
handler: async (args) => { handler: async (args) => {
if (args.refresh) { if (args.refresh) {
await ModelsDev.refresh() await ModelsDev.refresh(true)
UI.println(UI.Style.TEXT_SUCCESS_BOLD + "Models cache refreshed" + UI.Style.TEXT_NORMAL) UI.println(UI.Style.TEXT_SUCCESS_BOLD + "Models cache refreshed" + UI.Style.TEXT_NORMAL)
} }

View file

@ -303,7 +303,7 @@ export const ProvidersLoginCommand = cmd({
prompts.outro("Done") prompts.outro("Done")
return return
} }
await ModelsDev.refresh().catch(() => {}) await ModelsDev.refresh(true).catch(() => {})
const config = await Config.get() const config = await Config.get()

View file

@ -6,6 +6,8 @@ import { Installation } from "../installation"
import { Flag } from "../flag/flag" import { Flag } from "../flag/flag"
import { lazy } from "@/util/lazy" import { lazy } from "@/util/lazy"
import { Filesystem } from "../util/filesystem" import { Filesystem } from "../util/filesystem"
import { Flock } from "@/util/flock"
import { Hash } from "@/util/hash"
// Try to import bundled snapshot (generated at build time) // Try to import bundled snapshot (generated at build time)
// Falls back to undefined in dev mode when snapshot doesn't exist // Falls back to undefined in dev mode when snapshot doesn't exist
@ -13,7 +15,12 @@ import { Filesystem } from "../util/filesystem"
export namespace ModelsDev { export namespace ModelsDev {
const log = Log.create({ service: "models.dev" }) const log = Log.create({ service: "models.dev" })
const filepath = path.join(Global.Path.cache, "models.json") const source = url()
const filepath = path.join(
Global.Path.cache,
source === "https://models.dev" ? "models.json" : `models-${Hash.fast(source)}.json`,
)
const ttl = 5 * 60 * 1000
export const Model = z.object({ export const Model = z.object({
id: z.string(), id: z.string(),
@ -85,6 +92,22 @@ export namespace ModelsDev {
return Flag.OPENCODE_MODELS_URL || "https://models.dev" return Flag.OPENCODE_MODELS_URL || "https://models.dev"
} }
function fresh() {
return Date.now() - Number(Filesystem.stat(filepath)?.mtimeMs ?? 0) < ttl
}
function skip(force: boolean) {
return !force && fresh()
}
const fetchApi = async () => {
const result = await fetch(`${url()}/api.json`, {
headers: { "User-Agent": Installation.USER_AGENT },
signal: AbortSignal.timeout(10000),
})
return { ok: result.ok, text: await result.text() }
}
export const Data = lazy(async () => { export const Data = lazy(async () => {
const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {}) const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {})
if (result) return result if (result) return result
@ -94,8 +117,17 @@ export namespace ModelsDev {
.catch(() => undefined) .catch(() => undefined)
if (snapshot) return snapshot if (snapshot) return snapshot
if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {} if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {}
const json = await fetch(`${url()}/api.json`).then((x) => x.text()) return Flock.withLock(`models-dev:${filepath}`, async () => {
return JSON.parse(json) const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {})
if (result) return result
const result2 = await fetchApi()
if (result2.ok) {
await Filesystem.write(filepath, result2.text).catch((e) => {
log.error("Failed to write models cache", { error: e })
})
}
return JSON.parse(result2.text)
})
}) })
export async function get() { export async function get() {
@ -103,21 +135,19 @@ export namespace ModelsDev {
return result as Record<string, Provider> return result as Record<string, Provider>
} }
export async function refresh() { export async function refresh(force = false) {
const result = await fetch(`${url()}/api.json`, { if (skip(force)) return ModelsDev.Data.reset()
headers: { await Flock.withLock(`models-dev:${filepath}`, async () => {
"User-Agent": Installation.USER_AGENT, if (skip(force)) return ModelsDev.Data.reset()
}, const result = await fetchApi()
signal: AbortSignal.timeout(10 * 1000), if (!result.ok) return
await Filesystem.write(filepath, result.text)
ModelsDev.Data.reset()
}).catch((e) => { }).catch((e) => {
log.error("Failed to fetch models.dev", { log.error("Failed to fetch models.dev", {
error: e, error: e,
}) })
}) })
if (result && result.ok) {
await Filesystem.write(filepath, await result.text())
ModelsDev.Data.reset()
}
} }
} }