Explorar o código

support global config for providers

Dax Raad hai 9 meses
pai
achega
d579c5e8aa

+ 44 - 4
README.md

@@ -61,9 +61,51 @@ The Models.dev dataset is also used to detect common environment variables like
 
 If there are additional providers you want to use you can submit a PR to the [Models.dev repo](https://github.com/sst/models.dev). If configuring just for yourself check out the Config section below.
 
+### Global Config
+
+Some basic configuration is available in the global config file.
+
+```toml title="~/.config/opencode/config.toml"
+theme = "opencode"
+provider = "anthropic"
+model = "claude-sonnet-4-20250514"
+autoupdate = true
+```
+
+You can also extend the models.dev database with your own providers and models by placing a `provider.toml` file in `~/.config/opencode/providers`.
+
+```toml title="~/.config/opencode/providers/openrouter/provider.toml"
+[provider]
+name = "OpenRouter"
+env = ["OPENROUTER_API_KEY"]
+id = "openrouter"
+npm = "@openrouter/ai-sdk-provider"
+```
+
+And models in `~/.config/opencode/providers/openrouter/models/[model-id]`.
+
+```toml title="~/.config/opencode/providers/openrouter/models/anthropic/claude-3.5-sonnet.toml"
+name = "Claude 4 Sonnet"
+attachment = true
+reasoning = false
+temperature = true
+
+[cost]
+input = 3.00
+output = 15.00
+inputCached = 3.75
+outputCached = 0.30
+
+[limit]
+context = 200_000
+output = 50_000
+```
+
+This mirrors the structure found [here](https://github.com/sst/models.dev/tree/dev/providers/anthropic)
+
 ### Project Config
 
-Project configuration is optional. You can place an `opencode.json` file in the root of your repo, and it'll be loaded.
+Project configuration is optional. You can place an `opencode.json` file in the root of your repo and is meant to be checked in and shared with your team.
 
 ```json title="opencode.json"
 {
@@ -106,9 +148,7 @@ You can use opencode with any provider listed at [here](https://ai-sdk.dev/provi
         "baseURL": "http://localhost:11434/v1"
       },
       "models": {
-        "llama2": {
-          "name": "llama2"
-        }
+        "llama2": {}
       }
     }
   }

+ 1 - 9
opencode.json

@@ -1,13 +1,5 @@
 {
   "$schema": "https://opencode.ai/config.json",
   "mcp": {},
-  "provider": {
-    "openrouter": {
-      "npm": "@openrouter/ai-sdk-provider",
-      "name": "OpenRouter",
-      "models": {
-        "anthropic/claude-sonnet-4": {}
-      }
-    }
-  }
+  "provider": {}
 }

+ 8 - 6
packages/opencode/src/global/index.ts

@@ -8,17 +8,19 @@ const data = path.join(xdgData!, app)
 const cache = path.join(xdgCache!, app)
 const config = path.join(xdgConfig!, app)
 
-await Promise.all([
-  fs.mkdir(data, { recursive: true }),
-  fs.mkdir(config, { recursive: true }),
-  fs.mkdir(cache, { recursive: true }),
-])
-
 export namespace Global {
   export const Path = {
     data,
     bin: path.join(data, "bin"),
+    providers: path.join(config, "providers"),
     cache,
     config,
   } as const
 }
+
+await Promise.all([
+  fs.mkdir(data, { recursive: true }),
+  fs.mkdir(config, { recursive: true }),
+  fs.mkdir(cache, { recursive: true }),
+  fs.mkdir(Global.Path.providers, { recursive: true }),
+])

+ 31 - 3
packages/opencode/src/provider/provider.ts

@@ -1,4 +1,5 @@
 import z from "zod"
+import path from "path"
 import { App } from "../app/app"
 import { Config } from "../config/config"
 import { mergeDeep, sortBy } from "remeda"
@@ -24,6 +25,7 @@ import { NamedError } from "../util/error"
 import { Auth } from "../auth"
 import { TaskTool } from "../tool/task"
 import { GlobalConfig } from "../global/config"
+import { Global } from "../global"
 
 export namespace Provider {
   const log = Log.create({ service: "provider" })
@@ -103,9 +105,35 @@ export namespace Provider {
       provider.source = source
     }
 
-    for (const [providerID, provider] of Object.entries(
-      config.provider ?? {},
-    )) {
+    const configProviders = Object.entries(config.provider ?? {})
+    for await (const providerPath of new Bun.Glob("*/provider.toml").scan({
+      cwd: Global.Path.providers,
+    })) {
+      const [providerID] = providerPath.split("/")
+      const toml = await import(
+        path.join(Global.Path.providers, providerPath),
+        {
+          with: {
+            type: "toml",
+          },
+        }
+      ).then((mod) => mod.default)
+      toml.models = {}
+      const modelsPath = path.join(Global.Path.providers, providerID, "models")
+      for await (const modelPath of new Bun.Glob("**/*.toml").scan({
+        cwd: modelsPath,
+      })) {
+        const modelID = modelPath.slice(0, -5)
+        toml.models[modelID] = await import(path.join(modelsPath, modelPath), {
+          with: {
+            type: "toml",
+          },
+        })
+      }
+      configProviders.unshift([providerID, toml])
+    }
+
+    for (const [providerID, provider] of configProviders) {
       const existing = database[providerID]
       const parsed: ModelsDev.Provider = {
         id: providerID,