Bladeren bron

refactor(provider): remove async facade exports (#22320)

Kit Langton 5 dagen geleden
bovenliggende
commit
663e798e76

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

@@ -125,7 +125,14 @@ function parseToolParams(input?: string) {
 async function createToolContext(agent: Agent.Info) {
   const session = await Session.create({ title: `Debug tool run (${agent.name})` })
   const messageID = MessageID.ascending()
-  const model = agent.model ?? (await Provider.defaultModel())
+  const model =
+    agent.model ??
+    (await AppRuntime.runPromise(
+      Effect.gen(function* () {
+        const provider = yield* Provider.Service
+        return yield* provider.defaultModel()
+      }),
+    ))
   const now = Date.now()
   const message: MessageV2.Assistant = {
     id: messageID,

+ 41 - 31
packages/opencode/src/cli/cmd/models.ts

@@ -6,6 +6,8 @@ import { ModelsDev } from "../../provider/models"
 import { cmd } from "./cmd"
 import { UI } from "../ui"
 import { EOL } from "os"
+import { AppRuntime } from "@/effect/app-runtime"
+import { Effect } from "effect"
 
 export const ModelsCommand = cmd({
   command: "models [provider]",
@@ -35,43 +37,51 @@ export const ModelsCommand = cmd({
     await Instance.provide({
       directory: process.cwd(),
       async fn() {
-        const providers = await Provider.list()
+        await AppRuntime.runPromise(
+          Effect.gen(function* () {
+            const svc = yield* Provider.Service
+            const providers = yield* svc.list()
 
-        function printModels(providerID: ProviderID, verbose?: boolean) {
-          const provider = providers[providerID]
-          const sortedModels = Object.entries(provider.models).sort(([a], [b]) => a.localeCompare(b))
-          for (const [modelID, model] of sortedModels) {
-            process.stdout.write(`${providerID}/${modelID}`)
-            process.stdout.write(EOL)
-            if (verbose) {
-              process.stdout.write(JSON.stringify(model, null, 2))
-              process.stdout.write(EOL)
+            const print = (providerID: ProviderID, verbose?: boolean) => {
+              const provider = providers[providerID]
+              const sorted = Object.entries(provider.models).sort(([a], [b]) => a.localeCompare(b))
+              for (const [modelID, model] of sorted) {
+                process.stdout.write(`${providerID}/${modelID}`)
+                process.stdout.write(EOL)
+                if (verbose) {
+                  process.stdout.write(JSON.stringify(model, null, 2))
+                  process.stdout.write(EOL)
+                }
+              }
             }
-          }
-        }
 
-        if (args.provider) {
-          const provider = providers[ProviderID.make(args.provider)]
-          if (!provider) {
-            UI.error(`Provider not found: ${args.provider}`)
-            return
-          }
+            if (args.provider) {
+              const providerID = ProviderID.make(args.provider)
+              const provider = providers[providerID]
+              if (!provider) {
+                yield* Effect.sync(() => UI.error(`Provider not found: ${args.provider}`))
+                return
+              }
 
-          printModels(ProviderID.make(args.provider), args.verbose)
-          return
-        }
+              yield* Effect.sync(() => print(providerID, args.verbose))
+              return
+            }
 
-        const providerIDs = Object.keys(providers).sort((a, b) => {
-          const aIsOpencode = a.startsWith("opencode")
-          const bIsOpencode = b.startsWith("opencode")
-          if (aIsOpencode && !bIsOpencode) return -1
-          if (!aIsOpencode && bIsOpencode) return 1
-          return a.localeCompare(b)
-        })
+            const ids = Object.keys(providers).sort((a, b) => {
+              const aIsOpencode = a.startsWith("opencode")
+              const bIsOpencode = b.startsWith("opencode")
+              if (aIsOpencode && !bIsOpencode) return -1
+              if (!aIsOpencode && bIsOpencode) return 1
+              return a.localeCompare(b)
+            })
 
-        for (const providerID of providerIDs) {
-          printModels(ProviderID.make(providerID), args.verbose)
-        }
+            yield* Effect.sync(() => {
+              for (const providerID of ids) {
+                print(ProviderID.make(providerID), args.verbose)
+              }
+            })
+          }),
+        )
       },
     })
   },

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

@@ -21,7 +21,6 @@ import path from "path"
 import { Effect, Layer, Context } from "effect"
 import { EffectLogger } from "@/effect/logger"
 import { InstanceState } from "@/effect/instance-state"
-import { makeRuntime } from "@/effect/run-service"
 import { AppFileSystem } from "@/filesystem"
 import { isRecord } from "@/util/record"
 
@@ -1693,36 +1692,6 @@ export namespace Provider {
     ),
   )
 
-  const { runPromise } = makeRuntime(Service, defaultLayer)
-
-  export async function list() {
-    return runPromise((svc) => svc.list())
-  }
-
-  export async function getProvider(providerID: ProviderID) {
-    return runPromise((svc) => svc.getProvider(providerID))
-  }
-
-  export async function getModel(providerID: ProviderID, modelID: ModelID) {
-    return runPromise((svc) => svc.getModel(providerID, modelID))
-  }
-
-  export async function getLanguage(model: Model) {
-    return runPromise((svc) => svc.getLanguage(model))
-  }
-
-  export async function closest(providerID: ProviderID, query: string[]) {
-    return runPromise((svc) => svc.closest(providerID, query))
-  }
-
-  export async function getSmallModel(providerID: ProviderID) {
-    return runPromise((svc) => svc.getSmallModel(providerID))
-  }
-
-  export async function defaultModel() {
-    return runPromise((svc) => svc.defaultModel())
-  }
-
   const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
   export function sort<T extends { id: string }>(models: T[]) {
     return sortBy(

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

@@ -7,6 +7,8 @@ import { mapValues } from "remeda"
 import { errors } from "../error"
 import { Log } from "../../util/log"
 import { lazy } from "../../util/lazy"
+import { AppRuntime } from "../../effect/app-runtime"
+import { Effect } from "effect"
 
 const log = Log.create({ service: "server" })
 
@@ -82,7 +84,12 @@ export const ConfigRoutes = lazy(() =>
       }),
       async (c) => {
         using _ = log.time("providers")
-        const providers = await Provider.list().then((x) => mapValues(x, (item) => item))
+        const providers = await AppRuntime.runPromise(
+          Effect.gen(function* () {
+            const svc = yield* Provider.Service
+            return mapValues(yield* svc.list(), (item) => item)
+          }),
+        )
         return c.json({
           providers: Object.values(providers),
           default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),

+ 28 - 19
packages/opencode/src/server/instance/provider.ts

@@ -11,6 +11,7 @@ import { mapValues } from "remeda"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 import { Log } from "../../util/log"
+import { Effect } from "effect"
 
 const log = Log.create({ service: "server" })
 
@@ -40,27 +41,35 @@ export const ProviderRoutes = lazy(() =>
         },
       }),
       async (c) => {
-        const config = await Config.get()
-        const disabled = new Set(config.disabled_providers ?? [])
-        const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
-
-        const allProviders = await ModelsDev.get()
-        const filteredProviders: Record<string, (typeof allProviders)[string]> = {}
-        for (const [key, value] of Object.entries(allProviders)) {
-          if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
-            filteredProviders[key] = value
-          }
-        }
-
-        const connected = await Provider.list()
-        const providers = Object.assign(
-          mapValues(filteredProviders, (x) => Provider.fromModelsDevProvider(x)),
-          connected,
+        const result = await AppRuntime.runPromise(
+          Effect.gen(function* () {
+            const svc = yield* Provider.Service
+            const config = yield* Effect.promise(() => Config.get())
+            const all = yield* Effect.promise(() => ModelsDev.get())
+            const disabled = new Set(config.disabled_providers ?? [])
+            const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
+            const filtered: Record<string, (typeof all)[string]> = {}
+            for (const [key, value] of Object.entries(all)) {
+              if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
+                filtered[key] = value
+              }
+            }
+            const connected = yield* svc.list()
+            const providers = Object.assign(
+              mapValues(filtered, (x) => Provider.fromModelsDevProvider(x)),
+              connected,
+            )
+            return {
+              all: Object.values(providers),
+              default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
+              connected: Object.keys(connected),
+            }
+          }),
         )
         return c.json({
-          all: Object.values(providers),
-          default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
-          connected: Object.keys(connected),
+          all: result.all,
+          default: result.default,
+          connected: result.connected,
         })
       },
     )

+ 21 - 10
packages/opencode/test/provider/amazon-bedrock.test.ts

@@ -9,6 +9,17 @@ import { Provider } from "../../src/provider/provider"
 import { Env } from "../../src/env"
 import { Global } from "../../src/global"
 import { Filesystem } from "../../src/util/filesystem"
+import { Effect } from "effect"
+import { AppRuntime } from "../../src/effect/app-runtime"
+
+async function list() {
+  return AppRuntime.runPromise(
+    Effect.gen(function* () {
+      const provider = yield* Provider.Service
+      return yield* provider.list()
+    }),
+  )
+}
 
 test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
   await using tmp = await tmpdir({
@@ -35,7 +46,7 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async ()
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
     },
@@ -60,7 +71,7 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async ()
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
     },
@@ -116,7 +127,7 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
         Env.set("AWS_BEARER_TOKEN_BEDROCK", "")
       },
       fn: async () => {
-        const providers = await Provider.list()
+        const providers = await list()
         expect(providers[ProviderID.amazonBedrock]).toBeDefined()
         expect(providers[ProviderID.amazonBedrock].options?.region).toBe("eu-west-1")
       },
@@ -161,7 +172,7 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async
       Env.set("AWS_ACCESS_KEY_ID", "test-key-id")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1")
     },
@@ -192,7 +203,7 @@ test("Bedrock: includes custom endpoint in options when specified", async () =>
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.endpoint).toBe(
         "https://bedrock-runtime.us-east-1.vpce-xxxxx.amazonaws.com",
@@ -228,7 +239,7 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async ()
       Env.set("AWS_ACCESS_KEY_ID", "")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].options?.region).toBe("us-east-1")
     },
@@ -268,7 +279,7 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () =>
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       // The model should exist with the us. prefix
       expect(providers[ProviderID.amazonBedrock].models["us.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
@@ -305,7 +316,7 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => {
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].models["global.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
     },
@@ -341,7 +352,7 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () =>
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       expect(providers[ProviderID.amazonBedrock].models["eu.anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()
     },
@@ -377,7 +388,7 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a
       Env.set("AWS_PROFILE", "default")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.amazonBedrock]).toBeDefined()
       // Non-prefixed model should still be registered
       expect(providers[ProviderID.amazonBedrock].models["anthropic.claude-opus-4-5-20251101-v1:0"]).toBeDefined()

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

@@ -30,7 +30,7 @@
 //       Env.set("GITLAB_TOKEN", "test-gitlab-token")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].key).toBe("test-gitlab-token")
 //     },
@@ -62,7 +62,7 @@
 //       Env.set("GITLAB_INSTANCE_URL", "https://gitlab.example.com")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].options?.instanceUrl).toBe("https://gitlab.example.com")
 //     },
@@ -100,7 +100,7 @@
 //       Env.set("GITLAB_TOKEN", "")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //     },
 //   })
@@ -135,7 +135,7 @@
 //       Env.set("GITLAB_TOKEN", "")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].key).toBe("glpat-test-pat-token")
 //     },
@@ -167,7 +167,7 @@
 //       Env.set("GITLAB_INSTANCE_URL", "https://gitlab.company.internal")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].options?.instanceUrl).toBe("https://gitlab.company.internal")
 //     },
@@ -198,7 +198,7 @@
 //       Env.set("GITLAB_TOKEN", "env-token")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //     },
 //   })
@@ -221,7 +221,7 @@
 //       Env.set("GITLAB_TOKEN", "test-token")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].options?.aiGatewayHeaders?.["anthropic-beta"]).toContain(
 //         "context-1m-2025-08-07",
@@ -257,7 +257,7 @@
 //       Env.set("GITLAB_TOKEN", "test-token")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       expect(providers[ProviderID.gitlab].options?.featureFlags).toBeDefined()
 //       expect(providers[ProviderID.gitlab].options?.featureFlags?.duo_agent_platform_agentic_chat).toBe(true)
@@ -282,7 +282,7 @@
 //       Env.set("GITLAB_TOKEN", "test-token")
 //     },
 //     fn: async () => {
-//       const providers = await Provider.list()
+//       const providers = await list()
 //       expect(providers[ProviderID.gitlab]).toBeDefined()
 //       const models = Object.keys(providers[ProviderID.gitlab].models)
 //       expect(models.length).toBeGreaterThan(0)
@@ -306,7 +306,7 @@
 //         Env.set("GITLAB_TOKEN", "test-token")
 //       },
 //       fn: async () => {
-//         const providers = await Provider.list()
+//         const providers = await list()
 //         const gitlab = providers[ProviderID.gitlab]
 //         expect(gitlab).toBeDefined()
 //         gitlab.models["duo-workflow-sonnet-4-6"] = {
@@ -332,10 +332,10 @@
 //           release_date: "",
 //           variants: {},
 //         }
-//         const model = await Provider.getModel(ProviderID.gitlab, ModelID.make("duo-workflow-sonnet-4-6"))
+//         const model = await getModel(ProviderID.gitlab, ModelID.make("duo-workflow-sonnet-4-6"))
 //         expect(model).toBeDefined()
 //         expect(model.options?.workflowRef).toBe("claude_sonnet_4_6")
-//         const language = await Provider.getLanguage(model)
+//         const language = await getLanguage(model)
 //         expect(language).toBeDefined()
 //         expect(language).toBeInstanceOf(GitLabWorkflowLanguageModel)
 //       },
@@ -354,11 +354,11 @@
 //         Env.set("GITLAB_TOKEN", "test-token")
 //       },
 //       fn: async () => {
-//         const providers = await Provider.list()
+//         const providers = await list()
 //         expect(providers[ProviderID.gitlab]).toBeDefined()
-//         const model = await Provider.getModel(ProviderID.gitlab, ModelID.make("duo-chat-sonnet-4-5"))
+//         const model = await getModel(ProviderID.gitlab, ModelID.make("duo-chat-sonnet-4-5"))
 //         expect(model).toBeDefined()
-//         const language = await Provider.getLanguage(model)
+//         const language = await getLanguage(model)
 //         expect(language).toBeDefined()
 //         expect(language).not.toBeInstanceOf(GitLabWorkflowLanguageModel)
 //       },
@@ -377,10 +377,10 @@
 //         Env.set("GITLAB_TOKEN", "test-token")
 //       },
 //       fn: async () => {
-//         const providers = await Provider.list()
+//         const providers = await list()
 //         const gitlab = providers[ProviderID.gitlab]
 //         expect(gitlab.options?.featureFlags).toBeDefined()
-//         const model = await Provider.getModel(ProviderID.gitlab, ModelID.make("duo-chat-sonnet-4-5"))
+//         const model = await getModel(ProviderID.gitlab, ModelID.make("duo-chat-sonnet-4-5"))
 //         expect(model).toBeDefined()
 //         expect(model.options).toBeDefined()
 //       },
@@ -401,7 +401,7 @@
 //         Env.set("GITLAB_TOKEN", "test-token")
 //       },
 //       fn: async () => {
-//         const providers = await Provider.list()
+//         const providers = await list()
 //         const models = Object.keys(providers[ProviderID.gitlab].models)
 //         expect(models).toContain("duo-chat-haiku-4-5")
 //         expect(models).toContain("duo-chat-sonnet-4-5")

+ 117 - 78
packages/opencode/test/provider/provider.test.ts

@@ -11,8 +11,47 @@ import { Provider } from "../../src/provider/provider"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "../../src/util/filesystem"
 import { Env } from "../../src/env"
+import { Effect } from "effect"
+import { AppRuntime } from "../../src/effect/app-runtime"
+
+async function run<A, E>(fn: (provider: Provider.Interface) => Effect.Effect<A, E, never>) {
+  return AppRuntime.runPromise(
+    Effect.gen(function* () {
+      const provider = yield* Provider.Service
+      return yield* fn(provider)
+    }),
+  )
+}
+
+async function list() {
+  return run((provider) => provider.list())
+}
+
+async function getProvider(providerID: ProviderID) {
+  return run((provider) => provider.getProvider(providerID))
+}
+
+async function getModel(providerID: ProviderID, modelID: ModelID) {
+  return run((provider) => provider.getModel(providerID, modelID))
+}
+
+async function getLanguage(model: Provider.Model) {
+  return run((provider) => provider.getLanguage(model))
+}
+
+async function closest(providerID: ProviderID, query: string[]) {
+  return run((provider) => provider.closest(providerID, query))
+}
+
+async function getSmallModel(providerID: ProviderID) {
+  return run((provider) => provider.getSmallModel(providerID))
+}
+
+async function defaultModel() {
+  return run((provider) => provider.defaultModel())
+}
 
-function paid(providers: Awaited<ReturnType<typeof Provider.list>>) {
+function paid(providers: Awaited<ReturnType<typeof list>>) {
   const item = providers[ProviderID.make("opencode")]
   expect(item).toBeDefined()
   return Object.values(item.models).filter((model) => model.cost.input > 0).length
@@ -35,7 +74,7 @@ test("provider loaded from env variable", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       // Provider should retain its connection source even if custom loaders
       // merge additional options.
@@ -66,7 +105,7 @@ test("provider loaded from config with apiKey option", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
     },
   })
@@ -90,7 +129,7 @@ test("disabled_providers excludes provider", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeUndefined()
     },
   })
@@ -115,7 +154,7 @@ test("enabled_providers restricts to only listed providers", async () => {
       Env.set("OPENAI_API_KEY", "test-openai-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeUndefined()
     },
@@ -144,7 +183,7 @@ test("model whitelist filters models for provider", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
       expect(models).toContain("claude-sonnet-4-20250514")
@@ -175,7 +214,7 @@ test("model blacklist excludes specific models", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
       expect(models).not.toContain("claude-sonnet-4-20250514")
@@ -210,7 +249,7 @@ test("custom model alias via config", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined()
       expect(providers[ProviderID.anthropic].models["my-alias"].name).toBe("My Custom Alias")
@@ -253,7 +292,7 @@ test("custom provider with npm package", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("custom-provider")]).toBeDefined()
       expect(providers[ProviderID.make("custom-provider")].name).toBe("Custom Provider")
       expect(providers[ProviderID.make("custom-provider")].models["custom-model"]).toBeDefined()
@@ -286,7 +325,7 @@ test("env variable takes precedence, config merges options", async () => {
       Env.set("ANTHROPIC_API_KEY", "env-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       // Config options should be merged
       expect(providers[ProviderID.anthropic].options.timeout).toBe(60000)
@@ -312,11 +351,11 @@ test("getModel returns model for valid provider/model", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model = await Provider.getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
+      const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
       expect(model).toBeDefined()
       expect(String(model.providerID)).toBe("anthropic")
       expect(String(model.id)).toBe("claude-sonnet-4-20250514")
-      const language = await Provider.getLanguage(model)
+      const language = await getLanguage(model)
       expect(language).toBeDefined()
     },
   })
@@ -339,7 +378,7 @@ test("getModel throws ModelNotFoundError for invalid model", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      expect(Provider.getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow()
+      expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow()
     },
   })
 })
@@ -358,7 +397,7 @@ test("getModel throws ModelNotFoundError for invalid provider", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      expect(Provider.getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"))).rejects.toThrow()
+      expect(getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"))).rejects.toThrow()
     },
   })
 })
@@ -392,7 +431,7 @@ test("defaultModel returns first available model when no config set", async () =
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model = await Provider.defaultModel()
+      const model = await defaultModel()
       expect(model.providerID).toBeDefined()
       expect(model.modelID).toBeDefined()
     },
@@ -417,7 +456,7 @@ test("defaultModel respects config model setting", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model = await Provider.defaultModel()
+      const model = await defaultModel()
       expect(String(model.providerID)).toBe("anthropic")
       expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
     },
@@ -456,7 +495,7 @@ test("provider with baseURL from config", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("custom-openai")]).toBeDefined()
       expect(providers[ProviderID.make("custom-openai")].options.baseURL).toBe("https://custom.openai.com/v1")
     },
@@ -494,7 +533,7 @@ test("model cost defaults to zero when not specified", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("test-provider")].models["test-model"]
       expect(model.cost.input).toBe(0)
       expect(model.cost.output).toBe(0)
@@ -532,7 +571,7 @@ test("model options are merged from existing model", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.options.customOption).toBe("custom-value")
     },
@@ -561,7 +600,7 @@ test("provider removed when all models filtered out", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeUndefined()
     },
   })
@@ -584,7 +623,7 @@ test("closest finds model by partial match", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const result = await Provider.closest(ProviderID.anthropic, ["sonnet-4"])
+      const result = await closest(ProviderID.anthropic, ["sonnet-4"])
       expect(result).toBeDefined()
       expect(String(result?.providerID)).toBe("anthropic")
       expect(String(result?.modelID)).toContain("sonnet-4")
@@ -606,7 +645,7 @@ test("closest returns undefined for nonexistent provider", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const result = await Provider.closest(ProviderID.make("nonexistent"), ["model"])
+      const result = await closest(ProviderID.make("nonexistent"), ["model"])
       expect(result).toBeUndefined()
     },
   })
@@ -639,10 +678,10 @@ test("getModel uses realIdByKey for aliased models", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic].models["my-sonnet"]).toBeDefined()
 
-      const model = await Provider.getModel(ProviderID.anthropic, ModelID.make("my-sonnet"))
+      const model = await getModel(ProviderID.anthropic, ModelID.make("my-sonnet"))
       expect(model).toBeDefined()
       expect(String(model.id)).toBe("my-sonnet")
       expect(model.name).toBe("My Sonnet Alias")
@@ -682,7 +721,7 @@ test("provider api field sets model api.url", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       // api field is stored on model.api.url, used by getSDK to set baseURL
       expect(providers[ProviderID.make("custom-api")].models["model-1"].api.url).toBe("https://api.example.com/v1")
     },
@@ -722,7 +761,7 @@ test("explicit baseURL overrides api field", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("custom-api")].options.baseURL).toBe("https://custom.override.com/v1")
     },
   })
@@ -754,7 +793,7 @@ test("model inherits properties from existing database model", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.name).toBe("Custom Name for Sonnet")
       expect(model.capabilities.toolcall).toBe(true)
@@ -782,7 +821,7 @@ test("disabled_providers prevents loading even with env var", async () => {
       Env.set("OPENAI_API_KEY", "test-openai-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.openai]).toBeUndefined()
     },
   })
@@ -807,7 +846,7 @@ test("enabled_providers with empty array allows no providers", async () => {
       Env.set("OPENAI_API_KEY", "test-openai-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(Object.keys(providers).length).toBe(0)
     },
   })
@@ -836,7 +875,7 @@ test("whitelist and blacklist can be combined", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       const models = Object.keys(providers[ProviderID.anthropic].models)
       expect(models).toContain("claude-sonnet-4-20250514")
@@ -875,7 +914,7 @@ test("model modalities default correctly", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("test-provider")].models["test-model"]
       expect(model.capabilities.input.text).toBe(true)
       expect(model.capabilities.output.text).toBe(true)
@@ -918,7 +957,7 @@ test("model with custom cost values", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("test-provider")].models["test-model"]
       expect(model.cost.input).toBe(5)
       expect(model.cost.output).toBe(15)
@@ -945,7 +984,7 @@ test("getSmallModel returns appropriate small model", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model = await Provider.getSmallModel(ProviderID.anthropic)
+      const model = await getSmallModel(ProviderID.anthropic)
       expect(model).toBeDefined()
       expect(model?.id).toContain("haiku")
     },
@@ -970,7 +1009,7 @@ test("getSmallModel respects config small_model override", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model = await Provider.getSmallModel(ProviderID.anthropic)
+      const model = await getSmallModel(ProviderID.anthropic)
       expect(model).toBeDefined()
       expect(String(model?.providerID)).toBe("anthropic")
       expect(String(model?.id)).toBe("claude-sonnet-4-20250514")
@@ -1019,7 +1058,7 @@ test("multiple providers can be configured simultaneously", async () => {
       Env.set("OPENAI_API_KEY", "test-openai-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeDefined()
       expect(providers[ProviderID.anthropic].options.timeout).toBe(30000)
@@ -1060,7 +1099,7 @@ test("provider with custom npm package", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("local-llm")]).toBeDefined()
       expect(providers[ProviderID.make("local-llm")].models["llama-3"].api.npm).toBe("@ai-sdk/openai-compatible")
       expect(providers[ProviderID.make("local-llm")].options.baseURL).toBe("http://localhost:11434/v1")
@@ -1097,7 +1136,7 @@ test("model alias name defaults to alias key when id differs", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic].models["sonnet"].name).toBe("sonnet")
     },
   })
@@ -1137,7 +1176,7 @@ test("provider with multiple env var options only includes apiKey when single en
       Env.set("MULTI_ENV_KEY_1", "test-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("multi-env")]).toBeDefined()
       // When multiple env options exist, key should NOT be auto-set
       expect(providers[ProviderID.make("multi-env")].key).toBeUndefined()
@@ -1179,7 +1218,7 @@ test("provider with single env var includes apiKey automatically", async () => {
       Env.set("SINGLE_ENV_KEY", "my-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("single-env")]).toBeDefined()
       // Single env option should auto-set key
       expect(providers[ProviderID.make("single-env")].key).toBe("my-api-key")
@@ -1216,7 +1255,7 @@ test("model cost overrides existing cost values", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.cost.input).toBe(999)
       expect(model.cost.output).toBe(888)
@@ -1263,7 +1302,7 @@ test("completely new provider not in database can be configured", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("brand-new-provider")]).toBeDefined()
       expect(providers[ProviderID.make("brand-new-provider")].name).toBe("Brand New")
       const model = providers[ProviderID.make("brand-new-provider")].models["new-model"]
@@ -1297,7 +1336,7 @@ test("disabled_providers and enabled_providers interaction", async () => {
       Env.set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       // anthropic: in enabled, not in disabled = allowed
       expect(providers[ProviderID.anthropic]).toBeDefined()
       // openai: in enabled, but also in disabled = NOT allowed
@@ -1337,7 +1376,7 @@ test("model with tool_call false", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("no-tools")].models["basic-model"].capabilities.toolcall).toBe(false)
     },
   })
@@ -1372,7 +1411,7 @@ test("model defaults tool_call to true when not specified", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("default-tools")].models["model"].capabilities.toolcall).toBe(true)
     },
   })
@@ -1411,7 +1450,7 @@ test("model headers are preserved", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("headers-provider")].models["model"]
       expect(model.headers).toEqual({
         "X-Custom-Header": "custom-value",
@@ -1454,7 +1493,7 @@ test("provider env fallback - second env var used if first missing", async () =>
       Env.set("FALLBACK_KEY", "fallback-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       // Provider should load because fallback env var is set
       expect(providers[ProviderID.make("fallback-env")]).toBeDefined()
     },
@@ -1478,8 +1517,8 @@ test("getModel returns consistent results", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const model1 = await Provider.getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
-      const model2 = await Provider.getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
+      const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
+      const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
       expect(model1.providerID).toEqual(model2.providerID)
       expect(model1.id).toEqual(model2.id)
       expect(model1).toEqual(model2)
@@ -1516,7 +1555,7 @@ test("provider name defaults to id when not in database", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("my-custom-id")].name).toBe("my-custom-id")
     },
   })
@@ -1540,7 +1579,7 @@ test("ModelNotFoundError includes suggestions for typos", async () => {
     },
     fn: async () => {
       try {
-        await Provider.getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
+        await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
         expect(true).toBe(false) // Should not reach here
       } catch (e: any) {
         expect(e.data.suggestions).toBeDefined()
@@ -1568,7 +1607,7 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
     },
     fn: async () => {
       try {
-        await Provider.getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
+        await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
         expect(true).toBe(false) // Should not reach here
       } catch (e: any) {
         expect(e.data.suggestions).toBeDefined()
@@ -1592,7 +1631,7 @@ test("getProvider returns undefined for nonexistent provider", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const provider = await Provider.getProvider(ProviderID.make("nonexistent"))
+      const provider = await getProvider(ProviderID.make("nonexistent"))
       expect(provider).toBeUndefined()
     },
   })
@@ -1615,7 +1654,7 @@ test("getProvider returns provider info", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const provider = await Provider.getProvider(ProviderID.anthropic)
+      const provider = await getProvider(ProviderID.anthropic)
       expect(provider).toBeDefined()
       expect(String(provider?.id)).toBe("anthropic")
     },
@@ -1639,7 +1678,7 @@ test("closest returns undefined when no partial match found", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const result = await Provider.closest(ProviderID.anthropic, ["nonexistent-xyz-model"])
+      const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"])
       expect(result).toBeUndefined()
     },
   })
@@ -1663,7 +1702,7 @@ test("closest checks multiple query terms in order", async () => {
     },
     fn: async () => {
       // First term won't match, second will
-      const result = await Provider.closest(ProviderID.anthropic, ["nonexistent", "haiku"])
+      const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"])
       expect(result).toBeDefined()
       expect(result?.modelID).toContain("haiku")
     },
@@ -1699,7 +1738,7 @@ test("model limit defaults to zero when not specified", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("no-limit")].models["model"]
       expect(model.limit.context).toBe(0)
       expect(model.limit.output).toBe(0)
@@ -1734,7 +1773,7 @@ test("provider options are deeply merged", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       // Custom options should be merged
       expect(providers[ProviderID.anthropic].options.timeout).toBe(30000)
       expect(providers[ProviderID.anthropic].options.headers["X-Custom"]).toBe("custom-value")
@@ -1772,7 +1811,7 @@ test("custom model inherits npm package from models.dev provider config", async
       Env.set("OPENAI_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.openai].models["my-custom-model"]
       expect(model).toBeDefined()
       expect(model.api.npm).toBe("@ai-sdk/openai")
@@ -1807,7 +1846,7 @@ test("custom model inherits api.url from models.dev provider", async () => {
       Env.set("OPENROUTER_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.openrouter]).toBeDefined()
 
       // New model not in database should inherit api.url from provider
@@ -1908,7 +1947,7 @@ test("model variants are generated for reasoning models", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       // Claude sonnet 4 has reasoning capability
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.capabilities.reasoning).toBe(true)
@@ -1946,7 +1985,7 @@ test("model variants can be disabled via config", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants).toBeDefined()
       expect(model.variants!["high"]).toBeUndefined()
@@ -1989,7 +2028,7 @@ test("model variants can be customized via config", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["high"]).toBeDefined()
       expect(model.variants!["high"].thinking.budgetTokens).toBe(20000)
@@ -2028,7 +2067,7 @@ test("disabled key is stripped from variant config", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["max"]).toBeDefined()
       expect(model.variants!["max"].disabled).toBeUndefined()
@@ -2066,7 +2105,7 @@ test("all variants can be disabled via config", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants).toBeDefined()
       expect(Object.keys(model.variants!).length).toBe(0)
@@ -2104,7 +2143,7 @@ test("variant config merges with generated variants", async () => {
       Env.set("ANTHROPIC_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
       expect(model.variants!["high"]).toBeDefined()
       // Should have both the generated thinking config and the custom option
@@ -2142,7 +2181,7 @@ test("variants filtered in second pass for database models", async () => {
       Env.set("OPENAI_API_KEY", "test-api-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.openai].models["gpt-5"]
       expect(model.variants).toBeDefined()
       expect(model.variants!["high"]).toBeUndefined()
@@ -2188,7 +2227,7 @@ test("custom model with variants enabled and disabled", async () => {
   await Instance.provide({
     directory: tmp.path,
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("custom-reasoning")].models["reasoning-model"]
       expect(model.variants).toBeDefined()
       // Enabled variants should exist
@@ -2246,7 +2285,7 @@ test("Google Vertex: retains baseURL for custom proxy", async () => {
       Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("vertex-proxy")]).toBeDefined()
       expect(providers[ProviderID.make("vertex-proxy")].options.baseURL).toBe("https://my-proxy.com/v1")
     },
@@ -2291,7 +2330,7 @@ test("Google Vertex: supports OpenAI compatible models", async () => {
       Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       const model = providers[ProviderID.make("vertex-openai")].models["gpt-4"]
 
       expect(model).toBeDefined()
@@ -2319,7 +2358,7 @@ test("cloudflare-ai-gateway loads with env variables", async () => {
       Env.set("CLOUDFLARE_API_TOKEN", "test-token")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
     },
   })
@@ -2351,7 +2390,7 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
       Env.set("CLOUDFLARE_API_TOKEN", "test-token")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
       expect(providers[ProviderID.make("cloudflare-ai-gateway")].options.metadata).toEqual({
         invoked_by: "test",
@@ -2399,7 +2438,7 @@ test("plugin config providers persist after instance dispose", async () => {
     directory: tmp.path,
     fn: async () => {
       await Plugin.init()
-      return Provider.list()
+      return list()
     },
   })
   expect(first[ProviderID.make("demo")]).toBeDefined()
@@ -2409,7 +2448,7 @@ test("plugin config providers persist after instance dispose", async () => {
 
   const second = await Instance.provide({
     directory: tmp.path,
-    fn: async () => Provider.list(),
+    fn: async () => list(),
   })
   expect(second[ProviderID.make("demo")]).toBeDefined()
   expect(second[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined()
@@ -2445,7 +2484,7 @@ test("plugin config enabled and disabled providers are honored", async () => {
       Env.set("OPENAI_API_KEY", "test-openai-key")
     },
     fn: async () => {
-      const providers = await Provider.list()
+      const providers = await list()
       expect(providers[ProviderID.anthropic]).toBeDefined()
       expect(providers[ProviderID.openai]).toBeUndefined()
     },
@@ -2466,7 +2505,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
 
   const none = await Instance.provide({
     directory: base.path,
-    fn: async () => paid(await Provider.list()),
+    fn: async () => paid(await list()),
   })
 
   await using keyed = await tmpdir({
@@ -2489,7 +2528,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
 
   const keyedCount = await Instance.provide({
     directory: keyed.path,
-    fn: async () => paid(await Provider.list()),
+    fn: async () => paid(await list()),
   })
 
   expect(none).toBe(0)
@@ -2510,7 +2549,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
 
   const none = await Instance.provide({
     directory: base.path,
-    fn: async () => paid(await Provider.list()),
+    fn: async () => paid(await list()),
   })
 
   await using keyed = await tmpdir({
@@ -2544,7 +2583,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
 
     const keyedCount = await Instance.provide({
       directory: keyed.path,
-      fn: async () => paid(await Provider.list()),
+      fn: async () => paid(await list()),
     })
 
     expect(none).toBe(0)

+ 19 - 9
packages/opencode/test/session/llm.test.ts

@@ -1,7 +1,7 @@
 import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
 import path from "path"
 import { tool, type ModelMessage } from "ai"
-import { Cause, Exit, Stream } from "effect"
+import { Cause, Effect, Exit, Stream } from "effect"
 import z from "zod"
 import { makeRuntime } from "../../src/effect/run-service"
 import { LLM } from "../../src/session/llm"
@@ -15,6 +15,16 @@ import { tmpdir } from "../fixture/fixture"
 import type { Agent } from "../../src/agent/agent"
 import type { MessageV2 } from "../../src/session/message-v2"
 import { SessionID, MessageID } from "../../src/session/schema"
+import { AppRuntime } from "../../src/effect/app-runtime"
+
+async function getModel(providerID: ProviderID, modelID: ModelID) {
+  return AppRuntime.runPromise(
+    Effect.gen(function* () {
+      const provider = yield* Provider.Service
+      return yield* provider.getModel(providerID, modelID)
+    }),
+  )
+}
 
 describe("session.llm.hasToolCalls", () => {
   test("returns false for empty messages array", () => {
@@ -325,7 +335,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-1")
         const agent = {
           name: "test",
@@ -416,7 +426,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-raw-abort")
         const agent = {
           name: "test",
@@ -490,7 +500,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-service-abort")
         const agent = {
           name: "test",
@@ -581,7 +591,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-tools")
         const agent = {
           name: "test",
@@ -699,7 +709,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.openai, ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.openai, ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-2")
         const agent = {
           name: "test",
@@ -819,7 +829,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.openai, ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.openai, ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-data-url")
         const agent = {
           name: "test",
@@ -942,7 +952,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-3")
         const agent = {
           name: "test",
@@ -1043,7 +1053,7 @@ describe("session.llm.stream", () => {
     await Instance.provide({
       directory: tmp.path,
       fn: async () => {
-        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id))
         const sessionID = SessionID.make("session-test-4")
         const agent = {
           name: "test",

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

@@ -204,7 +204,7 @@ const it = testEffect(makeHttp())
 const unix = process.platform !== "win32" ? it.live : it.live.skip
 
 // Config that registers a custom "test" provider with a "test-model" model
-// so Provider.getModel("test", "test-model") succeeds inside the loop.
+// so provider model lookup succeeds inside the loop.
 const cfg = {
   provider: {
     test: {