Dax Raad 2 месяцев назад
Родитель
Сommit
e3c1861a3e

+ 1 - 0
packages/opencode/.gitignore

@@ -2,3 +2,4 @@ research
 dist
 gen
 app.log
+src/provider/models-snapshot.ts

+ 8 - 0
packages/opencode/script/build.ts

@@ -15,6 +15,14 @@ process.chdir(dir)
 import pkg from "../package.json"
 import { Script } from "@opencode-ai/script"
 
+// Fetch and generate models.dev snapshot
+const modelsData = await fetch(`https://models.dev/api.json`).then((x) => x.text())
+await Bun.write(
+  path.join(dir, "src/provider/models-snapshot.ts"),
+  `// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData} as const\n`,
+)
+console.log("Generated models-snapshot.ts")
+
 const singleFlag = process.argv.includes("--single")
 const baselineFlag = process.argv.includes("--baseline")
 const skipInstall = process.argv.includes("--skip-install")

+ 1 - 0
packages/opencode/src/flag/flag.ts

@@ -46,6 +46,7 @@ export namespace Flag {
   export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
   export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK")
   export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE")
+  export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
 
   function number(key: string) {
     const value = process.env[key]

+ 0 - 4
packages/opencode/src/global/index.ts

@@ -22,10 +22,6 @@ export namespace Global {
     cache,
     config,
     state,
-    // Allow overriding models.dev URL for offline deployments
-    get modelsDevUrl() {
-      return process.env.OPENCODE_MODELS_URL || "https://models.dev"
-    },
   }
 }
 

+ 0 - 14
packages/opencode/src/provider/models-macro.ts

@@ -1,14 +0,0 @@
-import { Global } from "../global"
-
-export async function data() {
-  const path = Bun.env.MODELS_DEV_API_JSON
-  if (path) {
-    const file = Bun.file(path)
-    if (await file.exists()) {
-      return await file.text()
-    }
-  }
-  const url = Global.Path.modelsDevUrl
-  const json = await fetch(`${url}/api.json`).then((x) => x.text())
-  return json
-}

+ 37 - 15
packages/opencode/src/provider/models.ts

@@ -2,9 +2,16 @@ import { Global } from "../global"
 import { Log } from "../util/log"
 import path from "path"
 import z from "zod"
-import { data } from "./models-macro" with { type: "macro" }
 import { Installation } from "../installation"
 import { Flag } from "../flag/flag"
+import { lazy } from "@/util/lazy"
+
+// Try to import bundled snapshot (generated at build time)
+// Falls back to undefined in dev mode when snapshot doesn't exist
+/* @ts-ignore */
+const SNAPSHOT = await import("./models-snapshot")
+  .then((m) => m.snapshot as Record<string, unknown>)
+  .catch(() => undefined)
 
 export namespace ModelsDev {
   const log = Log.create({ service: "models.dev" })
@@ -76,18 +83,24 @@ export namespace ModelsDev {
 
   export type Provider = z.infer<typeof Provider>
 
-  export async function get() {
-    refresh()
+  function url() {
+    return Flag.OPENCODE_MODELS_URL || "https://models.dev"
+  }
+
+  export const Data = lazy(async () => {
     const file = Bun.file(filepath)
     const result = await file.json().catch(() => {})
-    if (result) return result as Record<string, Provider>
-    if (typeof data === "function") {
-      const json = await data()
-      return JSON.parse(json) as Record<string, Provider>
-    }
-    const url = Global.Path.modelsDevUrl
-    const json = await fetch(`${url}/api.json`).then((x) => x.text())
-    return JSON.parse(json) as Record<string, Provider>
+    if (result) return result
+    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)
+  })
+
+  export async function get() {
+    refresh()
+    const result = await Data()
+    return result as Record<string, Provider>
   }
 
   export async function refresh() {
@@ -96,8 +109,7 @@ export namespace ModelsDev {
     log.info("refreshing", {
       file,
     })
-    const url = Global.Path.modelsDevUrl
-    const result = await fetch(`${url}/api.json`, {
+    const result = await fetch(`${url()}/api.json`, {
       headers: {
         "User-Agent": Installation.USER_AGENT,
       },
@@ -107,8 +119,18 @@ export namespace ModelsDev {
         error: e,
       })
     })
-    if (result && result.ok) await Bun.write(file, await result.text())
+    if (result && result.ok) {
+      await Bun.write(file, await result.text())
+      ModelsDev.Data.reset()
+    }
   }
 }
 
-setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref()
+if (!Flag.OPENCODE_DISABLE_MODELS_FETCH) {
+  setInterval(
+    async () => {
+      await ModelsDev.refresh()
+    },
+    60 * 1000 * 60,
+  ).unref()
+}

+ 1 - 10
packages/opencode/test/preload.ts

@@ -5,7 +5,6 @@ import path from "path"
 import fs from "fs/promises"
 import fsSync from "fs"
 import { afterAll } from "bun:test"
-const { Global } = await import("../src/global")
 
 const dir = path.join(os.tmpdir(), "opencode-test-data-" + process.pid)
 await fs.mkdir(dir, { recursive: true })
@@ -23,18 +22,10 @@ process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
 process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
 process.env["XDG_STATE_HOME"] = path.join(dir, "state")
 
-// Pre-fetch models.json so tests don't need the macro fallback
-// Also write the cache version file to prevent global/index.ts from clearing the cache
+// Write the cache version file to prevent global/index.ts from clearing the cache
 const cacheDir = path.join(dir, "cache", "opencode")
 await fs.mkdir(cacheDir, { recursive: true })
 await fs.writeFile(path.join(cacheDir, "version"), "14")
-const url = Global.Path.modelsDevUrl
-const response = await fetch(`${url}/api.json`)
-if (response.ok) {
-  await fs.writeFile(path.join(cacheDir, "models.json"), await response.text())
-}
-// Disable models.dev refresh to avoid race conditions during tests
-process.env["OPENCODE_DISABLE_MODELS_FETCH"] = "true"
 
 // Clear provider env vars to ensure clean test state
 delete process.env["ANTHROPIC_API_KEY"]