1
0
Aiden Cline 6 сар өмнө
parent
commit
dbd58974a8

+ 1 - 1
packages/opencode/src/provider/provider.ts

@@ -960,7 +960,7 @@ export namespace Provider {
     return state().then((state) => state.providers)
   }
 
-  function resolveModelBaseURL(model: Model, options: Record<string, any>): string {
+  export function resolveModelBaseURL(model: Model, options: Record<string, any>): string {
     const template = model.api?.url ?? ""
     if (!template) return ""
     const matches = [...template.matchAll(/{{([^}]+)}}/g)]

+ 154 - 0
packages/opencode/test/provider/provider.test.ts

@@ -381,6 +381,160 @@ test("parseModel handles model IDs with slashes", () => {
   expect(result.modelID).toBe("anthropic/claude-3-opus")
 })
 
+test("resolveModelBaseURL returns empty when url missing", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "" } } as Provider.Model
+      expect(Provider.resolveModelBaseURL(model, {})).toBe("")
+    },
+  })
+})
+
+test("resolveModelBaseURL returns template when no placeholders", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "https://api.example.com/v1" } } as Provider.Model
+      expect(Provider.resolveModelBaseURL(model, {})).toBe("https://api.example.com/v1")
+    },
+  })
+})
+
+test("resolveModelBaseURL replaces placeholders from env", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    init: async () => {
+      Env.set("CUSTOM_HOST", "env.example.com")
+    },
+    fn: async () => {
+      const model = { api: { url: "https://{{CUSTOM_HOST}}/v1" } } as Provider.Model
+      expect(Provider.resolveModelBaseURL(model, {})).toBe("https://env.example.com/v1")
+      Env.remove("CUSTOM_HOST")
+    },
+  })
+})
+
+test("resolveModelBaseURL falls back to options when env missing", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "https://{{HOST}}/v1" } } as Provider.Model
+      expect(Provider.resolveModelBaseURL(model, { HOST: "options.example.com" })).toBe(
+        "https://options.example.com/v1",
+      )
+    },
+  })
+})
+
+test("resolveModelBaseURL chooses first non-empty key", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "https://{{PRIMARY|SECONDARY}}/v1" } } as Provider.Model
+      const options = {
+        PRIMARY: "",
+        SECONDARY: "fallback.example.com",
+      }
+      expect(Provider.resolveModelBaseURL(model, options)).toBe("https://fallback.example.com/v1")
+    },
+  })
+})
+
+test("resolveModelBaseURL keeps unresolved placeholders", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "https://{{MISSING}}/v1" } } as Provider.Model
+      expect(Provider.resolveModelBaseURL(model, {})).toBe("https://{{MISSING}}/v1")
+    },
+  })
+})
+
+test("resolveModelBaseURL replaces multiple placeholders", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await Instance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      const model = { api: { url: "https://{{HOST}}/{{VERSION}}" } } as Provider.Model
+      const options = {
+        HOST: "multi.example.com",
+        VERSION: "v1",
+      }
+      expect(Provider.resolveModelBaseURL(model, options)).toBe("https://multi.example.com/v1")
+    },
+  })
+})
+
 test("defaultModel returns first available model when no config set", async () => {
   await using tmp = await tmpdir({
     init: async (dir) => {