Download src/plugin-sdk/test-helpers/provider-runtime-contract.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 34.6 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/plugin-sdk/test-helpers/provider-runtime-contract.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/plugin-sdk/test-helpers/provider-runtime-contract.ts
-
curl -L -o provider-runtime-contract.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/plugin-sdk/test-helpers/provider-runtime-contract.ts
34.6 kB
| // 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<ProviderRuntimeModel> & Pick<ProviderRuntimeModel, "id">) { | |
| 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> = {}): 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<string, unknown> { | |
| expect(value, label).toBeTypeOf("object"); | |
| expect(value, label).not.toBeNull(); | |
| return value as Record<string, unknown>; | |
| } | |
| function expectFields(value: unknown, fields: Record<string, unknown>) { | |
| 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<typeof registerProviderPlugin>[0]["plugin"]; | |
| }>; | |
| function installRuntimeHooks(fixtures: readonly ProviderRuntimeContractFixture[]) { | |
| const providers = new Map<string, ProviderPlugin>(); | |
| let loadPromise: Promise<void> | 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. */ | |