Răsfoiți Sursa

fix(app): support anthropic models on azure cognitive services (#8335)

Unies Ananda Raja 1 lună în urmă
părinte
comite
b8e2895dfc

+ 21 - 4
packages/opencode/src/provider/provider.ts

@@ -586,6 +586,13 @@ export namespace Provider {
     })
     })
   export type Info = z.infer<typeof Info>
   export type Info = z.infer<typeof Info>
 
 
+  export function isAzureAnthropic(model: Model): boolean {
+    return (
+      model.providerID === "azure-cognitive-services" &&
+      (model.api.id.includes("claude") || model.api.id.includes("anthropic"))
+    )
+  }
+
   function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
   function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
     const m: Model = {
     const m: Model = {
       id: model.id,
       id: model.id,
@@ -1006,9 +1013,16 @@ export namespace Provider {
         })
         })
       }
       }
 
 
-      // Special case: google-vertex-anthropic uses a subpath import
-      const bundledKey =
-        model.providerID === "google-vertex-anthropic" ? "@ai-sdk/google-vertex/anthropic" : model.api.npm
+      // Special cases for providers that use different npm packages
+      if (isAzureAnthropic(model)) {
+        const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
+        if (resourceName) options["baseURL"] = `https://${resourceName}.services.ai.azure.com/anthropic/v1/`
+      }
+      const bundledKey = iife(() => {
+        if (model.providerID === "google-vertex-anthropic") return "@ai-sdk/google-vertex/anthropic"
+        if (isAzureAnthropic(model)) return "@ai-sdk/anthropic"
+        return model.api.npm
+      })
       const bundledFn = BUNDLED_PROVIDERS[bundledKey]
       const bundledFn = BUNDLED_PROVIDERS[bundledKey]
       if (bundledFn) {
       if (bundledFn) {
         log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
         log.info("using bundled provider", { providerID: model.providerID, pkg: bundledKey })
@@ -1074,8 +1088,11 @@ export namespace Provider {
     const provider = s.providers[model.providerID]
     const provider = s.providers[model.providerID]
     const sdk = await getSDK(model)
     const sdk = await getSDK(model)
 
 
+    // Skip custom model loader for Azure Anthropic models since they use @ai-sdk/anthropic
+    const useCustomLoader = s.modelLoaders[model.providerID] && !isAzureAnthropic(model)
+
     try {
     try {
-      const language = s.modelLoaders[model.providerID]
+      const language = useCustomLoader
         ? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
         ? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
         : sdk.languageModel(model.api.id)
         : sdk.languageModel(model.api.id)
       s.models.set(key, language)
       s.models.set(key, language)

+ 45 - 3
packages/opencode/src/provider/transform.ts

@@ -16,6 +16,17 @@ function mimeToModality(mime: string): Modality | undefined {
 }
 }
 
 
 export namespace ProviderTransform {
 export namespace ProviderTransform {
+  function isAzureAnthropic(model: Provider.Model): boolean {
+    return (
+      model.providerID === "azure-cognitive-services" &&
+      (model.api.id.includes("claude") || model.api.id.includes("anthropic"))
+    )
+  }
+
+  function usesAnthropicSDK(model: Provider.Model): boolean {
+    return model.api.npm === "@ai-sdk/anthropic" || isAzureAnthropic(model)
+  }
+
   function normalizeMessages(
   function normalizeMessages(
     msgs: ModelMessage[],
     msgs: ModelMessage[],
     model: Provider.Model,
     model: Provider.Model,
@@ -50,7 +61,7 @@ export namespace ProviderTransform {
 
 
     // Anthropic rejects messages with empty content - filter out empty string messages
     // Anthropic rejects messages with empty content - filter out empty string messages
     // and remove empty text/reasoning parts from array content
     // and remove empty text/reasoning parts from array content
-    if (model.api.npm === "@ai-sdk/anthropic") {
+    if (usesAnthropicSDK(model)) {
       msgs = msgs
       msgs = msgs
         .map((msg) => {
         .map((msg) => {
           if (typeof msg.content === "string") {
           if (typeof msg.content === "string") {
@@ -256,7 +267,7 @@ export namespace ProviderTransform {
       model.providerID === "anthropic" ||
       model.providerID === "anthropic" ||
       model.api.id.includes("anthropic") ||
       model.api.id.includes("anthropic") ||
       model.api.id.includes("claude") ||
       model.api.id.includes("claude") ||
-      model.api.npm === "@ai-sdk/anthropic"
+      usesAnthropicSDK(model)
     ) {
     ) {
       msgs = applyCaching(msgs, model.providerID)
       msgs = applyCaching(msgs, model.providerID)
     }
     }
@@ -308,6 +319,23 @@ export namespace ProviderTransform {
     const id = model.id.toLowerCase()
     const id = model.id.toLowerCase()
     if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
     if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
 
 
+    if (isAzureAnthropic(model)) {
+      return {
+        high: {
+          thinking: {
+            type: "enabled",
+            budgetTokens: 16000,
+          },
+        },
+        max: {
+          thinking: {
+            type: "enabled",
+            budgetTokens: 31999,
+          },
+        },
+      }
+    }
+
     switch (model.api.npm) {
     switch (model.api.npm) {
       case "@openrouter/ai-sdk-provider":
       case "@openrouter/ai-sdk-provider":
         if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("grok-4")) return {}
         if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("grok-4")) return {}
@@ -578,6 +606,9 @@ export namespace ProviderTransform {
   }
   }
 
 
   export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
   export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
+    if (isAzureAnthropic(model)) {
+      return { ["anthropic" as string]: options }
+    }
     switch (model.api.npm) {
     switch (model.api.npm) {
       case "@ai-sdk/github-copilot":
       case "@ai-sdk/github-copilot":
       case "@ai-sdk/openai":
       case "@ai-sdk/openai":
@@ -613,16 +644,27 @@ export namespace ProviderTransform {
     }
     }
   }
   }
 
 
+  export function maxOutputTokens(model: Provider.Model, options: Record<string, any>, globalLimit: number): number
   export function maxOutputTokens(
   export function maxOutputTokens(
     npm: string,
     npm: string,
     options: Record<string, any>,
     options: Record<string, any>,
     modelLimit: number,
     modelLimit: number,
     globalLimit: number,
     globalLimit: number,
+  ): number
+  export function maxOutputTokens(
+    arg1: Provider.Model | string,
+    options: Record<string, any>,
+    arg3: number,
+    arg4?: number,
   ): number {
   ): number {
+    const model = typeof arg1 === "object" ? arg1 : null
+    const npm = model ? model.api.npm : (arg1 as string)
+    const modelLimit = model ? model.limit.output : arg3
+    const globalLimit = model ? arg3 : arg4!
     const modelCap = modelLimit || globalLimit
     const modelCap = modelLimit || globalLimit
     const standardLimit = Math.min(modelCap, globalLimit)
     const standardLimit = Math.min(modelCap, globalLimit)
 
 
-    if (npm === "@ai-sdk/anthropic") {
+    if (model ? usesAnthropicSDK(model) : npm === "@ai-sdk/anthropic") {
       const thinking = options?.["thinking"]
       const thinking = options?.["thinking"]
       const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
       const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
       const enabled = thinking?.["type"] === "enabled"
       const enabled = thinking?.["type"] === "enabled"

+ 1 - 6
packages/opencode/src/session/llm.ts

@@ -133,12 +133,7 @@ export namespace LLM {
 
 
     const maxOutputTokens = isCodex
     const maxOutputTokens = isCodex
       ? undefined
       ? undefined
-      : ProviderTransform.maxOutputTokens(
-          input.model.api.npm,
-          params.options,
-          input.model.limit.output,
-          OUTPUT_TOKEN_MAX,
-        )
+      : ProviderTransform.maxOutputTokens(input.model, params.options, OUTPUT_TOKEN_MAX)
 
 
     const tools = await resolveTools(input)
     const tools = await resolveTools(input)