fix(coding-agent): resolve models.json auth per request closes #1835

This commit is contained in:
Mario Zechner
2026-03-27 00:19:00 +01:00
parent fb10d9aef9
commit 7a786d88aa
18 changed files with 396 additions and 228 deletions

View File

@@ -317,6 +317,32 @@ export class AgentSession {
return this._modelRegistry;
}
private async _getRequiredRequestAuth(model: Model<any>): Promise<{
apiKey: string;
headers?: Record<string, string>;
}> {
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
if (!result.ok) {
throw new Error(result.error);
}
if (result.apiKey) {
return { apiKey: result.apiKey, headers: result.headers };
}
const isOAuth = this._modelRegistry.isUsingOAuth(model);
if (isOAuth) {
throw new Error(
`Authentication failed for "${model.provider}". ` +
`Credentials may have expired or network is unavailable. ` +
`Run '/login ${model.provider}' to re-authenticate.`,
);
}
throw new Error(
`No API key found for ${model.provider}.\n\n` +
`Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`,
);
}
/**
* Install tool hooks once on the Agent instance.
*
@@ -946,9 +972,7 @@ export class AgentSession {
);
}
// Validate API key
const apiKey = await this._modelRegistry.getApiKey(this.model);
if (!apiKey) {
if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
const isOAuth = this._modelRegistry.isUsingOAuth(this.model);
if (isOAuth) {
throw new Error(
@@ -1384,12 +1408,11 @@ export class AgentSession {
/**
* Set model directly.
* Validates API key, saves to session and settings.
* @throws Error if no API key available for the model
* Validates that auth is configured, saves to session and settings.
* @throws Error if no auth is configured for the model
*/
async setModel(model: Model<any>): Promise<void> {
const apiKey = await this._modelRegistry.getApiKey(model);
if (!apiKey) {
if (!this._modelRegistry.hasConfiguredAuth(model)) {
throw new Error(`No API key for ${model.provider}/${model.id}`);
}
@@ -1418,30 +1441,12 @@ export class AgentSession {
return this._cycleAvailableModel(direction);
}
private async _getScopedModelsWithApiKey(): Promise<Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>> {
const apiKeysByProvider = new Map<string, string | undefined>();
const result: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }> = [];
for (const scoped of this._scopedModels) {
const provider = scoped.model.provider;
let apiKey: string | undefined;
if (apiKeysByProvider.has(provider)) {
apiKey = apiKeysByProvider.get(provider);
} else {
apiKey = await this._modelRegistry.getApiKeyForProvider(provider);
apiKeysByProvider.set(provider, apiKey);
}
if (apiKey) {
result.push(scoped);
}
}
return result;
private _getScopedModelsWithAuth(): Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }> {
return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
}
private async _cycleScopedModel(direction: "forward" | "backward"): Promise<ModelCycleResult | undefined> {
const scopedModels = await this._getScopedModelsWithApiKey();
const scopedModels = this._getScopedModelsWithAuth();
if (scopedModels.length <= 1) return undefined;
const currentModel = this.model;
@@ -1481,11 +1486,6 @@ export class AgentSession {
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
const nextModel = availableModels[nextIndex];
const apiKey = await this._modelRegistry.getApiKey(nextModel);
if (!apiKey) {
throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
}
const thinkingLevel = this._getThinkingLevelForModelSwitch();
this.agent.setModel(nextModel);
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
@@ -1633,10 +1633,7 @@ export class AgentSession {
throw new Error("No model selected");
}
const apiKey = await this._modelRegistry.getApiKey(this.model);
if (!apiKey) {
throw new Error(`No API key for ${this.model.provider}`);
}
const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
const pathEntries = this.sessionManager.getBranch();
const settings = this.settingsManager.getCompactionSettings();
@@ -1690,6 +1687,7 @@ export class AgentSession {
preparation,
this.model,
apiKey,
headers,
customInstructions,
this._compactionAbortController.signal,
);
@@ -1853,11 +1851,12 @@ export class AgentSession {
return;
}
const apiKey = await this._modelRegistry.getApiKey(this.model);
if (!apiKey) {
const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
if (!authResult.ok || !authResult.apiKey) {
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
return;
}
const { apiKey, headers } = authResult;
const pathEntries = this.sessionManager.getBranch();
@@ -1907,6 +1906,7 @@ export class AgentSession {
preparation,
this.model,
apiKey,
headers,
undefined,
this._autoCompactionAbortController.signal,
);
@@ -2155,8 +2155,7 @@ export class AgentSession {
refreshTools: () => this._refreshToolRegistry(),
getCommands,
setModel: async (model) => {
const key = await this.modelRegistry.getApiKey(model);
if (!key) return false;
if (!this.modelRegistry.hasConfiguredAuth(model)) return false;
await this.setModel(model);
return true;
},
@@ -2856,14 +2855,12 @@ export class AgentSession {
let summaryDetails: unknown;
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
const model = this.model!;
const apiKey = await this._modelRegistry.getApiKey(model);
if (!apiKey) {
throw new Error(`No API key for ${model.provider}`);
}
const { apiKey, headers } = await this._getRequiredRequestAuth(model);
const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
const result = await generateBranchSummary(entriesToSummarize, {
model,
apiKey,
headers,
signal: this._branchSummaryAbortController.signal,
customInstructions,
replaceInstructions,