refactor(core): remove dead AISDK hooks (#48523)

This commit is contained in:
Aiden Cline
2026-09-11 12:41:20 -05:00
committed by GitHub
Unverified
parent 923bda07a2
commit 6e8ff0c2b7
9 changed files with 0 additions and 435 deletions
-4
View File
@@ -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,
@@ -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)
}),
)
}),
})
@@ -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),
)
}),
)
}),
})
@@ -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(
@@ -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)
},
})
@@ -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<T>(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")
}),
)
})
@@ -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"])
}),
)
})
@@ -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
@@ -11,7 +11,6 @@ import { fakeSelectorSdk } from "../fixture/selector"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const vertexOptions: Record<string, any>[] = []
const googleAuthOptions: Record<string, any>[] = []
const it = testEffect(PluginTestLayer)
@@ -48,15 +47,6 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
)
}
void mock.module("@ai-sdk/google-vertex", () => ({
createVertex: (options: Record<string, any>) => {
vertexOptions.push(options)
return {
languageModel: (modelID: string) => ({ modelID, provider: "google-vertex", specificationVersion: "v3" }),
}
},
}))
void mock.module("google-auth-library", () => ({
GoogleAuth: class {
constructor(options: Record<string, any>) {
@@ -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