Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/coding-agent/.changes/derive-connection-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Kept available model lists in sync with the current catalog and configured providers.
11 changes: 6 additions & 5 deletions packages/coding-agent/src/modes/interactive/interactive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,6 @@ export class InteractiveMode {

private skillCommands = new Map<string, string>();
private connectionCommands: AgentConnectionSlashCommand[] = [];
private connectionModels: AgentConnectionModel[] = [];
private connectionModelCatalog: AgentConnectionModel[] = [];
private connectionConfiguredProviders = new Set<string>();
private connectionModelsFetchedAt = 0;
Expand Down Expand Up @@ -7772,7 +7771,10 @@ export class InteractiveMode {
private applyConnectionModelCatalog(catalog: AgentConnectionModelCatalog): void {
this.connectionModelCatalog = [...catalog.models];
this.connectionConfiguredProviders = new Set(catalog.configuredProviders);
this.connectionModels = catalog.models.filter((model) => this.connectionConfiguredProviders.has(model.provider));
}

private getAvailableConnectionModels(): AgentConnectionModel[] {
return this.connectionModelCatalog.filter((model) => this.connectionConfiguredProviders.has(model.provider));
}

private async getConnectionAvailableModels(): Promise<AgentConnectionModel[]> {
Expand All @@ -7784,11 +7786,11 @@ export class InteractiveMode {
const version = this.connectionModelsRefreshVersion;
const promise = this.agentConnection.getModelCatalog().then((catalog) => {
if (version !== this.connectionModelsRefreshVersion) {
return [...this.connectionModels];
return this.getAvailableConnectionModels();
}
this.applyConnectionModelCatalog(catalog);
this.connectionModelsFetchedAt = Date.now();
return [...this.connectionModels];
return this.getAvailableConnectionModels();
});
this.connectionModelsRefreshInFlight = { version, promise };

Expand Down Expand Up @@ -7839,7 +7841,6 @@ export class InteractiveMode {
}

private invalidateConnectionModels(): void {
this.connectionModels = [];
this.connectionConfiguredProviders = new Set();
this.connectionModelsFetchedAt = 0;
this.invalidateConnectionModelRefresh();
Expand Down
19 changes: 7 additions & 12 deletions packages/coding-agent/test/interactive-mode-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2172,14 +2172,14 @@ describe("InteractiveMode startup onboarding warnings", () => {
describe("InteractiveMode model candidates", () => {
type ModelCandidatesHarness = {
agentConnection: { getModelCatalog: () => Promise<AgentConnectionModelCatalog> };
connectionModels: AgentConnectionModel[];
connectionModelCatalog: AgentConnectionModel[];
connectionConfiguredProviders: Set<string>;
connectionModelsFetchedAt: number;
connectionModelsRefreshVersion: number;
connectionModelsRefreshInFlight: { version: number; promise: Promise<AgentConnectionModel[]> } | undefined;
getScopedModelState(): AgentConnectionState["scopedModels"];
applyConnectionModelCatalog(catalog: AgentConnectionModelCatalog): void;
getAvailableConnectionModels(): AgentConnectionModel[];
getConnectionAvailableModels(): Promise<AgentConnectionModel[]>;
getModelCandidates(): Promise<AgentConnectionModel[]>;
getScopedModelsFromModelIds(
Expand All @@ -2201,14 +2201,14 @@ describe("InteractiveMode model candidates", () => {
const getModelCatalog = vi.fn(async () => ({ models: [model], configuredProviders: [model.provider] }));
const fakeThis: ModelCandidatesHarness = {
agentConnection: { getModelCatalog },
connectionModels: [],
connectionModelCatalog: [],
connectionConfiguredProviders: new Set(),
connectionModelsFetchedAt: 0,
connectionModelsRefreshVersion: 0,
connectionModelsRefreshInFlight: undefined,
getScopedModelState: () => [],
applyConnectionModelCatalog: prototype.applyConnectionModelCatalog,
getAvailableConnectionModels: prototype.getAvailableConnectionModels,
getConnectionAvailableModels: prototype.getConnectionAvailableModels,
getModelCandidates: prototype.getModelCandidates,
getScopedModelsFromModelIds: prototype.getScopedModelsFromModelIds,
Expand All @@ -2218,7 +2218,7 @@ describe("InteractiveMode model candidates", () => {

expect(result).toEqual([model]);
expect(getModelCatalog).toHaveBeenCalledTimes(1);
expect(fakeThis.connectionModels).toEqual([model]);
expect(fakeThis.getAvailableConnectionModels()).toEqual([model]);
});

test("uses connection state for scoped model candidates", async () => {
Expand All @@ -2229,14 +2229,14 @@ describe("InteractiveMode model candidates", () => {
});
const fakeThis: ModelCandidatesHarness = {
agentConnection: { getModelCatalog },
connectionModels: [],
connectionModelCatalog: [],
connectionConfiguredProviders: new Set(),
connectionModelsFetchedAt: 0,
connectionModelsRefreshVersion: 0,
connectionModelsRefreshInFlight: undefined,
getScopedModelState: () => [{ model, thinkingLevel: "medium" }],
applyConnectionModelCatalog: prototype.applyConnectionModelCatalog,
getAvailableConnectionModels: prototype.getAvailableConnectionModels,
getConnectionAvailableModels: prototype.getConnectionAvailableModels,
getModelCandidates: prototype.getModelCandidates,
getScopedModelsFromModelIds: prototype.getScopedModelsFromModelIds,
Expand Down Expand Up @@ -2299,7 +2299,6 @@ describe("InteractiveMode model selection persistence", () => {
getModelCatalog(): Promise<AgentConnectionModelCatalog>;
setModel(provider: string, modelId: string): Promise<void>;
};
connectionModels: AgentConnectionModel[];
connectionModelCatalog: AgentConnectionModel[];
connectionConfiguredProviders: Set<string>;
connectionModelsFetchedAt: number;
Expand All @@ -2321,6 +2320,7 @@ describe("InteractiveMode model selection persistence", () => {
getScopedModelState(): AgentConnectionState["scopedModels"];
getCurrentModel(): AgentConnectionModel | undefined;
applyConnectionModelCatalog(catalog: AgentConnectionModelCatalog): void;
getAvailableConnectionModels(): AgentConnectionModel[];
findExactModelMatch(searchTerm: string): Promise<AgentConnectionModel | undefined>;
getConnectionAvailableModels(): Promise<AgentConnectionModel[]>;
getCachedModelCandidates(): AgentConnectionModel[];
Expand Down Expand Up @@ -2409,7 +2409,6 @@ describe("InteractiveMode model selection persistence", () => {
}),
setModel: vi.fn(async () => {}),
};
fakeThis.connectionModels = [...options.connectionModels];
fakeThis.connectionModelCatalog = catalogModels;
fakeThis.connectionConfiguredProviders = configuredProviders;
fakeThis.connectionModelsFetchedAt = options.connectionModelsFetchedAt ?? 0;
Expand Down Expand Up @@ -2947,7 +2946,6 @@ describe("InteractiveMode model selection persistence", () => {
getResourceSnapshot: vi.fn(async () => ({})),
setModel: vi.fn(async () => {}),
} as never;
fakeThis.connectionModels = [];
fakeThis.connectionModelCatalog = [];
fakeThis.connectionConfiguredProviders = new Set();
fakeThis.connectionModelsFetchedAt = 0;
Expand All @@ -2973,7 +2971,7 @@ describe("InteractiveMode model selection persistence", () => {

await expect(staleRefresh).resolves.toEqual([freshModel]);

expect(fakeThis.connectionModels).toEqual([freshModel]);
expect(fakeThis.getAvailableConnectionModels()).toEqual([freshModel]);
});

test("keeps the cached model catalog when a catalog refresh fails", async () => {
Expand All @@ -2997,7 +2995,6 @@ describe("InteractiveMode model selection persistence", () => {
getResourceSnapshot: vi.fn(async () => ({})),
setModel: vi.fn(async () => {}),
} as never;
fakeThis.connectionModels = [cachedModel];
fakeThis.connectionModelCatalog = [cachedModel];
fakeThis.connectionConfiguredProviders = new Set([cachedModel.provider]);
fakeThis.connectionModelsFetchedAt = Date.now();
Expand All @@ -3015,7 +3012,7 @@ describe("InteractiveMode model selection persistence", () => {
await expect(fakeThis.refreshConnectionCatalog()).resolves.toBeUndefined();

expect(fakeThis.connectionCommands).toEqual([]);
expect(fakeThis.connectionModels).toEqual([expect.objectContaining({ id: "fresh" })]);
expect(fakeThis.getAvailableConnectionModels()).toEqual([expect.objectContaining({ id: "fresh" })]);
expect(fakeThis.connectionModelsFetchedAt).toBeGreaterThan(0);
});

Expand Down Expand Up @@ -3223,7 +3220,6 @@ describe("InteractiveMode Prime CLI onboarding", () => {
};
type OnboardingFake = OnboardingHarness & {
connectionState: AgentConnectionState;
connectionModels: AgentConnectionModel[];
agentConnection: {
getAvailableModels?: () => Promise<AgentConnectionModel[]>;
setModel?: (provider: string, modelId: string) => Promise<void>;
Expand Down Expand Up @@ -3898,7 +3894,6 @@ describe("InteractiveMode Prime CLI onboarding", () => {
function createPrimeCliHarness(shown: boolean): OnboardingFake {
const fakeThis = Object.create(InteractiveMode.prototype) as OnboardingFake;
fakeThis.connectionState = createConnectionState({ model: primeModel });
fakeThis.connectionModels = [primeModel];
fakeThis.agentConnection = {
getAvailableModels: vi.fn(async () => [primeModel]),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { createHarness, type Harness } from "../harness.js";

interface ConnectionAuthRefreshHarness {
agentConnection: { getModelCatalog(): Promise<AgentConnectionModelCatalog> };
connectionModels: AgentConnectionModel[];
connectionModelCatalog: AgentConnectionModel[];
connectionConfiguredProviders: Set<string>;
connectionModelsFetchedAt: number;
connectionModelsRefreshVersion: number;
connectionModelsRefreshInFlight: { version: number; promise: Promise<AgentConnectionModel[]> } | undefined;
invalidateConnectionModels(): void;
applyConnectionModelCatalog(catalog: AgentConnectionModelCatalog): void;
getAvailableConnectionModels(): AgentConnectionModel[];
getConnectionAvailableModels(): Promise<AgentConnectionModel[]>;
getConnectionModelCatalog(): Promise<AgentConnectionModel[]>;
refreshConnectionModelsAfterAuthChange(): Promise<void>;
Expand Down Expand Up @@ -124,7 +124,6 @@ describe("ENG-4575 model authentication", () => {
const getModelCatalog = vi.fn(async () => ({ models: [model], configuredProviders: [] }));
const fakeThis = Object.create(InteractiveMode.prototype) as ConnectionAuthRefreshHarness;
fakeThis.agentConnection = { getModelCatalog };
fakeThis.connectionModels = [model];
fakeThis.connectionModelCatalog = [model];
fakeThis.connectionConfiguredProviders = new Set([model.provider]);
fakeThis.connectionModelsFetchedAt = Date.now();
Expand All @@ -135,7 +134,7 @@ describe("ENG-4575 model authentication", () => {

expect(getModelCatalog).toHaveBeenCalledOnce();
expect(fakeThis.connectionConfiguredProviders).toEqual(new Set());
expect(fakeThis.connectionModels).toEqual([]);
expect(fakeThis.getAvailableConnectionModels()).toEqual([]);
expect(fakeThis.connectionModelCatalog).toEqual([model]);
});

Expand All @@ -147,15 +146,14 @@ describe("ENG-4575 model authentication", () => {
fakeThis.agentConnection = {
getModelCatalog: vi.fn(async () => ({ models: [model], configuredProviders: [] })),
};
fakeThis.connectionModels = [];
fakeThis.connectionModelCatalog = [];
fakeThis.connectionConfiguredProviders = new Set();
fakeThis.connectionModelsFetchedAt = 0;
fakeThis.connectionModelsRefreshVersion = 0;
fakeThis.connectionModelsRefreshInFlight = undefined;

await expect(fakeThis.getConnectionModelCatalog()).resolves.toEqual([model]);
expect(fakeThis.connectionModels).toEqual([]);
expect(fakeThis.getAvailableConnectionModels()).toEqual([]);
});

test("uses the full public catalog for scoped-session model autocomplete", async () => {
Expand Down
Loading