Преглед на файлове

fix(core): retire legacy provider aliases (#40487)

Aiden Cline преди 4 дни
родител
ревизия
ae494ffce5

+ 22 - 23
packages/ai/src/providers/azure.ts

@@ -5,6 +5,7 @@ import type { ProviderPackage } from "../provider-package"
 import { ProviderID, type ModelID } from "../schema"
 import * as OpenAIChat from "../protocols/openai-chat"
 import * as OpenAIResponses from "../protocols/openai-responses"
+import { ProviderShared } from "../protocols/shared"
 import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
 
 export const id = ProviderID.make("azure")
@@ -19,7 +20,7 @@ export type LanguageModelOptions = AzureURL &
   ProviderAuthOption<"optional"> & {
     readonly apiVersion?: string
     readonly queryParams?: Record<string, string>
-    readonly useCompletionUrls?: boolean
+    readonly useDeploymentBasedUrls?: boolean
     readonly providerOptions?: OpenAIProviderOptionsInput
   }
 export type Config = LanguageModelOptions
@@ -29,27 +30,22 @@ export type Settings = ProviderPackage.Settings &
     readonly apiKey?: string
     readonly apiVersion?: string
     readonly queryParams?: Readonly<Record<string, string>>
+    readonly useDeploymentBasedUrls?: boolean
     readonly providerOptions?: OpenAIProviderOptionsInput
   }
 
-const resourceBaseURL = (resourceName: string) => `https://${resourceName.trim()}.openai.azure.com/openai/v1`
+const resourceBaseURL = (resourceName: string) => `https://${resourceName.trim()}.openai.azure.com/openai`
 
 const responsesRoute = OpenAIResponses.route.with({
   id: "azure-openai-responses",
   provider: id,
   auth: routeAuth,
-  endpoint: {
-    query: { "api-version": "v1" },
-  },
 })
 
 const chatRoute = OpenAIChat.route.with({
   id: "azure-openai-chat",
   provider: id,
   auth: routeAuth,
-  endpoint: {
-    query: { "api-version": "v1" },
-  },
 })
 
 export const routes = [responsesRoute, chatRoute]
@@ -59,7 +55,7 @@ const defaults = (input: Config) => {
     apiKey: _,
     apiVersion: _apiVersion,
     resourceName: _resourceName,
-    useCompletionUrls: _useCompletionUrls,
+    useDeploymentBasedUrls: _useDeploymentBasedUrls,
     baseURL: _baseURL,
     queryParams: _queryParams,
     ...rest
@@ -80,37 +76,39 @@ const auth = (input: Config) => {
   )
 }
 
-const configuredRoute = <Body, Prepared>(route: RouteDef<Body, Prepared>, input: Config) =>
+const configuredRoute = <Body, Prepared>(route: RouteDef<Body, Prepared>, input: Config, modelID: string | ModelID) =>
   route.with({
     auth: auth(input),
-    endpoint: {
-      // AtLeastOne guarantees at least one is set; baseURL wins if both are.
-      baseURL: input.baseURL ?? resourceBaseURL(input.resourceName!),
-      query: {
-        ...(input.apiVersion ? { "api-version": input.apiVersion } : {}),
-        ...input.queryParams,
-      },
-    },
+    endpoint: endpoint(input, modelID),
   })
 
+function endpoint(input: Config, modelID: string | ModelID) {
+  const baseURL = ProviderShared.trimBaseUrl(input.baseURL ?? resourceBaseURL(input.resourceName!))
+  const query = { "api-version": input.apiVersion ?? "v1", ...input.queryParams }
+
+  if (input.useDeploymentBasedUrls) return { baseURL: `${baseURL}/deployments/${modelID}`, query }
+  if (input.baseURL !== undefined && !new URL(input.baseURL).hostname.endsWith(".openai.azure.com")) {
+    return { baseURL, query: input.queryParams }
+  }
+  return { baseURL: `${baseURL}/v1`, query }
+}
+
 export const configure = (input: Config) => {
-  const configuredResponsesRoute = configuredRoute(responsesRoute, input)
-  const configuredChatRoute = configuredRoute(chatRoute, input)
   const modelDefaults = defaults(input)
 
   const responses = (modelID: string | ModelID) =>
-    configuredResponsesRoute
+    configuredRoute(responsesRoute, input, modelID)
       .with(withOpenAIOptions(modelID, modelDefaults))
       .model<OpenAIProviderOptionsInput>({ id: modelID })
 
   const chat = (modelID: string | ModelID) =>
-    configuredChatRoute
+    configuredRoute(chatRoute, input, modelID)
       .with(withOpenAIOptions(modelID, modelDefaults))
       .model<OpenAIProviderOptionsInput>({ id: modelID })
 
   return {
     id,
-    model: (modelID: string | ModelID) => (input.useCompletionUrls === true ? chat(modelID) : responses(modelID)),
+    model: responses,
     responses,
     chat,
     configure,
@@ -131,6 +129,7 @@ const config = (settings: Settings): Config => {
     limits: settings.limits,
     providerOptions: settings.providerOptions,
     queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
+    useDeploymentBasedUrls: settings.useDeploymentBasedUrls,
   }
   if (settings.baseURL !== undefined) return { ...common, baseURL: settings.baseURL }
   if (settings.resourceName !== undefined) return { ...common, resourceName: settings.resourceName }

+ 1 - 2
packages/ai/src/providers/google-vertex-messages.ts

@@ -15,8 +15,7 @@ export type AnthropicThinkingInput = AnthropicMessages.ThinkingInput
 
 const VERSION = "vertex-2023-10-16" as const
 
-// models.dev uses this provider id even though the API contract is Anthropic Messages.
-export const id = ProviderID.make("google-vertex-anthropic")
+export const id = ProviderID.make("google-vertex")
 
 export type Config = RouteDefaultsInput &
   GoogleVertexShared.OAuthOptions & {

+ 21 - 0
packages/ai/test/provider-package.test.ts

@@ -209,6 +209,27 @@ describe("provider package entrypoints", () => {
     expect(chat.route.id).toBe("azure-openai-chat")
   })
 
+  test("constructs Azure deployment URLs and preserves custom gateway URLs", async () => {
+    const Azure = await import("@opencode-ai/ai/providers/azure")
+    const deployment = Azure.model("custom-deployment", {
+      apiKey: "fixture",
+      resourceName: "opencode-test",
+      apiVersion: "2025-01-01-preview",
+      useDeploymentBasedUrls: true,
+    })
+    const gateway = Azure.model("gateway-model", {
+      apiKey: "fixture",
+      baseURL: "https://gateway.example/azure/",
+    })
+
+    expect(deployment.route.endpoint).toMatchObject({
+      baseURL: "https://opencode-test.openai.azure.com/openai/deployments/custom-deployment",
+      query: { "api-version": "2025-01-01-preview" },
+    })
+    expect(gateway.route.endpoint.baseURL).toBe("https://gateway.example/azure")
+    expect(gateway.route.endpoint.query).toBeUndefined()
+  })
+
   test("maps Google package settings onto the Gemini model", async () => {
     const Google = await import("@opencode-ai/ai/providers/google")
     const selected = Google.model("gemini-2.5-flash", {

+ 7 - 5
packages/ai/test/provider/google-vertex.test.ts

@@ -56,13 +56,14 @@ describe("Google Vertex providers", () => {
 
   it.effect("projects Anthropic Messages onto the Vertex raw-predict API", () =>
     Effect.gen(function* () {
+      const model = GoogleVertexMessages.configure({
+        accessToken: "vertex-token",
+        location: "eu",
+        project: "vertex-project",
+      }).model("claude-sonnet-4-6")
       const response = yield* LLMClient.generate(
         LLM.request({
-          model: GoogleVertexMessages.configure({
-            accessToken: "vertex-token",
-            location: "eu",
-            project: "vertex-project",
-          }).model("claude-sonnet-4-6"),
+          model,
           prompt: "Say hello.",
         }),
       ).pipe(
@@ -97,6 +98,7 @@ describe("Google Vertex providers", () => {
         ),
       )
 
+      expect(model.provider).toBe("google-vertex")
       expect(response.text).toBe("Hello.")
     }),
   )

+ 15 - 0
packages/core/src/aisdk-native.ts

@@ -27,6 +27,21 @@ export function map(input: MapInput): Mapping | undefined {
       }
     case "@ai-sdk/amazon-bedrock/mantle":
       return mapBedrockMantle(input, baseSettings)
+    case "@ai-sdk/azure":
+      return {
+        package: `@opencode-ai/ai/providers/azure/${input.settings.useCompletionUrls === true ? "chat" : "responses"}`,
+        settings: {
+          ...baseSettings,
+          ...mapAPIKey(input.settings),
+          ...(typeof input.settings.resourceName === "string" ? { resourceName: input.settings.resourceName } : {}),
+          ...(typeof input.settings.apiVersion === "string" ? { apiVersion: input.settings.apiVersion } : {}),
+          ...(isStringRecord(input.settings.queryParams) ? { queryParams: input.settings.queryParams } : {}),
+          ...(typeof input.settings.useDeploymentBasedUrls === "boolean"
+            ? { useDeploymentBasedUrls: input.settings.useDeploymentBasedUrls }
+            : {}),
+          ...mapOpenAIOptions(input.settings),
+        },
+      }
     case "@ai-sdk/google":
       return {
         package: "@opencode-ai/ai/providers/google",

+ 1 - 1
packages/core/src/catalog.ts

@@ -213,7 +213,7 @@ const layer = Layer.effect(
           const provider = record.provider
 
           // TODO: Remove these provider-specific assumptions once model syncing reliably reports available deployments.
-          if (providerID === Provider.ID.azure || providerID === Provider.ID.make("azure-cognitive-services")) {
+          if (providerID === Provider.ID.azure) {
             return
           }
 

+ 70 - 76
packages/core/src/config/plugin/provider.ts

@@ -12,96 +12,84 @@ export const Plugin = define({
     const config = yield* Config.Service
     const loaded = { entries: yield* config.entries() }
     yield* ctx.integration.transform((integrations) => {
-      const files = loaded.entries.filter((entry): entry is Config.Document => entry.type === "document")
       const configuredIntegrations = new Set(
-        files.flatMap((file) =>
-          Object.entries(file.info.providers ?? {}).flatMap(([id, provider]) =>
-            provider.env === undefined ? [] : [id],
-          ),
-        ),
+        configuredProviders(loaded.entries).flatMap(([id, provider]) => (provider.env === undefined ? [] : [id])),
       )
-      for (const file of files) {
-        for (const [id, item] of Object.entries(file.info.providers ?? {})) {
-          const integrationID = id
-          if (!configuredIntegrations.has(id) && !integrations.get(integrationID)) continue
-          integrations.update(integrationID, (integration) => {
-            integration.name = item.name ?? integration.name
+      for (const [id, provider] of configuredProviders(loaded.entries)) {
+        const integrationID = id
+        if (!configuredIntegrations.has(id) && !integrations.get(integrationID)) continue
+        integrations.update(integrationID, (integration) => {
+          integration.name = provider.name ?? integration.name
+        })
+        if (provider.env !== undefined) {
+          integrations.method.update({
+            integrationID,
+            method: { type: "env", names: [...provider.env] },
           })
-          if (item.env !== undefined) {
-            integrations.method.update({
-              integrationID,
-              method: { type: "env", names: [...item.env] },
-            })
-          }
         }
       }
     })
 
     yield* ctx.catalog.transform((catalog) => {
-      const files = loaded.entries.filter((entry): entry is Config.Document => entry.type === "document")
       const configuredDefault = Config.latest(loaded.entries, "model")
       if (configuredDefault !== undefined)
         catalog.model.default.set(configuredDefault.providerID, configuredDefault.model)
-      for (const file of files) {
-        for (const [id, item] of Object.entries(file.info.providers ?? {})) {
-          const providerID = id
-          catalog.provider.update(providerID, (provider) => {
-            if (item.name !== undefined) provider.name = item.name
-            if (item.package !== undefined) provider.package = item.package
-            if (item.settings !== undefined)
-              provider.settings = Provider.mergeOverlay(provider.settings, item.settings)
-            if (item.headers !== undefined) provider.headers = Provider.mergeHeaders(provider.headers, item.headers)
-            if (item.body !== undefined) provider.body = Provider.mergeOverlay(provider.body, item.body)
-          })
-          for (const [id, config] of Object.entries(item.models ?? {})) {
-            catalog.model.update(providerID, id, (model) => {
-              if (config.family !== undefined) model.family = config.family
-              if (config.name !== undefined) model.name = config.name
-              if (config.modelID !== undefined) model.modelID = config.modelID
-              if (config.compatibility !== undefined)
-                model.compatibility = { ...model.compatibility, ...config.compatibility }
-              if (config.package !== undefined) model.package = config.package
-              if (config.settings !== undefined)
-                model.settings = Provider.mergeOverlay(model.settings, config.settings)
-              if (config.headers !== undefined) model.headers = Provider.mergeHeaders(model.headers, config.headers)
-              if (config.body !== undefined) model.body = Provider.mergeOverlay(model.body, config.body)
-              if (config.capabilities !== undefined) {
-                model.capabilities = {
-                  tools: config.capabilities.tools,
-                  input: [...config.capabilities.input],
-                  output: [...config.capabilities.output],
-                }
+      for (const [id, item] of configuredProviders(loaded.entries)) {
+        const providerID = id
+        catalog.provider.update(providerID, (provider) => {
+          if (item.name !== undefined) provider.name = item.name
+          if (item.package !== undefined) provider.package = item.package
+          if (item.settings !== undefined) provider.settings = Provider.mergeOverlay(provider.settings, item.settings)
+          if (item.headers !== undefined) provider.headers = Provider.mergeHeaders(provider.headers, item.headers)
+          if (item.body !== undefined) provider.body = Provider.mergeOverlay(provider.body, item.body)
+        })
+        for (const [id, config] of Object.entries(item.models ?? {})) {
+          catalog.model.update(providerID, id, (model) => {
+            if (config.family !== undefined) model.family = config.family
+            if (config.name !== undefined) model.name = config.name
+            if (config.modelID !== undefined) model.modelID = config.modelID
+            if (config.compatibility !== undefined)
+              model.compatibility = { ...model.compatibility, ...config.compatibility }
+            if (config.package !== undefined) model.package = config.package
+            if (config.settings !== undefined) model.settings = Provider.mergeOverlay(model.settings, config.settings)
+            if (config.headers !== undefined) model.headers = Provider.mergeHeaders(model.headers, config.headers)
+            if (config.body !== undefined) model.body = Provider.mergeOverlay(model.body, config.body)
+            if (config.capabilities !== undefined) {
+              model.capabilities = {
+                tools: config.capabilities.tools,
+                input: [...config.capabilities.input],
+                output: [...config.capabilities.output],
               }
-              if (config.variants !== undefined) {
-                model.variants ??= []
-                for (const variant of config.variants) {
-                  let existing = model.variants.find((item) => item.id === variant.id)
-                  if (!existing) {
-                    existing = { id: variant.id }
-                    model.variants.push(existing)
-                  }
-                  if (variant.settings !== undefined)
-                    existing.settings = Provider.mergeOverlay(existing.settings, variant.settings)
-                  if (variant.headers !== undefined)
-                    existing.headers = Provider.mergeHeaders(existing.headers, variant.headers)
-                  if (variant.body !== undefined) existing.body = Provider.mergeOverlay(existing.body, variant.body)
+            }
+            if (config.variants !== undefined) {
+              model.variants ??= []
+              for (const variant of config.variants) {
+                let existing = model.variants.find((item) => item.id === variant.id)
+                if (!existing) {
+                  existing = { id: variant.id }
+                  model.variants.push(existing)
                 }
+                if (variant.settings !== undefined)
+                  existing.settings = Provider.mergeOverlay(existing.settings, variant.settings)
+                if (variant.headers !== undefined)
+                  existing.headers = Provider.mergeHeaders(existing.headers, variant.headers)
+                if (variant.body !== undefined) existing.body = Provider.mergeOverlay(existing.body, variant.body)
               }
-              if (config.cost !== undefined) {
-                model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({
-                  tier: cost.tier && { ...cost.tier },
-                  input: cost.input,
-                  output: cost.output,
-                  cache: {
-                    read: cost.cache?.read ?? Money.USDPerMillionTokens.zero,
-                    write: cost.cache?.write ?? Money.USDPerMillionTokens.zero,
-                  },
-                }))
-              }
-              if (config.disabled !== undefined) model.enabled = !config.disabled
-              if (config.limit !== undefined) model.limit = { ...model.limit, ...config.limit }
-            })
-          }
+            }
+            if (config.cost !== undefined) {
+              model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({
+                tier: cost.tier && { ...cost.tier },
+                input: cost.input,
+                output: cost.output,
+                cache: {
+                  read: cost.cache?.read ?? Money.USDPerMillionTokens.zero,
+                  write: cost.cache?.write ?? Money.USDPerMillionTokens.zero,
+                },
+              }))
+            }
+            if (config.disabled !== undefined) model.enabled = !config.disabled
+            if (config.limit !== undefined) model.limit = { ...model.limit, ...config.limit }
+          })
         }
       }
     })
@@ -118,3 +106,9 @@ export const Plugin = define({
     )
   }),
 })
+
+function configuredProviders(entries: readonly Config.Entry[]) {
+  return entries
+    .filter((entry): entry is Config.Document => entry.type === "document")
+    .flatMap((file) => Object.entries(file.info.providers ?? {}))
+}

+ 18 - 3
packages/core/src/plugin/models-dev.ts

@@ -3,13 +3,14 @@ import { Integration } from "@opencode-ai/schema/integration"
 import { Effect, Stream } from "effect"
 import { Bus } from "../bus"
 import { ModelsDev } from "../models-dev"
+import { Provider } from "../provider"
 
 export const ModelsDevPlugin = define({
   id: "opencode.models-dev",
   effect: Effect.fn(function* (ctx) {
     const modelsDev = yield* ModelsDev.Service
     const bus = yield* Bus.Service
-    const loaded = { data: structuredClone(yield* modelsDev.get()) }
+    const loaded = { data: snapshots(yield* modelsDev.get()) }
     yield* ctx.integration.transform((integrations) => {
       for (const provider of loaded.data) {
         if (provider.environment.length === 0) continue
@@ -21,7 +22,10 @@ export const ModelsDevPlugin = define({
         })
         integrations.method.update({
           integrationID,
-          method: { type: "env", names: [...provider.environment] },
+          method: {
+            type: "env",
+            names: environmentNames(provider),
+          },
         })
       }
     })
@@ -39,7 +43,7 @@ export const ModelsDevPlugin = define({
     yield* bus.subscribe(ModelsDev.Event.Refreshed).pipe(
       Stream.runForEach(() =>
         modelsDev.get().pipe(
-          Effect.tap((data) => Effect.sync(() => (loaded.data = structuredClone(data)))),
+          Effect.tap((data) => Effect.sync(() => (loaded.data = snapshots(data)))),
           Effect.andThen(ctx.integration.reload()),
           Effect.andThen(ctx.catalog.reload()),
         ),
@@ -48,3 +52,14 @@ export const ModelsDevPlugin = define({
     )
   }),
 })
+
+function environmentNames(provider: ModelsDev.Snapshot) {
+  if (provider.info.id !== Provider.ID.azure) return [...provider.environment]
+  return [...provider.environment.filter((name) => name.endsWith("_API_KEY")), "AZURE_COGNITIVE_SERVICES_API_KEY"]
+}
+
+function snapshots(data: readonly ModelsDev.Snapshot[]) {
+  return structuredClone(data).filter(
+    (provider) => provider.info.id !== "azure-cognitive-services" && provider.info.id !== "google-vertex-anthropic",
+  )
+}

+ 2 - 4
packages/core/src/plugin/provider.ts

@@ -1,7 +1,7 @@
 import { AlibabaPlugin } from "./provider/alibaba"
 import { AmazonBedrockPlugin } from "./provider/amazon-bedrock"
 import { AnthropicPlugin } from "./provider/anthropic"
-import { AzureCognitiveServicesPlugin, AzurePlugin } from "./provider/azure"
+import { AzurePlugin } from "./provider/azure"
 import { CerebrasPlugin } from "./provider/cerebras"
 import { CloudflareAIGatewayPlugin } from "./provider/cloudflare-ai-gateway"
 import { CloudflareWorkersAIPlugin } from "./provider/cloudflare-workers-ai"
@@ -11,7 +11,7 @@ import { DynamicProviderPlugin } from "./provider/dynamic"
 import { GatewayPlugin } from "./provider/gateway"
 import { GithubCopilotPlugin } from "./provider/github-copilot"
 import { GitLabPlugin } from "./provider/gitlab"
-import { GoogleVertexAnthropicPlugin, GoogleVertexPlugin } from "./provider/google-vertex"
+import { GoogleVertexPlugin } from "./provider/google-vertex"
 import { GroqPlugin } from "./provider/groq"
 import { KiloPlugin } from "./provider/kilo"
 import { LLMGatewayPlugin } from "./provider/llmgateway"
@@ -35,7 +35,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
   AlibabaPlugin,
   AmazonBedrockPlugin,
   AnthropicPlugin,
-  AzureCognitiveServicesPlugin,
   AzurePlugin,
   CerebrasPlugin,
   CloudflareAIGatewayPlugin,
@@ -45,7 +44,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
   GatewayPlugin,
   GithubCopilotPlugin,
   GitLabPlugin,
-  GoogleVertexAnthropicPlugin,
   GoogleVertexPlugin,
   GroqPlugin,
   KiloPlugin,

+ 31 - 40
packages/core/src/plugin/provider/azure.ts

@@ -15,15 +15,28 @@ export const AzurePlugin = define({
   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/azure") continue
-        const configured = item.provider.settings?.resourceName
-        const resourceName =
-          typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME
+        if (item.provider.id !== Provider.ID.azure && Provider.packageName(item.provider.package) !== "@ai-sdk/azure")
+          continue
+        const resourceName = resolveResourceName(item.provider.settings)
         if (!resourceName) continue
         evt.provider.update(item.provider.id, (provider) => {
-          provider.settings = { ...provider.settings, resourceName }
+          provider.settings = {
+            ...provider.settings,
+            resourceName,
+            ...(typeof provider.settings?.baseURL === "string"
+              ? { baseURL: expandResourceName(provider.settings.baseURL, resourceName) }
+              : {}),
+          }
         })
+        for (const model of item.models.values()) {
+          evt.model.update(item.provider.id, model.id, (draft) => {
+            if (typeof draft.settings?.baseURL !== "string") return
+            draft.settings.baseURL = expandResourceName(
+              draft.settings.baseURL,
+              resolveResourceName(draft.settings, resourceName) ?? resourceName,
+            )
+          })
+        }
       }
     })
     yield* ctx.aisdk.hook(
@@ -36,9 +49,7 @@ export const AzurePlugin = define({
             !evt.options.baseURL &&
             (!Provider.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string")
           ) {
-            throw new Error(
-              "AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
-            )
+            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"))
@@ -59,34 +70,14 @@ export const AzurePlugin = define({
   }),
 })
 
-export const AzureCognitiveServicesPlugin = define({
-  id: "opencode.provider.azure-cognitive-services",
-  effect: Effect.fn(function* (ctx) {
-    yield* ctx.catalog.transform((evt) => {
-      const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
-      if (!resourceName) return
-      for (const item of evt.provider.list()) {
-        if (!Provider.isAISDK(item.provider.package)) continue
-        if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
-        if (!item.provider.id.includes("azure-cognitive-services")) continue
-        evt.provider.update(item.provider.id, (provider) => {
-          provider.settings = {
-            ...provider.settings,
-            baseURL: `https://${resourceName}.cognitiveservices.azure.com/openai`,
-          }
-        })
-      }
-    })
-    yield* ctx.aisdk.hook(
-      "language",
-      Effect.fn(function* (evt) {
-        if (evt.model.providerID !== Provider.ID.make("azure-cognitive-services")) return
-        evt.language = selectLanguage(
-          evt.sdk,
-          evt.model.modelID ?? evt.model.id,
-          Boolean(evt.options.useCompletionUrls),
-        )
-      }),
-    )
-  }),
-})
+function resolveResourceName(settings: Readonly<Record<string, unknown>> | undefined, fallback?: string) {
+  const configured = settings?.resourceName
+  if (typeof configured === "string" && configured.trim() !== "") return configured
+  return fallback ?? process.env.AZURE_RESOURCE_NAME ?? process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
+}
+
+function expandResourceName(baseURL: string, resourceName: string) {
+  return baseURL
+    .replaceAll("${AZURE_RESOURCE_NAME}", resourceName)
+    .replaceAll("${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}", resourceName)
+}

+ 16 - 59
packages/core/src/plugin/provider/google-vertex.ts

@@ -92,6 +92,22 @@ 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)
@@ -114,62 +130,3 @@ export const GoogleVertexPlugin = define({
     )
   }),
 })
-
-export const GoogleVertexAnthropicPlugin = define({
-  id: "opencode.provider.google-vertex-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/google-vertex/anthropic") continue
-        const project =
-          item.provider.settings?.project ??
-          process.env.GOOGLE_CLOUD_PROJECT ??
-          process.env.GCP_PROJECT ??
-          process.env.GCLOUD_PROJECT
-        const location =
-          item.provider.settings?.location ??
-          process.env.GOOGLE_CLOUD_LOCATION ??
-          process.env.VERTEX_LOCATION ??
-          "global"
-        evt.provider.update(item.provider.id, (provider) => {
-          provider.settings = { ...provider.settings, ...(project ? { project } : {}), location }
-        })
-      }
-    })
-    yield* ctx.aisdk.hook(
-      "sdk",
-      Effect.fn(function* (evt) {
-        if (evt.package !== "@ai-sdk/google-vertex/anthropic") return
-        const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
-        const project =
-          typeof evt.options.project === "string"
-            ? evt.options.project
-            : (process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT)
-        const location =
-          typeof evt.options.location === "string"
-            ? evt.options.location
-            : (process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global")
-        evt.sdk = mod.createVertexAnthropic({
-          ...evt.options,
-          project,
-          location,
-          // Continental multi-regions (eu, us) require Regional Endpoint Platform
-          // domains; the default {region}-aiplatform.googleapis.com does not resolve.
-          ...((location === "eu" || location === "us") && project && !evt.options.baseURL
-            ? {
-                baseURL: `https://aiplatform.${location}.rep.googleapis.com/v1/projects/${project}/locations/${location}/publishers/anthropic/models`,
-              }
-            : {}),
-        })
-      }),
-    )
-    yield* ctx.aisdk.hook(
-      "language",
-      Effect.fn(function* (evt) {
-        if (evt.model.providerID !== Provider.ID.make("google-vertex-anthropic")) return
-        evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
-      }),
-    )
-  }),
-})

+ 4 - 0
packages/core/src/session/runner/model.ts

@@ -23,6 +23,10 @@ export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavaila
   { providerID: Provider.ID, modelID: ID },
 ) {
   override get message() {
+    if (this.providerID === "azure-cognitive-services")
+      return `Model unavailable: ${this.providerID}/${this.modelID}. This provider has been deprecated; use azure/${this.modelID} instead.`
+    if (this.providerID === "google-vertex-anthropic")
+      return `Model unavailable: ${this.providerID}/${this.modelID}. This provider has been deprecated; use google-vertex/${this.modelID} instead.`
     return `Model unavailable: ${this.providerID}/${this.modelID}`
   }
 }

+ 60 - 5
packages/core/src/v1/config/migrate.ts

@@ -94,13 +94,13 @@ function experimental(info: typeof ConfigV1.Info.Type) {
           { action: "provider.use" as const, resource: "*", effect: "deny" as const },
           ...info.enabled_providers.map((resource) => ({
             action: "provider.use" as const,
-            resource,
+            resource: providerID(resource),
             effect: "allow" as const,
           })),
         ]),
     ...(info.disabled_providers ?? []).map((resource) => ({
       action: "provider.use" as const,
-      resource,
+      resource: providerID(resource),
       effect: "deny" as const,
     })),
   ]
@@ -188,7 +188,7 @@ function modelSelection(input?: string, variant?: string) {
   if (input === undefined || !/^[^/#]+\/[^#]+$/.test(input)) return undefined
   const separator = input.indexOf("/")
   return {
-    providerID: input.slice(0, separator),
+    providerID: providerID(input.slice(0, separator)),
     model: input.slice(separator + 1),
     ...(variant === undefined || variant.length === 0 || variant.includes("#") ? {} : { variant }),
   }
@@ -234,10 +234,23 @@ function migrateMcp(info: ConfigMCPV1.Info) {
 
 function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
   if (!info) return undefined
-  return Object.fromEntries(Object.entries(info).map(([name, provider]) => [name, migrateProvider(provider)]))
+  return Object.fromEntries(
+    Object.entries(info).flatMap(([name, provider]) => {
+      const id = providerID(name)
+      // If both names are present, keep the settings under the current name and ignore the old one.
+      if (id !== name && info[id]) return []
+      return [[id, migrateProvider(name, provider)]]
+    }),
+  )
 }
 
-function migrateProvider(info: ConfigProviderV1.Info) {
+function migrateProvider(sourceID: string, info: ConfigProviderV1.Info) {
+  if (sourceID === "azure-cognitive-services") return migrateAzureCognitiveServicesProvider(info)
+  if (sourceID === "google-vertex-anthropic") return migrateGoogleVertexAnthropicProvider(info)
+  return migrateStandardProvider(info)
+}
+
+function migrateStandardProvider(info: ConfigProviderV1.Info) {
   const options = ConfigProviderOptionsV1.provider(info.options ?? {})
   return {
     name: info.name,
@@ -252,6 +265,48 @@ function migrateProvider(info: ConfigProviderV1.Info) {
   }
 }
 
+function migrateAzureCognitiveServicesProvider(info: ConfigProviderV1.Info) {
+  const standard = migrateStandardProvider(info)
+  const migrated = {
+    ...standard,
+    env: standard.env?.filter((name) => name !== "AZURE_COGNITIVE_SERVICES_RESOURCE_NAME"),
+  }
+  if (info.npm !== "@ai-sdk/openai-compatible" || info.api) return migrated
+  return {
+    ...migrated,
+    settings: {
+      ...migrated.settings,
+      baseURL: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.cognitiveservices.azure.com/openai",
+    },
+  }
+}
+
+function migrateGoogleVertexAnthropicProvider(info: ConfigProviderV1.Info) {
+  const migrated = migrateStandardProvider(info)
+  const packageName = migrated.package ?? Provider.aisdk("@ai-sdk/google-vertex/anthropic")
+  return {
+    ...migrated,
+    // The current Google Vertex provider includes Gemini and Claude. Keep the Anthropic SDK on Claude models
+    // instead of changing the package inherited by every model on the provider.
+    package: undefined,
+    models:
+      migrated.models &&
+      Object.fromEntries(
+        Object.entries(migrated.models).map(([name, model]) => [
+          name,
+          model.package ? model : { ...model, package: packageName },
+        ]),
+      ),
+  }
+}
+
+// Rename these only in files detected as V1 by a field that exists only in the old config format.
+function providerID(input: string) {
+  if (input === "azure-cognitive-services") return "azure"
+  if (input === "google-vertex-anthropic") return "google-vertex"
+  return input
+}
+
 function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
   const settings = info.options && ConfigProviderOptionsV1.model(info.options)
   const costs = info.cost && [

+ 25 - 0
packages/core/test/aisdk-native.test.ts

@@ -16,6 +16,31 @@ describe("AISDKNative", () => {
     })
   })
 
+  test("maps Azure deployments and settings to native routes", () => {
+    const settings = {
+      apiKey: "secret",
+      resourceName: "resource",
+      apiVersion: "2025-01-01-preview",
+      queryParams: { feature: "enabled" },
+      useDeploymentBasedUrls: true,
+      reasoningEffort: "high",
+    }
+    expect(map("@ai-sdk/azure", settings, "deployment")).toEqual({
+      package: "@opencode-ai/ai/providers/azure/responses",
+      settings: {
+        apiKey: "secret",
+        resourceName: "resource",
+        apiVersion: "2025-01-01-preview",
+        queryParams: { feature: "enabled" },
+        useDeploymentBasedUrls: true,
+        providerOptions: { openai: { reasoningEffort: "high" } },
+      },
+    })
+    expect(map("@ai-sdk/azure", { ...settings, useCompletionUrls: true }, "custom-deployment")?.package).toBe(
+      "@opencode-ai/ai/providers/azure/chat",
+    )
+  })
+
   test("maps Bedrock provider and request options", () => {
     expect(
       map(

+ 105 - 0
packages/core/test/config/config.test.ts

@@ -475,6 +475,111 @@ describe("Config", () => {
     }),
   )
 
+  it.effect("renames old provider IDs while migrating v1 configuration", () =>
+    Effect.sync(() => {
+      const migrated = ConfigMigrateV1.migrate({
+        model: "azure-cognitive-services/deployment",
+        enabled_providers: ["google-vertex-anthropic"],
+        disabled_providers: ["azure-cognitive-services"],
+        agent: {
+          reviewer: { model: "google-vertex-anthropic/claude-sonnet" },
+        },
+        command: {
+          review: { template: "Review", model: "azure-cognitive-services/deployment" },
+        },
+        provider: {
+          "azure-cognitive-services": {
+            npm: "@ai-sdk/azure",
+            env: ["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME", "AZURE_COGNITIVE_SERVICES_API_KEY"],
+            models: { deployment: {} },
+          },
+          "google-vertex-anthropic": {
+            npm: "@ai-sdk/google-vertex/anthropic",
+            options: { project: "test-project", location: "us-central1" },
+            models: { "claude-sonnet": {} },
+          },
+        },
+      })
+
+      expect(migrated.model).toEqual({ providerID: "azure", model: "deployment" })
+      expect(migrated.agents?.reviewer?.model).toEqual({ providerID: "google-vertex", model: "claude-sonnet" })
+      expect(migrated.commands?.review?.model).toEqual({ providerID: "azure", model: "deployment" })
+      expect(migrated.experimental?.policies).toEqual([
+        { action: "provider.use", resource: "*", effect: "deny" },
+        { action: "provider.use", resource: "google-vertex", effect: "allow" },
+        { action: "provider.use", resource: "azure", effect: "deny" },
+      ])
+      expect(migrated.providers?.azure).toMatchObject({
+        env: ["AZURE_COGNITIVE_SERVICES_API_KEY"],
+        package: Provider.aisdk("@ai-sdk/azure"),
+        models: { deployment: {} },
+      })
+      expect(migrated.providers?.["azure-cognitive-services"]).toBeUndefined()
+      expect(migrated.providers?.["google-vertex"]).toMatchObject({
+        package: undefined,
+        settings: { project: "test-project", location: "us-central1" },
+        models: {
+          "claude-sonnet": { package: Provider.aisdk("@ai-sdk/google-vertex/anthropic") },
+        },
+      })
+      expect(migrated.providers?.["google-vertex-anthropic"]).toBeUndefined()
+    }),
+  )
+
+  it.effect("preserves the generated base URL for v1 Azure OpenAI-compatible providers", () =>
+    Effect.sync(() => {
+      const migrated = ConfigMigrateV1.migrate({
+        provider: {
+          "azure-cognitive-services": {
+            npm: "@ai-sdk/openai-compatible",
+            env: ["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME", "AZURE_COGNITIVE_SERVICES_API_KEY"],
+          },
+        },
+      })
+
+      expect(migrated.providers?.azure).toMatchObject({
+        env: ["AZURE_COGNITIVE_SERVICES_API_KEY"],
+        package: Provider.aisdk("@ai-sdk/openai-compatible"),
+        settings: {
+          baseURL: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.cognitiveservices.azure.com/openai",
+        },
+      })
+    }),
+  )
+
+  it.effect("ignores old provider IDs when the current provider ID is configured", () =>
+    Effect.sync(() => {
+      const migrated = ConfigMigrateV1.migrate({
+        provider: {
+          azure: { models: { current: {} } },
+          "azure-cognitive-services": { models: { legacy: {} } },
+          "google-vertex": { models: { gemini: {} } },
+          "google-vertex-anthropic": { models: { claude: {} } },
+        },
+      })
+
+      expect(migrated.providers?.azure?.models).toEqual({ current: expect.anything() })
+      expect(migrated.providers?.["google-vertex"]?.models).toEqual({ gemini: expect.anything() })
+    }),
+  )
+
+  it.effect("preserves the built-in package for v1 Vertex Anthropic custom models", () =>
+    Effect.sync(() => {
+      const migrated = ConfigMigrateV1.migrate({
+        provider: {
+          "google-vertex-anthropic": {
+            models: { claude: {} },
+          },
+        },
+      })
+
+      expect(migrated.providers?.["google-vertex"]?.package).toBeUndefined()
+      expect(migrated.providers?.["google-vertex"]?.models?.claude?.package).toBe(
+        Provider.aisdk("@ai-sdk/google-vertex/anthropic"),
+      )
+    }),
+  )
+
   it.effect("migrates v1 interleaved fields to compatibility", () =>
     Effect.sync(() => {
       const migrated = ConfigMigrateV1.migrate({

+ 44 - 0
packages/core/test/location-layer.test.ts

@@ -666,6 +666,50 @@ describe("LocationServiceMap", () => {
     ),
   )
 
+  it.live("explains replacements for unavailable legacy provider models", () =>
+    Effect.acquireRelease(
+      Effect.promise(() => tmpdir()),
+      (dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
+    ).pipe(
+      Effect.flatMap((dir) =>
+        Effect.gen(function* () {
+          const location = Location.Ref.make({ directory: AbsolutePath.make(dir.path) })
+          for (const [providerID, replacement] of [
+            ["azure-cognitive-services", "azure"],
+            ["google-vertex-anthropic", "google-vertex"],
+          ] as const) {
+            const failure = yield* SessionRunnerModel.Service.use((models) =>
+              models.resolve(
+                Session.Info.make({
+                  id: Session.ID.make(`ses_removed_${providerID}`),
+                  projectID: Project.ID.global,
+                  title: "test",
+                  model: {
+                    id: Model.ID.make("chat"),
+                    providerID: Provider.ID.make(providerID),
+                  },
+                  cost: Money.USD.zero,
+                  tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
+                  time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
+                  location,
+                }),
+              ),
+            ).pipe(Effect.provide(LocationServiceMap.Service.get(location)), Effect.flip)
+
+            expect(failure).toMatchObject({
+              _tag: "SessionRunnerModel.ModelUnavailableError",
+              providerID,
+              modelID: "chat",
+            })
+            expect(failure.message).toBe(
+              `Model unavailable: ${providerID}/chat. This provider has been deprecated; use ${replacement}/chat instead.`,
+            )
+          }
+        }),
+      ),
+    ),
+  )
+
   it.live("preserves the selected catalog identity when the package model id differs", () =>
     Effect.acquireRelease(
       Effect.promise(() => tmpdir()),

+ 61 - 1
packages/core/test/model-resolver.test.ts

@@ -12,6 +12,7 @@ import { ModelResolver } from "@opencode-ai/core/model-resolver"
 import { it } from "./lib/effect"
 
 interface ModelOptions {
+  readonly providerID?: Provider.ID
   readonly modelID?: string
   readonly compatibility?: Compatibility
   readonly settings?: Info["settings"]
@@ -25,7 +26,7 @@ const model = (packageName: string | undefined, options: ModelOptions = {}) =>
   Info.make({
     id: ID.make("test-model"),
     modelID: ID.make(options.modelID ?? "api-test-model"),
-    providerID: Provider.ID.make("test-provider"),
+    providerID: options.providerID ?? Provider.ID.make("test-provider"),
     name: "Test model",
     compatibility: options.compatibility,
     package: packageName,
@@ -42,6 +43,65 @@ const model = (packageName: string | undefined, options: ModelOptions = {}) =>
   })
 
 describe("ModelResolver", () => {
+  it.effect("constructs native Azure requests with deployment IDs and projected resource URLs", () =>
+    Effect.gen(function* () {
+      const responses = yield* ModelResolver.fromCatalogModel(
+        model(Provider.aisdk("@ai-sdk/azure"), {
+          providerID: Provider.ID.azure,
+          modelID: "responses-deployment",
+          settings: { resourceName: "modern-resource", apiVersion: "2025-01-01-preview" },
+        }),
+        Credential.Key.make({ type: "key", key: "secret" }),
+      )
+      const chat = yield* ModelResolver.fromCatalogModel(
+        model(Provider.aisdk("@ai-sdk/azure"), {
+          providerID: Provider.ID.azure,
+          modelID: "chat-deployment",
+          settings: { resourceName: "modern-resource", useCompletionUrls: true },
+        }),
+      )
+      const deployment = yield* ModelResolver.fromCatalogModel(
+        model(Provider.aisdk("@ai-sdk/azure"), {
+          providerID: Provider.ID.azure,
+          modelID: "legacy-url-deployment",
+          settings: {
+            resourceName: "modern-resource",
+            apiVersion: "2025-01-01-preview",
+            useDeploymentBasedUrls: true,
+          },
+        }),
+      )
+      const compatible = yield* ModelResolver.fromCatalogModel(
+        model(Provider.aisdk("@ai-sdk/openai-compatible"), {
+          providerID: Provider.ID.azure,
+          modelID: "legacy-deployment",
+          settings: {
+            resourceName: "legacy-resource",
+            baseURL: "https://legacy-resource.cognitiveservices.azure.com/openai",
+          },
+        }),
+      )
+
+      expect(responses).toMatchObject({ id: "responses-deployment", provider: "azure" })
+      expect(responses.route).toMatchObject({
+        id: "azure-openai-responses",
+        endpoint: {
+          baseURL: "https://modern-resource.openai.azure.com/openai/v1",
+          query: { "api-version": "2025-01-01-preview" },
+        },
+      })
+      expect(chat).toMatchObject({ id: "chat-deployment", provider: "azure" })
+      expect(chat.route.id).toBe("azure-openai-chat")
+      expect(deployment).toMatchObject({ id: "legacy-url-deployment", provider: "azure" })
+      expect(deployment.route.endpoint).toMatchObject({
+        baseURL: "https://modern-resource.openai.azure.com/openai/deployments/legacy-url-deployment",
+        query: { "api-version": "2025-01-01-preview" },
+      })
+      expect(compatible).toMatchObject({ id: "legacy-deployment", provider: "azure" })
+      expect(compatible.route.endpoint.baseURL).toBe("https://legacy-resource.cognitiveservices.azure.com/openai")
+    }),
+  )
+
   it.effect("maps Bedrock Mantle models to native Responses and safeguards to Chat", () =>
     Effect.gen(function* () {
       const credential = Credential.Key.make({ type: "key", key: "secret" })

+ 58 - 8
packages/core/test/plugin/models-dev.test.ts

@@ -11,6 +11,7 @@ import { Location } from "@opencode-ai/core/location"
 import { Model } from "@opencode-ai/core/model"
 import { ModelsDev } from "@opencode-ai/core/models-dev"
 import { ModelsDevPlugin } from "@opencode-ai/core/plugin/models-dev"
+import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
 import { Provider } from "@opencode-ai/core/provider"
 import { AbsolutePath } from "@opencode-ai/core/schema"
 import { location } from "../fixture/location"
@@ -220,6 +221,61 @@ describe("ModelsDevPlugin", () => {
     }).pipe(Effect.provide(models(path.join(import.meta.dir, "fixtures", "models-dev.json")))),
   )
 
+  it.effect("omits legacy provider aliases", () =>
+    Effect.gen(function* () {
+      const integrations = yield* Integration.Service
+      const catalog = yield* Catalog.Service
+      const snapshots = [
+        ["azure", "Azure", "AZURE_API_KEY", "@ai-sdk/azure"],
+        ["azure-cognitive-services", "Azure Cognitive Services", "AZURE_COGNITIVE_SERVICES_API_KEY", "@ai-sdk/azure"],
+        ["google-vertex", "Google Vertex", "GOOGLE_APPLICATION_CREDENTIALS", "@ai-sdk/google-vertex"],
+        [
+          "google-vertex-anthropic",
+          "Google Vertex Anthropic",
+          "GOOGLE_APPLICATION_CREDENTIALS",
+          "@ai-sdk/google-vertex/anthropic",
+        ],
+      ].map(([id, name, environment, packageName]) => ({
+        info: {
+          id: Provider.ID.make(id),
+          name,
+          package: Provider.aisdk(packageName),
+        },
+        environment: id === "azure" ? ["AZURE_RESOURCE_NAME", environment] : [environment],
+        models: [],
+      })) satisfies readonly ModelsDev.Snapshot[]
+
+      yield* ModelsDevPlugin.effect(
+        host({
+          catalog: catalogHost(catalog),
+          integration: integrationHost(integrations),
+        }),
+      ).pipe(
+        Effect.provideService(
+          ModelsDev.Service,
+          ModelsDev.Service.of({
+            get: () => Effect.succeed(snapshots),
+            refresh: () => Effect.void,
+          }),
+        ),
+      )
+
+      expect(yield* catalog.provider.get(Provider.ID.azure)).toBeDefined()
+      expect(yield* catalog.provider.get(Provider.ID.make("google-vertex"))).toBeDefined()
+      expect(yield* catalog.provider.get(Provider.ID.make("azure-cognitive-services"))).toBeUndefined()
+      expect(yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic"))).toBeUndefined()
+      expect(yield* integrations.get(Integration.ID.make("azure"))).toBeDefined()
+      expect(yield* integrations.get(Integration.ID.make("azure"))).toMatchObject({
+        methods: [{ type: "key" }, { type: "env", names: ["AZURE_API_KEY", "AZURE_COGNITIVE_SERVICES_API_KEY"] }],
+      })
+      expect(yield* integrations.get(Integration.ID.make("google-vertex"))).toBeDefined()
+      expect(yield* integrations.get(Integration.ID.make("azure-cognitive-services"))).toBeUndefined()
+      expect(yield* integrations.get(Integration.ID.make("google-vertex-anthropic"))).toBeUndefined()
+      expect(ProviderPlugins.map((plugin) => plugin.id)).not.toContain("opencode.provider.azure-cognitive-services")
+      expect(ProviderPlugins.map((plugin) => plugin.id)).not.toContain("opencode.provider.google-vertex-anthropic")
+    }),
+  )
+
   it.effect("converts reasoning options into settings variants", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
@@ -389,10 +445,7 @@ describe("ModelsDevPlugin", () => {
         },
       ])
 
-      const openrouter = yield* catalog.model.get(
-        Provider.ID.make("openrouter"),
-        Model.ID.make("openrouter-toggle"),
-      )
+      const openrouter = yield* catalog.model.get(Provider.ID.make("openrouter"), Model.ID.make("openrouter-toggle"))
       expect(openrouter?.variants).toEqual([
         { id: Model.VariantID.make("none"), settings: { reasoning: { enabled: false } } },
         { id: Model.VariantID.make("thinking"), settings: { reasoning: { enabled: true } } },
@@ -414,10 +467,7 @@ describe("ModelsDevPlugin", () => {
         },
       ])
 
-      const vertex = yield* catalog.model.get(
-        Provider.ID.make("google-vertex"),
-        Model.ID.make("gemini-2.5-flash-lite"),
-      )
+      const vertex = yield* catalog.model.get(Provider.ID.make("google-vertex"), Model.ID.make("gemini-2.5-flash-lite"))
       expect(vertex?.variants).toEqual([
         {
           id: Model.VariantID.make("none"),

+ 0 - 205
packages/core/test/plugin/provider-azure-cognitive-services.test.ts

@@ -1,205 +0,0 @@
-import { AISDK } from "@opencode-ai/core/aisdk"
-import { describe, expect } from "bun:test"
-import type { LanguageModelV3 } from "@ai-sdk/provider"
-import { Effect } from "effect"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { Model } from "@opencode-ai/core/model"
-import { Plugin } from "@opencode-ai/core/plugin"
-import { PluginHost } from "@opencode-ai/core/plugin/host"
-import { AzureCognitiveServicesPlugin } from "@opencode-ai/core/plugin/provider/azure"
-import { Provider } from "@opencode-ai/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 aisdk = yield* AISDK.Service
-  const host = yield* PluginHost.make(plugin)
-  yield* AzureCognitiveServicesPlugin.effect(host)
-})
-
-function required<T>(value: T | undefined): T {
-  if (value === undefined) throw new Error("Expected value")
-  return value
-}
-
-function withEnv<A, E, R>(vars: Record<string, string | undefined>, fx: () => Effect.Effect<A, E, R>) {
-  return Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
-      Object.entries(vars).forEach(([key, value]) => {
-        if (value === undefined) delete process.env[key]
-        else process.env[key] = value
-      })
-      return previous
-    }),
-    fx,
-    (previous) =>
-      Effect.sync(() => {
-        Object.entries(previous).forEach(([key, value]) => {
-          if (value === undefined) delete process.env[key]
-          else process.env[key] = value
-        })
-      }),
-  )
-}
-
-function fakeSelectorSdk(calls: string[]) {
-  const make = (method: string) => (id: string) => {
-    calls.push(`${method}:${id}`)
-    return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3
-  }
-  return {
-    responses: make("responses"),
-    messages: make("messages"),
-    chat: make("chat"),
-    languageModel: make("languageModel"),
-  }
-}
-
-describe("AzureCognitiveServicesPlugin", () => {
-  it.effect("maps the resource env var to the Azure SDK baseURL", () =>
-    withEnv({ AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: "cognitive" }, () =>
-      Effect.gen(function* () {
-        const catalog = yield* Catalog.Service
-        yield* catalog.transform((catalog) => {
-          catalog.provider.update(Provider.ID.make("azure-cognitive-services"), (item) => {
-            item.package = Provider.aisdk("@ai-sdk/openai-compatible")
-          })
-        })
-        yield* addPlugin()
-        const result = required(yield* catalog.provider.get(Provider.ID.make("azure-cognitive-services")))
-        expect(result).toMatchObject({
-          package: "aisdk:@ai-sdk/openai-compatible",
-          settings: { baseURL: "https://cognitive.cognitiveservices.azure.com/openai" },
-        })
-        expect(result.settings?.resourceName).toBeUndefined()
-      }),
-    ),
-  )
-
-  it.effect("leaves baseURL unset without resource env and ignores other providers", () =>
-    withEnv({ AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: undefined }, () =>
-      Effect.gen(function* () {
-        const catalog = yield* Catalog.Service
-        yield* catalog.transform((catalog) => {
-          const azure = Provider.Info.make({
-            ...Provider.Info.empty(Provider.ID.make("azure-cognitive-services")),
-            package: "aisdk:@ai-sdk/openai-compatible",
-          })
-          const openai = Provider.Info.make({
-            ...Provider.Info.empty(Provider.ID.openai),
-            package: "aisdk:test-provider",
-          })
-          catalog.provider.update(azure.id, (item) => {
-            item.package = azure.package
-            item.package = azure.package
-          })
-          catalog.provider.update(openai.id, (item) => {
-            item.package = openai.package
-            item.package = openai.package
-          })
-        })
-        yield* addPlugin()
-        const azure = required(yield* catalog.provider.get(Provider.ID.make("azure-cognitive-services")))
-        const openai = required(yield* catalog.provider.get(Provider.ID.openai))
-        expect(azure.settings?.baseURL).toBeUndefined()
-        expect(azure).toMatchObject({ package: "aisdk:@ai-sdk/openai-compatible" })
-        expect(openai.settings?.baseURL).toBeUndefined()
-        expect(openai).toMatchObject({ package: "aisdk:test-provider" })
-      }),
-    ),
-  )
-
-  it.effect("selects chat only for completion URLs", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      yield* addPlugin()
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("deployment")),
-          modelID: Model.ID.make("deployment"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: fakeSelectorSdk(calls),
-        options: { useCompletionUrls: true },
-      })
-      expect(calls).toEqual(["chat:deployment"])
-    }),
-  )
-
-  it.effect("uses the legacy Azure selector order and provider guard", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      yield* addPlugin()
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("deployment")),
-          modelID: Model.ID.make("deployment"),
-          package: "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: "aisdk:test-provider",
-        }),
-        sdk: fakeSelectorSdk(calls),
-        options: {},
-      })
-      expect(calls).toEqual(["responses:deployment"])
-      expect(ignored.language).toBeUndefined()
-    }),
-  )
-
-  it.effect("falls back from responses to messages, chat, then languageModel", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      const sdk = fakeSelectorSdk(calls)
-      yield* addPlugin()
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("messages-deployment")),
-          modelID: Model.ID.make("messages-deployment"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: { messages: sdk.messages, chat: sdk.chat, languageModel: sdk.languageModel },
-        options: {},
-      })
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("chat-deployment")),
-          modelID: Model.ID.make("chat-deployment"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: { chat: sdk.chat, languageModel: sdk.languageModel },
-        options: {},
-      })
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("language-deployment")),
-          modelID: Model.ID.make("language-deployment"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: { languageModel: sdk.languageModel },
-        options: {},
-      })
-      expect(calls).toEqual([
-        "messages:messages-deployment",
-        "chat:chat-deployment",
-        "languageModel:language-deployment",
-      ])
-    }),
-  )
-})

+ 50 - 0
packages/core/test/plugin/provider-azure.test.ts

@@ -75,6 +75,56 @@ describe("AzurePlugin", () => {
     ),
   )
 
+  it.effect("resolves resourceName from the legacy env", () =>
+    withEnv({ AZURE_RESOURCE_NAME: undefined, AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: "legacy-resource" }, () =>
+      Effect.gen(function* () {
+        const catalog = yield* Catalog.Service
+        yield* catalog.transform((catalog) => {
+          catalog.provider.update(Provider.ID.azure, (item) => {
+            item.package = Provider.aisdk("@ai-sdk/azure")
+          })
+        })
+        yield* addPlugin()
+        expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings?.resourceName).toBe("legacy-resource")
+      }),
+    ),
+  )
+
+  it.effect("expands provider and model resource URLs", () =>
+    withEnv({ AZURE_RESOURCE_NAME: "from-env", AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: "legacy-env" }, () =>
+      Effect.gen(function* () {
+        const catalog = yield* Catalog.Service
+        yield* catalog.transform((catalog) => {
+          catalog.provider.update(Provider.ID.azure, (provider) => {
+            provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
+            provider.settings = {
+              baseURL: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.cognitiveservices.azure.com/openai",
+            }
+          })
+          catalog.model.update(Provider.ID.azure, Model.ID.make("anthropic"), (model) => {
+            model.package = Provider.aisdk("@ai-sdk/anthropic")
+            model.settings = {
+              resourceName: "model-resource",
+              baseURL: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1",
+            }
+          })
+        })
+        yield* addPlugin()
+
+        expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings).toMatchObject({
+          resourceName: "from-env",
+          baseURL: "https://from-env.cognitiveservices.azure.com/openai",
+        })
+        expect(
+          required(yield* catalog.model.get(Provider.ID.azure, Model.ID.make("anthropic"))).settings,
+        ).toMatchObject({
+          resourceName: "model-resource",
+          baseURL: "https://model-resource.services.ai.azure.com/anthropic/v1",
+        })
+      }),
+    ),
+  )
+
   it.effect("keeps explicit resourceName over env and ignores other providers", () =>
     withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () =>
       Effect.gen(function* () {

+ 0 - 271
packages/core/test/plugin/provider-google-vertex-anthropic.test.ts

@@ -1,271 +0,0 @@
-import { AISDK } from "@opencode-ai/core/aisdk"
-import type { LanguageModelV3 } from "@ai-sdk/provider"
-import { describe, expect } from "bun:test"
-import { Effect } from "effect"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { Model } from "@opencode-ai/core/model"
-import { Plugin } from "@opencode-ai/core/plugin"
-import { PluginHost } from "@opencode-ai/core/plugin/host"
-import { GoogleVertexAnthropicPlugin, GoogleVertexPlugin } from "@opencode-ai/core/plugin/provider/google-vertex"
-import { Provider } from "@opencode-ai/core/provider"
-import { testEffect } from "../lib/effect"
-import { PluginTestLayer } from "./fixture"
-
-const it = testEffect(PluginTestLayer)
-
-const addPlugin = Effect.fn(function* (definition: typeof GoogleVertexAnthropicPlugin | typeof GoogleVertexPlugin) {
-  const plugin = yield* Plugin.Service
-  const aisdk = yield* AISDK.Service
-  const host = yield* PluginHost.make(plugin)
-  yield* definition.effect(host)
-})
-
-function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) {
-  return Effect.acquireUseRelease(
-    Effect.sync(() => {
-      const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]]))
-      Object.entries(vars).forEach(([key, value]) => {
-        if (value === undefined) delete process.env[key]
-        else process.env[key] = value
-      })
-      return previous
-    }),
-    effect,
-    (previous) =>
-      Effect.sync(() => {
-        Object.entries(previous).forEach(([key, value]) => {
-          if (value === undefined) delete process.env[key]
-          else process.env[key] = value
-        })
-      }),
-  )
-}
-
-function selector(calls: string[]) {
-  return (id: string) => {
-    calls.push(`languageModel:${id}`)
-    return { modelId: id, provider: "languageModel", specificationVersion: "v3" } as unknown as LanguageModelV3
-  }
-}
-
-describe("GoogleVertexAnthropicPlugin", () => {
-  it.effect("resolves legacy project and location env on provider update", () =>
-    withEnv(
-      {
-        GOOGLE_CLOUD_PROJECT: "cloud-project",
-        GCP_PROJECT: "gcp-project",
-        GCLOUD_PROJECT: "gcloud-project",
-        GOOGLE_CLOUD_LOCATION: "cloud-location",
-        VERTEX_LOCATION: "vertex-location",
-        GOOGLE_VERTEX_LOCATION: "google-vertex-location",
-      },
-      () =>
-        Effect.gen(function* () {
-          const catalog = yield* Catalog.Service
-          yield* catalog.transform((catalog) =>
-            catalog.provider.update(Provider.ID.make("google-vertex-anthropic"), (provider) => {
-              provider.package = Provider.aisdk("@ai-sdk/google-vertex/anthropic")
-            }),
-          )
-          yield* addPlugin(GoogleVertexAnthropicPlugin)
-          expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
-            "cloud-project",
-          )
-          expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
-            "cloud-location",
-          )
-        }),
-    ),
-  )
-
-  it.effect("keeps configured project and location over env fallback", () =>
-    withEnv({ GOOGLE_CLOUD_PROJECT: "env-project", GOOGLE_CLOUD_LOCATION: "env-location" }, () =>
-      Effect.gen(function* () {
-        const catalog = yield* Catalog.Service
-        yield* catalog.transform((catalog) =>
-          catalog.provider.update(Provider.ID.make("google-vertex-anthropic"), (provider) => {
-            provider.package = Provider.aisdk("@ai-sdk/google-vertex/anthropic")
-            provider.settings = { ...provider.settings, project: "configured-project", location: "configured-location" }
-          }),
-        )
-        yield* addPlugin(GoogleVertexAnthropicPlugin)
-        expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
-          "configured-project",
-        )
-        expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
-          "configured-location",
-        )
-      }),
-    ),
-  )
-
-  it.effect("creates SDKs from legacy env fallback and default location", () =>
-    withEnv(
-      {
-        GOOGLE_CLOUD_PROJECT: undefined,
-        GCP_PROJECT: "gcp-project",
-        GCLOUD_PROJECT: "gcloud-project",
-        GOOGLE_CLOUD_LOCATION: undefined,
-        VERTEX_LOCATION: undefined,
-        GOOGLE_VERTEX_LOCATION: "ignored-location",
-      },
-      () =>
-        Effect.gen(function* () {
-          const plugin = yield* Plugin.Service
-          const aisdk = yield* AISDK.Service
-          yield* addPlugin(GoogleVertexAnthropicPlugin)
-          const result = yield* aisdk.runSDK({
-            model: Model.Info.make({
-              ...Model.Info.default(
-                Provider.ID.make("google-vertex-anthropic"),
-                Model.ID.make("claude-sonnet-4-5"),
-              ),
-              modelID: Model.ID.make("claude-sonnet-4-5"),
-              package: "aisdk:test-provider",
-            }),
-            package: "@ai-sdk/google-vertex/anthropic",
-            options: { name: "google-vertex-anthropic" },
-          })
-          expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe(
-            "https://aiplatform.googleapis.com/v1/projects/gcp-project/locations/global/publishers/anthropic/models",
-          )
-        }),
-    ),
-  )
-
-  it.effect("uses GOOGLE_CLOUD_LOCATION before VERTEX_LOCATION when creating SDKs", () =>
-    withEnv(
-      { GOOGLE_CLOUD_PROJECT: "project", GOOGLE_CLOUD_LOCATION: "cloud-location", VERTEX_LOCATION: "vertex-location" },
-      () =>
-        Effect.gen(function* () {
-          const plugin = yield* Plugin.Service
-          const aisdk = yield* AISDK.Service
-          yield* addPlugin(GoogleVertexAnthropicPlugin)
-          const result = yield* aisdk.runSDK({
-            model: Model.Info.make({
-              ...Model.Info.default(
-                Provider.ID.make("google-vertex-anthropic"),
-                Model.ID.make("claude-sonnet-4-5"),
-              ),
-              modelID: Model.ID.make("claude-sonnet-4-5"),
-              package: "aisdk:test-provider",
-            }),
-            package: "@ai-sdk/google-vertex/anthropic",
-            options: { name: "google-vertex-anthropic" },
-          })
-          expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe(
-            "https://cloud-location-aiplatform.googleapis.com/v1/projects/project/locations/cloud-location/publishers/anthropic/models",
-          )
-        }),
-    ),
-  )
-
-  it.effect("creates SDKs for google-vertex Anthropic models with multi-region endpoints", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      yield* addPlugin(GoogleVertexAnthropicPlugin)
-      const result = yield* aisdk.runSDK({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
-          modelID: Model.ID.make("claude-sonnet-4-5"),
-          package: "aisdk:test-provider",
-        }),
-        package: "@ai-sdk/google-vertex/anthropic",
-        options: { name: "google-vertex", project: "project", location: "eu" },
-      })
-      expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe(
-        "https://aiplatform.eu.rep.googleapis.com/v1/projects/project/locations/eu/publishers/anthropic/models",
-      )
-    }),
-  )
-
-  it.effect("keeps configured baseURL for google-vertex Anthropic models", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      yield* addPlugin(GoogleVertexAnthropicPlugin)
-      const result = yield* aisdk.runSDK({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
-          modelID: Model.ID.make("claude-sonnet-4-5"),
-          package: "aisdk:test-provider",
-        }),
-        package: "@ai-sdk/google-vertex/anthropic",
-        options: { name: "google-vertex", project: "project", location: "eu", baseURL: "https://proxy.example/v1" },
-      })
-      expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe("https://proxy.example/v1")
-    }),
-  )
-
-  it.effect("selects google-vertex Anthropic language models through plugins", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      yield* addPlugin(GoogleVertexPlugin)
-      yield* addPlugin(GoogleVertexAnthropicPlugin)
-      const sdkResult = yield* aisdk.runSDK({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make(" claude-sonnet-4-5 ")),
-          modelID: Model.ID.make(" claude-sonnet-4-5 "),
-          package: "aisdk:test-provider",
-        }),
-        package: "@ai-sdk/google-vertex/anthropic",
-        options: { name: "google-vertex", project: "project", location: "us" },
-      })
-      const languageResult = yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make(" claude-sonnet-4-5 ")),
-          modelID: Model.ID.make(" claude-sonnet-4-5 "),
-          package: "aisdk:test-provider",
-        }),
-        sdk: sdkResult.sdk,
-        options: {},
-      })
-      const language = languageResult.language as unknown as { config: { baseURL: string }; modelId: string }
-      expect(language.config.baseURL).toBe(
-        "https://aiplatform.us.rep.googleapis.com/v1/projects/project/locations/us/publishers/anthropic/models",
-      )
-      expect(language.modelId).toBe("claude-sonnet-4-5")
-    }),
-  )
-
-  it.effect("trims model IDs before selecting language models", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      yield* addPlugin(GoogleVertexAnthropicPlugin)
-      yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex-anthropic"), Model.ID.make(" claude-sonnet-4-5 ")),
-          modelID: Model.ID.make(" claude-sonnet-4-5 "),
-          package: "aisdk:test-provider",
-        }),
-        sdk: { languageModel: selector(calls) },
-        options: {},
-      })
-      expect(calls).toEqual(["languageModel:claude-sonnet-4-5"])
-    }),
-  )
-
-  it.effect("ignores non Vertex Anthropic providers for language selection", () =>
-    Effect.gen(function* () {
-      const plugin = yield* Plugin.Service
-      const aisdk = yield* AISDK.Service
-      const calls: string[] = []
-      yield* addPlugin(GoogleVertexAnthropicPlugin)
-      const result = yield* aisdk.runLanguage({
-        model: Model.Info.make({
-          ...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
-          modelID: Model.ID.make("claude-sonnet-4-5"),
-          package: "aisdk:test-provider",
-        }),
-        sdk: { languageModel: selector(calls) },
-        options: {},
-      })
-      expect(calls).toEqual([])
-      expect(result.language).toBeUndefined()
-    }),
-  )
-})

+ 21 - 0
packages/core/test/plugin/provider-google-vertex.test.ts

@@ -309,6 +309,27 @@ describe("GoogleVertexPlugin", () => {
     ),
   )
 
+  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("keeps Google auth fetch for OpenAI-compatible Vertex endpoints", () =>
     Effect.gen(function* () {
       googleAuthOptions.length = 0