From 663e798e7624c345dd302fb316c67c7ce10551b0 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 13 Apr 2026 12:40:00 -0400 Subject: [PATCH] refactor(provider): remove async facade exports (#22320) --- packages/opencode/src/cli/cmd/debug/agent.ts | 9 +- packages/opencode/src/cli/cmd/models.ts | 72 ++++--- packages/opencode/src/provider/provider.ts | 31 --- .../opencode/src/server/instance/config.ts | 9 +- .../opencode/src/server/instance/provider.ts | 47 +++-- .../test/provider/amazon-bedrock.test.ts | 31 ++- .../opencode/test/provider/gitlab-duo.test.ts | 36 ++-- .../opencode/test/provider/provider.test.ts | 195 +++++++++++------- packages/opencode/test/session/llm.test.ts | 28 ++- .../test/session/prompt-effect.test.ts | 2 +- 10 files changed, 261 insertions(+), 199 deletions(-) diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index 25a32d23b3..fbaf8d78dc 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/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, diff --git a/packages/opencode/src/cli/cmd/models.ts b/packages/opencode/src/cli/cmd/models.ts index 4670aa5f2f..ad9300da2e 100644 --- a/packages/opencode/src/cli/cmd/models.ts +++ b/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) + } + }) + }), + ) }, }) }, diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index a26b254d5a..bf27f090ab 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/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(models: T[]) { return sortBy( diff --git a/packages/opencode/src/server/instance/config.ts b/packages/opencode/src/server/instance/config.ts index 85d28f6aa6..2b28ba450d 100644 --- a/packages/opencode/src/server/instance/config.ts +++ b/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), diff --git a/packages/opencode/src/server/instance/provider.ts b/packages/opencode/src/server/instance/provider.ts index efd126ea0f..ca203d6a4b 100644 --- a/packages/opencode/src/server/instance/provider.ts +++ b/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 = {} - 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 = {} + 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, }) }, ) diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index 3358e92300..712f36086f 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/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() diff --git a/packages/opencode/test/provider/gitlab-duo.test.ts b/packages/opencode/test/provider/gitlab-duo.test.ts index b669a1e21a..9b5441fe22 100644 --- a/packages/opencode/test/provider/gitlab-duo.test.ts +++ b/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") diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 9cadc391a1..73e77be5fd 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/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" -function paid(providers: Awaited>) { +async function run(fn: (provider: Provider.Interface) => Effect.Effect) { + 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>) { 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) diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 1fa2e61eb2..3974ca9810 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/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", diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index 911c9f3443..eafe682067 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/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: {