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:
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user