fix(coding-agent): reload model config in picker (#7036)

This commit is contained in:
Armin Ronacher 2026-07-24 07:52:49 +02:00 • committed by GitHub
parent 8472c486f4
commit f87468131f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 10 deletions

View file

@ -25,8 +25,8 @@ export class ModelRegistry {
}
/** Reload models.json asynchronously. Await before making synchronous registry reads. */
refresh(): Promise<void> {
return this.runtime.reloadConfig();
async refresh(): Promise<void> {
await this.runtime.refresh();
}
getError(): string | undefined {

View file

@ -513,14 +513,10 @@ export class ModelRuntime implements Models {
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
}
async reloadConfig(): Promise<void> {
async refresh(options: ModelsRefreshOptions = {}): Promise<ModelsRefreshResult> {
this.config = await ModelConfig.load(this.modelsPath);
this.configureRadiusProviders();
this.rebuildProviders();
await this.refresh({ allowNetwork: this.modelNetworkEnabled });
}
async refresh(options: ModelsRefreshOptions = {}): Promise<ModelsRefreshResult> {
const refreshOptions = {
...options,
allowNetwork: options.allowNetwork ?? this.modelNetworkEnabled,

View file

@ -1,9 +1,9 @@
import { createInMemoryModelRegistry, getModelRuntime } from "../model-runtime-test-utils.ts";
import { createInMemoryModelRegistry, createModelRegistry, getModelRuntime } from "../model-runtime-test-utils.ts";
/**
* Local test harness for the new coding-agent test suite.
*/
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { AgentMessage, AgentTool } from "@earendil-works/pi-agent-core";
@ -71,6 +71,7 @@ export interface HarnessOptions {
resourceLoader?: ResourceLoader;
extensionFactories?: Array<InlineExtension | CreateTestExtensionsResultInput>;
withConfiguredAuth?: boolean;
modelsJson?: Record<string, unknown>;
}
export interface Harness {
@ -115,7 +116,11 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
if (withConfiguredAuth) {
await authStorage.modify(model.provider, async () => ({ type: "api_key", key: "faux-key" }));
}
const modelRegistry = await createInMemoryModelRegistry(authStorage);
const modelsPath = options.modelsJson === undefined ? undefined : join(tempDir, "models.json");
if (modelsPath) writeFileSync(modelsPath, JSON.stringify(options.modelsJson));
const modelRegistry = modelsPath
? await createModelRegistry(authStorage, modelsPath)
: await createInMemoryModelRegistry(authStorage);
if (withConfiguredAuth) {
modelRegistry.registerProvider(model.provider, {
baseUrl: model.baseUrl,

View file

@ -0,0 +1,68 @@
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import { setKeybindings, type TUI } from "@earendil-works/pi-tui";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { KeybindingsManager } from "../../../src/core/keybindings.ts";
import { ModelSelectorComponent } from "../../../src/modes/interactive/components/model-selector.ts";
import { initTheme } from "../../../src/modes/interactive/theme/theme.ts";
import { stripAnsi } from "../../../src/utils/ansi.ts";
import { createHarness, type Harness } from "../harness.ts";
function createFakeTui(): TUI {
return {
requestRender: () => {},
} as unknown as TUI;
}
function modelsJson(provider: string, model: string): Record<string, unknown> {
return {
providers: {
[provider]: {
baseUrl: "https://example.test/v1",
api: "openai-completions",
apiKey: "test-key",
models: [{ id: model }],
},
},
};
}
describe("issue #6999 models.json hot reload", () => {
let harness: Harness | undefined;
beforeAll(() => {
initTheme("dark");
});
beforeEach(() => {
setKeybindings(new KeybindingsManager());
});
afterEach(() => {
harness?.cleanup();
harness = undefined;
});
it("reloads models.json when opening /model", async () => {
harness = await createHarness({ modelsJson: modelsJson("old-provider", "old-model") });
expect(harness.session.modelRuntime.getModel("old-provider", "old-model")).toBeDefined();
writeFileSync(join(harness.tempDir, "models.json"), JSON.stringify(modelsJson("new-provider", "new-model")));
const selector = new ModelSelectorComponent(
createFakeTui(),
harness.getModel(),
harness.settingsManager,
harness.session.modelRuntime,
[],
() => {},
() => {},
);
await vi.waitFor(() => {
const rendered = stripAnsi(selector.render(120).join("\n"));
expect(rendered).toContain("new-model [new-provider]");
expect(rendered).toContain("Model catalogs refreshed.");
});
expect(harness.session.modelRuntime.getModel("old-provider", "old-model")).toBeUndefined();
});
});