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