Dax Raad 4 månader sedan
förälder
incheckning
bbbffbf928

+ 1 - 9
packages/opencode/src/config/config.ts

@@ -474,15 +474,7 @@ export namespace Config {
     .extend({
       whitelist: z.array(z.string()).optional(),
       blacklist: z.array(z.string()).optional(),
-      models: z
-        .record(
-          z.string(),
-          ModelsDev.Model.partial().refine(
-            (input) => input.id === undefined,
-            "The model.id field can no longer be specified. Use model.target to specify an alternate model id to use when calling the provider.",
-          ),
-        )
-        .optional(),
+      models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
       options: z
         .object({
           apiKey: z.string().optional(),

+ 24 - 19
packages/opencode/src/provider/provider.ts

@@ -481,8 +481,14 @@ export namespace Provider {
     function mergeProvider(providerID: string, provider: Partial<Info>) {
       const match = database[providerID]
       if (!match) return
-      // @ts-expect-error
-      providers[providerID] = mergeDeep(match, provider)
+      const existing = providers[providerID]
+      if (existing) {
+        // @ts-expect-error
+        providers[providerID] = mergeDeep(existing, provider)
+      } else {
+        // @ts-expect-error
+        providers[providerID] = mergeDeep(match, provider)
+      }
     }
 
     // extend database from config
@@ -494,7 +500,7 @@ export namespace Provider {
         env: provider.env ?? existing?.env ?? [],
         options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
         source: "config",
-        models: {},
+        models: existing?.models ?? {},
       }
 
       for (const [modelID, model] of Object.entries(provider.models ?? {})) {
@@ -520,18 +526,18 @@ export namespace Provider {
             attachment: model.attachment ?? existing?.capabilities.attachment ?? false,
             toolcall: model.tool_call ?? existing?.capabilities.toolcall ?? true,
             input: {
-              text: model.modalities?.input?.includes("text") ?? false,
-              audio: model.modalities?.input?.includes("audio") ?? false,
-              image: model.modalities?.input?.includes("image") ?? false,
-              video: model.modalities?.input?.includes("video") ?? false,
-              pdf: model.modalities?.input?.includes("pdf") ?? false,
+              text: model.modalities?.input?.includes("text") ?? existing?.capabilities.input.text ?? true,
+              audio: model.modalities?.input?.includes("audio") ?? existing?.capabilities.input.audio ?? false,
+              image: model.modalities?.input?.includes("image") ?? existing?.capabilities.input.image ?? false,
+              video: model.modalities?.input?.includes("video") ?? existing?.capabilities.input.video ?? false,
+              pdf: model.modalities?.input?.includes("pdf") ?? existing?.capabilities.input.pdf ?? false,
             },
             output: {
-              text: model.modalities?.output?.includes("text") ?? false,
-              audio: model.modalities?.output?.includes("audio") ?? false,
-              image: model.modalities?.output?.includes("image") ?? false,
-              video: model.modalities?.output?.includes("video") ?? false,
-              pdf: model.modalities?.output?.includes("pdf") ?? false,
+              text: model.modalities?.output?.includes("text") ?? existing?.capabilities.output.text ?? true,
+              audio: model.modalities?.output?.includes("audio") ?? existing?.capabilities.output.audio ?? false,
+              image: model.modalities?.output?.includes("image") ?? existing?.capabilities.output.image ?? false,
+              video: model.modalities?.output?.includes("video") ?? existing?.capabilities.output.video ?? false,
+              pdf: model.modalities?.output?.includes("pdf") ?? existing?.capabilities.output.pdf ?? false,
             },
           },
           cost: {
@@ -638,12 +644,11 @@ export namespace Provider {
 
     // load config
     for (const [providerID, provider] of configProviders) {
-      mergeProvider(providerID, {
-        source: "config",
-        env: provider.env,
-        name: provider.name,
-        options: provider.options,
-      })
+      const partial: Partial<Info> = { source: "config" }
+      if (provider.env) partial.env = provider.env
+      if (provider.name) partial.name = provider.name
+      if (provider.options) partial.options = provider.options
+      mergeProvider(providerID, partial)
     }
 
     for (const [providerID, provider] of Object.entries(providers)) {

+ 7 - 6
packages/opencode/test/provider/provider.test.ts

@@ -634,7 +634,7 @@ test("getModel uses realIdByKey for aliased models", async () => {
   })
 })
 
-test("provider api field sets default baseURL", async () => {
+test("provider api field sets model api.url", async () => {
   await using tmp = await tmpdir({
     init: async (dir) => {
       await Bun.write(
@@ -667,7 +667,8 @@ test("provider api field sets default baseURL", async () => {
     directory: tmp.path,
     fn: async () => {
       const providers = await Provider.list()
-      expect(providers["custom-api"].options.baseURL).toBe("https://api.example.com/v1")
+      // api field is stored on model.api.url, used by getSDK to set baseURL
+      expect(providers["custom-api"].models["model-1"].api.url).toBe("https://api.example.com/v1")
     },
   })
 })
@@ -1122,8 +1123,8 @@ test("provider with multiple env var options only includes apiKey when single en
     fn: async () => {
       const providers = await Provider.list()
       expect(providers["multi-env"]).toBeDefined()
-      // When multiple env options exist, apiKey should NOT be auto-set
-      expect(providers["multi-env"].options.apiKey).toBeUndefined()
+      // When multiple env options exist, key should NOT be auto-set
+      expect(providers["multi-env"].key).toBeUndefined()
     },
   })
 })
@@ -1164,8 +1165,8 @@ test("provider with single env var includes apiKey automatically", async () => {
     fn: async () => {
       const providers = await Provider.list()
       expect(providers["single-env"]).toBeDefined()
-      // Single env option should auto-set apiKey
-      expect(providers["single-env"].options.apiKey).toBe("my-api-key")
+      // Single env option should auto-set key
+      expect(providers["single-env"].key).toBe("my-api-key")
     },
   })
 })