fix(server): route list_provider_models through the snapshot manager

The legacy list_provider_models_request handler was calling
providerRegistry[p].fetchModels() directly, so every client call spawned
the provider fresh (e.g. repeated copilot ENOENT errors). Old clients
still send this RPC.

Route it through ProviderSnapshotManager so simultaneous calls dedupe
through the in-flight warmUps map, cached entries (ready / error /
unavailable) are served without spawning, and at most one spawn occurs
per cwd per TTL window regardless of client count. Direct-fetch path
kept only as a fallback for the null-manager case (tests).
This commit is contained in:
Mohamed Boudra
2026-04-17 15:20:09 +07:00
parent 07d10f55f9
commit 4b82ca77c8
+88 -28
View File
@@ -3068,36 +3068,93 @@ export class Session {
private async handleListProviderModelsRequest(
msg: Extract<SessionInboundMessage, { type: "list_provider_models_request" }>,
): Promise<void> {
const cwd = msg.cwd ? expandTilde(msg.cwd) : undefined;
const fetchedAt = new Date().toISOString();
try {
const models = await this.providerRegistry[msg.provider].fetchModels({
cwd: msg.cwd ? expandTilde(msg.cwd) : undefined,
});
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
models,
error: null,
fetchedAt,
requestId: msg.requestId,
},
});
} catch (error) {
this.sessionLogger.error(
{ err: error, provider: msg.provider },
`Failed to list models for ${msg.provider}`,
);
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
error: (error as Error)?.message ?? String(error),
fetchedAt,
requestId: msg.requestId,
},
});
const manager = this.providerSnapshotManager;
if (!manager) {
try {
const models = await this.providerRegistry[msg.provider].fetchModels({ cwd });
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
models,
error: null,
fetchedAt,
requestId: msg.requestId,
},
});
} catch (error) {
this.sessionLogger.error(
{ err: error, provider: msg.provider },
`Failed to list models for ${msg.provider}`,
);
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
error: (error as Error)?.message ?? String(error),
fetchedAt,
requestId: msg.requestId,
},
});
}
return;
}
const findEntry = () =>
manager.getSnapshot(cwd).find((candidate) => candidate.provider === msg.provider);
let entry = findEntry();
if (!entry || entry.status === "loading") {
// Awaits the in-flight warmup (deduped per-cwd) so old clients still get
// a resolved answer rather than a loading placeholder.
await manager.refresh({ cwd, providers: [msg.provider] });
entry = findEntry();
}
if (!entry) {
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
error: `Unknown provider: ${msg.provider}`,
fetchedAt,
requestId: msg.requestId,
},
});
return;
}
if (entry.status === "ready") {
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
models: entry.models ?? [],
error: null,
fetchedAt: entry.fetchedAt ?? fetchedAt,
requestId: msg.requestId,
},
});
return;
}
const errorMessage =
entry.status === "error"
? (entry.error ?? `Failed to list models for ${msg.provider}`)
: `Provider ${msg.provider} is not available`;
this.emit({
type: "list_provider_models_response",
payload: {
provider: msg.provider,
error: errorMessage,
fetchedAt,
requestId: msg.requestId,
},
});
}
private async handleListProviderModesRequest(
@@ -4771,6 +4828,7 @@ export class Session {
isPathWithinRoot: (rootPath, candidatePath) =>
this.isPathWithinRoot(rootPath, candidatePath),
killTerminalsUnderPath: (rootPath) => this.killTerminalsUnderPath(rootPath),
sessionLogger: this.sessionLogger,
},
msg,
);
@@ -8467,6 +8525,8 @@ export class Session {
isPathWithinRoot: (pathRoot, candidatePath) =>
this.isPathWithinRoot(pathRoot, candidatePath),
killTrackedTerminal: (terminalId, options) => this.killTrackedTerminal(terminalId, options),
detachTerminalStream: (terminalId, options) =>
void this.detachTerminalStream(terminalId, options),
sessionLogger: this.sessionLogger,
terminalManager: this.terminalManager,
},