feat(coding-agent): accept native extension providers

Allow extensions to register complete pi-ai Provider objects while preserving models.json composition and resolved provider auth access.

Refs #4823 and #4824.
This commit is contained in:
Mario Zechner
2026-07-17 10:58:24 +02:00
parent c8560b8d70
commit 019e4ad687
12 changed files with 349 additions and 13 deletions
@@ -1,6 +1,7 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Provider } from "@earendil-works/pi-ai";
import { getModel } from "@earendil-works/pi-ai/compat";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.ts";
@@ -11,6 +12,28 @@ import { createAgentSession } from "../src/core/sdk.ts";
import { SessionManager } from "../src/core/session-manager.ts";
import { SettingsManager } from "../src/core/settings-manager.ts";
function nativeAnthropicProvider(baseUrl: string): Provider {
const model = { ...getModel("anthropic", "claude-sonnet-4-5")!, baseUrl };
return {
id: "anthropic",
name: "Native Anthropic",
baseUrl,
auth: {
apiKey: {
name: "Test API key",
resolve: async () => ({ auth: { apiKey: "test-key" }, source: "test" }),
},
},
getModels: () => [model],
stream: () => {
throw new Error("unused");
},
streamSimple: () => {
throw new Error("unused");
},
};
}
describe("AgentSession dynamic provider registration", () => {
let tempDir: string;
let agentDir: string;
@@ -99,6 +122,19 @@ describe("AgentSession dynamic provider registration", () => {
session.dispose();
});
it("registers native pi-ai providers during extension loading", async () => {
const session = await createSession([
(pi) => {
pi.registerProvider(nativeAnthropicProvider("http://localhost:8080/native-top-level"));
},
]);
expect(session.model?.baseUrl).toBe("http://localhost:8080/native-top-level");
expect(await capturePromptBaseUrl(session)).toBe("http://localhost:8080/native-top-level");
session.dispose();
});
it("applies command-time registerProvider overrides without reload", async () => {
const session = await createSession([
(pi) => {
@@ -119,4 +155,25 @@ describe("AgentSession dynamic provider registration", () => {
session.dispose();
});
it("registers native pi-ai providers at command time", async () => {
const session = await createSession([
(pi) => {
pi.registerCommand("use-native", {
description: "Use native provider",
handler: async () => {
pi.registerProvider(nativeAnthropicProvider("http://localhost:8080/native-command"));
},
});
},
]);
await session.bindExtensions({});
await session.prompt("/use-native");
expect(session.model?.baseUrl).toBe("http://localhost:8080/native-command");
expect(await capturePromptBaseUrl(session)).toBe("http://localhost:8080/native-command");
session.dispose();
});
});