feat(coding-agent): replace model registry with model runtime
Move provider auth and OAuth flows onto pi-ai Models, compose models.json and extension overlays through ModelRuntime, and retain ModelRegistry as an extension compatibility facade.
This commit is contained in:
@@ -52,7 +52,7 @@ describe("AgentSessionRuntime characterization", () => {
|
||||
faux.setResponses([fauxAssistantMessage("one"), fauxAssistantMessage("two"), fauxAssistantMessage("three")]);
|
||||
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
authStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await authStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
|
||||
const runtimeOptions = {
|
||||
agentDir: tempDir,
|
||||
@@ -343,7 +343,7 @@ describe("AgentSessionRuntime characterization", () => {
|
||||
faux.setResponses([fauxAssistantMessage("one"), fauxAssistantMessage("two"), fauxAssistantMessage("three")]);
|
||||
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
authStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await authStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
|
||||
const runtimeOptions = {
|
||||
agentDir: tempDir,
|
||||
@@ -454,7 +454,7 @@ describe("AgentSessionRuntime characterization", () => {
|
||||
mkdirSync(secondDir, { recursive: true });
|
||||
const { runtime, faux, tempDir } = await createRuntimeForTest(() => {}, { cwd: firstDir });
|
||||
const otherAuthStorage = AuthStorage.inMemory();
|
||||
otherAuthStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await otherAuthStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
const otherRuntimeOptions = {
|
||||
agentDir: tempDir,
|
||||
authStorage: otherAuthStorage,
|
||||
@@ -527,7 +527,7 @@ describe("AgentSessionRuntime characterization", () => {
|
||||
const otherDir = join(tempDir, "other");
|
||||
mkdirSync(otherDir, { recursive: true });
|
||||
const otherAuthStorage = AuthStorage.inMemory();
|
||||
otherAuthStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await otherAuthStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
const otherRuntimeOptions = {
|
||||
agentDir: tempDir,
|
||||
authStorage: otherAuthStorage,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createInMemoryModelRegistry, getModelRuntime } from "../model-runtime-test-utils.ts";
|
||||
/**
|
||||
* Local test harness for the new coding-agent test suite.
|
||||
*/
|
||||
@@ -18,7 +19,6 @@ import { AgentSession, type AgentSessionEvent } from "../../src/core/agent-sessi
|
||||
import { AuthStorage } from "../../src/core/auth-storage.ts";
|
||||
import type { ExtensionRunner } from "../../src/core/extensions/index.ts";
|
||||
import { convertToLlm } from "../../src/core/messages.ts";
|
||||
import { ModelRegistry } from "../../src/core/model-registry.ts";
|
||||
import { SessionManager } from "../../src/core/session-manager.ts";
|
||||
import type { Settings } from "../../src/core/settings-manager.ts";
|
||||
import { SettingsManager } from "../../src/core/settings-manager.ts";
|
||||
@@ -113,9 +113,9 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
|
||||
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
if (withConfiguredAuth) {
|
||||
authStorage.setRuntimeApiKey(model.provider, "faux-key");
|
||||
await authStorage.modify(model.provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
}
|
||||
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
||||
const modelRegistry = await createInMemoryModelRegistry(authStorage);
|
||||
if (withConfiguredAuth) {
|
||||
modelRegistry.registerProvider(model.provider, {
|
||||
baseUrl: model.baseUrl,
|
||||
@@ -178,7 +178,7 @@ export async function createHarness(options: HarnessOptions = {}): Promise<Harne
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
cwd: tempDir,
|
||||
modelRegistry,
|
||||
modelRuntime: getModelRuntime(modelRegistry),
|
||||
resourceLoader,
|
||||
baseToolsOverride: toolMap,
|
||||
initialActiveToolNames: options.initialActiveToolNames,
|
||||
|
||||
+7
-2
@@ -10,6 +10,7 @@ import {
|
||||
createAgentSessionServices,
|
||||
} from "../../../src/core/agent-session-runtime.ts";
|
||||
import { AuthStorage } from "../../../src/core/auth-storage.ts";
|
||||
import { ModelRuntime } from "../../../src/core/model-runtime.ts";
|
||||
import { SessionManager } from "../../../src/core/session-manager.ts";
|
||||
|
||||
describe("issue #2753 reload stale resource settings", () => {
|
||||
@@ -32,13 +33,17 @@ describe("issue #2753 reload stale resource settings", () => {
|
||||
models: [{ id: "faux-1", reasoning: false }],
|
||||
});
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
authStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await authStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
const modelRuntime = await ModelRuntime.create({
|
||||
credentials: authStorage,
|
||||
modelsPath: join(agentDir, "models.json"),
|
||||
});
|
||||
|
||||
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
||||
const services = await createAgentSessionServices({
|
||||
cwd,
|
||||
agentDir,
|
||||
authStorage,
|
||||
modelRuntime,
|
||||
resourceLoaderOptions: {
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
createAgentSessionServices,
|
||||
} from "../../../src/core/agent-session-runtime.ts";
|
||||
import { AuthStorage } from "../../../src/core/auth-storage.ts";
|
||||
import { ModelRuntime } from "../../../src/core/model-runtime.ts";
|
||||
import { SessionManager } from "../../../src/core/session-manager.ts";
|
||||
import type { ExtensionAPI, ExtensionCommandContext, ExtensionFactory } from "../../../src/index.ts";
|
||||
|
||||
@@ -45,13 +46,17 @@ describe("regression #2860: replaced session callbacks", () => {
|
||||
faux.setResponses(responses.map((response) => fauxAssistantMessage(response)));
|
||||
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
authStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
|
||||
await authStorage.modify(faux.getModel().provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
const modelRuntime = await ModelRuntime.create({
|
||||
credentials: authStorage,
|
||||
modelsPath: join(tempDir, "models.json"),
|
||||
});
|
||||
|
||||
const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
||||
const services = await createAgentSessionServices({
|
||||
cwd,
|
||||
agentDir: tempDir,
|
||||
authStorage,
|
||||
modelRuntime,
|
||||
resourceLoaderOptions: {
|
||||
extensionFactories: [
|
||||
(pi: ExtensionAPI) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { setKeybindings, type TUI } from "@earendil-works/pi-tui";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
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 { ScopedModelsSelectorComponent } from "../../../src/modes/interactive/components/scoped-models-selector.ts";
|
||||
@@ -13,10 +13,6 @@ function createFakeTui(): TUI {
|
||||
} as unknown as TUI;
|
||||
}
|
||||
|
||||
async function waitForAsyncRender(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
describe("issue #3217 scoped model ordering", () => {
|
||||
const harnesses: Harness[] = [];
|
||||
|
||||
@@ -83,13 +79,15 @@ describe("issue #3217 scoped model ordering", () => {
|
||||
createFakeTui(),
|
||||
modelOne,
|
||||
harness.settingsManager,
|
||||
harness.session.modelRegistry,
|
||||
harness.session.modelRuntime,
|
||||
[{ model: modelTwo }, { model: modelOne }, { model: modelThree }],
|
||||
() => {},
|
||||
() => {},
|
||||
);
|
||||
|
||||
await waitForAsyncRender();
|
||||
await vi.waitFor(() => {
|
||||
expect(stripAnsi(selector.render(120).join("\n"))).toContain(`[${modelOne.provider}]`);
|
||||
});
|
||||
|
||||
const renderedLines = stripAnsi(selector.render(120).join("\n"))
|
||||
.split("\n")
|
||||
|
||||
+14
@@ -70,6 +70,20 @@ describe("LoginDialogComponent OAuth prompts", () => {
|
||||
expect(output).toContain("First prompt:");
|
||||
});
|
||||
|
||||
test("preserves neutral information and links when showing a prompt", () => {
|
||||
const dialog = createDialog();
|
||||
|
||||
dialog.showInfo("Configure credentials outside pi.", [
|
||||
{ label: "Provider documentation", url: "https://example.invalid/docs" },
|
||||
]);
|
||||
dialog.showPrompt("Press Enter to continue:");
|
||||
|
||||
const output = renderDialog(dialog).join("\n");
|
||||
expect(output).toContain("Configure credentials outside pi.");
|
||||
expect(output).toContain("Provider documentation: https://example.invalid/docs");
|
||||
expect(output).toContain("Press Enter to continue:");
|
||||
});
|
||||
|
||||
test("keeps previous manual input stable when a later prompt is active", async () => {
|
||||
const dialog = createDialog();
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
import { AgentSession } from "../../../src/core/agent-session.ts";
|
||||
import { AuthStorage } from "../../../src/core/auth-storage.ts";
|
||||
import { convertToLlm } from "../../../src/core/messages.ts";
|
||||
import { ModelRegistry } from "../../../src/core/model-registry.ts";
|
||||
import { SessionManager } from "../../../src/core/session-manager.ts";
|
||||
import { SettingsManager } from "../../../src/core/settings-manager.ts";
|
||||
import { initTheme } from "../../../src/modes/interactive/theme/theme.ts";
|
||||
import { createInMemoryModelRegistry, getModelRuntime } from "../../model-runtime-test-utils.ts";
|
||||
import { createTestResourceLoader } from "../../utilities.ts";
|
||||
|
||||
describe("regression #5596: missing configured theme export", () => {
|
||||
@@ -32,8 +32,8 @@ describe("regression #5596: missing configured theme export", () => {
|
||||
|
||||
const model = faux.getModel();
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
authStorage.setRuntimeApiKey(model.provider, "faux-key");
|
||||
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
||||
await authStorage.modify(model.provider, async () => ({ type: "api_key", key: "faux-key" }));
|
||||
const modelRegistry = await createInMemoryModelRegistry(authStorage);
|
||||
modelRegistry.registerProvider(model.provider, {
|
||||
baseUrl: model.baseUrl,
|
||||
apiKey: "faux-key",
|
||||
@@ -67,7 +67,7 @@ describe("regression #5596: missing configured theme export", () => {
|
||||
sessionManager,
|
||||
settingsManager,
|
||||
cwd: tempDir,
|
||||
modelRegistry,
|
||||
modelRuntime: getModelRuntime(modelRegistry),
|
||||
resourceLoader: createTestResourceLoader(),
|
||||
});
|
||||
cleanups.push(() => {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { ENV_AGENT_DIR } from "../../../src/config.ts";
|
||||
import { AuthStorage } from "../../../src/core/auth-storage.ts";
|
||||
import { ModelRegistry } from "../../../src/core/model-registry.ts";
|
||||
import { runMigrations } from "../../../src/migrations.ts";
|
||||
import { createModelRegistry } from "../../model-runtime-test-utils.ts";
|
||||
import { createHarness } from "../harness.ts";
|
||||
|
||||
describe("regression #5661: uppercase models.json header values", () => {
|
||||
@@ -79,7 +79,7 @@ describe("regression #5661: uppercase models.json header values", () => {
|
||||
expect(migrated.providers["my-provider"]?.apiKey).toBe("CUSTOM_API_KEY");
|
||||
expect(migrated.providers["my-provider"]?.headers?.Authorization).toBe("BEARER");
|
||||
|
||||
const registry = ModelRegistry.create(AuthStorage.create(join(harness.tempDir, "auth.json")), modelsPath);
|
||||
const registry = await createModelRegistry(AuthStorage.create(join(harness.tempDir, "auth.json")), modelsPath);
|
||||
const model = registry.find("my-provider", "my-model");
|
||||
expect(model).toBeDefined();
|
||||
expect(await registry.getApiKeyAndHeaders(model!)).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user