fix(coding-agent): defer startup model catalog refresh

This commit is contained in:
Mario Zechner
2026-07-20 17:13:06 +02:00
parent f8b74a4507
commit c889eb8809
4 changed files with 41 additions and 15 deletions
+21 -14
View File
@@ -62,7 +62,9 @@ export interface CreateModelRuntimeOptions {
modelsPath?: string | null;
modelsStore?: ModelsStore;
modelsStorePath?: string;
/** Allow create() to refresh model catalogs over the network. Defaults to false. */
allowModelNetwork?: boolean;
/** Timeout for the create-time network model refresh. */
modelRefreshTimeoutMs?: number;
catalogBaseUrl?: string;
}
@@ -98,7 +100,7 @@ export class ModelRuntime implements Models {
private readonly extensionProviders = new Map<string, ProviderConfigInput>();
private readonly compositionErrors = new Map<string, string>();
private readonly modelsPath: string | undefined;
private readonly allowModelNetwork: boolean;
private readonly modelNetworkEnabled: boolean;
private config: ModelConfig;
private snapshot: ModelRuntimeSnapshot = {
all: [],
@@ -116,12 +118,12 @@ export class ModelRuntime implements Models {
modelsPath: string | undefined,
modelsStore: ModelsStore,
providers: readonly Provider[],
allowModelNetwork: boolean,
modelNetworkEnabled: boolean,
) {
this.credentials = credentials;
this.config = config;
this.modelsPath = modelsPath;
this.allowModelNetwork = allowModelNetwork;
this.modelNetworkEnabled = modelNetworkEnabled;
this.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));
for (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);
this.models = createModels({ credentials, modelsStore });
@@ -149,16 +151,17 @@ export class ModelRuntime implements Models {
modelsPath,
modelsStore,
providers,
options.allowModelNetwork ?? process.env.PI_OFFLINE === undefined,
process.env.PI_OFFLINE === undefined,
);
runtime.configureRadiusProviders();
runtime.rebuildProviders();
const controller = new AbortController();
const timeout = runtime.allowModelNetwork
const refreshFromNetwork = runtime.modelNetworkEnabled && options.allowModelNetwork === true;
const controller = refreshFromNetwork ? new AbortController() : undefined;
const timeout = controller
? setTimeout(() => controller.abort(), options.modelRefreshTimeoutMs ?? 15_000)
: undefined;
try {
await runtime.refresh({ allowNetwork: runtime.allowModelNetwork, signal: controller.signal });
await runtime.refresh({ allowNetwork: refreshFromNetwork, signal: controller?.signal });
} finally {
if (timeout) clearTimeout(timeout);
}
@@ -389,7 +392,11 @@ export class ModelRuntime implements Models {
};
}
async setRuntimeApiKey(providerId: string, apiKey: string): Promise<void> {
async setRuntimeApiKey(
providerId: string,
apiKey: string,
refreshOptions: ModelsRefreshOptions = {},
): Promise<void> {
this.credentials.setRuntimeApiKey(providerId, apiKey);
const auth = new Map(this.snapshot.auth).set(providerId, { type: "api_key", source: "runtime API key" });
const configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);
@@ -401,12 +408,12 @@ export class ModelRuntime implements Models {
storedProviders,
available: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),
};
await this.refresh({ allowNetwork: this.allowModelNetwork });
await this.refresh(refreshOptions);
}
async removeRuntimeApiKey(providerId: string): Promise<void> {
this.credentials.removeRuntimeApiKey(providerId);
await this.refresh({ allowNetwork: this.allowModelNetwork });
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
}
listCredentials(): Promise<readonly CredentialInfo[]> {
@@ -492,7 +499,7 @@ export class ModelRuntime implements Models {
async login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential> {
const credential = await this.models.login(providerId, type, interaction);
await this.refresh({ allowNetwork: this.allowModelNetwork });
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
return credential;
}
@@ -500,20 +507,20 @@ export class ModelRuntime implements Models {
await this.models.logout(providerId);
// Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.
this.recomposeProvider(providerId);
await this.refresh({ allowNetwork: this.allowModelNetwork });
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
}
async reloadConfig(): Promise<void> {
this.config = await ModelConfig.load(this.modelsPath);
this.configureRadiusProviders();
this.rebuildProviders();
await this.refresh({ allowNetwork: this.allowModelNetwork });
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
}
async refresh(options: ModelsRefreshOptions = {}): Promise<ModelsRefreshResult> {
const refreshOptions = {
...options,
allowNetwork: options.allowNetwork ?? this.allowModelNetwork,
allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled,
};
// Published pi-ai builds before ModelsStore returned void and accepted a provider ID.
// The fallback keeps source-mode CLI tests working without rebuilding workspace dependencies.