Просмотр исходного кода

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

Luke Parker 2 недель назад
Родитель
Сommit
eb6f1dada8

+ 1 - 1
packages/opencode/src/cli/cmd/models.ts

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

+ 1 - 1
packages/opencode/src/cli/cmd/providers.ts

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

+ 43 - 13
packages/opencode/src/provider/models.ts

@@ -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()
-    }
   }
 }