feat(FN-5601): add OpenAI Responses API type support to custom providers
Added OpenAI Responses API as a new custom provider type, wiring `apiType: "responses"` through the core registry, engine routes, and dashboard UI with a dropdown selector; includes test coverage across the registry, routes, and component layers. Fusion-Task-Id: FN-5601 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> Fusion-Task-Id: FN-5601
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
||||
import { completeSimple } from "@mariozechner/pi-ai";
|
||||
import { customProviderRegistryKey, type CustomProvider } from "@fusion/core";
|
||||
|
||||
describe("custom providers openai-completions regression", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("registers under slug key and completes a chat round-trip", async () => {
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
||||
|
||||
const providers: CustomProvider[] = [
|
||||
{
|
||||
id: "550e8400-e29b-41d4-a716-446655440000",
|
||||
name: "My AI Provider",
|
||||
apiType: "openai-compatible",
|
||||
baseUrl: "https://example.test/v1",
|
||||
apiKey: "CUSTOM_KEY",
|
||||
models: [{ id: "my-model", name: "My Model" }],
|
||||
},
|
||||
];
|
||||
|
||||
const provider = providers[0]!;
|
||||
modelRegistry.registerProvider(customProviderRegistryKey(provider, providers), {
|
||||
baseUrl: provider.baseUrl,
|
||||
api: "openai-completions",
|
||||
apiKey: provider.apiKey,
|
||||
models: [{
|
||||
id: "my-model",
|
||||
name: "My Model",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 16384,
|
||||
}],
|
||||
});
|
||||
modelRegistry.refresh();
|
||||
|
||||
const registered = modelRegistry.getAll().find((model) => model.id === "my-model");
|
||||
expect(registered?.provider).toBe("my-ai-provider");
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn(async () => {
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("data: {\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"Hello from mock transport\"},\"finish_reason\":null}]}\n\n"));
|
||||
controller.enqueue(new TextEncoder().encode("data: {\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":1,\"total_tokens\":2}}\n\n"));
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
});
|
||||
}));
|
||||
|
||||
const model = modelRegistry.find("my-ai-provider", "my-model");
|
||||
expect(model).toBeDefined();
|
||||
const response = await completeSimple(model!, {
|
||||
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
|
||||
});
|
||||
expect(response.role).toBe("assistant");
|
||||
});
|
||||
});
|
||||
@@ -1124,13 +1124,21 @@ describe("createFnAgent", () => {
|
||||
it("registers custom providers from global settings", async () => {
|
||||
readCustomProvidersMock.mockReturnValue([
|
||||
{
|
||||
id: "custom-openai",
|
||||
id: "550e8400-e29b-41d4-a716-446655440000",
|
||||
name: "Custom OpenAI",
|
||||
apiType: "openai-compatible",
|
||||
baseUrl: "https://custom.example/v1",
|
||||
apiKey: "CUSTOM_API_KEY",
|
||||
models: [{ id: "custom-model", name: "Custom Model" }],
|
||||
},
|
||||
{
|
||||
id: "660e8400-e29b-41d4-a716-446655440001",
|
||||
name: "Custom Responses",
|
||||
apiType: "openai-responses",
|
||||
baseUrl: "https://responses.example/v1",
|
||||
apiKey: "RESPONSES_API_KEY",
|
||||
models: [{ id: "responses-model", name: "Responses Model" }],
|
||||
},
|
||||
] as any);
|
||||
|
||||
const { createFnAgent } = await import("../pi.js");
|
||||
@@ -1149,6 +1157,12 @@ describe("createFnAgent", () => {
|
||||
apiKey: "CUSTOM_API_KEY",
|
||||
models: [expect.objectContaining({ id: "custom-model", name: "Custom Model" })],
|
||||
}));
|
||||
expect(registerProviderMock).toHaveBeenCalledWith("custom-responses", expect.objectContaining({
|
||||
baseUrl: "https://responses.example/v1",
|
||||
api: "openai-responses",
|
||||
apiKey: "RESPONSES_API_KEY",
|
||||
models: [expect.objectContaining({ id: "responses-model", name: "Responses Model" })],
|
||||
}));
|
||||
});
|
||||
|
||||
it("avoids lock-based SettingsManager.create when loading extension providers", async () => {
|
||||
|
||||
Reference in New Issue
Block a user