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.
This commit is contained in:
haxudev 2026-03-19 18:36:33 +08:00
parent 0c1efec1c9
commit 41cc7c8bf1

View File

@ -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<FoundrySelection> {
@ -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) {