import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ProviderPlugin, ProviderRuntimeModel } from "./types.js"; const resolvePluginProvidersMock = vi.fn((_: unknown) => [] as ProviderPlugin[]); const resolveOwningPluginIdsForProviderMock = vi.fn( (_: unknown) => undefined as string[] | undefined, ); vi.mock("./providers.js", () => ({ resolvePluginProviders: (params: unknown) => resolvePluginProvidersMock(params as never), resolveOwningPluginIdsForProvider: (params: unknown) => resolveOwningPluginIdsForProviderMock(params as never), })); import { augmentModelCatalogWithProviderPlugins, buildProviderAuthDoctorHintWithPlugin, buildProviderMissingAuthMessageWithPlugin, formatProviderAuthProfileApiKeyWithPlugin, prepareProviderExtraParams, resolveProviderCacheTtlEligibility, resolveProviderBinaryThinking, resolveProviderBuiltInModelSuppression, resolveProviderDefaultThinkingLevel, resolveProviderModernModelRef, resolveProviderUsageSnapshotWithPlugin, resolveProviderCapabilitiesWithPlugin, resolveProviderUsageAuthWithPlugin, resolveProviderXHighThinking, normalizeProviderResolvedModelWithPlugin, prepareProviderDynamicModel, prepareProviderRuntimeAuth, refreshProviderOAuthCredentialWithPlugin, resolveProviderRuntimePlugin, runProviderDynamicModel, wrapProviderStreamFn, } from "./provider-runtime.js"; const MODEL: ProviderRuntimeModel = { id: "demo-model", name: "Demo Model", api: "openai-responses", provider: "demo", baseUrl: "https://api.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128_000, maxTokens: 8_192, }; describe("provider-runtime", () => { beforeEach(() => { resolvePluginProvidersMock.mockReset(); resolvePluginProvidersMock.mockReturnValue([]); resolveOwningPluginIdsForProviderMock.mockReset(); resolveOwningPluginIdsForProviderMock.mockReturnValue(undefined); }); it("matches providers by alias for runtime hook lookup", () => { resolvePluginProvidersMock.mockReturnValue([ { id: "openrouter", label: "OpenRouter", aliases: ["Open Router"], auth: [], }, ]); const plugin = resolveProviderRuntimePlugin({ provider: "Open Router" }); expect(plugin?.id).toBe("openrouter"); expect(resolveOwningPluginIdsForProviderMock).toHaveBeenCalledWith( expect.objectContaining({ provider: "Open Router", }), ); expect(resolvePluginProvidersMock).toHaveBeenCalledWith( expect.objectContaining({ bundledProviderAllowlistCompat: true, bundledProviderVitestCompat: true, }), ); }); it("dispatches runtime hooks for the matched provider", async () => { const prepareDynamicModel = vi.fn(async () => undefined); const prepareRuntimeAuth = vi.fn(async () => ({ apiKey: "runtime-token", baseUrl: "https://runtime.example.com/v1", expiresAt: 123, })); const refreshOAuth = vi.fn(async (cred) => ({ ...cred, access: "refreshed-access-token", })); const resolveUsageAuth = vi.fn(async () => ({ token: "usage-token", accountId: "usage-account", })); const fetchUsageSnapshot = vi.fn(async () => ({ provider: "zai" as const, displayName: "Demo", windows: [{ label: "Day", usedPercent: 25 }], })); resolvePluginProvidersMock.mockImplementation((_params: unknown) => { return [ { id: "demo", label: "Demo", auth: [], resolveDynamicModel: () => MODEL, prepareDynamicModel, capabilities: { providerFamily: "openai", }, prepareExtraParams: ({ extraParams }) => ({ ...extraParams, transport: "auto", }), wrapStreamFn: ({ streamFn }) => streamFn, normalizeResolvedModel: ({ model }) => ({ ...model, api: "openai-codex-responses", }), formatApiKey: (cred) => cred.type === "oauth" ? JSON.stringify({ token: cred.access }) : "", refreshOAuth, buildAuthDoctorHint: ({ provider, profileId }) => provider === "demo" ? `Repair ${profileId}` : undefined, prepareRuntimeAuth, resolveUsageAuth, fetchUsageSnapshot, isCacheTtlEligible: ({ modelId }) => modelId.startsWith("anthropic/"), isBinaryThinking: () => true, supportsXHighThinking: ({ modelId }) => modelId === "gpt-5.4", resolveDefaultThinkingLevel: ({ reasoning }) => (reasoning ? "low" : "off"), isModernModelRef: ({ modelId }) => modelId.startsWith("gpt-5"), }, { id: "openai", label: "OpenAI", auth: [], buildMissingAuthMessage: () => 'No API key found for provider "openai". Use openai-codex/gpt-5.4.', suppressBuiltInModel: ({ provider, modelId }) => provider === "azure-openai-responses" && modelId === "gpt-5.3-codex-spark" ? { suppress: true, errorMessage: "openai-codex/gpt-5.3-codex-spark" } : undefined, augmentModelCatalog: () => [ { provider: "openai", id: "gpt-5.4", name: "gpt-5.4" }, { provider: "openai", id: "gpt-5.4-pro", name: "gpt-5.4-pro" }, { provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" }, { provider: "openai-codex", id: "gpt-5.3-codex-spark", name: "gpt-5.3-codex-spark", }, ], }, ]; }); expect( runProviderDynamicModel({ provider: "demo", context: { provider: "demo", modelId: MODEL.id, modelRegistry: { find: () => null } as never, }, }), ).toMatchObject(MODEL); await prepareProviderDynamicModel({ provider: "demo", context: { provider: "demo", modelId: MODEL.id, modelRegistry: { find: () => null } as never, }, }); expect( resolveProviderCapabilitiesWithPlugin({ provider: "demo", }), ).toMatchObject({ providerFamily: "openai", }); expect( prepareProviderExtraParams({ provider: "demo", context: { provider: "demo", modelId: MODEL.id, extraParams: { temperature: 0.3 }, }, }), ).toMatchObject({ temperature: 0.3, transport: "auto", }); expect( wrapProviderStreamFn({ provider: "demo", context: { provider: "demo", modelId: MODEL.id, streamFn: vi.fn(), }, }), ).toBeTypeOf("function"); expect( normalizeProviderResolvedModelWithPlugin({ provider: "demo", context: { provider: "demo", modelId: MODEL.id, model: MODEL, }, }), ).toMatchObject({ ...MODEL, api: "openai-codex-responses", }); await expect( prepareProviderRuntimeAuth({ provider: "demo", env: process.env, context: { env: process.env, provider: "demo", modelId: MODEL.id, model: MODEL, apiKey: "source-token", authMode: "api-key", }, }), ).resolves.toMatchObject({ apiKey: "runtime-token", baseUrl: "https://runtime.example.com/v1", expiresAt: 123, }); expect( formatProviderAuthProfileApiKeyWithPlugin({ provider: "demo", context: { type: "oauth", provider: "demo", access: "oauth-access", refresh: "oauth-refresh", expires: Date.now() + 60_000, }, }), ).toBe('{"token":"oauth-access"}'); await expect( refreshProviderOAuthCredentialWithPlugin({ provider: "demo", context: { type: "oauth", provider: "demo", access: "oauth-access", refresh: "oauth-refresh", expires: Date.now() + 60_000, }, }), ).resolves.toMatchObject({ access: "refreshed-access-token", }); await expect( buildProviderAuthDoctorHintWithPlugin({ provider: "demo", context: { provider: "demo", profileId: "demo:default", store: { version: 1, profiles: {} }, }, }), ).resolves.toBe("Repair demo:default"); await expect( resolveProviderUsageAuthWithPlugin({ provider: "demo", env: process.env, context: { config: {} as never, env: process.env, provider: "demo", resolveApiKeyFromConfigAndStore: () => "source-token", resolveOAuthToken: async () => null, }, }), ).resolves.toMatchObject({ token: "usage-token", accountId: "usage-account", }); await expect( resolveProviderUsageSnapshotWithPlugin({ provider: "demo", env: process.env, context: { config: {} as never, env: process.env, provider: "demo", token: "usage-token", timeoutMs: 5_000, fetchFn: vi.fn() as never, }, }), ).resolves.toMatchObject({ provider: "zai", windows: [{ label: "Day", usedPercent: 25 }], }); expect( resolveProviderCacheTtlEligibility({ provider: "demo", context: { provider: "demo", modelId: "anthropic/claude-sonnet-4-5", }, }), ).toBe(true); expect( resolveProviderBinaryThinking({ provider: "demo", context: { provider: "demo", modelId: "glm-5", }, }), ).toBe(true); expect( resolveProviderXHighThinking({ provider: "demo", context: { provider: "demo", modelId: "gpt-5.4", }, }), ).toBe(true); expect( resolveProviderDefaultThinkingLevel({ provider: "demo", context: { provider: "demo", modelId: "gpt-5.4", reasoning: true, }, }), ).toBe("low"); expect( resolveProviderModernModelRef({ provider: "demo", context: { provider: "demo", modelId: "gpt-5.4", }, }), ).toBe(true); expect( buildProviderMissingAuthMessageWithPlugin({ provider: "openai", env: process.env, context: { env: process.env, provider: "openai", listProfileIds: (providerId) => (providerId === "openai-codex" ? ["p1"] : []), }, }), ).toContain("openai-codex/gpt-5.4"); expect( resolveProviderBuiltInModelSuppression({ env: process.env, context: { env: process.env, provider: "azure-openai-responses", modelId: "gpt-5.3-codex-spark", }, }), ).toMatchObject({ suppress: true, errorMessage: expect.stringContaining("openai-codex/gpt-5.3-codex-spark"), }); await expect( augmentModelCatalogWithProviderPlugins({ env: process.env, context: { env: process.env, entries: [ { provider: "openai", id: "gpt-5.2", name: "GPT-5.2" }, { provider: "openai", id: "gpt-5.2-pro", name: "GPT-5.2 Pro" }, { provider: "openai-codex", id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, ], }, }), ).resolves.toEqual([ { provider: "openai", id: "gpt-5.4", name: "gpt-5.4" }, { provider: "openai", id: "gpt-5.4-pro", name: "gpt-5.4-pro" }, { provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" }, { provider: "openai-codex", id: "gpt-5.3-codex-spark", name: "gpt-5.3-codex-spark", }, ]); expect(prepareDynamicModel).toHaveBeenCalledTimes(1); expect(refreshOAuth).toHaveBeenCalledTimes(1); expect(prepareRuntimeAuth).toHaveBeenCalledTimes(1); expect(resolveUsageAuth).toHaveBeenCalledTimes(1); expect(fetchUsageSnapshot).toHaveBeenCalledTimes(1); }); });