// Provider runtime contract helpers define reusable runtime tests for provider plugins. import { normalizeModelCatalog } from "@openclaw/model-catalog-core/model-catalog-normalize"; import { expectDefined } from "@openclaw/normalization-core"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createPluginMetadataSnapshot } from "../../config/plugin-auto-enable.test-helpers.js"; import type { ProviderRuntimeModel } from "../plugin-entry.js"; import { registerProviderPlugin, requireRegisteredProvider } from "../plugin-test-runtime.js"; import { buildManifestModelProviderConfig } from "../provider-catalog-shared.js"; import type { ProviderPlugin } from "../provider-model-shared.js"; import { createProviderUsageFetch, makeResponse } from "../test-env.js"; const CONTRACT_SETUP_TIMEOUT_MS = 300_000; const OPENAI_CODEX_PROVIDER_RUNTIME_MODULE_ID = "../../../extensions/openai/openai-chatgpt-provider.runtime.js"; const refreshOpenAICodexTokenMock = vi.fn(); function installProviderRuntimeContractMocks() { vi.doMock(OPENAI_CODEX_PROVIDER_RUNTIME_MODULE_ID, () => ({ refreshOpenAICodexToken: refreshOpenAICodexTokenMock, })); } function removeProviderRuntimeContractMocks() { vi.doUnmock(OPENAI_CODEX_PROVIDER_RUNTIME_MODULE_ID); } function createModel(overrides: Partial & Pick) { return { id: overrides.id, name: overrides.name ?? overrides.id, api: overrides.api ?? "openai-responses", provider: overrides.provider ?? "demo", baseUrl: overrides.baseUrl ?? "https://api.example.com/v1", reasoning: overrides.reasoning ?? true, input: overrides.input ?? ["text"], cost: overrides.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: overrides.contextWindow ?? 200_000, maxTokens: overrides.maxTokens ?? 8_192, } satisfies ProviderRuntimeModel; } function createManifestModelFactory(providerId: string, catalog: unknown) { const provider = buildManifestModelProviderConfig({ providerId, catalog }); return (modelId: string, overrides: Partial = {}): ProviderRuntimeModel => { const model = provider.models.find((candidate) => candidate.id === modelId); if (!model) { throw new Error(`Missing ${providerId} manifest model ${modelId}`); } const input = model.input.filter( (item): item is "text" | "image" => item === "text" || item === "image", ); if (input.length !== model.input.length) { throw new Error(`Unsupported ${providerId} manifest model input for ${modelId}`); } return createModel({ ...model, id: model.id, provider: providerId, baseUrl: model.baseUrl ?? provider.baseUrl, api: model.api ?? provider.api, input, ...overrides, }); }; } function requireRecord(value: unknown, label: string): Record { expect(value, label).toBeTypeOf("object"); expect(value, label).not.toBeNull(); return value as Record; } function expectFields(value: unknown, fields: Record) { const record = requireRecord(value, "record"); for (const [key, expected] of Object.entries(fields)) { expect(record[key]).toEqual(expected); } } type ProviderRuntimeContractFixture = { providerIds: string[]; pluginId: string; name: string; load: ProviderRuntimeContractPluginLoader; }; export type ProviderRuntimeContractPluginLoader = () => Promise<{ default: Parameters[0]["plugin"]; }>; function installRuntimeHooks(fixtures: readonly ProviderRuntimeContractFixture[]) { const providers = new Map(); let loadPromise: Promise | null = null; function requireProviderContractProvider(providerId: string): ProviderPlugin { const provider = providers.get(providerId); if (!provider) { throw new Error(`provider runtime contract fixture missing for ${providerId}`); } return provider; } async function ensureProvidersLoaded() { if (!loadPromise) { loadPromise = (async () => { providers.clear(); const registeredFixtures = await Promise.all( fixtures.map(async (fixture) => { const plugin = await fixture.load(); return { fixture, providers: ( await registerProviderPlugin({ plugin: plugin.default, id: fixture.pluginId, name: fixture.name, }) ).providers, }; }), ); for (const { fixture, providers: registeredProviders } of registeredFixtures) { for (const providerId of fixture.providerIds) { providers.set( providerId, requireRegisteredProvider(registeredProviders, providerId, "provider"), ); } } })(); } await loadPromise; } beforeAll(async () => { installProviderRuntimeContractMocks(); await ensureProvidersLoaded(); }, CONTRACT_SETUP_TIMEOUT_MS); afterAll(() => { removeProviderRuntimeContractMocks(); }); beforeEach(() => { refreshOpenAICodexTokenMock.mockReset(); }, CONTRACT_SETUP_TIMEOUT_MS); return requireProviderContractProvider; } export function describeAnthropicProviderRuntimeContract( load: ProviderRuntimeContractPluginLoader, ) { describe("anthropic provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["anthropic"], pluginId: "anthropic", name: "Anthropic", load }, ]); it("owns anthropic 4.6 forward-compat resolution", () => { const provider = requireProviderContractProvider("anthropic"); // The dated 4.6 template has no owning manifest row; keep its remap fixture literal. const model = provider.resolveDynamicModel?.({ provider: "anthropic", modelId: "claude-sonnet-4.6-20260219", modelRegistry: { find: (_provider: string, id: string) => id === "claude-sonnet-4-6-20260219" ? createModel({ id, api: "anthropic-messages", provider: "anthropic", baseUrl: "https://api.anthropic.com", }) : null, } as never, }); expectFields(model, { id: "claude-sonnet-4.6-20260219", provider: "anthropic", api: "anthropic-messages", baseUrl: "https://api.anthropic.com", }); }); it("owns usage auth resolution", async () => { const provider = requireProviderContractProvider("anthropic"); await expect( provider.resolveUsageAuth?.({ config: {} as never, env: {} as NodeJS.ProcessEnv, provider: "anthropic", resolveApiKeyFromConfigAndStore: () => undefined, resolveOAuthToken: async () => ({ token: "anthropic-oauth-token", }), }), ).resolves.toEqual({ token: "anthropic-oauth-token", }); }); it("owns auth doctor hint generation", async () => { const provider = requireProviderContractProvider("anthropic"); const hint = await provider.buildAuthDoctorHint?.({ provider: "anthropic", profileId: "anthropic:default", config: { auth: { profiles: { "anthropic:default": { provider: "anthropic", mode: "oauth", }, }, }, } as never, store: { version: 1, profiles: { "anthropic:oauth-user@example.com": { type: "oauth", provider: "anthropic", access: "oauth-access", refresh: "oauth-refresh", expires: Date.now() + 60_000, }, }, }, }); expect(hint).toContain("suggested profile: anthropic:oauth-user@example.com"); expect(hint).toContain("openclaw doctor --yes"); }); it("owns usage snapshot fetching", async () => { const provider = requireProviderContractProvider("anthropic"); const mockFetch = createProviderUsageFetch(async (url) => { if (url.includes("api.anthropic.com/api/oauth/usage")) { return makeResponse(200, { five_hour: { utilization: 20, resets_at: "2026-01-07T01:00:00Z" }, seven_day: { utilization: 35, resets_at: "2026-01-09T01:00:00Z" }, }); } return makeResponse(404, "not found"); }); await expect( provider.fetchUsageSnapshot?.({ config: {} as never, env: {} as NodeJS.ProcessEnv, provider: "anthropic", token: "anthropic-oauth-token", timeoutMs: 5_000, fetchFn: mockFetch as unknown as typeof fetch, }), ).resolves.toEqual({ provider: "anthropic", displayName: "Claude", windows: [ { label: "5h", usedPercent: 20, resetAt: Date.parse("2026-01-07T01:00:00Z") }, { label: "Week", usedPercent: 35, resetAt: Date.parse("2026-01-09T01:00:00Z") }, ], }); }); }); } export function describeGithubCopilotProviderRuntimeContract( load: ProviderRuntimeContractPluginLoader, manifestCatalog: unknown, ) { describe( "github-copilot provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["github-copilot"], pluginId: "github-copilot", name: "GitHub Copilot", load, }, ]); const createManifestModel = createManifestModelFactory("github-copilot", manifestCatalog); async function resolveStaticModel(modelId: string) { const { createBundledStaticCatalogModelResolver } = await import("../../agents/embedded-agent-runner/model.static-catalog.js"); const config = { models: { catalogRefresh: { enabled: false } }, }; const metadataSnapshot = createPluginMetadataSnapshot({ config, manifestRegistry: { diagnostics: [], plugins: [ { id: "github-copilot", origin: "bundled", providers: ["github-copilot"], channels: [], cliBackends: [], skills: [], hooks: [], rootDir: "/fixtures/github-copilot", source: "/fixtures/github-copilot/index.js", manifestPath: "/fixtures/github-copilot/openclaw.plugin.json", modelCatalog: normalizeModelCatalog( { providers: { "github-copilot": manifestCatalog }, discovery: { "github-copilot": "runtime" }, }, { ownedProviders: new Set(["github-copilot"]) }, ), }, ], }, }); const resolveModel = createBundledStaticCatalogModelResolver({ cfg: config, env: {}, metadataSnapshot, includeRuntimeDiscovery: true, }); return { config, model: expectDefined(resolveModel({ provider: "github-copilot", modelId }), modelId), }; } it.each([ ["gemini-3.6-flash", "openai-completions"], ["gemini-3.1-pro-preview", "openai-completions"], ["gemini-3.5-flash", "openai-completions"], ["gemini-2.5-pro", "openai-completions"], ["gpt-5.6-sol", "openai-responses"], ["claude-sonnet-5", "anthropic-messages"], ] as const)("routes static %s through %s", async (modelId, api) => { const { config, model } = await resolveStaticModel(modelId); expect(model.api).toBe(api); if (api === "openai-completions") { expect(model.compat).toMatchObject({ supportsStore: false, supportsDeveloperRole: false, supportsUsageInStreaming: false, maxTokensField: "max_tokens", }); } const provider = requireProviderContractProvider("github-copilot"); expect( provider.preferRuntimeResolvedModel?.({ config, provider: "github-copilot", modelId, }), ).toBe(true); // A missing prepared live row also occurs when enabled discovery is unavailable. expect( provider.resolveDynamicModel?.({ config, provider: "github-copilot", modelId, modelRegistry: { find: () => model, getAll: () => [model], getAvailable: () => [], hasConfiguredAuth: () => false, }, }), ).toBeUndefined(); }); it.each([ ["minimal", "low"], ["xhigh", "xhigh"], ] as const)("sends static GPT-5.4 mini %s thinking as %s", async (level, effort) => { const { createApiRegistry, createLlmRuntime } = await import("@openclaw/ai"); const { streamOpenAIResponses, streamSimpleOpenAIResponses } = await import("@openclaw/ai/internal/openai"); const { config, model } = await resolveStaticModel("gpt-5.4-mini"); const provider = requireProviderContractProvider("github-copilot"); const profile = provider.resolveThinkingProfile?.({ provider: model.provider, modelId: model.id, api: model.api, compat: model.compat, }); const registry = createApiRegistry(); registry.registerApiProvider({ api: "openai-responses", stream: streamOpenAIResponses, streamSimple: streamSimpleOpenAIResponses, }); const stream = expectDefined( provider.wrapStreamFn?.({ config, provider: model.provider, modelId: model.id, model, streamFn: createLlmRuntime(registry).streamSimple, }), "Copilot stream wrapper", ); const network = vi .spyOn(globalThis, "fetch") .mockRejectedValue(new Error("Unexpected network")); const payloads: unknown[] = []; try { const response = await stream( model, { messages: [{ role: "user", content: "Say hello", timestamp: 0 }], }, { apiKey: "test-token", reasoning: level, maxTokens: 1024, onPayload: (payload) => { payloads.push(payload); throw new Error("Captured payload before network"); }, }, ); await response.result(); expect(network).not.toHaveBeenCalled(); expect.soft(profile?.levels.map(({ id }) => id)).toContain(level); expect(profile?.levels.map(({ id }) => id)).not.toContain("max"); expect(payloads).toEqual([ expect.objectContaining({ reasoning: expect.objectContaining({ effort }) }), ]); } finally { network.mockRestore(); } }); it("owns Copilot-specific forward-compat fallbacks", () => { const provider = requireProviderContractProvider("github-copilot"); const expected = createManifestModel("gpt-5.4"); const model = provider.resolveDynamicModel?.({ provider: "github-copilot", modelId: "gpt-5.4", modelRegistry: { find: () => null, } as never, }); const { baseUrl: _providerDefault, ...manifestFields } = expected; expectFields(model, manifestFields); }); }, ); } export function describeGoogleProviderRuntimeContract(load: ProviderRuntimeContractPluginLoader) { describe("google provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["google", "google-gemini-cli"], pluginId: "google", name: "Google", load }, ]); it("owns google direct gemini 3.1 forward-compat resolution", () => { const provider = requireProviderContractProvider("google"); // Google catalog rows are runtime-discovered, so the retired 3.0 template stays literal. const model = provider.resolveDynamicModel?.({ provider: "google", modelId: "gemini-3.1-pro-preview", modelRegistry: { find: (_provider: string, id: string) => id === "gemini-3-pro-preview" ? createModel({ id, api: "google-generative-ai", provider: "google", baseUrl: "https://generativelanguage.googleapis.com", reasoning: false, contextWindow: 1_048_576, maxTokens: 65_536, }) : null, } as never, }); expectFields(model, { id: "gemini-3.1-pro-preview", provider: "google", api: "google-generative-ai", baseUrl: "https://generativelanguage.googleapis.com", reasoning: true, }); }); it("owns gemini cli 3.1 forward-compat resolution", () => { const provider = requireProviderContractProvider("google-gemini-cli"); const model = provider.resolveDynamicModel?.({ provider: "google-gemini-cli", modelId: "gemini-3.1-pro-preview", modelRegistry: { find: (_provider: string, id: string) => id === "gemini-3-pro-preview" ? createModel({ id, api: "google-gemini-cli", provider: "google-gemini-cli", baseUrl: "https://cloudcode-pa.googleapis.com", reasoning: false, contextWindow: 1_048_576, maxTokens: 65_536, }) : null, } as never, }); expectFields(model, { id: "gemini-3.1-pro-preview", provider: "google-gemini-cli", reasoning: true, }); }); it("keeps retired usage telemetry hooks absent", () => { const provider = requireProviderContractProvider("google-gemini-cli"); expect(provider.resolveUsageAuth).toBeUndefined(); expect(provider.fetchUsageSnapshot).toBeUndefined(); }); it("owns OAuth auth-profile formatting", () => { const provider = requireProviderContractProvider("google-gemini-cli"); expect( provider.formatApiKey?.({ type: "oauth", provider: "google-gemini-cli", access: "google-oauth-token", refresh: "refresh-token", expires: Date.now() + 60_000, projectId: "proj-123", }), ).toBe('{"token":"google-oauth-token","projectId":"proj-123"}'); }); }); } export function describeOpenAIProviderRuntimeContract( load: ProviderRuntimeContractPluginLoader, manifestCatalog: unknown, ) { describe("openai provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const createManifestModel = createManifestModelFactory("openai", manifestCatalog); const codexProviderConfig = { api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", } as const; const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["openai", "openai"], pluginId: "openai", name: "OpenAI", load }, ]); it("owns openai gpt-5.4 forward-compat resolution", () => { const provider = requireProviderContractProvider("openai"); // Neither gpt-5.4-pro nor its 5.2 template has an owning manifest row. const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.4-pro", modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5.2-pro" ? createModel({ id, provider: "openai", baseUrl: "https://api.openai.com/v1", input: ["text", "image"], }) : null, } as never, }); expectFields(model, { id: "gpt-5.4-pro", provider: "openai", api: "openai-responses", baseUrl: "https://api.openai.com/v1", contextWindow: 1_050_000, maxTokens: 128_000, }); }); it("owns openai gpt-5.5 forward-compat resolution", () => { const provider = requireProviderContractProvider("openai"); const expected = createManifestModel("gpt-5.5", { name: "gpt-5.5" }); // OpenAI has no gpt-5.4 manifest row; distinct literal metadata keeps the upgrade asserted. const olderTemplate = createModel({ id: "gpt-5.4", provider: "openai", baseUrl: "https://api.openai.com/v1", input: ["text", "image"], }); const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.5", modelRegistry: { find: (_provider: string, id: string) => (id === olderTemplate.id ? olderTemplate : null), } as never, }); expectFields(model, expected); }); it("owns openai gpt-5.4 mini forward-compat resolution", () => { const provider = requireProviderContractProvider("openai"); // The OpenAI manifest has no gpt-5.4-mini row, so its family patch stays literal. const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.4-mini", modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5-mini" ? createModel({ id, provider: "openai", api: "openai-responses", baseUrl: "https://api.openai.com/v1", input: ["text", "image"], reasoning: true, contextWindow: 400_000, maxTokens: 128_000, }) : null, } as never, }); expectFields(model, { id: "gpt-5.4-mini", provider: "openai", api: "openai-responses", baseUrl: "https://api.openai.com/v1", contextWindow: 400_000, maxTokens: 128_000, }); }); it("owns direct openai transport normalization", () => { const provider = requireProviderContractProvider("openai"); // The normalized gpt-5.4 input is intentionally outside the current OpenAI manifest. expectFields( provider.normalizeResolvedModel?.({ provider: "openai", modelId: "gpt-5.4", model: createModel({ id: "gpt-5.4", provider: "openai", api: "openai-completions", baseUrl: "https://api.openai.com/v1", input: ["text", "image"], contextWindow: 1_050_000, maxTokens: 128_000, }), }), { api: "openai-responses", }, ); }); it("owns refresh fallback for accountId extraction failures", async () => { const provider = requireProviderContractProvider("openai"); const credential = { type: "oauth" as const, provider: "openai", access: "cached-access-token", refresh: "refresh-token", expires: Date.now() - 60_000, }; refreshOpenAICodexTokenMock.mockRejectedValueOnce( new Error("Failed to extract accountId from token"), ); await expect(provider.refreshOAuth?.(credential)).resolves.toEqual(credential); }); it("owns forward-compat codex models", () => { const provider = requireProviderContractProvider("openai"); // Codex gpt-5.4 has no OpenAI manifest row; this keeps its transport mapping independent. const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.4", authProfileMode: "oauth", providerConfig: codexProviderConfig, modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5.2-codex" ? createModel({ id, api: "openai-chatgpt-responses", provider: "openai", baseUrl: "https://chatgpt.com/backend-api", }) : null, } as never, }); expectFields(model, { id: "gpt-5.4", provider: "openai", api: "openai-chatgpt-responses", contextWindow: 1_050_000, maxTokens: 128_000, }); }); it("keeps OpenClaw cost metadata but applies Codex context metadata for gpt-5.5 models", () => { const provider = requireProviderContractProvider("openai"); const manifestModel = createManifestModel("gpt-5.5", { api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", contextWindow: 272_000, }); const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.5", authProfileMode: "oauth", providerConfig: codexProviderConfig, modelRegistry: { find: (_provider: string, id: string) => (id === "gpt-5.5" ? manifestModel : null), } as never, }); expectFields(model, { ...manifestModel, api: "openai-chatgpt-responses", baseUrl: codexProviderConfig.baseUrl, contextWindow: 400_000, }); }); it("claims codex mini models through the Codex OAuth route", () => { const provider = requireProviderContractProvider("openai"); const manifestTemplate = createManifestModel("gpt-5.5", { id: "gpt-5.4", name: "gpt-5.4", api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", contextWindow: 272_000, }); const model = provider.resolveDynamicModel?.({ provider: "openai", modelId: "gpt-5.4-mini", authProfileMode: "oauth", providerConfig: codexProviderConfig, modelRegistry: { find: (_provider: string, id: string) => (id === "gpt-5.4" ? manifestTemplate : null), } as never, }); expectFields(model, { id: "gpt-5.4-mini", provider: "openai", api: "openai-chatgpt-responses", contextWindow: 400_000, contextTokens: 272_000, maxTokens: 128_000, cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, }); }); it("owns codex transport defaults", () => { const provider = requireProviderContractProvider("openai"); expect( provider.prepareExtraParams?.({ provider: "openai", modelId: "gpt-5.4", model: createModel({ id: "gpt-5.4", provider: "openai", api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }), extraParams: { temperature: 0.2 }, }), ).toEqual({ temperature: 0.2, transport: "auto", }); }); it("owns usage snapshot fetching", async () => { const provider = requireProviderContractProvider("openai"); const mockFetch = createProviderUsageFetch(async (url) => { if (url.includes("chatgpt.com/backend-api/wham/usage")) { return makeResponse(200, { rate_limit: { primary_window: { used_percent: 12, limit_window_seconds: 10800, reset_at: 1_705_000, }, }, plan_type: "Plus", }); } return makeResponse(404, "not found"); }); await expect( provider.fetchUsageSnapshot?.({ config: {} as never, env: {} as NodeJS.ProcessEnv, provider: "openai", token: "codex-token", accountId: "acc-1", timeoutMs: 5_000, fetchFn: mockFetch as unknown as typeof fetch, }), ).resolves.toEqual({ provider: "openai", displayName: "OpenAI", windows: [{ label: "3h", usedPercent: 12, resetAt: 1_705_000_000 }], plan: "Plus", }); }); }); } export function describeOpenRouterProviderRuntimeContract( load: ProviderRuntimeContractPluginLoader, ) { describe("openrouter provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["openrouter"], pluginId: "openrouter", name: "OpenRouter", load }, ]); it("owns dynamic OpenRouter model defaults", () => { const provider = requireProviderContractProvider("openrouter"); // OpenRouter owns a runtime-discovered catalog, so these synthetic defaults stay literal. const model = provider.resolveDynamicModel?.({ provider: "openrouter", modelId: "x-ai/grok-4-1-fast", modelRegistry: { find: () => null, } as never, }); expectFields(model, { id: "x-ai/grok-4-1-fast", provider: "openrouter", api: "openai-completions", baseUrl: "https://openrouter.ai/api/v1", maxTokens: 8192, }); }); }); } export function describeVeniceProviderRuntimeContract( load: ProviderRuntimeContractPluginLoader, manifestCatalog: unknown, ) { describe("venice provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const createManifestModel = createManifestModelFactory("venice", manifestCatalog); const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["venice"], pluginId: "venice", name: "Venice", load }, ]); it("owns xai downstream compat flags for grok-backed Venice models", () => { const provider = requireProviderContractProvider("venice"); const manifestModel = createManifestModel("grok-4-5"); const model = provider.normalizeResolvedModel?.({ provider: "venice", modelId: manifestModel.id, model: manifestModel, }); const { compat: _manifestCompat, ...manifestFields } = manifestModel; expectFields(model, manifestFields); expect(requireRecord(model?.compat, "compat")).toMatchObject({ toolSchemaProfile: "xai", toolCallArgumentsEncoding: "html-entities", }); }); }); } export function describeZAIProviderRuntimeContract(load: ProviderRuntimeContractPluginLoader) { describe("zai provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ { providerIds: ["zai"], pluginId: "zai", name: "Z.AI", load }, ]); it("owns glm-5 forward-compat resolution", () => { const provider = requireProviderContractProvider("zai"); // Neither the synthetic glm-5 id nor its glm-4.7 template has a current manifest row. const model = provider.resolveDynamicModel?.({ provider: "zai", modelId: "glm-5", modelRegistry: { find: (_provider: string, id: string) => id === "glm-4.7" ? createModel({ id, api: "openai-completions", provider: "zai", baseUrl: "https://api.z.ai/api/paas/v4", reasoning: false, contextWindow: 202_752, maxTokens: 16_384, }) : null, } as never, }); expectFields(model, { id: "glm-5", provider: "zai", api: "openai-completions", reasoning: true, }); }); it("owns Z.AI token-limit overflow classification", () => { const provider = requireProviderContractProvider("zai"); expect( provider.matchesContextOverflowError?.({ errorMessage: "code 1210: tokens in request more than max tokens allowed", }), ).toBe(true); expect( provider.matchesContextOverflowError?.({ errorMessage: "code 1261: Prompt exceeds max length", }), ).toBe(true); expect( provider.matchesContextOverflowError?.({ errorMessage: "code 1210: invalid request parameters", }), ).toBe(false); }); it("owns usage auth resolution", async () => { const provider = requireProviderContractProvider("zai"); await expect( provider.resolveUsageAuth?.({ config: {} as never, env: { ZAI_API_KEY: "env-zai-token", } as NodeJS.ProcessEnv, provider: "zai", resolveApiKeyFromConfigAndStore: () => "env-zai-token", resolveOAuthToken: async () => null, }), ).resolves.toEqual({ token: "env-zai-token", }); }); it("owns usage snapshot fetching", async () => { const provider = requireProviderContractProvider("zai"); const mockFetch = createProviderUsageFetch(async (url) => { if (url.includes("api.z.ai/api/monitor/usage/quota/limit")) { return makeResponse(200, { success: true, code: 200, data: { planName: "Pro", limits: [ { type: "TOKENS_LIMIT", percentage: 25, unit: 3, number: 6, nextResetTime: "2026-01-07T06:00:00Z", }, ], }, }); } return makeResponse(404, "not found"); }); await expect( provider.fetchUsageSnapshot?.({ config: {} as never, env: {} as NodeJS.ProcessEnv, provider: "zai", token: "env-zai-token", timeoutMs: 5_000, fetchFn: mockFetch as unknown as typeof fetch, }), ).resolves.toEqual({ provider: "zai", displayName: "z.ai", windows: [{ label: "Tokens (6h)", usedPercent: 25, resetAt: 1_767_765_600_000 }], plan: "Pro", }); }); }); } /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */