From f7c03ef6a207e35a7edb2b351f9872f27d7bb7cd Mon Sep 17 00:00:00 2001 From: Michael Renner Date: Mon, 2 Feb 2026 18:05:15 +0100 Subject: [PATCH] fix(coding-agent): handle scoped models after logout (#1194) --- .../coding-agent/src/core/agent-session.ts | 37 ++++++++++++++----- .../components/scoped-models-selector.ts | 13 ++++--- .../src/modes/interactive/interactive-mode.ts | 2 + 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index e0acf8c9..c2c6295a 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -1170,22 +1170,39 @@ export class AgentSession { return this._cycleAvailableModel(direction); } + private async _getScopedModelsWithApiKey(): Promise; thinkingLevel: ThinkingLevel }>> { + const apiKeysByProvider = new Map(); + const result: Array<{ model: Model; 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 async _cycleScopedModel(direction: "forward" | "backward"): Promise { - if (this._scopedModels.length <= 1) return undefined; + const scopedModels = await this._getScopedModelsWithApiKey(); + if (scopedModels.length <= 1) return undefined; const currentModel = this.model; - let currentIndex = this._scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel)); + let currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel)); if (currentIndex === -1) currentIndex = 0; - const len = this._scopedModels.length; + const len = scopedModels.length; const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len; - const next = this._scopedModels[nextIndex]; - - // Validate API key - const apiKey = await this._modelRegistry.getApiKey(next.model); - if (!apiKey) { - throw new Error(`No API key for ${next.model.provider}/${next.model.id}`); - } + const next = scopedModels[nextIndex]; // Apply model this.agent.setModel(next.model); diff --git a/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts b/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts index 3895ddee..8252270f 100644 --- a/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts @@ -155,11 +155,14 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl } private buildItems(): ModelItem[] { - return getSortedIds(this.enabledIds, this.allIds).map((id) => ({ - fullId: id, - model: this.modelsById.get(id)!, - enabled: isEnabled(this.enabledIds, id), - })); + // Filter out IDs that no longer have a corresponding model (e.g., after logout) + return getSortedIds(this.enabledIds, this.allIds) + .filter((id) => this.modelsById.has(id)) + .map((id) => ({ + fullId: id, + model: this.modelsById.get(id)!, + enabled: isEnabled(this.enabledIds, id), + })); } private getFooterText(): string { diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index b1363be2..300a7516 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -3261,6 +3261,8 @@ export class InteractiveMode { // All enabled or none enabled = no filter this.session.setScopedModels([]); } + await this.updateAvailableProviderCount(); + this.ui.requestRender(); }; this.showSelector((done) => {