Просмотр исходного кода

feat: unwrap Provider namespace + improved automation script (#22690)

Kit Langton 4 месяцев назад
Родитель
Сommit
1d81335ab5
40 измененных файлов с 1653 добавлено и 1541 удалено
  1. 153 40
      packages/opencode/script/unwrap-namespace.ts
  2. 1 1
      packages/opencode/src/acp/agent.ts
  3. 1 1
      packages/opencode/src/agent/agent.ts
  4. 1 1
      packages/opencode/src/cli/cmd/agent.ts
  5. 1 1
      packages/opencode/src/cli/cmd/debug/agent.ts
  6. 1 1
      packages/opencode/src/cli/cmd/github.ts
  7. 1 1
      packages/opencode/src/cli/cmd/models.ts
  8. 1 1
      packages/opencode/src/cli/cmd/run.ts
  9. 1 1
      packages/opencode/src/cli/cmd/tui/app.tsx
  10. 1 1
      packages/opencode/src/cli/cmd/tui/context/local.tsx
  11. 1 1
      packages/opencode/src/cli/error.ts
  12. 1 1
      packages/opencode/src/effect/app-runtime.ts
  13. 1 0
      packages/opencode/src/provider/index.ts
  14. 1461 1463
      packages/opencode/src/provider/provider.ts
  15. 1 1
      packages/opencode/src/provider/transform.ts
  16. 1 1
      packages/opencode/src/server/instance/config.ts
  17. 1 1
      packages/opencode/src/server/instance/provider.ts
  18. 1 1
      packages/opencode/src/server/middleware.ts
  19. 1 1
      packages/opencode/src/session/compaction.ts
  20. 1 1
      packages/opencode/src/session/index.ts
  21. 1 1
      packages/opencode/src/session/llm.ts
  22. 1 1
      packages/opencode/src/session/message-v2.ts
  23. 1 1
      packages/opencode/src/session/overflow.ts
  24. 1 1
      packages/opencode/src/session/processor.ts
  25. 1 1
      packages/opencode/src/session/prompt.ts
  26. 1 1
      packages/opencode/src/session/system.ts
  27. 1 1
      packages/opencode/src/share/share-next.ts
  28. 1 1
      packages/opencode/src/tool/plan.ts
  29. 1 1
      packages/opencode/src/tool/registry.ts
  30. 1 1
      packages/opencode/test/fake/provider.ts
  31. 1 1
      packages/opencode/test/provider/amazon-bedrock.test.ts
  32. 1 1
      packages/opencode/test/provider/gitlab-duo.test.ts
  33. 1 1
      packages/opencode/test/provider/provider.test.ts
  34. 1 1
      packages/opencode/test/session/compaction.test.ts
  35. 1 1
      packages/opencode/test/session/llm.test.ts
  36. 1 1
      packages/opencode/test/session/message-v2.test.ts
  37. 1 1
      packages/opencode/test/session/processor-effect.test.ts
  38. 2 2
      packages/opencode/test/session/prompt-effect.test.ts
  39. 1 1
      packages/opencode/test/session/snapshot-tool-race.test.ts
  40. 1 1
      packages/opencode/test/share/share-next.test.ts

+ 153 - 40
packages/opencode/script/unwrap-namespace.ts

@@ -10,11 +10,11 @@
  *   1. Reads the file and finds the `export namespace Foo { ... }` block
  *      (uses ast-grep for accurate AST-based boundary detection)
  *   2. Removes the namespace wrapper and dedents the body
- *   3. If the file is index.ts, renames it to <lowercase-name>.ts
- *   4. Creates/updates index.ts with `export * as Foo from "./<file>"`
- *   5. Prints the import rewrite commands to run across the codebase
- *
- * Does NOT auto-rewrite imports — prints the commands so you can review them.
+ *   3. Fixes self-references (e.g. Config.PermissionAction → PermissionAction)
+ *   4. If the file is index.ts, renames it to <lowercase-name>.ts
+ *   5. Creates/updates index.ts with `export * as Foo from "./<file>"`
+ *   6. Rewrites import paths across src/, test/, and script/
+ *   7. Fixes sibling imports within the same directory
  *
  * Requires: ast-grep (`brew install ast-grep` or `cargo install ast-grep`)
  */
@@ -90,22 +90,107 @@ const after = lines.slice(closeLine + 1)
 const dedented = body.map((line) => {
   if (line === "") return ""
   if (line.startsWith("  ")) return line.slice(2)
-  return line // don't touch lines that aren't indented (shouldn't happen)
+  return line
 })
 
-const newContent = [...before, ...dedented, ...after].join("\n")
+let newContent = [...before, ...dedented, ...after].join("\n")
+
+// --- Fix self-references ---
+// After unwrapping, references like `Config.PermissionAction` inside the same file
+// need to become just `PermissionAction`. Only fix code positions, not strings.
+const exportedNames = new Set<string>()
+const exportRegex = /export\s+(?:const|function|class|interface|type|enum|abstract\s+class)\s+(\w+)/g
+for (const line of dedented) {
+  for (const m of line.matchAll(exportRegex)) exportedNames.add(m[1])
+}
+const reExportRegex = /export\s*\{\s*([^}]+)\}/g
+for (const line of dedented) {
+  for (const m of line.matchAll(reExportRegex)) {
+    for (const name of m[1].split(",")) {
+      const trimmed = name
+        .trim()
+        .split(/\s+as\s+/)
+        .pop()!
+        .trim()
+      if (trimmed) exportedNames.add(trimmed)
+    }
+  }
+}
+
+let selfRefCount = 0
+if (exportedNames.size > 0) {
+  const fixedLines = newContent.split("\n").map((line) => {
+    // Split line into string-literal and code segments to avoid replacing inside strings
+    const segments: Array<{ text: string; isString: boolean }> = []
+    let i = 0
+    let current = ""
+    let inString: string | null = null
+
+    while (i < line.length) {
+      const ch = line[i]
+      if (inString) {
+        current += ch
+        if (ch === "\\" && i + 1 < line.length) {
+          current += line[i + 1]
+          i += 2
+          continue
+        }
+        if (ch === inString) {
+          segments.push({ text: current, isString: true })
+          current = ""
+          inString = null
+        }
+        i++
+        continue
+      }
+      if (ch === '"' || ch === "'" || ch === "`") {
+        if (current) segments.push({ text: current, isString: false })
+        current = ch
+        inString = ch
+        i++
+        continue
+      }
+      if (ch === "/" && i + 1 < line.length && line[i + 1] === "/") {
+        current += line.slice(i)
+        segments.push({ text: current, isString: true })
+        current = ""
+        i = line.length
+        continue
+      }
+      current += ch
+      i++
+    }
+    if (current) segments.push({ text: current, isString: !!inString })
+
+    return segments
+      .map((seg) => {
+        if (seg.isString) return seg.text
+        let result = seg.text
+        for (const name of exportedNames) {
+          const pattern = `${nsName}.${name}`
+          while (result.includes(pattern)) {
+            const idx = result.indexOf(pattern)
+            const charBefore = idx > 0 ? result[idx - 1] : " "
+            const charAfter = idx + pattern.length < result.length ? result[idx + pattern.length] : " "
+            if (/\w/.test(charBefore) || /\w/.test(charAfter)) break
+            result = result.slice(0, idx) + name + result.slice(idx + pattern.length)
+            selfRefCount++
+          }
+        }
+        return result
+      })
+      .join("")
+  })
+  newContent = fixedLines.join("\n")
+}
 
 // Figure out file naming
 const dir = path.dirname(absPath)
 const basename = path.basename(absPath, ".ts")
 const isIndex = basename === "index"
-
-// The implementation file name (lowercase namespace name if currently index.ts)
 const implName = isIndex ? nsName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase() : basename
 const implFile = path.join(dir, `${implName}.ts`)
 const indexFile = path.join(dir, "index.ts")
-
-// The barrel line
 const barrelLine = `export * as ${nsName} from "./${implName}"\n`
 
 console.log("")
@@ -114,6 +199,7 @@ if (isIndex) {
 } else {
   console.log(`Plan: rewrite ${basename}.ts in place, create index.ts barrel`)
 }
+if (selfRefCount > 0) console.log(`Fixed ${selfRefCount} self-reference(s) (${nsName}.X → X)`)
 console.log("")
 
 if (dryRun) {
@@ -128,19 +214,23 @@ if (dryRun) {
   console.log("")
   console.log(`=== index.ts ===`)
   console.log(`  ${barrelLine.trim()}`)
+  console.log("")
+  if (!isIndex) {
+    const relDir = path.relative(path.resolve("src"), dir)
+    console.log(`=== Import rewrites (would apply) ===`)
+    console.log(`  ${relDir}/${basename}" → ${relDir}" across src/, test/, script/`)
+  } else {
+    console.log("No import rewrites needed (was index.ts)")
+  }
 } else {
-  // Write the implementation file
   if (isIndex) {
-    // Rename: write new content to implFile, then overwrite index.ts with barrel
     fs.writeFileSync(implFile, newContent)
     fs.writeFileSync(indexFile, barrelLine)
     console.log(`Wrote ${implName}.ts (${newContent.split("\n").length} lines)`)
     console.log(`Wrote index.ts (barrel)`)
   } else {
-    // Rewrite in place, create index.ts
     fs.writeFileSync(absPath, newContent)
     if (fs.existsSync(indexFile)) {
-      // Append to existing barrel
       const existing = fs.readFileSync(indexFile, "utf-8")
       if (!existing.includes(`export * as ${nsName}`)) {
         fs.appendFileSync(indexFile, barrelLine)
@@ -154,37 +244,60 @@ if (dryRun) {
     }
     console.log(`Rewrote ${basename}.ts (${newContent.split("\n").length} lines)`)
   }
-}
 
-// Print the import rewrite guidance
-const relDir = path.relative(path.resolve("src"), dir)
+  // --- Rewrite import paths across src/, test/, script/ ---
+  const relDir = path.relative(path.resolve("src"), dir)
+  if (!isIndex) {
+    const oldTail = `${relDir}/${basename}`
+    const searchDirs = ["src", "test", "script"].filter((d) => fs.existsSync(d))
+    const rgResult = Bun.spawnSync(["rg", "-l", `from.*${oldTail}"`, ...searchDirs], {
+      stdout: "pipe",
+      stderr: "pipe",
+    })
+    const filesToRewrite = rgResult.stdout
+      .toString()
+      .trim()
+      .split("\n")
+      .filter((f) => f.length > 0)
 
-console.log("")
-console.log("=== Import rewrites ===")
-console.log("")
-
-if (!isIndex) {
-  // Non-index files: imports like "../provider/provider" need to become "../provider"
-  const oldTail = `${relDir}/${basename}`
+    if (filesToRewrite.length > 0) {
+      console.log(`\nRewriting imports in ${filesToRewrite.length} file(s)...`)
+      for (const file of filesToRewrite) {
+        const content = fs.readFileSync(file, "utf-8")
+        fs.writeFileSync(file, content.replaceAll(`${oldTail}"`, `${relDir}"`))
+      }
+      console.log(`  Done: ${oldTail}" → ${relDir}"`)
+    } else {
+      console.log("\nNo import rewrites needed")
+    }
+  } else {
+    console.log("\nNo import rewrites needed (was index.ts)")
+  }
 
-  console.log(`# Find all imports to rewrite:`)
-  console.log(`rg 'from.*${oldTail}' src/ --files-with-matches`)
-  console.log("")
+  // --- Fix sibling imports within the same directory ---
+  const siblingFiles = fs.readdirSync(dir).filter((f) => {
+    if (!f.endsWith(".ts")) return false
+    if (f === "index.ts" || f === `${implName}.ts`) return false
+    return true
+  })
 
-  // Auto-rewrite with sed (safe: only rewrites the import path, not other occurrences)
-  console.log("# Auto-rewrite (review diff afterward):")
-  console.log(`rg -l 'from.*${oldTail}' src/ | xargs sed -i '' 's|${oldTail}"|${relDir}"|g'`)
-  console.log("")
-  console.log("# What changes:")
-  console.log(`#   import { ${nsName} } from ".../${oldTail}"`)
-  console.log(`#   import { ${nsName} } from ".../${relDir}"`)
-} else {
-  console.log("# File was index.ts — import paths already resolve correctly.")
-  console.log("# No import rewrites needed!")
+  let siblingFixCount = 0
+  for (const sibFile of siblingFiles) {
+    const sibPath = path.join(dir, sibFile)
+    const content = fs.readFileSync(sibPath, "utf-8")
+    const pattern = new RegExp(`from\\s+["']\\./${basename}["']`, "g")
+    if (pattern.test(content)) {
+      fs.writeFileSync(sibPath, content.replace(pattern, `from "."`))
+      siblingFixCount++
+    }
+  }
+  if (siblingFixCount > 0) {
+    console.log(`Fixed ${siblingFixCount} sibling import(s) in ${path.basename(dir)}/ (./${basename} → .)`)
+  }
 }
 
 console.log("")
 console.log("=== Verify ===")
 console.log("")
-console.log("bun typecheck     # from packages/opencode")
-console.log("bun run test      # run tests")
+console.log("bunx --bun tsgo --noEmit   # typecheck")
+console.log("bun run test               # run tests")

+ 1 - 1
packages/opencode/src/acp/agent.ts

@@ -37,7 +37,7 @@ import { Filesystem } from "../util/filesystem"
 import { Hash } from "@opencode-ai/shared/util/hash"
 import { ACPSessionManager } from "./session"
 import type { ACPConfig } from "./types"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { ModelID, ProviderID } from "../provider/schema"
 import { Agent as AgentModule } from "../agent/agent"
 import { AppRuntime } from "@/effect/app-runtime"

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

@@ -1,6 +1,6 @@
 import { Config } from "../config"
 import z from "zod"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { ModelID, ProviderID } from "../provider/schema"
 import { generateObject, streamObject, type ModelMessage } from "ai"
 import { Instance } from "../project/instance"

+ 1 - 1
packages/opencode/src/cli/cmd/agent.ts

@@ -4,7 +4,7 @@ import { AppRuntime } from "@/effect/app-runtime"
 import { UI } from "../ui"
 import { Global } from "../../global"
 import { Agent } from "../../agent/agent"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import path from "path"
 import fs from "fs/promises"
 import { Filesystem } from "../../util/filesystem"

+ 1 - 1
packages/opencode/src/cli/cmd/debug/agent.ts

@@ -2,7 +2,7 @@ import { EOL } from "os"
 import { basename } from "path"
 import { Effect } from "effect"
 import { Agent } from "../../../agent/agent"
-import { Provider } from "../../../provider/provider"
+import { Provider } from "../../../provider"
 import { Session } from "../../../session"
 import type { MessageV2 } from "../../../session/message-v2"
 import { MessageID, PartID } from "../../../session/schema"

+ 1 - 1
packages/opencode/src/cli/cmd/github.ts

@@ -25,7 +25,7 @@ import { SessionShare } from "@/share/session"
 import { Session } from "../../session"
 import type { SessionID } from "../../session/schema"
 import { MessageID, PartID } from "../../session/schema"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import { Bus } from "../../bus"
 import { MessageV2 } from "../../session/message-v2"
 import { SessionPrompt } from "@/session/prompt"

+ 1 - 1
packages/opencode/src/cli/cmd/models.ts

@@ -1,6 +1,6 @@
 import type { Argv } from "yargs"
 import { Instance } from "../../project/instance"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import { ProviderID } from "../../provider/schema"
 import { ModelsDev } from "../../provider/models"
 import { cmd } from "./cmd"

+ 1 - 1
packages/opencode/src/cli/cmd/run.ts

@@ -9,7 +9,7 @@ import { EOL } from "os"
 import { Filesystem } from "../../util/filesystem"
 import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
 import { Server } from "../../server/server"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import { Agent } from "../../agent/agent"
 import { Permission } from "../../permission"
 import { Tool } from "../../tool/tool"

+ 1 - 1
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -52,7 +52,7 @@ import { ExitProvider, useExit } from "./context/exit"
 import { Session as SessionApi } from "@/session"
 import { TuiEvent } from "./event"
 import { KVProvider, useKV } from "./context/kv"
-import { Provider } from "@/provider/provider"
+import { Provider } from "@/provider"
 import { ArgsProvider, useArgs, type Args } from "./context/args"
 import open from "open"
 import { PromptRefProvider, usePromptRef } from "./context/prompt"

+ 1 - 1
packages/opencode/src/cli/cmd/tui/context/local.tsx

@@ -8,7 +8,7 @@ import { Global } from "@/global"
 import { iife } from "@/util/iife"
 import { createSimpleContext } from "./helper"
 import { useToast } from "../ui/toast"
-import { Provider } from "@/provider/provider"
+import { Provider } from "@/provider"
 import { useArgs } from "./args"
 import { useSDK } from "./sdk"
 import { RGBA } from "@opentui/core"

+ 1 - 1
packages/opencode/src/cli/error.ts

@@ -3,7 +3,7 @@ import { ConfigMarkdown } from "@/config/markdown"
 import { errorFormat } from "@/util/error"
 import { Config } from "../config"
 import { MCP } from "../mcp"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { UI } from "./ui"
 
 export function FormatError(input: unknown) {

+ 1 - 1
packages/opencode/src/effect/app-runtime.ts

@@ -15,7 +15,7 @@ import { FileWatcher } from "@/file/watcher"
 import { Storage } from "@/storage/storage"
 import { Snapshot } from "@/snapshot"
 import { Plugin } from "@/plugin"
-import { Provider } from "@/provider/provider"
+import { Provider } from "@/provider"
 import { ProviderAuth } from "@/provider/auth"
 import { Agent } from "@/agent/agent"
 import { Skill } from "@/skill"

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

@@ -0,0 +1 @@
+export * as Provider from "./provider"

+ 1461 - 1463
packages/opencode/src/provider/provider.ts

@@ -59,1651 +59,1649 @@ import { ProviderTransform } from "./transform"
 import { Installation } from "../installation"
 import { ModelID, ProviderID } from "./schema"
 
-export namespace Provider {
-  const log = Log.create({ service: "provider" })
+const log = Log.create({ service: "provider" })
 
-  function shouldUseCopilotResponsesApi(modelID: string): boolean {
-    const match = /^gpt-(\d+)/.exec(modelID)
-    if (!match) return false
-    return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
-  }
+function shouldUseCopilotResponsesApi(modelID: string): boolean {
+  const match = /^gpt-(\d+)/.exec(modelID)
+  if (!match) return false
+  return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
+}
 
-  function wrapSSE(res: Response, ms: number, ctl: AbortController) {
-    if (typeof ms !== "number" || ms <= 0) return res
-    if (!res.body) return res
-    if (!res.headers.get("content-type")?.includes("text/event-stream")) return res
-
-    const reader = res.body.getReader()
-    const body = new ReadableStream<Uint8Array>({
-      async pull(ctrl) {
-        const part = await new Promise<Awaited<ReturnType<typeof reader.read>>>((resolve, reject) => {
-          const id = setTimeout(() => {
-            const err = new Error("SSE read timed out")
-            ctl.abort(err)
-            void reader.cancel(err)
+function wrapSSE(res: Response, ms: number, ctl: AbortController) {
+  if (typeof ms !== "number" || ms <= 0) return res
+  if (!res.body) return res
+  if (!res.headers.get("content-type")?.includes("text/event-stream")) return res
+
+  const reader = res.body.getReader()
+  const body = new ReadableStream<Uint8Array>({
+    async pull(ctrl) {
+      const part = await new Promise<Awaited<ReturnType<typeof reader.read>>>((resolve, reject) => {
+        const id = setTimeout(() => {
+          const err = new Error("SSE read timed out")
+          ctl.abort(err)
+          void reader.cancel(err)
+          reject(err)
+        }, ms)
+
+        reader.read().then(
+          (part) => {
+            clearTimeout(id)
+            resolve(part)
+          },
+          (err) => {
+            clearTimeout(id)
             reject(err)
-          }, ms)
-
-          reader.read().then(
-            (part) => {
-              clearTimeout(id)
-              resolve(part)
-            },
-            (err) => {
-              clearTimeout(id)
-              reject(err)
-            },
-          )
-        })
-
-        if (part.done) {
-          ctrl.close()
-          return
-        }
+          },
+        )
+      })
 
-        ctrl.enqueue(part.value)
-      },
-      async cancel(reason) {
-        ctl.abort(reason)
-        await reader.cancel(reason)
-      },
-    })
+      if (part.done) {
+        ctrl.close()
+        return
+      }
 
-    return new Response(body, {
-      headers: new Headers(res.headers),
-      status: res.status,
-      statusText: res.statusText,
-    })
-  }
+      ctrl.enqueue(part.value)
+    },
+    async cancel(reason) {
+      ctl.abort(reason)
+      await reader.cancel(reason)
+    },
+  })
+
+  return new Response(body, {
+    headers: new Headers(res.headers),
+    status: res.status,
+    statusText: res.statusText,
+  })
+}
 
-  type BundledSDK = {
-    languageModel(modelId: string): LanguageModelV3
-  }
+type BundledSDK = {
+  languageModel(modelId: string): LanguageModelV3
+}
 
-  const BUNDLED_PROVIDERS: Record<string, (options: any) => BundledSDK> = {
-    "@ai-sdk/amazon-bedrock": createAmazonBedrock,
-    "@ai-sdk/anthropic": createAnthropic,
-    "@ai-sdk/azure": createAzure,
-    "@ai-sdk/google": createGoogleGenerativeAI,
-    "@ai-sdk/google-vertex": createVertex,
-    "@ai-sdk/google-vertex/anthropic": createVertexAnthropic,
-    "@ai-sdk/openai": createOpenAI,
-    "@ai-sdk/openai-compatible": createOpenAICompatible,
-    "@openrouter/ai-sdk-provider": createOpenRouter,
-    "@ai-sdk/xai": createXai,
-    "@ai-sdk/mistral": createMistral,
-    "@ai-sdk/groq": createGroq,
-    "@ai-sdk/deepinfra": createDeepInfra,
-    "@ai-sdk/cerebras": createCerebras,
-    "@ai-sdk/cohere": createCohere,
-    "@ai-sdk/gateway": createGateway,
-    "@ai-sdk/togetherai": createTogetherAI,
-    "@ai-sdk/perplexity": createPerplexity,
-    "@ai-sdk/vercel": createVercel,
-    "@ai-sdk/alibaba": createAlibaba,
-    "gitlab-ai-provider": createGitLab,
-    "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
-    "venice-ai-sdk-provider": createVenice,
-  }
+const BUNDLED_PROVIDERS: Record<string, (options: any) => BundledSDK> = {
+  "@ai-sdk/amazon-bedrock": createAmazonBedrock,
+  "@ai-sdk/anthropic": createAnthropic,
+  "@ai-sdk/azure": createAzure,
+  "@ai-sdk/google": createGoogleGenerativeAI,
+  "@ai-sdk/google-vertex": createVertex,
+  "@ai-sdk/google-vertex/anthropic": createVertexAnthropic,
+  "@ai-sdk/openai": createOpenAI,
+  "@ai-sdk/openai-compatible": createOpenAICompatible,
+  "@openrouter/ai-sdk-provider": createOpenRouter,
+  "@ai-sdk/xai": createXai,
+  "@ai-sdk/mistral": createMistral,
+  "@ai-sdk/groq": createGroq,
+  "@ai-sdk/deepinfra": createDeepInfra,
+  "@ai-sdk/cerebras": createCerebras,
+  "@ai-sdk/cohere": createCohere,
+  "@ai-sdk/gateway": createGateway,
+  "@ai-sdk/togetherai": createTogetherAI,
+  "@ai-sdk/perplexity": createPerplexity,
+  "@ai-sdk/vercel": createVercel,
+  "@ai-sdk/alibaba": createAlibaba,
+  "gitlab-ai-provider": createGitLab,
+  "@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
+  "venice-ai-sdk-provider": createVenice,
+}
 
-  type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
-  type CustomVarsLoader = (options: Record<string, any>) => Record<string, string>
-  type CustomDiscoverModels = () => Promise<Record<string, Model>>
-  type CustomLoader = (provider: Info) => Effect.Effect<{
-    autoload: boolean
-    getModel?: CustomModelLoader
-    vars?: CustomVarsLoader
-    options?: Record<string, any>
-    discoverModels?: CustomDiscoverModels
-  }>
-
-  type CustomDep = {
-    auth: (id: string) => Effect.Effect<Auth.Info | undefined>
-    config: () => Effect.Effect<Config.Info>
-    env: () => Effect.Effect<Record<string, string | undefined>>
-    get: (key: string) => Effect.Effect<string | undefined>
-  }
+type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
+type CustomVarsLoader = (options: Record<string, any>) => Record<string, string>
+type CustomDiscoverModels = () => Promise<Record<string, Model>>
+type CustomLoader = (provider: Info) => Effect.Effect<{
+  autoload: boolean
+  getModel?: CustomModelLoader
+  vars?: CustomVarsLoader
+  options?: Record<string, any>
+  discoverModels?: CustomDiscoverModels
+}>
+
+type CustomDep = {
+  auth: (id: string) => Effect.Effect<Auth.Info | undefined>
+  config: () => Effect.Effect<Config.Info>
+  env: () => Effect.Effect<Record<string, string | undefined>>
+  get: (key: string) => Effect.Effect<string | undefined>
+}
 
-  function useLanguageModel(sdk: any) {
-    return sdk.responses === undefined && sdk.chat === undefined
-  }
+function useLanguageModel(sdk: any) {
+  return sdk.responses === undefined && sdk.chat === undefined
+}
 
-  function custom(dep: CustomDep): Record<string, CustomLoader> {
-    return {
-      anthropic: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
-            },
+function custom(dep: CustomDep): Record<string, CustomLoader> {
+  return {
+    anthropic: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
           },
-        }),
-      opencode: Effect.fnUntraced(function* (input: Info) {
-        const env = yield* dep.env()
-        const hasKey = iife(() => {
-          if (input.env.some((item) => env[item])) return true
-          return false
-        })
-        const ok =
-          hasKey ||
-          Boolean(yield* dep.auth(input.id)) ||
-          Boolean((yield* dep.config()).provider?.["opencode"]?.options?.apiKey)
-
-        if (!ok) {
-          for (const [key, value] of Object.entries(input.models)) {
-            if (value.cost.input === 0) continue
-            delete input.models[key]
-          }
+        },
+      }),
+    opencode: Effect.fnUntraced(function* (input: Info) {
+      const env = yield* dep.env()
+      const hasKey = iife(() => {
+        if (input.env.some((item) => env[item])) return true
+        return false
+      })
+      const ok =
+        hasKey ||
+        Boolean(yield* dep.auth(input.id)) ||
+        Boolean((yield* dep.config()).provider?.["opencode"]?.options?.apiKey)
+
+      if (!ok) {
+        for (const [key, value] of Object.entries(input.models)) {
+          if (value.cost.input === 0) continue
+          delete input.models[key]
         }
+      }
 
-        return {
-          autoload: Object.keys(input.models).length > 0,
-          options: ok ? {} : { apiKey: "public" },
-        }
+      return {
+        autoload: Object.keys(input.models).length > 0,
+        options: ok ? {} : { apiKey: "public" },
+      }
+    }),
+    openai: () =>
+      Effect.succeed({
+        autoload: false,
+        async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
+          return sdk.responses(modelID)
+        },
+        options: {},
       }),
-      openai: () =>
-        Effect.succeed({
-          autoload: false,
-          async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
-            return sdk.responses(modelID)
-          },
-          options: {},
-        }),
-      xai: () =>
-        Effect.succeed({
-          autoload: false,
-          async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
-            return sdk.responses(modelID)
-          },
-          options: {},
-        }),
-      "github-copilot": () =>
-        Effect.succeed({
-          autoload: false,
-          async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
-            if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
-            return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
-          },
-          options: {},
-        }),
-      azure: Effect.fnUntraced(function* (provider: Info) {
-        const env = yield* dep.env()
-        const resource = iife(() => {
-          const name = provider.options?.resourceName
-          if (typeof name === "string" && name.trim() !== "") return name
-          return env["AZURE_RESOURCE_NAME"]
-        })
-
-        return {
-          autoload: false,
-          async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
-            if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
-            if (options?.["useCompletionUrls"]) {
-              return sdk.chat(modelID)
-            } else {
-              return sdk.responses(modelID)
-            }
-          },
-          options: {},
-          vars(_options) {
-            return {
-              ...(resource && { AZURE_RESOURCE_NAME: resource }),
-            }
-          },
-        }
+    xai: () =>
+      Effect.succeed({
+        autoload: false,
+        async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
+          return sdk.responses(modelID)
+        },
+        options: {},
       }),
-      "azure-cognitive-services": Effect.fnUntraced(function* () {
-        const resourceName = yield* dep.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
-        return {
-          autoload: false,
-          async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
-            if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
-            if (options?.["useCompletionUrls"]) {
-              return sdk.chat(modelID)
-            } else {
-              return sdk.responses(modelID)
-            }
-          },
-          options: {
-            baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined,
-          },
-        }
+    "github-copilot": () =>
+      Effect.succeed({
+        autoload: false,
+        async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
+          if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
+          return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
+        },
+        options: {},
       }),
-      "amazon-bedrock": Effect.fnUntraced(function* () {
-        const providerConfig = (yield* dep.config()).provider?.["amazon-bedrock"]
-        const auth = yield* dep.auth("amazon-bedrock")
-        const env = yield* dep.env()
-
-        // Region precedence: 1) config file, 2) env var, 3) default
-        const configRegion = providerConfig?.options?.region
-        const envRegion = env["AWS_REGION"]
-        const defaultRegion = configRegion ?? envRegion ?? "us-east-1"
-
-        // Profile: config file takes precedence over env var
-        const configProfile = providerConfig?.options?.profile
-        const envProfile = env["AWS_PROFILE"]
-        const profile = configProfile ?? envProfile
-
-        const awsAccessKeyId = env["AWS_ACCESS_KEY_ID"]
-
-        // TODO: Using process.env directly because Env.set only updates a process.env shallow copy,
-        // until the scope of the Env API is clarified (test only or runtime?)
-        const awsBearerToken = iife(() => {
-          const envToken = process.env.AWS_BEARER_TOKEN_BEDROCK
-          if (envToken) return envToken
-          if (auth?.type === "api") {
-            process.env.AWS_BEARER_TOKEN_BEDROCK = auth.key
-            return auth.key
+    azure: Effect.fnUntraced(function* (provider: Info) {
+      const env = yield* dep.env()
+      const resource = iife(() => {
+        const name = provider.options?.resourceName
+        if (typeof name === "string" && name.trim() !== "") return name
+        return env["AZURE_RESOURCE_NAME"]
+      })
+
+      return {
+        autoload: false,
+        async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
+          if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
+          if (options?.["useCompletionUrls"]) {
+            return sdk.chat(modelID)
+          } else {
+            return sdk.responses(modelID)
           }
-          return undefined
-        })
+        },
+        options: {},
+        vars(_options) {
+          return {
+            ...(resource && { AZURE_RESOURCE_NAME: resource }),
+          }
+        },
+      }
+    }),
+    "azure-cognitive-services": Effect.fnUntraced(function* () {
+      const resourceName = yield* dep.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
+      return {
+        autoload: false,
+        async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
+          if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
+          if (options?.["useCompletionUrls"]) {
+            return sdk.chat(modelID)
+          } else {
+            return sdk.responses(modelID)
+          }
+        },
+        options: {
+          baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined,
+        },
+      }
+    }),
+    "amazon-bedrock": Effect.fnUntraced(function* () {
+      const providerConfig = (yield* dep.config()).provider?.["amazon-bedrock"]
+      const auth = yield* dep.auth("amazon-bedrock")
+      const env = yield* dep.env()
+
+      // Region precedence: 1) config file, 2) env var, 3) default
+      const configRegion = providerConfig?.options?.region
+      const envRegion = env["AWS_REGION"]
+      const defaultRegion = configRegion ?? envRegion ?? "us-east-1"
+
+      // Profile: config file takes precedence over env var
+      const configProfile = providerConfig?.options?.profile
+      const envProfile = env["AWS_PROFILE"]
+      const profile = configProfile ?? envProfile
+
+      const awsAccessKeyId = env["AWS_ACCESS_KEY_ID"]
+
+      // TODO: Using process.env directly because Env.set only updates a process.env shallow copy,
+      // until the scope of the Env API is clarified (test only or runtime?)
+      const awsBearerToken = iife(() => {
+        const envToken = process.env.AWS_BEARER_TOKEN_BEDROCK
+        if (envToken) return envToken
+        if (auth?.type === "api") {
+          process.env.AWS_BEARER_TOKEN_BEDROCK = auth.key
+          return auth.key
+        }
+        return undefined
+      })
 
-        const awsWebIdentityTokenFile = env["AWS_WEB_IDENTITY_TOKEN_FILE"]
+      const awsWebIdentityTokenFile = env["AWS_WEB_IDENTITY_TOKEN_FILE"]
 
-        const containerCreds = Boolean(
-          process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
-        )
+      const containerCreds = Boolean(
+        process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
+      )
 
-        if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds)
-          return { autoload: false }
+      if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds)
+        return { autoload: false }
 
-        const providerOptions: AmazonBedrockProviderSettings = {
-          region: defaultRegion,
-        }
+      const providerOptions: AmazonBedrockProviderSettings = {
+        region: defaultRegion,
+      }
 
-        // Only use credential chain if no bearer token exists
-        // Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens)
-        if (!awsBearerToken) {
-          // Build credential provider options (only pass profile if specified)
-          const credentialProviderOptions = profile ? { profile } : {}
+      // Only use credential chain if no bearer token exists
+      // Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens)
+      if (!awsBearerToken) {
+        // Build credential provider options (only pass profile if specified)
+        const credentialProviderOptions = profile ? { profile } : {}
 
-          providerOptions.credentialProvider = fromNodeProviderChain(credentialProviderOptions)
-        }
+        providerOptions.credentialProvider = fromNodeProviderChain(credentialProviderOptions)
+      }
 
-        // Add custom endpoint if specified (endpoint takes precedence over baseURL)
-        const endpoint = providerConfig?.options?.endpoint ?? providerConfig?.options?.baseURL
-        if (endpoint) {
-          providerOptions.baseURL = endpoint
-        }
+      // Add custom endpoint if specified (endpoint takes precedence over baseURL)
+      const endpoint = providerConfig?.options?.endpoint ?? providerConfig?.options?.baseURL
+      if (endpoint) {
+        providerOptions.baseURL = endpoint
+      }
 
-        return {
-          autoload: true,
-          options: providerOptions,
-          async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
-            // Skip region prefixing if model already has a cross-region inference profile prefix
-            // Models from models.dev may already include prefixes like us., eu., global., etc.
-            const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
-            if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) {
-              return sdk.languageModel(modelID)
-            }
+      return {
+        autoload: true,
+        options: providerOptions,
+        async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
+          // Skip region prefixing if model already has a cross-region inference profile prefix
+          // Models from models.dev may already include prefixes like us., eu., global., etc.
+          const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."]
+          if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) {
+            return sdk.languageModel(modelID)
+          }
 
-            // Region resolution precedence (highest to lowest):
-            // 1. options.region from opencode.json provider config
-            // 2. defaultRegion from AWS_REGION environment variable
-            // 3. Default "us-east-1" (baked into defaultRegion)
-            const region = options?.region ?? defaultRegion
-
-            let regionPrefix = region.split("-")[0]
-
-            switch (regionPrefix) {
-              case "us": {
-                const modelRequiresPrefix = [
-                  "nova-micro",
-                  "nova-lite",
-                  "nova-pro",
-                  "nova-premier",
-                  "nova-2",
-                  "claude",
-                  "deepseek",
-                ].some((m) => modelID.includes(m))
-                const isGovCloud = region.startsWith("us-gov")
-                if (modelRequiresPrefix && !isGovCloud) {
-                  modelID = `${regionPrefix}.${modelID}`
-                }
-                break
+          // Region resolution precedence (highest to lowest):
+          // 1. options.region from opencode.json provider config
+          // 2. defaultRegion from AWS_REGION environment variable
+          // 3. Default "us-east-1" (baked into defaultRegion)
+          const region = options?.region ?? defaultRegion
+
+          let regionPrefix = region.split("-")[0]
+
+          switch (regionPrefix) {
+            case "us": {
+              const modelRequiresPrefix = [
+                "nova-micro",
+                "nova-lite",
+                "nova-pro",
+                "nova-premier",
+                "nova-2",
+                "claude",
+                "deepseek",
+              ].some((m) => modelID.includes(m))
+              const isGovCloud = region.startsWith("us-gov")
+              if (modelRequiresPrefix && !isGovCloud) {
+                modelID = `${regionPrefix}.${modelID}`
+              }
+              break
+            }
+            case "eu": {
+              const regionRequiresPrefix = [
+                "eu-west-1",
+                "eu-west-2",
+                "eu-west-3",
+                "eu-north-1",
+                "eu-central-1",
+                "eu-south-1",
+                "eu-south-2",
+              ].some((r) => region.includes(r))
+              const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) =>
+                modelID.includes(m),
+              )
+              if (regionRequiresPrefix && modelRequiresPrefix) {
+                modelID = `${regionPrefix}.${modelID}`
               }
-              case "eu": {
-                const regionRequiresPrefix = [
-                  "eu-west-1",
-                  "eu-west-2",
-                  "eu-west-3",
-                  "eu-north-1",
-                  "eu-central-1",
-                  "eu-south-1",
-                  "eu-south-2",
-                ].some((r) => region.includes(r))
-                const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) =>
+              break
+            }
+            case "ap": {
+              const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
+              const isTokyoRegion = region === "ap-northeast-1"
+              if (
+                isAustraliaRegion &&
+                ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m))
+              ) {
+                regionPrefix = "au"
+                modelID = `${regionPrefix}.${modelID}`
+              } else if (isTokyoRegion) {
+                // Tokyo region uses jp. prefix for cross-region inference
+                const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
                   modelID.includes(m),
                 )
-                if (regionRequiresPrefix && modelRequiresPrefix) {
+                if (modelRequiresPrefix) {
+                  regionPrefix = "jp"
                   modelID = `${regionPrefix}.${modelID}`
                 }
-                break
-              }
-              case "ap": {
-                const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
-                const isTokyoRegion = region === "ap-northeast-1"
-                if (
-                  isAustraliaRegion &&
-                  ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m))
-                ) {
-                  regionPrefix = "au"
+              } else {
+                // Other APAC regions use apac. prefix
+                const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
+                  modelID.includes(m),
+                )
+                if (modelRequiresPrefix) {
+                  regionPrefix = "apac"
                   modelID = `${regionPrefix}.${modelID}`
-                } else if (isTokyoRegion) {
-                  // Tokyo region uses jp. prefix for cross-region inference
-                  const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
-                    modelID.includes(m),
-                  )
-                  if (modelRequiresPrefix) {
-                    regionPrefix = "jp"
-                    modelID = `${regionPrefix}.${modelID}`
-                  }
-                } else {
-                  // Other APAC regions use apac. prefix
-                  const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
-                    modelID.includes(m),
-                  )
-                  if (modelRequiresPrefix) {
-                    regionPrefix = "apac"
-                    modelID = `${regionPrefix}.${modelID}`
-                  }
                 }
-                break
               }
+              break
             }
+          }
 
-            return sdk.languageModel(modelID)
-          },
-        }
-      }),
-      openrouter: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "HTTP-Referer": "https://opencode.ai/",
-              "X-Title": "opencode",
-            },
-          },
-        }),
-      vercel: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "http-referer": "https://opencode.ai/",
-              "x-title": "opencode",
-            },
-          },
-        }),
-      "google-vertex": Effect.fnUntraced(function* (provider: Info) {
-        const env = yield* dep.env()
-        const project =
-          provider.options?.project ?? env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
-
-        const location = String(
-          provider.options?.location ??
-            env["GOOGLE_VERTEX_LOCATION"] ??
-            env["GOOGLE_CLOUD_LOCATION"] ??
-            env["VERTEX_LOCATION"] ??
-            "us-central1",
-        )
-
-        const autoload = Boolean(project)
-        if (!autoload) return { autoload: false }
-        return {
-          autoload: true,
-          vars(_options: Record<string, any>) {
-            const endpoint =
-              location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`
-            return {
-              ...(project && { GOOGLE_VERTEX_PROJECT: project }),
-              GOOGLE_VERTEX_LOCATION: location,
-              GOOGLE_VERTEX_ENDPOINT: endpoint,
-            }
-          },
-          options: {
-            project,
-            location,
-            fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
-              const auth = new GoogleAuth()
-              const client = await auth.getApplicationDefault()
-              const token = await client.credential.getAccessToken()
-
-              const headers = new Headers(init?.headers)
-              headers.set("Authorization", `Bearer ${token.token}`)
-
-              return fetch(input, { ...init, headers })
-            },
-          },
-          async getModel(sdk: any, modelID: string) {
-            const id = String(modelID).trim()
-            return sdk.languageModel(id)
+          return sdk.languageModel(modelID)
+        },
+      }
+    }),
+    openrouter: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "HTTP-Referer": "https://opencode.ai/",
+            "X-Title": "opencode",
           },
-        }
+        },
       }),
-      "google-vertex-anthropic": Effect.fnUntraced(function* () {
-        const env = yield* dep.env()
-        const project = env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
-        const location = env["GOOGLE_CLOUD_LOCATION"] ?? env["VERTEX_LOCATION"] ?? "global"
-        const autoload = Boolean(project)
-        if (!autoload) return { autoload: false }
-        return {
-          autoload: true,
-          options: {
-            project,
-            location,
-          },
-          async getModel(sdk: any, modelID) {
-            const id = String(modelID).trim()
-            return sdk.languageModel(id)
+    vercel: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "http-referer": "https://opencode.ai/",
+            "x-title": "opencode",
           },
-        }
+        },
       }),
-      "sap-ai-core": Effect.fnUntraced(function* () {
-        const auth = yield* dep.auth("sap-ai-core")
-        // TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env),
-        // until the scope of the Env API is clarified (test only or runtime?)
-        const envServiceKey = iife(() => {
-          const envAICoreServiceKey = process.env.AICORE_SERVICE_KEY
-          if (envAICoreServiceKey) return envAICoreServiceKey
-          if (auth?.type === "api") {
-            process.env.AICORE_SERVICE_KEY = auth.key
-            return auth.key
-          }
-          return undefined
-        })
-        const deploymentId = process.env.AICORE_DEPLOYMENT_ID
-        const resourceGroup = process.env.AICORE_RESOURCE_GROUP
+    "google-vertex": Effect.fnUntraced(function* (provider: Info) {
+      const env = yield* dep.env()
+      const project =
+        provider.options?.project ?? env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
+
+      const location = String(
+        provider.options?.location ??
+          env["GOOGLE_VERTEX_LOCATION"] ??
+          env["GOOGLE_CLOUD_LOCATION"] ??
+          env["VERTEX_LOCATION"] ??
+          "us-central1",
+      )
 
-        return {
-          autoload: !!envServiceKey,
-          options: envServiceKey ? { deploymentId, resourceGroup } : {},
-          async getModel(sdk: any, modelID: string) {
-            return sdk(modelID)
+      const autoload = Boolean(project)
+      if (!autoload) return { autoload: false }
+      return {
+        autoload: true,
+        vars(_options: Record<string, any>) {
+          const endpoint =
+            location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`
+          return {
+            ...(project && { GOOGLE_VERTEX_PROJECT: project }),
+            GOOGLE_VERTEX_LOCATION: location,
+            GOOGLE_VERTEX_ENDPOINT: endpoint,
+          }
+        },
+        options: {
+          project,
+          location,
+          fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
+            const auth = new GoogleAuth()
+            const client = await auth.getApplicationDefault()
+            const token = await client.credential.getAccessToken()
+
+            const headers = new Headers(init?.headers)
+            headers.set("Authorization", `Bearer ${token.token}`)
+
+            return fetch(input, { ...init, headers })
           },
+        },
+        async getModel(sdk: any, modelID: string) {
+          const id = String(modelID).trim()
+          return sdk.languageModel(id)
+        },
+      }
+    }),
+    "google-vertex-anthropic": Effect.fnUntraced(function* () {
+      const env = yield* dep.env()
+      const project = env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
+      const location = env["GOOGLE_CLOUD_LOCATION"] ?? env["VERTEX_LOCATION"] ?? "global"
+      const autoload = Boolean(project)
+      if (!autoload) return { autoload: false }
+      return {
+        autoload: true,
+        options: {
+          project,
+          location,
+        },
+        async getModel(sdk: any, modelID) {
+          const id = String(modelID).trim()
+          return sdk.languageModel(id)
+        },
+      }
+    }),
+    "sap-ai-core": Effect.fnUntraced(function* () {
+      const auth = yield* dep.auth("sap-ai-core")
+      // TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env),
+      // until the scope of the Env API is clarified (test only or runtime?)
+      const envServiceKey = iife(() => {
+        const envAICoreServiceKey = process.env.AICORE_SERVICE_KEY
+        if (envAICoreServiceKey) return envAICoreServiceKey
+        if (auth?.type === "api") {
+          process.env.AICORE_SERVICE_KEY = auth.key
+          return auth.key
         }
-      }),
-      zenmux: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "HTTP-Referer": "https://opencode.ai/",
-              "X-Title": "opencode",
-            },
+        return undefined
+      })
+      const deploymentId = process.env.AICORE_DEPLOYMENT_ID
+      const resourceGroup = process.env.AICORE_RESOURCE_GROUP
+
+      return {
+        autoload: !!envServiceKey,
+        options: envServiceKey ? { deploymentId, resourceGroup } : {},
+        async getModel(sdk: any, modelID: string) {
+          return sdk(modelID)
+        },
+      }
+    }),
+    zenmux: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "HTTP-Referer": "https://opencode.ai/",
+            "X-Title": "opencode",
           },
-        }),
-      gitlab: Effect.fnUntraced(function* (input: Info) {
-        const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
+        },
+      }),
+    gitlab: Effect.fnUntraced(function* (input: Info) {
+      const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
 
-        const auth = yield* dep.auth(input.id)
-        const apiKey = yield* Effect.sync(() => {
-          if (auth?.type === "oauth") return auth.access
-          if (auth?.type === "api") return auth.key
-          return undefined
-        })
-        const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
+      const auth = yield* dep.auth(input.id)
+      const apiKey = yield* Effect.sync(() => {
+        if (auth?.type === "oauth") return auth.access
+        if (auth?.type === "api") return auth.key
+        return undefined
+      })
+      const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
 
-        const providerConfig = (yield* dep.config()).provider?.["gitlab"]
+      const providerConfig = (yield* dep.config()).provider?.["gitlab"]
 
-        const aiGatewayHeaders = {
-          "User-Agent": `opencode/${Installation.VERSION} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
-          "anthropic-beta": "context-1m-2025-08-07",
-          ...providerConfig?.options?.aiGatewayHeaders,
-        }
+      const aiGatewayHeaders = {
+        "User-Agent": `opencode/${Installation.VERSION} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
+        "anthropic-beta": "context-1m-2025-08-07",
+        ...providerConfig?.options?.aiGatewayHeaders,
+      }
 
-        const featureFlags = {
-          duo_agent_platform_agentic_chat: true,
-          duo_agent_platform: true,
-          ...providerConfig?.options?.featureFlags,
-        }
+      const featureFlags = {
+        duo_agent_platform_agentic_chat: true,
+        duo_agent_platform: true,
+        ...providerConfig?.options?.featureFlags,
+      }
 
-        return {
-          autoload: !!token,
-          options: {
-            instanceUrl,
-            apiKey: token,
-            aiGatewayHeaders,
-            featureFlags,
-          },
-          async getModel(sdk: ReturnType<typeof createGitLab>, modelID: string, options?: Record<string, any>) {
-            if (modelID.startsWith("duo-workflow-")) {
-              const workflowRef = options?.workflowRef as string | undefined
-              // Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef
-              const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
-              const model = sdk.workflowChat(sdkModelID, {
-                featureFlags,
-                workflowDefinition: options?.workflowDefinition as string | undefined,
-              })
-              if (workflowRef) {
-                model.selectedModelRef = workflowRef
-              }
-              return model
-            }
-            return sdk.agenticChat(modelID, {
-              aiGatewayHeaders,
+      return {
+        autoload: !!token,
+        options: {
+          instanceUrl,
+          apiKey: token,
+          aiGatewayHeaders,
+          featureFlags,
+        },
+        async getModel(sdk: ReturnType<typeof createGitLab>, modelID: string, options?: Record<string, any>) {
+          if (modelID.startsWith("duo-workflow-")) {
+            const workflowRef = options?.workflowRef as string | undefined
+            // Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef
+            const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
+            const model = sdk.workflowChat(sdkModelID, {
               featureFlags,
+              workflowDefinition: options?.workflowDefinition as string | undefined,
             })
-          },
-          async discoverModels(): Promise<Record<string, Model>> {
-            if (!apiKey) {
-              log.info("gitlab model discovery skipped: no apiKey")
-              return {}
+            if (workflowRef) {
+              model.selectedModelRef = workflowRef
             }
+            return model
+          }
+          return sdk.agenticChat(modelID, {
+            aiGatewayHeaders,
+            featureFlags,
+          })
+        },
+        async discoverModels(): Promise<Record<string, Model>> {
+          if (!apiKey) {
+            log.info("gitlab model discovery skipped: no apiKey")
+            return {}
+          }
 
-            try {
-              const token = apiKey
-              const getHeaders = (): Record<string, string> =>
-                auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` }
-
-              log.info("gitlab model discovery starting", { instanceUrl })
-              const result = await discoverWorkflowModels(
-                { instanceUrl, getHeaders },
-                { workingDirectory: Instance.directory },
-              )
+          try {
+            const token = apiKey
+            const getHeaders = (): Record<string, string> =>
+              auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` }
+
+            log.info("gitlab model discovery starting", { instanceUrl })
+            const result = await discoverWorkflowModels(
+              { instanceUrl, getHeaders },
+              { workingDirectory: Instance.directory },
+            )
 
-              if (!result.models.length) {
-                log.info("gitlab model discovery skipped: no models found", {
-                  project: result.project
-                    ? {
-                        id: result.project.id,
-                        path: result.project.pathWithNamespace,
-                      }
-                    : null,
-                })
-                return {}
-              }
+            if (!result.models.length) {
+              log.info("gitlab model discovery skipped: no models found", {
+                project: result.project
+                  ? {
+                      id: result.project.id,
+                      path: result.project.pathWithNamespace,
+                    }
+                  : null,
+              })
+              return {}
+            }
 
-              const models: Record<string, Model> = {}
-              for (const m of result.models) {
-                if (!input.models[m.id]) {
-                  models[m.id] = {
-                    id: ModelID.make(m.id),
-                    providerID: ProviderID.make("gitlab"),
-                    name: `Agent Platform (${m.name})`,
-                    family: "",
-                    api: {
-                      id: m.id,
-                      url: instanceUrl,
-                      npm: "gitlab-ai-provider",
+            const models: Record<string, Model> = {}
+            for (const m of result.models) {
+              if (!input.models[m.id]) {
+                models[m.id] = {
+                  id: ModelID.make(m.id),
+                  providerID: ProviderID.make("gitlab"),
+                  name: `Agent Platform (${m.name})`,
+                  family: "",
+                  api: {
+                    id: m.id,
+                    url: instanceUrl,
+                    npm: "gitlab-ai-provider",
+                  },
+                  status: "active",
+                  headers: {},
+                  options: { workflowRef: m.ref },
+                  cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
+                  limit: { context: m.context, output: m.output },
+                  capabilities: {
+                    temperature: false,
+                    reasoning: true,
+                    attachment: true,
+                    toolcall: true,
+                    input: {
+                      text: true,
+                      audio: false,
+                      image: true,
+                      video: false,
+                      pdf: true,
                     },
-                    status: "active",
-                    headers: {},
-                    options: { workflowRef: m.ref },
-                    cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
-                    limit: { context: m.context, output: m.output },
-                    capabilities: {
-                      temperature: false,
-                      reasoning: true,
-                      attachment: true,
-                      toolcall: true,
-                      input: {
-                        text: true,
-                        audio: false,
-                        image: true,
-                        video: false,
-                        pdf: true,
-                      },
-                      output: {
-                        text: true,
-                        audio: false,
-                        image: false,
-                        video: false,
-                        pdf: false,
-                      },
-                      interleaved: false,
+                    output: {
+                      text: true,
+                      audio: false,
+                      image: false,
+                      video: false,
+                      pdf: false,
                     },
-                    release_date: "",
-                    variants: {},
-                  }
+                    interleaved: false,
+                  },
+                  release_date: "",
+                  variants: {},
                 }
               }
-
-              log.info("gitlab model discovery complete", {
-                count: Object.keys(models).length,
-                models: Object.keys(models),
-              })
-              return models
-            } catch (e) {
-              log.warn("gitlab model discovery failed", { error: e })
-              return {}
             }
+
+            log.info("gitlab model discovery complete", {
+              count: Object.keys(models).length,
+              models: Object.keys(models),
+            })
+            return models
+          } catch (e) {
+            log.warn("gitlab model discovery failed", { error: e })
+            return {}
+          }
+        },
+      }
+    }),
+    "cloudflare-workers-ai": Effect.fnUntraced(function* (input: Info) {
+      // When baseURL is already configured (e.g. corporate config routing through a proxy/gateway),
+      // skip the account ID check because the URL is already fully specified.
+      if (input.options?.baseURL) return { autoload: false }
+
+      const auth = yield* dep.auth(input.id)
+      const env = yield* dep.env()
+      const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+      if (!accountId)
+        return {
+          autoload: false,
+          async getModel() {
+            throw new Error(
+              "CLOUDFLARE_ACCOUNT_ID is missing. Set it with: export CLOUDFLARE_ACCOUNT_ID=<your-account-id>",
+            )
           },
         }
-      }),
-      "cloudflare-workers-ai": Effect.fnUntraced(function* (input: Info) {
-        // When baseURL is already configured (e.g. corporate config routing through a proxy/gateway),
-        // skip the account ID check because the URL is already fully specified.
-        if (input.options?.baseURL) return { autoload: false }
-
-        const auth = yield* dep.auth(input.id)
-        const env = yield* dep.env()
-        const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
-        if (!accountId)
-          return {
-            autoload: false,
-            async getModel() {
-              throw new Error(
-                "CLOUDFLARE_ACCOUNT_ID is missing. Set it with: export CLOUDFLARE_ACCOUNT_ID=<your-account-id>",
-              )
-            },
-          }
 
-        const apiKey = yield* Effect.gen(function* () {
-          const envToken = env["CLOUDFLARE_API_KEY"]
-          if (envToken) return envToken
-          if (auth?.type === "api") return auth.key
-          return undefined
-        })
+      const apiKey = yield* Effect.gen(function* () {
+        const envToken = env["CLOUDFLARE_API_KEY"]
+        if (envToken) return envToken
+        if (auth?.type === "api") return auth.key
+        return undefined
+      })
 
-        return {
-          autoload: !!apiKey,
-          options: {
-            apiKey,
-            headers: {
-              "User-Agent": `opencode/${Installation.VERSION} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
-            },
-          },
-          async getModel(sdk: any, modelID: string) {
-            return sdk.languageModel(modelID)
-          },
-          vars(_options) {
-            return {
-              CLOUDFLARE_ACCOUNT_ID: accountId,
-            }
+      return {
+        autoload: !!apiKey,
+        options: {
+          apiKey,
+          headers: {
+            "User-Agent": `opencode/${Installation.VERSION} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
           },
-        }
-      }),
-      "cloudflare-ai-gateway": Effect.fnUntraced(function* (input: Info) {
-        // When baseURL is already configured (e.g. corporate config), skip the ID checks.
-        if (input.options?.baseURL) return { autoload: false }
-
-        const auth = yield* dep.auth(input.id)
-        const env = yield* dep.env()
-        const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
-        const gateway = env["CLOUDFLARE_GATEWAY_ID"] || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined)
-
-        if (!accountId || !gateway) {
-          const missing = [
-            !accountId ? "CLOUDFLARE_ACCOUNT_ID" : undefined,
-            !gateway ? "CLOUDFLARE_GATEWAY_ID" : undefined,
-          ].filter((x): x is string => Boolean(x))
+        },
+        async getModel(sdk: any, modelID: string) {
+          return sdk.languageModel(modelID)
+        },
+        vars(_options) {
           return {
-            autoload: false,
-            async getModel() {
-              throw new Error(
-                `${missing.join(" and ")} missing. Set with: ${missing.map((x) => `export ${x}=<value>`).join(" && ")}`,
-              )
-            },
+            CLOUDFLARE_ACCOUNT_ID: accountId,
           }
+        },
+      }
+    }),
+    "cloudflare-ai-gateway": Effect.fnUntraced(function* (input: Info) {
+      // When baseURL is already configured (e.g. corporate config), skip the ID checks.
+      if (input.options?.baseURL) return { autoload: false }
+
+      const auth = yield* dep.auth(input.id)
+      const env = yield* dep.env()
+      const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+      const gateway = env["CLOUDFLARE_GATEWAY_ID"] || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined)
+
+      if (!accountId || !gateway) {
+        const missing = [
+          !accountId ? "CLOUDFLARE_ACCOUNT_ID" : undefined,
+          !gateway ? "CLOUDFLARE_GATEWAY_ID" : undefined,
+        ].filter((x): x is string => Boolean(x))
+        return {
+          autoload: false,
+          async getModel() {
+            throw new Error(
+              `${missing.join(" and ")} missing. Set with: ${missing.map((x) => `export ${x}=<value>`).join(" && ")}`,
+            )
+          },
         }
+      }
 
-        // Get API token from env or auth - required for authenticated gateways
-        const apiToken = yield* Effect.gen(function* () {
-          const envToken = env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"]
-          if (envToken) return envToken
-          if (auth?.type === "api") return auth.key
-          return undefined
-        })
+      // Get API token from env or auth - required for authenticated gateways
+      const apiToken = yield* Effect.gen(function* () {
+        const envToken = env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"]
+        if (envToken) return envToken
+        if (auth?.type === "api") return auth.key
+        return undefined
+      })
 
-        if (!apiToken) {
-          throw new Error(
-            "CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " +
-              "Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.",
-          )
-        }
+      if (!apiToken) {
+        throw new Error(
+          "CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " +
+            "Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.",
+        )
+      }
 
-        // Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility)
-        const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider"))
-        const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified"))
+      // Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility)
+      const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider"))
+      const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified"))
 
-        const metadata = iife(() => {
-          if (input.options?.metadata) return input.options.metadata
-          try {
-            return JSON.parse(input.options?.headers?.["cf-aig-metadata"])
-          } catch {
-            return undefined
-          }
-        })
-        const opts = {
-          metadata,
-          cacheTtl: input.options?.cacheTtl,
-          cacheKey: input.options?.cacheKey,
-          skipCache: input.options?.skipCache,
-          collectLog: input.options?.collectLog,
-          headers: {
-            "User-Agent": `opencode/${Installation.VERSION} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
-          },
+      const metadata = iife(() => {
+        if (input.options?.metadata) return input.options.metadata
+        try {
+          return JSON.parse(input.options?.headers?.["cf-aig-metadata"])
+        } catch {
+          return undefined
         }
+      })
+      const opts = {
+        metadata,
+        cacheTtl: input.options?.cacheTtl,
+        cacheKey: input.options?.cacheKey,
+        skipCache: input.options?.skipCache,
+        collectLog: input.options?.collectLog,
+        headers: {
+          "User-Agent": `opencode/${Installation.VERSION} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
+        },
+      }
 
-        const aigateway = createAiGateway({
-          accountId,
-          gateway,
-          apiKey: apiToken,
-          ...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}),
-        })
-        const unified = createUnified()
+      const aigateway = createAiGateway({
+        accountId,
+        gateway,
+        apiKey: apiToken,
+        ...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}),
+      })
+      const unified = createUnified()
 
-        return {
-          autoload: true,
-          async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
-            // Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5")
-            return aigateway(unified(modelID))
+      return {
+        autoload: true,
+        async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
+          // Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5")
+          return aigateway(unified(modelID))
+        },
+        options: {},
+      }
+    }),
+    cerebras: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "X-Cerebras-3rd-Party-Integration": "opencode",
           },
-          options: {},
-        }
+        },
       }),
-      cerebras: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "X-Cerebras-3rd-Party-Integration": "opencode",
-            },
-          },
-        }),
-      kilo: () =>
-        Effect.succeed({
-          autoload: false,
-          options: {
-            headers: {
-              "HTTP-Referer": "https://opencode.ai/",
-              "X-Title": "opencode",
-            },
+    kilo: () =>
+      Effect.succeed({
+        autoload: false,
+        options: {
+          headers: {
+            "HTTP-Referer": "https://opencode.ai/",
+            "X-Title": "opencode",
           },
-        }),
-    }
+        },
+      }),
   }
+}
 
-  export const Model = z
-    .object({
-      id: ModelID.zod,
-      providerID: ProviderID.zod,
-      api: z.object({
-        id: z.string(),
-        url: z.string(),
-        npm: z.string(),
+export const Model = z
+  .object({
+    id: ModelID.zod,
+    providerID: ProviderID.zod,
+    api: z.object({
+      id: z.string(),
+      url: z.string(),
+      npm: z.string(),
+    }),
+    name: z.string(),
+    family: z.string().optional(),
+    capabilities: z.object({
+      temperature: z.boolean(),
+      reasoning: z.boolean(),
+      attachment: z.boolean(),
+      toolcall: z.boolean(),
+      input: z.object({
+        text: z.boolean(),
+        audio: z.boolean(),
+        image: z.boolean(),
+        video: z.boolean(),
+        pdf: z.boolean(),
       }),
-      name: z.string(),
-      family: z.string().optional(),
-      capabilities: z.object({
-        temperature: z.boolean(),
-        reasoning: z.boolean(),
-        attachment: z.boolean(),
-        toolcall: z.boolean(),
-        input: z.object({
-          text: z.boolean(),
-          audio: z.boolean(),
-          image: z.boolean(),
-          video: z.boolean(),
-          pdf: z.boolean(),
-        }),
-        output: z.object({
-          text: z.boolean(),
-          audio: z.boolean(),
-          image: z.boolean(),
-          video: z.boolean(),
-          pdf: z.boolean(),
-        }),
-        interleaved: z.union([
-          z.boolean(),
-          z.object({
-            field: z.enum(["reasoning_content", "reasoning_details"]),
-          }),
-        ]),
+      output: z.object({
+        text: z.boolean(),
+        audio: z.boolean(),
+        image: z.boolean(),
+        video: z.boolean(),
+        pdf: z.boolean(),
       }),
-      cost: z.object({
-        input: z.number(),
-        output: z.number(),
-        cache: z.object({
-          read: z.number(),
-          write: z.number(),
+      interleaved: z.union([
+        z.boolean(),
+        z.object({
+          field: z.enum(["reasoning_content", "reasoning_details"]),
         }),
-        experimentalOver200K: z
-          .object({
-            input: z.number(),
-            output: z.number(),
-            cache: z.object({
-              read: z.number(),
-              write: z.number(),
-            }),
-          })
-          .optional(),
-      }),
-      limit: z.object({
-        context: z.number(),
-        input: z.number().optional(),
-        output: z.number(),
+      ]),
+    }),
+    cost: z.object({
+      input: z.number(),
+      output: z.number(),
+      cache: z.object({
+        read: z.number(),
+        write: z.number(),
       }),
-      status: z.enum(["alpha", "beta", "deprecated", "active"]),
-      options: z.record(z.string(), z.any()),
-      headers: z.record(z.string(), z.string()),
-      release_date: z.string(),
-      variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
-    })
-    .meta({
-      ref: "Model",
-    })
-  export type Model = z.infer<typeof Model>
-
-  export const Info = z
-    .object({
-      id: ProviderID.zod,
-      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>
-
-  export interface Interface {
-    readonly list: () => Effect.Effect<Record<ProviderID, Info>>
-    readonly getProvider: (providerID: ProviderID) => Effect.Effect<Info>
-    readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect<Model>
-    readonly getLanguage: (model: Model) => Effect.Effect<LanguageModelV3>
-    readonly closest: (
-      providerID: ProviderID,
-      query: string[],
-    ) => Effect.Effect<{ providerID: ProviderID; modelID: string } | undefined>
-    readonly getSmallModel: (providerID: ProviderID) => Effect.Effect<Model | undefined>
-    readonly defaultModel: () => Effect.Effect<{ providerID: ProviderID; modelID: ModelID }>
-  }
+      experimentalOver200K: z
+        .object({
+          input: z.number(),
+          output: z.number(),
+          cache: z.object({
+            read: z.number(),
+            write: z.number(),
+          }),
+        })
+        .optional(),
+    }),
+    limit: z.object({
+      context: z.number(),
+      input: z.number().optional(),
+      output: z.number(),
+    }),
+    status: z.enum(["alpha", "beta", "deprecated", "active"]),
+    options: z.record(z.string(), z.any()),
+    headers: z.record(z.string(), z.string()),
+    release_date: z.string(),
+    variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
+  })
+  .meta({
+    ref: "Model",
+  })
+export type Model = z.infer<typeof Model>
+
+export const Info = z
+  .object({
+    id: ProviderID.zod,
+    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>
+
+export interface Interface {
+  readonly list: () => Effect.Effect<Record<ProviderID, Info>>
+  readonly getProvider: (providerID: ProviderID) => Effect.Effect<Info>
+  readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect<Model>
+  readonly getLanguage: (model: Model) => Effect.Effect<LanguageModelV3>
+  readonly closest: (
+    providerID: ProviderID,
+    query: string[],
+  ) => Effect.Effect<{ providerID: ProviderID; modelID: string } | undefined>
+  readonly getSmallModel: (providerID: ProviderID) => Effect.Effect<Model | undefined>
+  readonly defaultModel: () => Effect.Effect<{ providerID: ProviderID; modelID: ModelID }>
+}
 
-  interface State {
-    models: Map<string, LanguageModelV3>
-    providers: Record<ProviderID, Info>
-    sdk: Map<string, BundledSDK>
-    modelLoaders: Record<string, CustomModelLoader>
-    varsLoaders: Record<string, CustomVarsLoader>
-  }
+interface State {
+  models: Map<string, LanguageModelV3>
+  providers: Record<ProviderID, Info>
+  sdk: Map<string, BundledSDK>
+  modelLoaders: Record<string, CustomModelLoader>
+  varsLoaders: Record<string, CustomVarsLoader>
+}
 
-  export class Service extends Context.Service<Service, Interface>()("@opencode/Provider") {}
+export class Service extends Context.Service<Service, Interface>()("@opencode/Provider") {}
 
-  function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
-    const result: Model["cost"] = {
-      input: c?.input ?? 0,
-      output: c?.output ?? 0,
+function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
+  const result: Model["cost"] = {
+    input: c?.input ?? 0,
+    output: c?.output ?? 0,
+    cache: {
+      read: c?.cache_read ?? 0,
+      write: c?.cache_write ?? 0,
+    },
+  }
+  if (c?.context_over_200k) {
+    result.experimentalOver200K = {
       cache: {
-        read: c?.cache_read ?? 0,
-        write: c?.cache_write ?? 0,
+        read: c.context_over_200k.cache_read ?? 0,
+        write: c.context_over_200k.cache_write ?? 0,
       },
+      input: c.context_over_200k.input,
+      output: c.context_over_200k.output,
     }
-    if (c?.context_over_200k) {
-      result.experimentalOver200K = {
-        cache: {
-          read: c.context_over_200k.cache_read ?? 0,
-          write: c.context_over_200k.cache_write ?? 0,
-        },
-        input: c.context_over_200k.input,
-        output: c.context_over_200k.output,
-      }
-    }
-    return result
   }
+  return result
+}
 
-  function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
-    const m: Model = {
-      id: ModelID.make(model.id),
-      providerID: ProviderID.make(provider.id),
-      name: model.name,
-      family: model.family,
-      api: {
-        id: model.id,
-        url: model.provider?.api ?? provider.api!,
-        npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
+function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
+  const m: Model = {
+    id: ModelID.make(model.id),
+    providerID: ProviderID.make(provider.id),
+    name: model.name,
+    family: model.family,
+    api: {
+      id: model.id,
+      url: model.provider?.api ?? provider.api!,
+      npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
+    },
+    status: model.status ?? "active",
+    headers: {},
+    options: {},
+    cost: cost(model.cost),
+    limit: {
+      context: model.limit.context,
+      input: model.limit.input,
+      output: model.limit.output,
+    },
+    capabilities: {
+      temperature: model.temperature,
+      reasoning: model.reasoning,
+      attachment: model.attachment,
+      toolcall: model.tool_call,
+      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,
       },
-      status: model.status ?? "active",
-      headers: {},
-      options: {},
-      cost: cost(model.cost),
-      limit: {
-        context: model.limit.context,
-        input: model.limit.input,
-        output: model.limit.output,
-      },
-      capabilities: {
-        temperature: model.temperature,
-        reasoning: model.reasoning,
-        attachment: model.attachment,
-        toolcall: model.tool_call,
-        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,
-        },
-        interleaved: model.interleaved ?? 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,
       },
-      release_date: model.release_date,
-      variants: {},
-    }
+      interleaved: model.interleaved ?? false,
+    },
+    release_date: model.release_date,
+    variants: {},
+  }
 
-    m.variants = mapValues(ProviderTransform.variants(m), (v) => v)
+  m.variants = mapValues(ProviderTransform.variants(m), (v) => v)
 
-    return m
-  }
+  return m
+}
 
-  export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
-    const models: Record<string, Model> = {}
-    for (const [key, model] of Object.entries(provider.models)) {
-      models[key] = fromModelsDevModel(provider, model)
-      for (const [mode, opts] of Object.entries(model.experimental?.modes ?? {})) {
-        const id = `${model.id}-${mode}`
-        const m = fromModelsDevModel(provider, model)
-        m.id = ModelID.make(id)
-        m.name = `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`
-        if (opts.cost) m.cost = mergeDeep(m.cost, cost(opts.cost))
-        // convert body params to camelCase for ai sdk compatibility
-        if (opts.provider?.body)
-          m.options = Object.fromEntries(
-            Object.entries(opts.provider.body).map(([k, v]) => [k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()), v]),
-          )
-        if (opts.provider?.headers) m.headers = opts.provider.headers
-        models[id] = m
-      }
-    }
-    return {
-      id: ProviderID.make(provider.id),
-      source: "custom",
-      name: provider.name,
-      env: provider.env ?? [],
-      options: {},
-      models,
+export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
+  const models: Record<string, Model> = {}
+  for (const [key, model] of Object.entries(provider.models)) {
+    models[key] = fromModelsDevModel(provider, model)
+    for (const [mode, opts] of Object.entries(model.experimental?.modes ?? {})) {
+      const id = `${model.id}-${mode}`
+      const m = fromModelsDevModel(provider, model)
+      m.id = ModelID.make(id)
+      m.name = `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`
+      if (opts.cost) m.cost = mergeDeep(m.cost, cost(opts.cost))
+      // convert body params to camelCase for ai sdk compatibility
+      if (opts.provider?.body)
+        m.options = Object.fromEntries(
+          Object.entries(opts.provider.body).map(([k, v]) => [k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()), v]),
+        )
+      if (opts.provider?.headers) m.headers = opts.provider.headers
+      models[id] = m
     }
   }
+  return {
+    id: ProviderID.make(provider.id),
+    source: "custom",
+    name: provider.name,
+    env: provider.env ?? [],
+    options: {},
+    models,
+  }
+}
 
-  const layer: Layer.Layer<
-    Service,
-    never,
-    Config.Service | Auth.Service | Plugin.Service | AppFileSystem.Service | Env.Service
-  > = Layer.effect(
-    Service,
-    Effect.gen(function* () {
-      const fs = yield* AppFileSystem.Service
-      const config = yield* Config.Service
-      const auth = yield* Auth.Service
-      const env = yield* Env.Service
-      const plugin = yield* Plugin.Service
-
-      const state = yield* InstanceState.make<State>(() =>
-        Effect.gen(function* () {
-          using _ = log.time("state")
-          const bridge = yield* EffectBridge.make()
-          const cfg = yield* config.get()
-          const modelsDev = yield* Effect.promise(() => ModelsDev.get())
-          const database = mapValues(modelsDev, fromModelsDevProvider)
-
-          const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
-          const languages = new Map<string, LanguageModelV3>()
-          const modelLoaders: {
-            [providerID: string]: CustomModelLoader
-          } = {}
-          const varsLoaders: {
-            [providerID: string]: CustomVarsLoader
-          } = {}
-          const sdk = new Map<string, BundledSDK>()
-          const discoveryLoaders: {
-            [providerID: string]: CustomDiscoverModels
-          } = {}
-          const dep = {
-            auth: (id: string) => auth.get(id).pipe(Effect.orDie),
-            config: () => config.get(),
-            env: () => env.all(),
-            get: (key: string) => env.get(key),
-          }
+const layer: Layer.Layer<
+  Service,
+  never,
+  Config.Service | Auth.Service | Plugin.Service | AppFileSystem.Service | Env.Service
+> = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const fs = yield* AppFileSystem.Service
+    const config = yield* Config.Service
+    const auth = yield* Auth.Service
+    const env = yield* Env.Service
+    const plugin = yield* Plugin.Service
+
+    const state = yield* InstanceState.make<State>(() =>
+      Effect.gen(function* () {
+        using _ = log.time("state")
+        const bridge = yield* EffectBridge.make()
+        const cfg = yield* config.get()
+        const modelsDev = yield* Effect.promise(() => ModelsDev.get())
+        const database = mapValues(modelsDev, fromModelsDevProvider)
+
+        const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
+        const languages = new Map<string, LanguageModelV3>()
+        const modelLoaders: {
+          [providerID: string]: CustomModelLoader
+        } = {}
+        const varsLoaders: {
+          [providerID: string]: CustomVarsLoader
+        } = {}
+        const sdk = new Map<string, BundledSDK>()
+        const discoveryLoaders: {
+          [providerID: string]: CustomDiscoverModels
+        } = {}
+        const dep = {
+          auth: (id: string) => auth.get(id).pipe(Effect.orDie),
+          config: () => config.get(),
+          env: () => env.all(),
+          get: (key: string) => env.get(key),
+        }
 
-          log.info("init")
+        log.info("init")
 
-          function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
-            const existing = providers[providerID]
-            if (existing) {
-              // @ts-expect-error
-              providers[providerID] = mergeDeep(existing, provider)
-              return
-            }
-            const match = database[providerID]
-            if (!match) return
+        function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
+          const existing = providers[providerID]
+          if (existing) {
             // @ts-expect-error
-            providers[providerID] = mergeDeep(match, provider)
+            providers[providerID] = mergeDeep(existing, provider)
+            return
           }
+          const match = database[providerID]
+          if (!match) return
+          // @ts-expect-error
+          providers[providerID] = mergeDeep(match, provider)
+        }
 
-          // load plugins first so config() hook runs before reading cfg.provider
-          const plugins = yield* plugin.list()
+        // load plugins first so config() hook runs before reading cfg.provider
+        const plugins = yield* plugin.list()
 
-          // now read config providers - includes any modifications from plugin config() hook
-          const configProviders = Object.entries(cfg.provider ?? {})
-          const disabled = new Set(cfg.disabled_providers ?? [])
-          const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null
+        // now read config providers - includes any modifications from plugin config() hook
+        const configProviders = Object.entries(cfg.provider ?? {})
+        const disabled = new Set(cfg.disabled_providers ?? [])
+        const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null
 
-          function isProviderAllowed(providerID: ProviderID): boolean {
-            if (enabled && !enabled.has(providerID)) return false
-            if (disabled.has(providerID)) return false
-            return true
-          }
+        function isProviderAllowed(providerID: ProviderID): boolean {
+          if (enabled && !enabled.has(providerID)) return false
+          if (disabled.has(providerID)) return false
+          return true
+        }
 
-          // extend database from config
-          for (const [providerID, provider] of configProviders) {
-            const existing = database[providerID]
-            const parsed: Info = {
-              id: ProviderID.make(providerID),
-              name: provider.name ?? existing?.name ?? providerID,
-              env: provider.env ?? existing?.env ?? [],
-              options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
-              source: "config",
-              models: existing?.models ?? {},
-            }
+        // extend database from config
+        for (const [providerID, provider] of configProviders) {
+          const existing = database[providerID]
+          const parsed: Info = {
+            id: ProviderID.make(providerID),
+            name: provider.name ?? existing?.name ?? providerID,
+            env: provider.env ?? existing?.env ?? [],
+            options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
+            source: "config",
+            models: existing?.models ?? {},
+          }
 
-            for (const [modelID, model] of Object.entries(provider.models ?? {})) {
-              const existingModel = parsed.models[model.id ?? modelID]
-              const name = iife(() => {
-                if (model.name) return model.name
-                if (model.id && model.id !== modelID) return modelID
-                return existingModel?.name ?? modelID
-              })
-              const parsedModel: Model = {
-                id: ModelID.make(modelID),
-                api: {
-                  id: model.id ?? existingModel?.api.id ?? modelID,
-                  npm:
-                    model.provider?.npm ??
-                    provider.npm ??
-                    existingModel?.api.npm ??
-                    modelsDev[providerID]?.npm ??
-                    "@ai-sdk/openai-compatible",
-                  url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
-                },
-                status: model.status ?? existingModel?.status ?? "active",
-                name,
-                providerID: ProviderID.make(providerID),
-                capabilities: {
-                  temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
-                  reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
-                  attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
-                  toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
-                  input: {
-                    text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
-                    audio:
-                      model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
-                    image:
-                      model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
-                    video:
-                      model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
-                    pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
-                  },
-                  output: {
-                    text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
-                    audio:
-                      model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
-                    image:
-                      model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
-                    video:
-                      model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
-                    pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
-                  },
-                  interleaved: model.interleaved ?? false,
+          for (const [modelID, model] of Object.entries(provider.models ?? {})) {
+            const existingModel = parsed.models[model.id ?? modelID]
+            const name = iife(() => {
+              if (model.name) return model.name
+              if (model.id && model.id !== modelID) return modelID
+              return existingModel?.name ?? modelID
+            })
+            const parsedModel: Model = {
+              id: ModelID.make(modelID),
+              api: {
+                id: model.id ?? existingModel?.api.id ?? modelID,
+                npm:
+                  model.provider?.npm ??
+                  provider.npm ??
+                  existingModel?.api.npm ??
+                  modelsDev[providerID]?.npm ??
+                  "@ai-sdk/openai-compatible",
+                url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
+              },
+              status: model.status ?? existingModel?.status ?? "active",
+              name,
+              providerID: ProviderID.make(providerID),
+              capabilities: {
+                temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
+                reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
+                attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
+                toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
+                input: {
+                  text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
+                  audio:
+                    model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
+                  image:
+                    model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
+                  video:
+                    model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
+                  pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
                 },
-                cost: {
-                  input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
-                  output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
-                  cache: {
-                    read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
-                    write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
-                  },
+                output: {
+                  text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
+                  audio:
+                    model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
+                  image:
+                    model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
+                  video:
+                    model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
+                  pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
                 },
-                options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
-                limit: {
-                  context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
-                  input: model.limit?.input ?? existingModel?.limit?.input,
-                  output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
+                interleaved: model.interleaved ?? false,
+              },
+              cost: {
+                input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
+                output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
+                cache: {
+                  read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
+                  write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
                 },
-                headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
-                family: model.family ?? existingModel?.family ?? "",
-                release_date: model.release_date ?? existingModel?.release_date ?? "",
-                variants: {},
-              }
-              const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
-              parsedModel.variants = mapValues(
-                pickBy(merged, (v) => !v.disabled),
-                (v) => omit(v, ["disabled"]),
-              )
-              parsed.models[modelID] = parsedModel
+              },
+              options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
+              limit: {
+                context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
+                input: model.limit?.input ?? existingModel?.limit?.input,
+                output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
+              },
+              headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
+              family: model.family ?? existingModel?.family ?? "",
+              release_date: model.release_date ?? existingModel?.release_date ?? "",
+              variants: {},
             }
-            database[providerID] = parsed
+            const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
+            parsedModel.variants = mapValues(
+              pickBy(merged, (v) => !v.disabled),
+              (v) => omit(v, ["disabled"]),
+            )
+            parsed.models[modelID] = parsedModel
           }
+          database[providerID] = parsed
+        }
 
-          // load env
-          const envs = yield* env.all()
-          for (const [id, provider] of Object.entries(database)) {
-            const providerID = ProviderID.make(id)
-            if (disabled.has(providerID)) continue
-            const apiKey = provider.env.map((item) => envs[item]).find(Boolean)
-            if (!apiKey) continue
+        // load env
+        const envs = yield* env.all()
+        for (const [id, provider] of Object.entries(database)) {
+          const providerID = ProviderID.make(id)
+          if (disabled.has(providerID)) continue
+          const apiKey = provider.env.map((item) => envs[item]).find(Boolean)
+          if (!apiKey) continue
+          mergeProvider(providerID, {
+            source: "env",
+            key: provider.env.length === 1 ? apiKey : undefined,
+          })
+        }
+
+        // load apikeys
+        const auths = yield* auth.all().pipe(Effect.orDie)
+        for (const [id, provider] of Object.entries(auths)) {
+          const providerID = ProviderID.make(id)
+          if (disabled.has(providerID)) continue
+          if (provider.type === "api") {
             mergeProvider(providerID, {
-              source: "env",
-              key: provider.env.length === 1 ? apiKey : undefined,
+              source: "api",
+              key: provider.key,
             })
           }
+        }
 
-          // load apikeys
-          const auths = yield* auth.all().pipe(Effect.orDie)
-          for (const [id, provider] of Object.entries(auths)) {
-            const providerID = ProviderID.make(id)
-            if (disabled.has(providerID)) continue
-            if (provider.type === "api") {
-              mergeProvider(providerID, {
-                source: "api",
-                key: provider.key,
-              })
-            }
-          }
+        // plugin auth loader - database now has entries for config providers
+        for (const plugin of plugins) {
+          if (!plugin.auth) continue
+          const providerID = ProviderID.make(plugin.auth.provider)
+          if (disabled.has(providerID)) continue
+
+          const stored = yield* auth.get(providerID).pipe(Effect.orDie)
+          if (!stored) continue
+          if (!plugin.auth.loader) continue
+
+          const options = yield* Effect.promise(() =>
+            plugin.auth!.loader!(
+              () => bridge.promise(auth.get(providerID).pipe(Effect.orDie)) as any,
+              database[plugin.auth!.provider],
+            ),
+          )
+          const opts = options ?? {}
+          const patch: Partial<Info> = providers[providerID] ? { options: opts } : { source: "custom", options: opts }
+          mergeProvider(providerID, patch)
+        }
 
-          // plugin auth loader - database now has entries for config providers
-          for (const plugin of plugins) {
-            if (!plugin.auth) continue
-            const providerID = ProviderID.make(plugin.auth.provider)
-            if (disabled.has(providerID)) continue
-
-            const stored = yield* auth.get(providerID).pipe(Effect.orDie)
-            if (!stored) continue
-            if (!plugin.auth.loader) continue
-
-            const options = yield* Effect.promise(() =>
-              plugin.auth!.loader!(
-                () => bridge.promise(auth.get(providerID).pipe(Effect.orDie)) as any,
-                database[plugin.auth!.provider],
-              ),
-            )
-            const opts = options ?? {}
-            const patch: Partial<Info> = providers[providerID] ? { options: opts } : { source: "custom", options: opts }
-            mergeProvider(providerID, patch)
+        for (const [id, fn] of Object.entries(custom(dep))) {
+          const providerID = ProviderID.make(id)
+          if (disabled.has(providerID)) continue
+          const data = database[providerID]
+          if (!data) {
+            log.error("Provider does not exist in model list " + providerID)
+            continue
           }
-
-          for (const [id, fn] of Object.entries(custom(dep))) {
-            const providerID = ProviderID.make(id)
-            if (disabled.has(providerID)) continue
-            const data = database[providerID]
-            if (!data) {
-              log.error("Provider does not exist in model list " + providerID)
-              continue
-            }
-            const result = yield* fn(data)
-            if (result && (result.autoload || providers[providerID])) {
-              if (result.getModel) modelLoaders[providerID] = result.getModel
-              if (result.vars) varsLoaders[providerID] = result.vars
-              if (result.discoverModels) discoveryLoaders[providerID] = result.discoverModels
-              const opts = result.options ?? {}
-              const patch: Partial<Info> = providers[providerID]
-                ? { options: opts }
-                : { source: "custom", options: opts }
-              mergeProvider(providerID, patch)
-            }
+          const result = yield* fn(data)
+          if (result && (result.autoload || providers[providerID])) {
+            if (result.getModel) modelLoaders[providerID] = result.getModel
+            if (result.vars) varsLoaders[providerID] = result.vars
+            if (result.discoverModels) discoveryLoaders[providerID] = result.discoverModels
+            const opts = result.options ?? {}
+            const patch: Partial<Info> = providers[providerID]
+              ? { options: opts }
+              : { source: "custom", options: opts }
+            mergeProvider(providerID, patch)
           }
+        }
 
-          // load config - re-apply with updated data
-          for (const [id, provider] of configProviders) {
-            const providerID = ProviderID.make(id)
-            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)
-          }
+        // load config - re-apply with updated data
+        for (const [id, provider] of configProviders) {
+          const providerID = ProviderID.make(id)
+          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)
+        }
 
-          const gitlab = ProviderID.make("gitlab")
-          if (discoveryLoaders[gitlab] && providers[gitlab] && isProviderAllowed(gitlab)) {
-            yield* Effect.promise(async () => {
-              try {
-                const discovered = await discoveryLoaders[gitlab]()
-                for (const [modelID, model] of Object.entries(discovered)) {
-                  if (!providers[gitlab].models[modelID]) {
-                    providers[gitlab].models[modelID] = model
-                  }
+        const gitlab = ProviderID.make("gitlab")
+        if (discoveryLoaders[gitlab] && providers[gitlab] && isProviderAllowed(gitlab)) {
+          yield* Effect.promise(async () => {
+            try {
+              const discovered = await discoveryLoaders[gitlab]()
+              for (const [modelID, model] of Object.entries(discovered)) {
+                if (!providers[gitlab].models[modelID]) {
+                  providers[gitlab].models[modelID] = model
                 }
-              } catch (e) {
-                log.warn("state discovery error", { id: "gitlab", error: e })
               }
-            })
-          }
+            } catch (e) {
+              log.warn("state discovery error", { id: "gitlab", error: e })
+            }
+          })
+        }
 
-          for (const hook of plugins) {
-            const p = hook.provider
-            const models = p?.models
-            if (!p || !models) continue
-
-            const providerID = ProviderID.make(p.id)
-            if (disabled.has(providerID)) continue
-
-            const provider = providers[providerID]
-            if (!provider) continue
-            const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
-
-            provider.models = yield* Effect.promise(async () => {
-              const next = await models(provider, { auth: pluginAuth })
-              return Object.fromEntries(
-                Object.entries(next).map(([id, model]) => [
-                  id,
-                  {
-                    ...model,
-                    id: ModelID.make(id),
-                    providerID,
-                  },
-                ]),
-              )
-            })
-          }
+        for (const hook of plugins) {
+          const p = hook.provider
+          const models = p?.models
+          if (!p || !models) continue
 
-          for (const [id, provider] of Object.entries(providers)) {
-            const providerID = ProviderID.make(id)
-            if (!isProviderAllowed(providerID)) {
-              delete providers[providerID]
-              continue
-            }
+          const providerID = ProviderID.make(p.id)
+          if (disabled.has(providerID)) continue
 
-            const configProvider = cfg.provider?.[providerID]
+          const provider = providers[providerID]
+          if (!provider) continue
+          const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
 
-            for (const [modelID, model] of Object.entries(provider.models)) {
-              model.api.id = model.api.id ?? model.id ?? modelID
-              if (
-                modelID === "gpt-5-chat-latest" ||
-                (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
-              )
-                delete provider.models[modelID]
-              if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) delete provider.models[modelID]
-              if (model.status === "deprecated") delete provider.models[modelID]
-              if (
-                (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
-                (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
-              )
-                delete provider.models[modelID]
+          provider.models = yield* Effect.promise(async () => {
+            const next = await models(provider, { auth: pluginAuth })
+            return Object.fromEntries(
+              Object.entries(next).map(([id, model]) => [
+                id,
+                {
+                  ...model,
+                  id: ModelID.make(id),
+                  providerID,
+                },
+              ]),
+            )
+          })
+        }
 
-              model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
+        for (const [id, provider] of Object.entries(providers)) {
+          const providerID = ProviderID.make(id)
+          if (!isProviderAllowed(providerID)) {
+            delete providers[providerID]
+            continue
+          }
 
-              const configVariants = configProvider?.models?.[modelID]?.variants
-              if (configVariants && model.variants) {
-                const merged = mergeDeep(model.variants, configVariants)
-                model.variants = mapValues(
-                  pickBy(merged, (v) => !v.disabled),
-                  (v) => omit(v, ["disabled"]),
-                )
-              }
-            }
+          const configProvider = cfg.provider?.[providerID]
 
-            if (Object.keys(provider.models).length === 0) {
-              delete providers[providerID]
-              continue
-            }
+          for (const [modelID, model] of Object.entries(provider.models)) {
+            model.api.id = model.api.id ?? model.id ?? modelID
+            if (
+              modelID === "gpt-5-chat-latest" ||
+              (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
+            )
+              delete provider.models[modelID]
+            if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) delete provider.models[modelID]
+            if (model.status === "deprecated") delete provider.models[modelID]
+            if (
+              (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
+              (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
+            )
+              delete provider.models[modelID]
 
-            log.info("found", { providerID })
+            model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
+
+            const configVariants = configProvider?.models?.[modelID]?.variants
+            if (configVariants && model.variants) {
+              const merged = mergeDeep(model.variants, configVariants)
+              model.variants = mapValues(
+                pickBy(merged, (v) => !v.disabled),
+                (v) => omit(v, ["disabled"]),
+              )
+            }
           }
 
-          return {
-            models: languages,
-            providers,
-            sdk,
-            modelLoaders,
-            varsLoaders,
+          if (Object.keys(provider.models).length === 0) {
+            delete providers[providerID]
+            continue
           }
-        }),
-      )
 
-      const list = Effect.fn("Provider.list")(() => InstanceState.use(state, (s) => s.providers))
+          log.info("found", { providerID })
+        }
 
-      async function resolveSDK(model: Model, s: State, envs: Record<string, string | undefined>) {
-        try {
-          using _ = log.time("getSDK", {
-            providerID: model.providerID,
-          })
-          const provider = s.providers[model.providerID]
-          const options = { ...provider.options }
+        return {
+          models: languages,
+          providers,
+          sdk,
+          modelLoaders,
+          varsLoaders,
+        }
+      }),
+    )
 
-          if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
-            delete options.fetch
-          }
+    const list = Effect.fn("Provider.list")(() => InstanceState.use(state, (s) => s.providers))
 
-          if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
-            options["includeUsage"] = true
-          }
+    async function resolveSDK(model: Model, s: State, envs: Record<string, string | undefined>) {
+      try {
+        using _ = log.time("getSDK", {
+          providerID: model.providerID,
+        })
+        const provider = s.providers[model.providerID]
+        const options = { ...provider.options }
 
-          const baseURL = iife(() => {
-            let url =
-              typeof options["baseURL"] === "string" && options["baseURL"] !== "" ? options["baseURL"] : model.api.url
-            if (!url) return
-
-            const loader = s.varsLoaders[model.providerID]
-            if (loader) {
-              const vars = loader(options)
-              for (const [key, value] of Object.entries(vars)) {
-                const field = "${" + key + "}"
-                url = url.replaceAll(field, value)
-              }
+        if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
+          delete options.fetch
+        }
+
+        if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
+          options["includeUsage"] = true
+        }
+
+        const baseURL = iife(() => {
+          let url =
+            typeof options["baseURL"] === "string" && options["baseURL"] !== "" ? options["baseURL"] : model.api.url
+          if (!url) return
+
+          const loader = s.varsLoaders[model.providerID]
+          if (loader) {
+            const vars = loader(options)
+            for (const [key, value] of Object.entries(vars)) {
+              const field = "${" + key + "}"
+              url = url.replaceAll(field, value)
             }
+          }
 
-            url = url.replace(/\$\{([^}]+)\}/g, (item, key) => {
-              const val = envs[String(key)]
-              return val ?? item
-            })
-            return url
+          url = url.replace(/\$\{([^}]+)\}/g, (item, key) => {
+            const val = envs[String(key)]
+            return val ?? item
           })
+          return url
+        })
 
-          if (baseURL !== undefined) options["baseURL"] = baseURL
-          if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
-          if (model.headers)
-            options["headers"] = {
-              ...options["headers"],
-              ...model.headers,
-            }
+        if (baseURL !== undefined) options["baseURL"] = baseURL
+        if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
+        if (model.headers)
+          options["headers"] = {
+            ...options["headers"],
+            ...model.headers,
+          }
 
-          const key = Hash.fast(
-            JSON.stringify({
-              providerID: model.providerID,
-              npm: model.api.npm,
-              options,
-            }),
-          )
-          const existing = s.sdk.get(key)
-          if (existing) return existing
-
-          const customFetch = options["fetch"]
-          const chunkTimeout = options["chunkTimeout"]
-          delete options["chunkTimeout"]
-
-          options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
-            const fetchFn = customFetch ?? fetch
-            const opts = init ?? {}
-            const chunkAbortCtl =
-              typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
-            const signals: AbortSignal[] = []
-
-            if (opts.signal) signals.push(opts.signal)
-            if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
-            if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false)
-              signals.push(AbortSignal.timeout(options["timeout"]))
-
-            const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
-            if (combined) opts.signal = combined
-
-            // Strip openai itemId metadata following what codex does
-            if (model.api.npm === "@ai-sdk/openai" && opts.body && opts.method === "POST") {
-              const body = JSON.parse(opts.body as string)
-              const isAzure = model.providerID.includes("azure")
-              const keepIds = isAzure && body.store === true
-              if (!keepIds && Array.isArray(body.input)) {
-                for (const item of body.input) {
-                  if ("id" in item) {
-                    delete item.id
-                  }
+        const key = Hash.fast(
+          JSON.stringify({
+            providerID: model.providerID,
+            npm: model.api.npm,
+            options,
+          }),
+        )
+        const existing = s.sdk.get(key)
+        if (existing) return existing
+
+        const customFetch = options["fetch"]
+        const chunkTimeout = options["chunkTimeout"]
+        delete options["chunkTimeout"]
+
+        options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
+          const fetchFn = customFetch ?? fetch
+          const opts = init ?? {}
+          const chunkAbortCtl =
+            typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
+          const signals: AbortSignal[] = []
+
+          if (opts.signal) signals.push(opts.signal)
+          if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
+          if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false)
+            signals.push(AbortSignal.timeout(options["timeout"]))
+
+          const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
+          if (combined) opts.signal = combined
+
+          // Strip openai itemId metadata following what codex does
+          if (model.api.npm === "@ai-sdk/openai" && opts.body && opts.method === "POST") {
+            const body = JSON.parse(opts.body as string)
+            const isAzure = model.providerID.includes("azure")
+            const keepIds = isAzure && body.store === true
+            if (!keepIds && Array.isArray(body.input)) {
+              for (const item of body.input) {
+                if ("id" in item) {
+                  delete item.id
                 }
-                opts.body = JSON.stringify(body)
               }
+              opts.body = JSON.stringify(body)
             }
-
-            const res = await fetchFn(input, {
-              ...opts,
-              // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
-              timeout: false,
-            })
-
-            if (!chunkAbortCtl) return res
-            return wrapSSE(res, chunkTimeout, chunkAbortCtl)
-          }
-
-          const bundledFn = BUNDLED_PROVIDERS[model.api.npm]
-          if (bundledFn) {
-            log.info("using bundled provider", {
-              providerID: model.providerID,
-              pkg: model.api.npm,
-            })
-            const loaded = bundledFn({
-              name: model.providerID,
-              ...options,
-            })
-            s.sdk.set(key, loaded)
-            return loaded as SDK
           }
 
-          let installedPath: string
-          if (!model.api.npm.startsWith("file://")) {
-            const item = await Npm.add(model.api.npm)
-            if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
-            installedPath = item.entrypoint
-          } else {
-            log.info("loading local provider", { pkg: model.api.npm })
-            installedPath = model.api.npm
-          }
+          const res = await fetchFn(input, {
+            ...opts,
+            // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
+            timeout: false,
+          })
 
-          const mod = await import(installedPath)
+          if (!chunkAbortCtl) return res
+          return wrapSSE(res, chunkTimeout, chunkAbortCtl)
+        }
 
-          const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
-          const loaded = fn({
+        const bundledFn = BUNDLED_PROVIDERS[model.api.npm]
+        if (bundledFn) {
+          log.info("using bundled provider", {
+            providerID: model.providerID,
+            pkg: model.api.npm,
+          })
+          const loaded = bundledFn({
             name: model.providerID,
             ...options,
           })
           s.sdk.set(key, loaded)
           return loaded as SDK
-        } catch (e) {
-          throw new InitError({ providerID: model.providerID }, { cause: e })
         }
+
+        let installedPath: string
+        if (!model.api.npm.startsWith("file://")) {
+          const item = await Npm.add(model.api.npm)
+          if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
+          installedPath = item.entrypoint
+        } else {
+          log.info("loading local provider", { pkg: model.api.npm })
+          installedPath = model.api.npm
+        }
+
+        const mod = await import(installedPath)
+
+        const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
+        const loaded = fn({
+          name: model.providerID,
+          ...options,
+        })
+        s.sdk.set(key, loaded)
+        return loaded as SDK
+      } catch (e) {
+        throw new InitError({ providerID: model.providerID }, { cause: e })
       }
+    }
 
-      const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderID) =>
-        InstanceState.use(state, (s) => s.providers[providerID]),
-      )
+    const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderID) =>
+      InstanceState.use(state, (s) => s.providers[providerID]),
+    )
 
-      const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderID, modelID: ModelID) {
-        const s = yield* InstanceState.get(state)
-        const provider = s.providers[providerID]
-        if (!provider) {
-          const available = Object.keys(s.providers)
-          const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
-          throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
-        }
+    const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderID, modelID: ModelID) {
+      const s = yield* InstanceState.get(state)
+      const provider = s.providers[providerID]
+      if (!provider) {
+        const available = Object.keys(s.providers)
+        const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
+        throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+      }
 
-        const info = provider.models[modelID]
-        if (!info) {
-          const available = Object.keys(provider.models)
-          const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
-          throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
-        }
-        return info
-      })
+      const info = provider.models[modelID]
+      if (!info) {
+        const available = Object.keys(provider.models)
+        const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
+        throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+      }
+      return info
+    })
 
-      const getLanguage = Effect.fn("Provider.getLanguage")(function* (model: Model) {
-        const s = yield* InstanceState.get(state)
-        const envs = yield* env.all()
-        const key = `${model.providerID}/${model.id}`
-        if (s.models.has(key)) return s.models.get(key)!
+    const getLanguage = Effect.fn("Provider.getLanguage")(function* (model: Model) {
+      const s = yield* InstanceState.get(state)
+      const envs = yield* env.all()
+      const key = `${model.providerID}/${model.id}`
+      if (s.models.has(key)) return s.models.get(key)!
 
-        return yield* Effect.promise(async () => {
-          const provider = s.providers[model.providerID]
-          const sdk = await resolveSDK(model, s, envs)
+      return yield* Effect.promise(async () => {
+        const provider = s.providers[model.providerID]
+        const sdk = await resolveSDK(model, s, envs)
 
-          try {
-            const language = s.modelLoaders[model.providerID]
-              ? await s.modelLoaders[model.providerID](sdk, model.api.id, {
-                  ...provider.options,
-                  ...model.options,
-                })
-              : sdk.languageModel(model.api.id)
-            s.models.set(key, language)
-            return language
-          } catch (e) {
-            if (e instanceof NoSuchModelError)
-              throw new ModelNotFoundError(
-                {
-                  modelID: model.id,
-                  providerID: model.providerID,
-                },
-                { cause: e },
-              )
-            throw e
-          }
-        })
+        try {
+          const language = s.modelLoaders[model.providerID]
+            ? await s.modelLoaders[model.providerID](sdk, model.api.id, {
+                ...provider.options,
+                ...model.options,
+              })
+            : sdk.languageModel(model.api.id)
+          s.models.set(key, language)
+          return language
+        } catch (e) {
+          if (e instanceof NoSuchModelError)
+            throw new ModelNotFoundError(
+              {
+                modelID: model.id,
+                providerID: model.providerID,
+              },
+              { cause: e },
+            )
+          throw e
+        }
       })
+    })
 
-      const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
-        const s = yield* InstanceState.get(state)
-        const provider = s.providers[providerID]
-        if (!provider) return undefined
-        for (const item of query) {
-          for (const modelID of Object.keys(provider.models)) {
-            if (modelID.includes(item)) return { providerID, modelID }
-          }
+    const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
+      const s = yield* InstanceState.get(state)
+      const provider = s.providers[providerID]
+      if (!provider) return undefined
+      for (const item of query) {
+        for (const modelID of Object.keys(provider.models)) {
+          if (modelID.includes(item)) return { providerID, modelID }
         }
-        return undefined
-      })
+      }
+      return undefined
+    })
 
-      const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderID) {
-        const cfg = yield* config.get()
+    const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderID) {
+      const cfg = yield* config.get()
 
-        if (cfg.small_model) {
-          const parsed = parseModel(cfg.small_model)
-          return yield* getModel(parsed.providerID, parsed.modelID)
-        }
+      if (cfg.small_model) {
+        const parsed = parseModel(cfg.small_model)
+        return yield* getModel(parsed.providerID, parsed.modelID)
+      }
 
-        const s = yield* InstanceState.get(state)
-        const provider = s.providers[providerID]
-        if (!provider) return undefined
-
-        let priority = [
-          "claude-haiku-4-5",
-          "claude-haiku-4.5",
-          "3-5-haiku",
-          "3.5-haiku",
-          "gemini-3-flash",
-          "gemini-2.5-flash",
-          "gpt-5-nano",
-        ]
-        if (providerID.startsWith("opencode")) {
-          priority = ["gpt-5-nano"]
-        }
-        if (providerID.startsWith("github-copilot")) {
-          priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
-        }
-        for (const item of priority) {
-          if (providerID === ProviderID.amazonBedrock) {
-            const crossRegionPrefixes = ["global.", "us.", "eu."]
-            const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
-
-            const globalMatch = candidates.find((m) => m.startsWith("global."))
-            if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
-
-            const region = provider.options?.region
-            if (region) {
-              const regionPrefix = region.split("-")[0]
-              if (regionPrefix === "us" || regionPrefix === "eu") {
-                const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
-                if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
-              }
+      const s = yield* InstanceState.get(state)
+      const provider = s.providers[providerID]
+      if (!provider) return undefined
+
+      let priority = [
+        "claude-haiku-4-5",
+        "claude-haiku-4.5",
+        "3-5-haiku",
+        "3.5-haiku",
+        "gemini-3-flash",
+        "gemini-2.5-flash",
+        "gpt-5-nano",
+      ]
+      if (providerID.startsWith("opencode")) {
+        priority = ["gpt-5-nano"]
+      }
+      if (providerID.startsWith("github-copilot")) {
+        priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
+      }
+      for (const item of priority) {
+        if (providerID === ProviderID.amazonBedrock) {
+          const crossRegionPrefixes = ["global.", "us.", "eu."]
+          const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
+
+          const globalMatch = candidates.find((m) => m.startsWith("global."))
+          if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
+
+          const region = provider.options?.region
+          if (region) {
+            const regionPrefix = region.split("-")[0]
+            if (regionPrefix === "us" || regionPrefix === "eu") {
+              const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
+              if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
             }
+          }
 
-            const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
-            if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
-          } else {
-            for (const model of Object.keys(provider.models)) {
-              if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
-            }
+          const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
+          if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
+        } else {
+          for (const model of Object.keys(provider.models)) {
+            if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
           }
         }
+      }
 
-        return undefined
-      })
-
-      const defaultModel = Effect.fn("Provider.defaultModel")(function* () {
-        const cfg = yield* config.get()
-        if (cfg.model) return parseModel(cfg.model)
-
-        const s = yield* InstanceState.get(state)
-        const recent = yield* fs.readJson(path.join(Global.Path.state, "model.json")).pipe(
-          Effect.map((x): { providerID: ProviderID; modelID: ModelID }[] => {
-            if (!isRecord(x) || !Array.isArray(x.recent)) return []
-            return x.recent.flatMap((item) => {
-              if (!isRecord(item)) return []
-              if (typeof item.providerID !== "string") return []
-              if (typeof item.modelID !== "string") return []
-              return [{ providerID: ProviderID.make(item.providerID), modelID: ModelID.make(item.modelID) }]
-            })
-          }),
-          Effect.catch(() => Effect.succeed([] as { providerID: ProviderID; modelID: ModelID }[])),
-        )
-        for (const entry of recent) {
-          const provider = s.providers[entry.providerID]
-          if (!provider) continue
-          if (!provider.models[entry.modelID]) continue
-          return { providerID: entry.providerID, modelID: entry.modelID }
-        }
+      return undefined
+    })
 
-        const provider = Object.values(s.providers).find(
-          (p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id),
-        )
-        if (!provider) throw new Error("no providers found")
-        const [model] = sort(Object.values(provider.models))
-        if (!model) throw new Error("no models found")
-        return {
-          providerID: provider.id,
-          modelID: model.id,
-        }
-      })
+    const defaultModel = Effect.fn("Provider.defaultModel")(function* () {
+      const cfg = yield* config.get()
+      if (cfg.model) return parseModel(cfg.model)
+
+      const s = yield* InstanceState.get(state)
+      const recent = yield* fs.readJson(path.join(Global.Path.state, "model.json")).pipe(
+        Effect.map((x): { providerID: ProviderID; modelID: ModelID }[] => {
+          if (!isRecord(x) || !Array.isArray(x.recent)) return []
+          return x.recent.flatMap((item) => {
+            if (!isRecord(item)) return []
+            if (typeof item.providerID !== "string") return []
+            if (typeof item.modelID !== "string") return []
+            return [{ providerID: ProviderID.make(item.providerID), modelID: ModelID.make(item.modelID) }]
+          })
+        }),
+        Effect.catch(() => Effect.succeed([] as { providerID: ProviderID; modelID: ModelID }[])),
+      )
+      for (const entry of recent) {
+        const provider = s.providers[entry.providerID]
+        if (!provider) continue
+        if (!provider.models[entry.modelID]) continue
+        return { providerID: entry.providerID, modelID: entry.modelID }
+      }
 
-      return Service.of({ list, getProvider, getModel, getLanguage, closest, getSmallModel, defaultModel })
-    }),
-  )
+      const provider = Object.values(s.providers).find(
+        (p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id),
+      )
+      if (!provider) throw new Error("no providers found")
+      const [model] = sort(Object.values(provider.models))
+      if (!model) throw new Error("no models found")
+      return {
+        providerID: provider.id,
+        modelID: model.id,
+      }
+    })
 
-  export const defaultLayer = Layer.suspend(() =>
-    layer.pipe(
-      Layer.provide(AppFileSystem.defaultLayer),
-      Layer.provide(Env.defaultLayer),
-      Layer.provide(Config.defaultLayer),
-      Layer.provide(Auth.defaultLayer),
-      Layer.provide(Plugin.defaultLayer),
-    ),
+    return Service.of({ list, getProvider, getModel, getLanguage, closest, getSmallModel, defaultModel })
+  }),
+)
+
+export const defaultLayer = Layer.suspend(() =>
+  layer.pipe(
+    Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Env.defaultLayer),
+    Layer.provide(Config.defaultLayer),
+    Layer.provide(Auth.defaultLayer),
+    Layer.provide(Plugin.defaultLayer),
+  ),
+)
+
+const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
+export function sort<T extends { id: string }>(models: T[]) {
+  return sortBy(
+    models,
+    [(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
+    [(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
+    [(model) => model.id, "desc"],
   )
+}
 
-  const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
-  export function sort<T extends { id: string }>(models: T[]) {
-    return sortBy(
-      models,
-      [(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
-      [(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
-      [(model) => model.id, "desc"],
-    )
-  }
-
-  export function parseModel(model: string) {
-    const [providerID, ...rest] = model.split("/")
-    return {
-      providerID: ProviderID.make(providerID),
-      modelID: ModelID.make(rest.join("/")),
-    }
+export function parseModel(model: string) {
+  const [providerID, ...rest] = model.split("/")
+  return {
+    providerID: ProviderID.make(providerID),
+    modelID: ModelID.make(rest.join("/")),
   }
-
-  export const ModelNotFoundError = NamedError.create(
-    "ProviderModelNotFoundError",
-    z.object({
-      providerID: ProviderID.zod,
-      modelID: ModelID.zod,
-      suggestions: z.array(z.string()).optional(),
-    }),
-  )
-
-  export const InitError = NamedError.create(
-    "ProviderInitError",
-    z.object({
-      providerID: ProviderID.zod,
-    }),
-  )
 }
+
+export const ModelNotFoundError = NamedError.create(
+  "ProviderModelNotFoundError",
+  z.object({
+    providerID: ProviderID.zod,
+    modelID: ModelID.zod,
+    suggestions: z.array(z.string()).optional(),
+  }),
+)
+
+export const InitError = NamedError.create(
+  "ProviderInitError",
+  z.object({
+    providerID: ProviderID.zod,
+  }),
+)

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

@@ -2,7 +2,7 @@ import type { ModelMessage } from "ai"
 import { mergeDeep, unique } from "remeda"
 import type { JSONSchema7 } from "@ai-sdk/provider"
 import type { JSONSchema } from "zod/v4/core"
-import type { Provider } from "./provider"
+import type { Provider } from "."
 import type { ModelsDev } from "./models"
 import { iife } from "@/util/iife"
 import { Flag } from "@/flag/flag"

+ 1 - 1
packages/opencode/src/server/instance/config.ts

@@ -2,7 +2,7 @@ import { Hono } from "hono"
 import { describeRoute, validator, resolver } from "hono-openapi"
 import z from "zod"
 import { Config } from "../../config"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import { mapValues } from "remeda"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"

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

@@ -2,7 +2,7 @@ import { Hono } from "hono"
 import { describeRoute, validator, resolver } from "hono-openapi"
 import z from "zod"
 import { Config } from "../../config"
-import { Provider } from "../../provider/provider"
+import { Provider } from "../../provider"
 import { ModelsDev } from "../../provider/models"
 import { ProviderAuth } from "../../provider/auth"
 import { ProviderID } from "../../provider/schema"

+ 1 - 1
packages/opencode/src/server/middleware.ts

@@ -1,4 +1,4 @@
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { NamedError } from "@opencode-ai/shared/util/error"
 import { NotFoundError } from "../storage/db"
 import { Session } from "../session"

+ 1 - 1
packages/opencode/src/session/compaction.ts

@@ -2,7 +2,7 @@ import { BusEvent } from "@/bus/bus-event"
 import { Bus } from "@/bus"
 import { Session } from "."
 import { SessionID, MessageID, PartID } from "./schema"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { MessageV2 } from "./message-v2"
 import z from "zod"
 import { Token } from "../util/token"

+ 1 - 1
packages/opencode/src/session/index.ts

@@ -24,7 +24,7 @@ import { ProjectID } from "../project/schema"
 import { WorkspaceID } from "../control-plane/schema"
 import { SessionID, MessageID, PartID } from "./schema"
 
-import type { Provider } from "@/provider/provider"
+import type { Provider } from "@/provider"
 import { Permission } from "@/permission"
 import { Global } from "@/global"
 import { Effect, Layer, Option, Context } from "effect"

+ 1 - 1
packages/opencode/src/session/llm.ts

@@ -1,4 +1,4 @@
-import { Provider } from "@/provider/provider"
+import { Provider } from "@/provider"
 import { Log } from "@/util/log"
 import { Context, Effect, Layer, Record } from "effect"
 import * as Stream from "effect/Stream"

+ 1 - 1
packages/opencode/src/session/message-v2.ts

@@ -12,7 +12,7 @@ import { ProviderError } from "@/provider/error"
 import { iife } from "@/util/iife"
 import { errorMessage } from "@/util/error"
 import type { SystemError } from "bun"
-import type { Provider } from "@/provider/provider"
+import type { Provider } from "@/provider"
 import { ModelID, ProviderID } from "@/provider/schema"
 import { Effect } from "effect"
 import { EffectLogger } from "@/effect/logger"

+ 1 - 1
packages/opencode/src/session/overflow.ts

@@ -1,5 +1,5 @@
 import type { Config } from "@/config"
-import type { Provider } from "@/provider/provider"
+import type { Provider } from "@/provider"
 import { ProviderTransform } from "@/provider/transform"
 import type { MessageV2 } from "./message-v2"
 

+ 1 - 1
packages/opencode/src/session/processor.ts

@@ -15,7 +15,7 @@ import type { SessionID } from "./schema"
 import { SessionRetry } from "./retry"
 import { SessionStatus } from "./status"
 import { SessionSummary } from "./summary"
-import type { Provider } from "@/provider/provider"
+import type { Provider } from "@/provider"
 import { Question } from "@/question"
 import { errorMessage } from "@/util/error"
 import { Log } from "@/util/log"

+ 1 - 1
packages/opencode/src/session/prompt.ts

@@ -7,7 +7,7 @@ import { Log } from "../util/log"
 import { SessionRevert } from "./revert"
 import { Session } from "."
 import { Agent } from "../agent/agent"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { ModelID, ProviderID } from "../provider/schema"
 import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
 import { SessionCompaction } from "./compaction"

+ 1 - 1
packages/opencode/src/session/system.ts

@@ -11,7 +11,7 @@ import PROMPT_KIMI from "./prompt/kimi.txt"
 
 import PROMPT_CODEX from "./prompt/codex.txt"
 import PROMPT_TRINITY from "./prompt/trinity.txt"
-import type { Provider } from "@/provider/provider"
+import type { Provider } from "@/provider"
 import type { Agent } from "@/agent/agent"
 import { Permission } from "@/permission"
 import { Skill } from "@/skill"

+ 1 - 1
packages/opencode/src/share/share-next.ts

@@ -4,7 +4,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } fr
 import { Account } from "@/account"
 import { Bus } from "@/bus"
 import { InstanceState } from "@/effect/instance-state"
-import { Provider } from "@/provider/provider"
+import { Provider } from "@/provider"
 import { ModelID, ProviderID } from "@/provider/schema"
 import { Session } from "@/session"
 import { MessageV2 } from "@/session/message-v2"

+ 1 - 1
packages/opencode/src/tool/plan.ts

@@ -5,7 +5,7 @@ import { Tool } from "./tool"
 import { Question } from "../question"
 import { Session } from "../session"
 import { MessageV2 } from "../session/message-v2"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { Instance } from "../project/instance"
 import { type SessionID, MessageID, PartID } from "../session/schema"
 import EXIT_DESCRIPTION from "./plan-exit.txt"

+ 1 - 1
packages/opencode/src/tool/registry.ts

@@ -17,7 +17,7 @@ import { Config } from "../config"
 import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin"
 import z from "zod"
 import { Plugin } from "../plugin"
-import { Provider } from "../provider/provider"
+import { Provider } from "../provider"
 import { ProviderID, type ModelID } from "../provider/schema"
 import { WebSearchTool } from "./websearch"
 import { CodeSearchTool } from "./codesearch"

+ 1 - 1
packages/opencode/test/fake/provider.ts

@@ -1,5 +1,5 @@
 import { Effect, Layer } from "effect"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 
 export namespace ProviderTest {

+ 1 - 1
packages/opencode/test/provider/amazon-bedrock.test.ts

@@ -5,7 +5,7 @@ import { unlink } from "fs/promises"
 import { ProviderID } from "../../src/provider/schema"
 import { tmpdir } from "../fixture/fixture"
 import { Instance } from "../../src/project/instance"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { Env } from "../../src/env"
 import { Global } from "../../src/global"
 import { Filesystem } from "../../src/util/filesystem"

+ 1 - 1
packages/opencode/test/provider/gitlab-duo.test.ts

@@ -9,7 +9,7 @@ export {}
 // import { ProviderID, ModelID } from "../../src/provider/schema"
 // import { tmpdir } from "../fixture/fixture"
 // import { Instance } from "../../src/project/instance"
-// import { Provider } from "../../src/provider/provider"
+// import { Provider } from "../../src/provider"
 // import { Env } from "../../src/env"
 // import { Global } from "../../src/global"
 // import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"

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

@@ -7,7 +7,7 @@ import { Global } from "../../src/global"
 import { Instance } from "../../src/project/instance"
 import { Plugin } from "../../src/plugin/index"
 import { ModelsDev } from "../../src/provider/models"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "../../src/util/filesystem"
 import { Env } from "../../src/env"

+ 1 - 1
packages/opencode/test/session/compaction.test.ts

@@ -20,7 +20,7 @@ import { MessageID, PartID, SessionID } from "../../src/session/schema"
 import { SessionStatus } from "../../src/session/status"
 import { SessionSummary } from "../../src/session/summary"
 import { ModelID, ProviderID } from "../../src/provider/schema"
-import type { Provider } from "../../src/provider/provider"
+import type { Provider } from "../../src/provider"
 import * as SessionProcessorModule from "../../src/session/processor"
 import { Snapshot } from "../../src/snapshot"
 import { ProviderTest } from "../fake/provider"

+ 1 - 1
packages/opencode/test/session/llm.test.ts

@@ -6,7 +6,7 @@ import z from "zod"
 import { makeRuntime } from "../../src/effect/run-service"
 import { LLM } from "../../src/session/llm"
 import { Instance } from "../../src/project/instance"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { ProviderTransform } from "../../src/provider/transform"
 import { ModelsDev } from "../../src/provider/models"
 import { ProviderID, ModelID } from "../../src/provider/schema"

+ 1 - 1
packages/opencode/test/session/message-v2.test.ts

@@ -1,7 +1,7 @@
 import { describe, expect, test } from "bun:test"
 import { APICallError } from "ai"
 import { MessageV2 } from "../../src/session/message-v2"
-import type { Provider } from "../../src/provider/provider"
+import type { Provider } from "../../src/provider"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { SessionID, MessageID, PartID } from "../../src/session/schema"
 import { Question } from "../../src/question"

+ 1 - 1
packages/opencode/test/session/processor-effect.test.ts

@@ -8,7 +8,7 @@ import { Bus } from "../../src/bus"
 import { Config } from "../../src/config"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { Session } from "../../src/session"
 import { LLM } from "../../src/session/llm"

+ 2 - 2
packages/opencode/test/session/prompt-effect.test.ts

@@ -12,9 +12,9 @@ import { LSP } from "../../src/lsp"
 import { MCP } from "../../src/mcp"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
-import { Provider as ProviderSvc } from "../../src/provider/provider"
+import { Provider as ProviderSvc } from "../../src/provider"
 import { Env } from "../../src/env"
-import type { Provider } from "../../src/provider/provider"
+import type { Provider } from "../../src/provider"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { Question } from "../../src/question"
 import { Todo } from "../../src/session/todo"

+ 1 - 1
packages/opencode/test/session/snapshot-tool-race.test.ts

@@ -38,7 +38,7 @@ import { LSP } from "../../src/lsp"
 import { MCP } from "../../src/mcp"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
-import { Provider as ProviderSvc } from "../../src/provider/provider"
+import { Provider as ProviderSvc } from "../../src/provider"
 import { Env } from "../../src/env"
 import { Question } from "../../src/question"
 import { Skill } from "../../src/skill"

+ 1 - 1
packages/opencode/test/share/share-next.test.ts

@@ -9,7 +9,7 @@ import { AccountRepo } from "../../src/account/repo"
 import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
 import { Bus } from "../../src/bus"
 import { Config } from "../../src/config"
-import { Provider } from "../../src/provider/provider"
+import { Provider } from "../../src/provider"
 import { Session } from "../../src/session"
 import type { SessionID } from "../../src/session/schema"
 import { ShareNext } from "../../src/share/share-next"