Dax Raad 4 månader sedan
förälder
incheckning
8b1c55f9fa
2 ändrade filer med 97 tillägg och 85 borttagningar
  1. 45 45
      packages/opencode/src/config/config.ts
  2. 52 40
      packages/opencode/src/provider/provider.ts

+ 45 - 45
packages/opencode/src/config/config.ts

@@ -470,6 +470,50 @@ export namespace Config {
   })
   export type Layout = z.infer<typeof Layout>
 
+  export const Provider = ModelsDev.Provider.partial()
+    .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(),
+      options: z
+        .object({
+          apiKey: z.string().optional(),
+          baseURL: z.string().optional(),
+          enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
+          setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
+          timeout: z
+            .union([
+              z
+                .number()
+                .int()
+                .positive()
+                .describe(
+                  "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
+                ),
+              z.literal(false).describe("Disable timeout for this provider entirely."),
+            ])
+            .optional()
+            .describe(
+              "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
+            ),
+        })
+        .catchall(z.any())
+        .optional(),
+    })
+    .strict()
+    .meta({
+      ref: "ProviderConfig",
+    })
+  export type Provider = z.infer<typeof Provider>
+
   export const Info = z
     .object({
       $schema: z.string().optional().describe("JSON schema reference for configuration validation"),
@@ -536,51 +580,7 @@ export namespace Config {
         .optional()
         .describe("Agent configuration, see https://opencode.ai/docs/agent"),
       provider: z
-        .record(
-          z.string(),
-          ModelsDev.Provider.partial()
-            .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(),
-              options: z
-                .object({
-                  apiKey: z.string().optional(),
-                  baseURL: z.string().optional(),
-                  enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
-                  setCacheKey: z
-                    .boolean()
-                    .optional()
-                    .describe("Enable promptCacheKey for this provider (default false)"),
-                  timeout: z
-                    .union([
-                      z
-                        .number()
-                        .int()
-                        .positive()
-                        .describe(
-                          "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
-                        ),
-                      z.literal(false).describe("Disable timeout for this provider entirely."),
-                    ])
-                    .optional()
-                    .describe(
-                      "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
-                    ),
-                })
-                .catchall(z.any())
-                .optional(),
-            })
-            .strict(),
-        )
+        .record(z.string(), Provider)
         .optional()
         .describe("Custom provider configurations and model overrides"),
       mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),

+ 52 - 40
packages/opencode/src/provider/provider.ts

@@ -1,7 +1,7 @@
 import z from "zod"
 import fuzzysort from "fuzzysort"
 import { Config } from "../config/config"
-import { entries, mapValues, mergeDeep, pipe, sortBy } from "remeda"
+import { mapValues, mergeDeep, sortBy } from "remeda"
 import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
 import { Log } from "../util/log"
 import { BunProc } from "../bun"
@@ -360,20 +360,25 @@ export namespace Provider {
     })
   export type Model = z.infer<typeof Model>
 
-  export const Info = z.object({
-    id: z.string(),
-    name: z.string(),
-    source: z.enum(["env", "config", "custom", "api"]),
-    env: z.string().array(),
-    key: z.string().optional(),
-    options: z.record(z.string(), z.any()),
-    models: z.record(z.string(), Model),
-  })
+  export const Info = z
+    .object({
+      id: z.string(),
+      name: z.string(),
+      source: z.enum(["env", "config", "custom", "api"]),
+      env: z.string().array(),
+      key: z.string().optional(),
+      options: z.record(z.string(), z.any()),
+      models: z.record(z.string(), Model),
+    })
+    .meta({
+      ref: "Provider",
+    })
   export type Info = z.infer<typeof Info>
 
   function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
     return {
       id: model.id,
+      providerID: provider.id,
       name: model.name,
       api: {
         id: model.id,
@@ -483,16 +488,16 @@ export namespace Provider {
       providers[providerID] = mergeDeep(match, provider)
     }
 
-    // TODO: load config
+    // extend database from config
     for (const [providerID, provider] of configProviders) {
       const existing = database[providerID]
-      const parsed: ModelsDev.Provider = {
+      const parsed: Info = {
         id: providerID,
-        npm: provider.npm ?? existing?.npm,
         name: provider.name ?? existing?.name ?? providerID,
         env: provider.env ?? existing?.env ?? [],
-        api: provider.api ?? existing?.api,
-        models: existing?.models ?? {},
+        options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
+        source: "config",
+        models: {},
       }
 
       for (const [modelID, model] of Object.entries(provider.models ?? {})) {
@@ -504,44 +509,51 @@ export namespace Provider {
         })
         const parsedModel: Model = {
           id: modelID,
-          apiID: model.target ?? existing?.target ?? modelID,
-          status: model.status ?? existing?.status ?? "alpha",
+          api: {
+            id: model.id ?? existing?.api.id ?? modelID,
+            npm: model.provider?.npm ?? provider.npm ?? existing?.api.npm ?? providerID,
+            url: provider?.api ?? existing?.api.url,
+          },
+          status: model.status ?? existing?.status ?? "active",
           name,
           providerID,
-          npm: model.provider?.npm ?? existing?.provider?.npm ?? provider.npm ?? providerID,
-          support: {
-            temperature: model.temperature ?? existing?.temperature ?? false,
-            reasoning: model.reasoning ?? existing?.reasoning ?? false,
-            attachment: model.attachment ?? existing?.attachment ?? false,
-            toolcall: model.tool_call ?? existing?.tool_call ?? true,
+          capabilities: {
+            temperature: model.temperature ?? existing?.capabilities.temperature ?? false,
+            reasoning: model.reasoning ?? existing?.capabilities.reasoning ?? false,
+            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,
+            },
+            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,
+            },
           },
           cost: {
             input: model?.cost?.input ?? existing?.cost?.input ?? 0,
             output: model?.cost?.output ?? existing?.cost?.output ?? 0,
             cache: {
-              read: model?.cost?.cache_read ?? existing?.cost?.cache_read ?? 0,
-              write: model?.cost?.cache_write ?? existing?.cost?.cache_write ?? 0,
+              read: model?.cost?.cache_read ?? existing?.cost?.cache.read ?? 0,
+              write: model?.cost?.cache_write ?? existing?.cost?.cache.write ?? 0,
             },
           },
-          options: {
-            ...existing?.options,
-            ...model.options,
+          options: mergeDeep(existing?.options ?? {}, model.options ?? {}),
+          limit: {
+            context: model.limit?.context ?? existing?.limit?.context ?? 0,
+            output: model.limit?.output ?? existing?.limit?.output ?? 0,
           },
-          limit: model.limit ??
-            existing?.limit ?? {
-              context: 0,
-              output: 0,
-            },
-          modalities: model.modalities ??
-            existing?.modalities ?? {
-              input: ["text"],
-              output: ["text"],
-            },
-          headers: model.headers ?? {},
+          headers: mergeDeep(existing?.headers ?? {}, model.headers ?? {}),
         }
         parsed.models[modelID] = parsedModel
       }
-
       database[providerID] = parsed
     }