import type { ProviderNormalizeResolvedModelContext } from "openclaw/plugin-sdk/core"; import type { ModelProviderConfig, ProviderPlugin, } from "openclaw/plugin-sdk/provider-model-shared"; import { apiKeyAuthMethod, entraIdAuthMethod } from "./auth.js"; import { prepareFoundryRuntimeAuth } from "./runtime.js"; import { PROVIDER_ID, applyFoundryProfileBinding, applyFoundryProviderConfig, buildFoundryModelCompat, buildFoundryProviderBaseUrl, extractFoundryEndpoint, isFoundryProviderApi, normalizeFoundryEndpoint, resolveConfiguredModelNameHint, resolveFoundryApi, resolveFoundryTargetProfileId, } from "./shared.js"; export function buildMicrosoftFoundryProvider(): ProviderPlugin { return { id: PROVIDER_ID, label: "Microsoft Foundry", docsPath: "/providers/models", envVars: ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"], auth: [entraIdAuthMethod, apiKeyAuthMethod], capabilities: { providerFamily: "openai" as const, }, onModelSelected: async (ctx) => { const providerConfig = ctx.config.models?.providers?.[PROVIDER_ID]; if (!providerConfig || !ctx.model.startsWith(`${PROVIDER_ID}/`)) { return; } const selectedModelId = ctx.model.slice(`${PROVIDER_ID}/`.length); const existingModel = providerConfig.models.find( (model: { id: string }) => model.id === selectedModelId, ); const selectedModelNameHint = resolveConfiguredModelNameHint( selectedModelId, existingModel?.name, ); const providerEndpoint = normalizeFoundryEndpoint(providerConfig.baseUrl ?? ""); // Prefer the persisted per-model API choice from onboarding/discovery so arbitrary // deployment aliases (for example prod-primary) do not fall back to name heuristics. const selectedModelApi = isFoundryProviderApi(existingModel?.api) ? existingModel.api : providerConfig.api; const selectedModelCompat = buildFoundryModelCompat( selectedModelId, selectedModelNameHint, selectedModelApi, ); const nextModels = providerConfig.models.map((model) => model.id === selectedModelId ? { ...model, api: resolveFoundryApi(selectedModelId, selectedModelNameHint, selectedModelApi), ...(selectedModelCompat ? { compat: selectedModelCompat } : {}), } : model, ); if (!nextModels.some((model) => model.id === selectedModelId)) { nextModels.push({ id: selectedModelId, name: selectedModelNameHint ?? selectedModelId, api: resolveFoundryApi(selectedModelId, selectedModelNameHint, selectedModelApi), reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128_000, maxTokens: 16_384, ...(selectedModelCompat ? { compat: selectedModelCompat } : {}), }); } const nextProviderConfig: ModelProviderConfig = { ...providerConfig, baseUrl: buildFoundryProviderBaseUrl( providerEndpoint, selectedModelId, selectedModelNameHint, selectedModelApi, ), api: resolveFoundryApi(selectedModelId, selectedModelNameHint, selectedModelApi), models: nextModels, }; const targetProfileId = resolveFoundryTargetProfileId(ctx.config); if (targetProfileId) { applyFoundryProfileBinding(ctx.config, targetProfileId); } applyFoundryProviderConfig(ctx.config, nextProviderConfig); }, normalizeResolvedModel: ({ modelId, model }: ProviderNormalizeResolvedModelContext) => { const endpoint = extractFoundryEndpoint(String(model.baseUrl ?? "")); if (!endpoint) { return model; } const modelNameHint = resolveConfiguredModelNameHint(modelId, model.name); const configuredApi = isFoundryProviderApi(model.api) ? model.api : undefined; const compat = buildFoundryModelCompat(modelId, modelNameHint, configuredApi); return { ...model, api: resolveFoundryApi(modelId, modelNameHint, configuredApi), baseUrl: buildFoundryProviderBaseUrl(endpoint, modelId, modelNameHint, configuredApi), ...(compat ? { compat } : {}), }; }, prepareRuntimeAuth: prepareFoundryRuntimeAuth, }; }