diff --git a/packages/core/src/plugin/provider.ts b/packages/core/src/plugin/provider.ts index a4301579081..731bdabddb1 100644 --- a/packages/core/src/plugin/provider.ts +++ b/packages/core/src/plugin/provider.ts @@ -1,6 +1,5 @@ import { AlibabaPlugin } from "./provider/alibaba.js" import { AmazonBedrockPlugin } from "./provider/amazon-bedrock.js" -import { AnthropicPlugin } from "./provider/anthropic.js" import { AzurePlugin } from "./provider/azure.js" import { CerebrasPlugin } from "./provider/cerebras.js" import { CloudflareAIGatewayPlugin } from "./provider/cloudflare-ai-gateway.js" @@ -15,7 +14,6 @@ import { GoogleVertexPlugin } from "./provider/google-vertex.js" import { KiloPlugin } from "./provider/kilo.js" import { LLMGatewayPlugin } from "./provider/llmgateway.js" import { LMStudioPlugin } from "./provider/lmstudio.js" -import { MistralPlugin } from "./provider/mistral.js" import { ModalPlugin } from "./provider/modal.js" import { NvidiaPlugin } from "./provider/nvidia.js" import { OllamaPlugin } from "./provider/ollama.js" @@ -37,7 +35,6 @@ import type { PluginInternal } from "./internal.js" export const ProviderPlugins: PluginInternal.InternalPlugin[] = [ AlibabaPlugin, AmazonBedrockPlugin, - AnthropicPlugin, AzurePlugin, CerebrasPlugin, CloudflareAIGatewayPlugin, @@ -51,7 +48,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [ KiloPlugin, LLMGatewayPlugin, LMStudioPlugin, - MistralPlugin, ModalPlugin, NvidiaPlugin, OllamaPlugin, diff --git a/packages/core/src/plugin/provider/anthropic.ts b/packages/core/src/plugin/provider/anthropic.ts deleted file mode 100644 index f2865a675f6..00000000000 --- a/packages/core/src/plugin/provider/anthropic.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Effect } from "effect" -import { define } from "@opencode/plugin/effect/plugin" -import { Provider } from "../../provider.js" - -export const AnthropicPlugin = define({ - id: "opencode.provider.anthropic", - effect: Effect.fn(function* (ctx) { - yield* ctx.catalog.transform((evt) => { - for (const item of evt.provider.list()) { - if (!Provider.isAISDK(item.provider.package)) continue - if (Provider.packageName(item.provider.package) !== "@ai-sdk/anthropic") continue - evt.provider.update(item.provider.id, (provider) => { - provider.headers = { - ...provider.headers, - "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", - } - }) - } - }) - yield* ctx.aisdk.hook( - "sdk", - Effect.fn(function* (evt) { - if (evt.package !== "@ai-sdk/anthropic") return - const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic")) - evt.sdk = mod.createAnthropic(evt.options) - }), - ) - }), -}) diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 06d3eb383b9..5064130ef66 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -24,14 +24,6 @@ const decodeToken = Schema.decodeUnknownEffect( expiresOn: Schema.optional(Schema.NonEmptyString), }), ) -function selectLanguage(sdk: any, modelID: string, useChat: boolean) { - if (useChat && sdk.chat) return sdk.chat(modelID) - if (sdk.responses) return sdk.responses(modelID) - if (sdk.messages) return sdk.messages(modelID) - if (sdk.chat) return sdk.chat(modelID) - return sdk.languageModel(modelID) -} - export const AzurePlugin = define({ id: "opencode.provider.azure", effect: Effect.fn(function* (ctx) { @@ -201,35 +193,6 @@ export const AzurePlugin = define({ }), { providerID: Provider.ID.azure }, ) - - yield* ctx.aisdk.hook( - "sdk", - Effect.fn(function* (evt) { - if (evt.package !== "@ai-sdk/azure") return - if (evt.model.providerID === Provider.ID.azure) { - if ( - !evt.options.resourceName && - !evt.options.baseURL && - (!Provider.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string") - ) { - throw new Error("Azure resource name is missing; set AZURE_RESOURCE_NAME or configure resourceName/baseURL") - } - } - const mod = yield* Effect.promise(() => import("@ai-sdk/azure")) - evt.sdk = mod.createAzure(evt.options) - }), - ) - yield* ctx.aisdk.hook( - "language", - Effect.fn(function* (evt) { - if (evt.model.providerID !== Provider.ID.azure) return - evt.language = selectLanguage( - evt.sdk, - evt.model.modelID ?? evt.model.id, - Boolean(evt.options.useCompletionUrls), - ) - }), - ) }), }) diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts index 1107494f899..84c783d6f7f 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -92,33 +92,6 @@ export const GoogleVertexPlugin = define({ evt.options.fetch = authFetch(evt.options.fetch) return } - if (evt.package === "@ai-sdk/google-vertex/anthropic") { - const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic")) - const project = resolveProject(evt.options) - const location = String(resolveLocation(evt.options)) - const regionalBaseURL = - (location === "eu" || location === "us") && project && !evt.options.baseURL - ? `https://aiplatform.${location}.rep.googleapis.com/v1/projects/${project}/locations/${location}/publishers/anthropic/models` - : undefined - evt.sdk = mod.createVertexAnthropic({ - ...evt.options, - project, - location, - ...(regionalBaseURL ? { baseURL: regionalBaseURL } : {}), - }) - return - } - if (evt.package !== "@ai-sdk/google-vertex") return - const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex")) - const project = resolveProject(evt.options) - const location = resolveLocation(evt.options) - const options = { ...evt.options } - delete options.fetch - evt.sdk = mod.createVertex({ - ...options, - project, - location, - }) }), ) yield* ctx.aisdk.hook( diff --git a/packages/core/src/plugin/provider/mistral.ts b/packages/core/src/plugin/provider/mistral.ts deleted file mode 100644 index 6fbd981e6c7..00000000000 --- a/packages/core/src/plugin/provider/mistral.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createProviderPlugin } from "./factory.js" - -export const MistralPlugin = createProviderPlugin({ - id: "opencode.provider.mistral", - package: "@ai-sdk/mistral", - load: async (options) => { - const { createMistral } = await import("@ai-sdk/mistral") - return createMistral(options) - }, -}) diff --git a/packages/core/test/plugin/provider-anthropic.test.ts b/packages/core/test/plugin/provider-anthropic.test.ts deleted file mode 100644 index 0d670cc5826..00000000000 --- a/packages/core/test/plugin/provider-anthropic.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { AISDK } from "@opencode/core/aisdk" -import { describe, expect } from "bun:test" -import { Effect } from "effect" -import { Catalog } from "@opencode/core/catalog" -import { Model } from "@opencode/core/model" -import { Plugin } from "@opencode/core/plugin" -import { PluginHost } from "@opencode/core/plugin/host" -import { AnthropicPlugin } from "@opencode/core/plugin/provider/anthropic" -import { Provider } from "@opencode/core/provider" -import { testEffect } from "../lib/effect" -import { PluginTestLayer } from "./fixture" - -const it = testEffect(PluginTestLayer) - -const addPlugin = Effect.fn(function* () { - const plugin = yield* Plugin.Service - const host = yield* PluginHost.make(plugin) - yield* AnthropicPlugin.effect(host) -}) - -function required(value: T | undefined): T { - if (value === undefined) throw new Error("Expected value") - return value -} - -describe("AnthropicPlugin", () => { - it.effect("applies legacy beta headers", () => - Effect.gen(function* () { - const catalog = yield* Catalog.Service - yield* catalog.transform((catalog) => { - catalog.provider.update(Provider.ID.anthropic, (draft) => { - draft.package = Provider.aisdk("@ai-sdk/anthropic") - draft.headers = { Existing: "1" } - }) - }) - yield* addPlugin() - expect(required(yield* catalog.provider.get(Provider.ID.anthropic)).headers?.["anthropic-beta"]).toBe( - "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", - ) - expect(required(yield* catalog.provider.get(Provider.ID.anthropic)).headers?.Existing).toBe("1") - }), - ) - - it.effect("ignores non-Anthropic providers", () => - Effect.gen(function* () { - const catalog = yield* Catalog.Service - yield* catalog.transform((catalog) => catalog.provider.update(Provider.ID.openai, () => {})) - yield* addPlugin() - expect(required(yield* catalog.provider.get(Provider.ID.openai)).headers?.["anthropic-beta"]).toBeUndefined() - }), - ) - - it.effect("creates Anthropic SDKs with the model provider ID as the SDK name", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - yield* addPlugin() - const result = yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.make("custom-anthropic"), Model.ID.make("claude-sonnet-4-5")), - modelID: Model.ID.make("claude-sonnet-4-5"), - package: Provider.aisdk("@ai-sdk/anthropic"), - }), - package: "@ai-sdk/anthropic", - options: { name: "custom-anthropic", apiKey: "test" }, - }) - expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("custom-anthropic") - }), - ) - - it.effect("uses the Anthropic provider ID as the SDK name for the bundled Anthropic provider", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - yield* addPlugin() - const result = yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.anthropic, Model.ID.make("claude-sonnet-4-5")), - modelID: Model.ID.make("claude-sonnet-4-5"), - package: Provider.aisdk("@ai-sdk/anthropic"), - }), - package: "@ai-sdk/anthropic", - options: { name: "anthropic", apiKey: "test" }, - }) - expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("anthropic") - }), - ) -}) diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index e0e2f5cbc17..a42861fd4e5 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -1,6 +1,5 @@ import { chmod } from "node:fs/promises" import { Agent } from "@opencode/core/agent" -import { AISDK } from "@opencode/core/aisdk" import { describe, expect } from "bun:test" import { Effect, Schedule } from "effect" import { Catalog } from "@opencode/core/catalog" @@ -15,7 +14,6 @@ import { Integration } from "@opencode/core/integration" import { Location } from "@opencode/core/location" import { Session } from "@opencode/core/session" import { AppProcess } from "@opencode/util/process" -import { fakeSelectorSdk } from "../fixture/selector" import { testEffect } from "../lib/effect" import { PluginTestLayer } from "./fixture" @@ -422,36 +420,6 @@ describe("AzurePlugin", () => { ), ) - it.effect("allows configured baseURL without resourceName", () => - withEnv({ AZURE_RESOURCE_NAME: undefined }, () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const catalog = yield* Catalog.Service - yield* catalog.transform((catalog) => - catalog.provider.update(Provider.ID.azure, (provider) => { - provider.settings = { ...provider.settings, baseURL: "https://proxy.example.com/openai" } - }), - ) - yield* addPlugin() - const integrations = yield* Integration.Service - expect((yield* integrations.get(Integration.ID.make("azure")))?.methods).toContainEqual({ - type: "key", - label: "API key", - }) - const result = yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - package: "@ai-sdk/azure", - options: { name: "azure", baseURL: "https://proxy.example.com/openai" }, - }) - expect(result.sdk).toBeDefined() - }), - ), - ) - it.effect("marks only Azure v1 Responses deployments as WebSocket capable", () => withEnv({ AZURE_RESOURCE_NAME: undefined, AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: undefined }, () => Effect.gen(function* () { @@ -500,138 +468,4 @@ describe("AzurePlugin", () => { ), ) - it.effect("rejects missing resourceName when baseURL is not configured", () => - withEnv({ AZURE_RESOURCE_NAME: undefined }, () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - yield* addPlugin() - const exit = yield* aisdk - .runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - package: "@ai-sdk/azure", - options: { name: "azure" }, - }) - .pipe(Effect.exit) - expect(exit._tag).toBe("Failure") - }), - ), - ) - - it.effect("selects chat only for completion URLs", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const calls: string[] = [] - yield* addPlugin() - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: fakeSelectorSdk(calls), - options: { useCompletionUrls: true }, - }) - expect(calls).toEqual(["chat:deployment"]) - }), - ) - - it.effect("selects chat from per-call useCompletionUrls", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const calls: string[] = [] - yield* addPlugin() - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: fakeSelectorSdk(calls), - options: { useCompletionUrls: true }, - }) - expect(calls).toEqual(["chat:deployment"]) - }), - ) - - it.effect("ignores model useCompletionUrls when per-call option is unset", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const calls: string[] = [] - yield* addPlugin() - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - body: { useCompletionUrls: true }, - }), - sdk: fakeSelectorSdk(calls), - options: {}, - }) - expect(calls).toEqual(["responses:deployment"]) - }), - ) - - it.effect("uses the legacy Azure selector order and provider guard", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const calls: string[] = [] - yield* addPlugin() - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: fakeSelectorSdk(calls), - options: {}, - }) - const ignored = yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.openai, Model.ID.make("deployment")), - modelID: Model.ID.make("deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: fakeSelectorSdk(calls), - options: {}, - }) - expect(calls).toEqual(["responses:deployment"]) - expect(ignored.language).toBeUndefined() - }), - ) - - it.effect("falls back through the legacy Azure selector order", () => - Effect.gen(function* () { - const aisdk = yield* AISDK.Service - const calls: string[] = [] - const make = (method: string) => (id: string) => { - calls.push(`${method}:${id}`) - return { modelId: id, provider: method, specificationVersion: "v3" } - } - yield* addPlugin() - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("messages-deployment")), - modelID: Model.ID.make("messages-deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: { messages: make("messages"), chat: make("chat"), languageModel: make("languageModel") }, - options: {}, - }) - yield* aisdk.runLanguage({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.azure, Model.ID.make("language-deployment")), - modelID: Model.ID.make("language-deployment"), - package: Provider.aisdk("test-provider"), - }), - sdk: { languageModel: make("languageModel") }, - options: {}, - }) - expect(calls).toEqual(["messages:messages-deployment", "languageModel:language-deployment"]) - }), - ) }) diff --git a/packages/core/test/plugin/provider-factory.test.ts b/packages/core/test/plugin/provider-factory.test.ts index cb6e3901dba..bcc885befdc 100644 --- a/packages/core/test/plugin/provider-factory.test.ts +++ b/packages/core/test/plugin/provider-factory.test.ts @@ -7,7 +7,6 @@ import { PluginHost } from "@opencode/core/plugin/host" import { AlibabaPlugin } from "@opencode/core/plugin/provider/alibaba" import { CoherePlugin } from "@opencode/core/plugin/provider/cohere" import { GatewayPlugin } from "@opencode/core/plugin/provider/gateway" -import { MistralPlugin } from "@opencode/core/plugin/provider/mistral" import { PerplexityPlugin } from "@opencode/core/plugin/provider/perplexity" import { VenicePlugin } from "@opencode/core/plugin/provider/venice" import { Provider } from "@opencode/core/provider" @@ -20,7 +19,6 @@ const providers = [ { id: "alibaba", plugin: AlibabaPlugin, package: "@ai-sdk/alibaba", provider: "alibaba.chat" }, { id: "cohere", plugin: CoherePlugin, package: "@ai-sdk/cohere", provider: "cohere.chat" }, { id: "gateway", plugin: GatewayPlugin, package: "@ai-sdk/gateway", provider: "gateway" }, - { id: "mistral", plugin: MistralPlugin, package: "@ai-sdk/mistral", provider: "mistral.chat" }, { id: "perplexity", plugin: PerplexityPlugin, package: "@ai-sdk/perplexity", provider: "perplexity" }, { id: "venice", plugin: VenicePlugin, package: "venice-ai-sdk-provider", provider: "custom-provider.chat" }, ] as const diff --git a/packages/core/test/plugin/provider-google-vertex.test.ts b/packages/core/test/plugin/provider-google-vertex.test.ts index 6f625d35935..306907def6b 100644 --- a/packages/core/test/plugin/provider-google-vertex.test.ts +++ b/packages/core/test/plugin/provider-google-vertex.test.ts @@ -11,7 +11,6 @@ import { fakeSelectorSdk } from "../fixture/selector" import { testEffect } from "../lib/effect" import { PluginTestLayer } from "./fixture" -const vertexOptions: Record[] = [] const googleAuthOptions: Record[] = [] const it = testEffect(PluginTestLayer) @@ -48,15 +47,6 @@ function withEnv(vars: Record, effect: () = ) } -void mock.module("@ai-sdk/google-vertex", () => ({ - createVertex: (options: Record) => { - vertexOptions.push(options) - return { - languageModel: (modelID: string) => ({ modelID, provider: "google-vertex", specificationVersion: "v3" }), - } - }, -})) - void mock.module("google-auth-library", () => ({ GoogleAuth: class { constructor(options: Record) { @@ -187,9 +177,6 @@ describe("GoogleVertexPlugin", () => { }, () => Effect.gen(function* () { - vertexOptions.length = 0 - const plugin = yield* Plugin.Service - const aisdk = yield* AISDK.Service const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => { @@ -203,15 +190,6 @@ describe("GoogleVertexPlugin", () => { ) yield* addPlugin() const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex"))) - yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")), - modelID: Model.ID.make("gemini"), - package: "aisdk:@ai-sdk/google-vertex", - }), - package: "@ai-sdk/google-vertex", - options: { name: "google-vertex" }, - }) expect(provider.settings?.project).toBe("vertex-project") expect(provider).toMatchObject({ @@ -221,8 +199,6 @@ describe("GoogleVertexPlugin", () => { "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4", }, }) - expect(vertexOptions[0].project).toBe("vertex-project") - expect(vertexOptions[0].location).toBe("europe-west4") }), ), ) @@ -316,56 +292,6 @@ describe("GoogleVertexPlugin", () => { ), ) - it.effect("does not pass Google auth fetch to the native Vertex SDK", () => - withEnv( - { - GOOGLE_CLOUD_PROJECT: "env-project", - GOOGLE_VERTEX_LOCATION: "env-location", - }, - () => - Effect.gen(function* () { - vertexOptions.length = 0 - const plugin = yield* Plugin.Service - const aisdk = yield* AISDK.Service - yield* addPlugin() - yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")), - modelID: Model.ID.make("gemini"), - package: "aisdk:@ai-sdk/google-vertex", - }), - package: "@ai-sdk/google-vertex", - options: { name: "google-vertex" }, - }) - expect(vertexOptions).toHaveLength(1) - expect(vertexOptions[0].project).toBe("env-project") - expect(vertexOptions[0].location).toBe("env-location") - expect(vertexOptions[0].fetch).toBeUndefined() - }), - ), - ) - - it.effect("creates Anthropic SDKs for canonical Google Vertex models", () => - Effect.gen(function* () { - const plugin = yield* Plugin.Service - const aisdk = yield* AISDK.Service - yield* addPlugin() - const result = yield* aisdk.runSDK({ - model: Model.Info.make({ - ...Model.Info.default(Provider.ID.googleVertex, Model.ID.make("claude-sonnet-4-6@default")), - modelID: Model.ID.make("claude-sonnet-4-6@default"), - package: "aisdk:@ai-sdk/google-vertex/anthropic", - }), - package: "@ai-sdk/google-vertex/anthropic", - options: { name: "google-vertex", project: "project", location: "eu" }, - }) - - expect(result.sdk.languageModel("claude-sonnet-4-6@default").config.baseURL).toBe( - "https://aiplatform.eu.rep.googleapis.com/v1/projects/project/locations/eu/publishers/anthropic/models", - ) - }), - ) - it.effect("wraps an injected transport with Google auth for OpenAI-compatible Vertex endpoints", () => Effect.gen(function* () { googleAuthOptions.length = 0