From 41cc7c8bf17d159f456f7208a2f657510a6313b2 Mon Sep 17 00:00:00 2001 From: haxudev Date: Thu, 19 Mar 2026 18:36:33 +0800 Subject: [PATCH] Microsoft Foundry: tighten onboarding retry and model selection hooks Only retry Azure login with an explicit tenant when the CLI failure actually points to tenant or subscription scope, keep HTTP 400 connection checks informative without treating them as a silent success, and move the model-selection hook onto the provider so manual Foundry setups can preserve GPT-5 family hints and resolve the right runtime endpoint. --- extensions/microsoft-foundry/index.ts | 107 ++++++++++++++++---------- 1 file changed, 68 insertions(+), 39 deletions(-) diff --git a/extensions/microsoft-foundry/index.ts b/extensions/microsoft-foundry/index.ts index f271c8e71e0..27e98eea23e 100644 --- a/extensions/microsoft-foundry/index.ts +++ b/extensions/microsoft-foundry/index.ts @@ -529,7 +529,18 @@ async function promptEndpointAndModelManually(ctx: ProviderAuthContext): Promise }, }), ).trim(); - return { endpoint, modelId, modelNameHint: modelId }; + const modelNameHintInput = String( + await ctx.prompter.text({ + message: "Underlying Azure model family (optional)", + initialValue: modelId, + placeholder: "gpt-5.4, gpt-4o, etc.", + }), + ).trim(); + return { + endpoint, + modelId, + modelNameHint: modelNameHintInput || modelId, + }; } async function promptApiKeyEndpointAndModel(ctx: ProviderAuthContext): Promise { @@ -711,6 +722,13 @@ async function loginWithTenantFallback(ctx: ProviderAuthContext): Promise<{ return { account: getLoggedInAccount() }; } catch (error) { const message = error instanceof Error ? error.message : String(error); + const isAzureTenantError = + /AADSTS/i.test(message) || + /no subscriptions found/i.test(message) || + /tenant/i.test(message); + if (!isAzureTenantError) { + throw error; + } const tenantId = await promptTenantId(ctx, { suggestions: extractTenantSuggestions(message), required: true, @@ -872,7 +890,15 @@ const entraIdAuthMethod = { "Connection Test", ); } else { - await ctx.prompter.note("Connection test successful!", "✓"); + const statusNote = res.status === 400 ? " (400 Bad Request — endpoint reachable)" : ""; + const statusBody = res.status === 400 ? await res.text().catch(() => "") : ""; + await ctx.prompter.note(`Connection test successful!${statusNote}`, "✓"); + if (statusBody) { + await ctx.prompter.note( + `Endpoint response: ${statusBody.slice(0, 200)}`, + "Connection Test", + ); + } } } catch (err) { await ctx.prompter.note( @@ -904,41 +930,6 @@ const entraIdAuthMethod = { ], }); }, - onModelSelected: async (ctx: ProviderModelSelectedContext) => { - 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 selectedModelCompat = buildFoundryModelCompat(selectedModelId, selectedModelNameHint); - const providerEndpoint = normalizeFoundryEndpoint(providerConfig.baseUrl ?? ""); - const nextProviderConfig: ModelProviderConfig = { - ...providerConfig, - baseUrl: buildFoundryProviderBaseUrl(providerEndpoint, selectedModelId, selectedModelNameHint), - api: resolveFoundryApi(selectedModelId, selectedModelNameHint), - models: [ - { - ...(existingModel ?? { - id: selectedModelId, - name: selectedModelId, - reasoning: false, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 128_000, - maxTokens: 16_384, - }), - ...(selectedModelCompat ? { compat: selectedModelCompat } : {}), - }, - ], - }; - applyFoundryProfileBinding(ctx.config, `${PROVIDER_ID}:entra`); - applyFoundryProviderConfig(ctx.config, nextProviderConfig); - }, }; // --------------------------------------------------------------------------- @@ -1017,8 +1008,9 @@ function refreshEntraToken(params?: { tenantId?: string; }): { apiKey: string; expiresAt: number } { const result = getAccessTokenResult(params); - const expiresAt = result.expiresOn - ? new Date(result.expiresOn).getTime() + const rawExpiry = result.expiresOn ? new Date(result.expiresOn).getTime() : Number.NaN; + const expiresAt = Number.isFinite(rawExpiry) + ? rawExpiry : Date.now() + 55 * 60 * 1000; // default ~55 min cachedTokens.set(getFoundryTokenCacheKey(params), { token: result.accessToken, @@ -1045,6 +1037,43 @@ export default definePluginEntry({ capabilities: { providerFamily: "openai", }, + onModelSelected: async (ctx: ProviderModelSelectedContext) => { + 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 selectedModelCompat = buildFoundryModelCompat(selectedModelId, selectedModelNameHint); + const providerEndpoint = normalizeFoundryEndpoint(providerConfig.baseUrl ?? ""); + const nextProviderConfig: ModelProviderConfig = { + ...providerConfig, + baseUrl: buildFoundryProviderBaseUrl(providerEndpoint, selectedModelId, selectedModelNameHint), + api: resolveFoundryApi(selectedModelId, selectedModelNameHint), + models: [ + { + ...(existingModel ?? { + id: selectedModelId, + name: selectedModelId, + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 16_384, + }), + ...(selectedModelCompat ? { compat: selectedModelCompat } : {}), + }, + ], + }; + applyFoundryProfileBinding(ctx.config, `${PROVIDER_ID}:entra`); + applyFoundryProviderConfig(ctx.config, nextProviderConfig); + }, normalizeResolvedModel: ({ modelId, model }) => { const endpoint = extractFoundryEndpoint(model.baseUrl ?? ""); if (!endpoint) {