import Anthropic from '@anthropic-ai/sdk' import { createLogger } from '@sim/logger' import { env } from '@/lib/core/config/env' import { createPinnedFetch, validateUrlWithDNS } from '@/lib/core/security/input-validation.server' import type { StreamingExecution } from '@/executor/types' import { executeAnthropicProviderRequest } from '@/providers/anthropic/core' import { getCachedProviderClient } from '@/providers/client-cache' import { getProviderDefaultModel, getProviderModels } from '@/providers/models' import type { ProviderConfig, ProviderRequest, ProviderResponse } from '@/providers/types' const logger = createLogger('AzureAnthropicProvider') export const azureAnthropicProvider: ProviderConfig = { id: 'azure-anthropic', name: 'Azure Anthropic', description: 'Anthropic Claude models via Azure AI Foundry', version: '1.0.0', models: getProviderModels('azure-anthropic'), defaultModel: getProviderDefaultModel('azure-anthropic'), executeRequest: async ( request: ProviderRequest ): Promise => { const userProvidedEndpoint = request.azureEndpoint const azureEndpoint = userProvidedEndpoint || env.AZURE_ANTHROPIC_ENDPOINT if (!azureEndpoint) { throw new Error( 'Azure endpoint is required for Azure Anthropic. Please provide it via the azureEndpoint parameter or AZURE_ANTHROPIC_ENDPOINT environment variable.' ) } let pinnedFetch: typeof fetch | undefined let pinnedIP: string | undefined if (userProvidedEndpoint) { const validation = await validateUrlWithDNS(userProvidedEndpoint, 'azureEndpoint') if (!validation.isValid) { logger.warn('Blocked SSRF attempt via azureEndpoint', { endpoint: userProvidedEndpoint, error: validation.error, }) throw new Error(`Invalid Azure Anthropic endpoint: ${validation.error}`) } if (!validation.resolvedIP) { throw new Error('Invalid Azure Anthropic endpoint: could not resolve a pinnable IP address') } pinnedIP = validation.resolvedIP pinnedFetch = createPinnedFetch(pinnedIP) } const apiKey = request.apiKey if (!apiKey) { throw new Error('API key is required for Azure Anthropic.') } // Strip the azure-anthropic/ prefix from the model name if present const modelName = request.model.replace(/^azure-anthropic\//, '') // Azure AI Foundry hosts Anthropic models at {endpoint}/anthropic // The SDK appends /v1/messages automatically const baseURL = `${azureEndpoint.replace(/\/$/, '')}/anthropic` const anthropicVersion = request.azureApiVersion || env.AZURE_ANTHROPIC_API_VERSION || '2023-06-01' return executeAnthropicProviderRequest( { ...request, model: modelName, apiKey, }, { providerId: 'azure-anthropic', providerLabel: 'Azure Anthropic', createClient: (apiKey, useNativeStructuredOutputs) => { const cacheKey = [ 'azure-anthropic', apiKey, baseURL, anthropicVersion, pinnedIP ?? 'no-pin', useNativeStructuredOutputs ? 'beta' : 'default', ].join('::') return getCachedProviderClient( cacheKey, () => new Anthropic({ baseURL, apiKey, ...(pinnedFetch ? { fetch: pinnedFetch } : {}), defaultHeaders: { 'api-key': apiKey, 'anthropic-version': anthropicVersion, ...(useNativeStructuredOutputs ? { 'anthropic-beta': 'structured-outputs-2025-11-13' } : {}), }, }) ) }, logger, } ) }, }