From bab7df10e590e12b334ef8713587866513396299 Mon Sep 17 00:00:00 2001 From: bhuvan2134686 Date: Fri, 14 Aug 2026 13:29:44 +1000 Subject: [PATCH 1/3] Integrate SCX.ai as a provider for GLM-5.2 and Qwen3.8 Max SCX.ai is an OpenAI-compatible inference provider running on renewable-powered infrastructure in Australia. Adds the provider plus two models the registry does not have yet: GLM-5.2 and Qwen3.8 Max. Verified live against api.scx.ai: model ids, the 131072 max_tokens ceiling, the error envelope, and OpenAI-shaped usage including prompt_tokens_details.cached_tokens. Co-Authored-By: Claude Opus 5 (1M context) --- .../registrySnapshots.test.ts.snap | 108 ++++++- packages/cost/models/authors/alibaba/index.ts | 4 + .../authors/alibaba/qwen3.8/endpoints.ts | 45 +++ .../models/authors/alibaba/qwen3.8/models.ts | 17 ++ .../models/authors/zai/glm-5/endpoints.ts | 49 ++++ .../cost/models/authors/zai/glm-5/models.ts | 17 ++ packages/cost/models/authors/zai/index.ts | 8 +- packages/cost/models/provider-helpers.ts | 5 + packages/cost/models/providers/index.ts | 3 + packages/cost/models/providers/priorities.ts | 1 + packages/cost/models/providers/scx.ts | 13 + packages/cost/providers/mappings.ts | 9 + packages/cost/usage/getUsageProcessor.ts | 1 + web/data/providers.ts | 11 + web/public/assets/home/providers/scx.webp | Bin 0 -> 7924 bytes .../test/ai-gateway/registry-alibaba.spec.ts | 198 +++++++++++++ worker/test/ai-gateway/registry-zai.spec.ts | 266 ++++++++++++++++++ worker/test/setup.ts | 18 ++ 18 files changed, 767 insertions(+), 6 deletions(-) create mode 100644 packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts create mode 100644 packages/cost/models/authors/alibaba/qwen3.8/models.ts create mode 100644 packages/cost/models/authors/zai/glm-5/endpoints.ts create mode 100644 packages/cost/models/authors/zai/glm-5/models.ts create mode 100644 packages/cost/models/providers/scx.ts create mode 100644 web/public/assets/home/providers/scx.webp diff --git a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap index ab023ddc00..c66572b6e8 100644 --- a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap +++ b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap @@ -299,6 +299,30 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` ], }, }, + "alibaba/qwen3.8": { + "qwen3.8-max:scx": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 131072, + "modelId": "Qwen3.8-Max", + "parameters": [ + "frequency_penalty", + "max_tokens", + "presence_penalty", + "reasoning", + "response_format", + "stop", + "temperature", + "tools", + "top_p", + ], + "provider": "scx", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, "anthropic/claude-3-haiku-20240307": { "claude-3-haiku-20240307:anthropic": { "context": 200000, @@ -7292,6 +7316,34 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` ], }, }, + "zai/glm-5": { + "glm-5.2:scx": { + "context": 1000000, + "crossRegion": false, + "maxTokens": 131072, + "modelId": "GLM-5.2", + "parameters": [ + "frequency_penalty", + "functions", + "max_tokens", + "presence_penalty", + "reasoning", + "response_format", + "seed", + "stop", + "structured_outputs", + "temperature", + "tool_choice", + "tools", + "top_p", + ], + "provider": "scx", + "ptbEnabled": true, + "regions": [ + "*", + ], + }, + }, } `; @@ -7312,6 +7364,9 @@ exports[`Registry Snapshots model coverage snapshot 1`] = ` "novita", "openrouter", ], + "alibaba/qwen3.8": [ + "scx", + ], "anthropic/claude-3-haiku-20240307": [ "anthropic", "helicone", @@ -7732,6 +7787,9 @@ exports[`Registry Snapshots model coverage snapshot 1`] = ` "novita", "novita", ], + "zai/glm-5": [ + "scx", + ], } `; @@ -7790,6 +7848,18 @@ exports[`Registry Snapshots pricing snapshot 1`] = ` }, ], }, + "alibaba/qwen3.8": { + "scx": [ + { + "cacheMultipliers": { + "cachedInput": 0.1157, + }, + "input": 0.000001815, + "output": 0.0000054461, + "threshold": 0, + }, + ], + }, "anthropic/claude-3-haiku-20240307": { "anthropic": [ { @@ -9953,6 +10023,18 @@ exports[`Registry Snapshots pricing snapshot 1`] = ` }, ], }, + "zai/glm-5": { + "scx": [ + { + "cacheMultipliers": { + "cachedInput": 0.25, + }, + "input": 6.6e-7, + "output": 0.0000023106, + "threshold": 0, + }, + ], + }, } `; @@ -10278,6 +10360,12 @@ exports[`Registry Snapshots verify registry state 1`] = ` "novita", ], }, + { + "model": "glm-5.2", + "providers": [ + "scx", + ], + }, { "model": "gpt-4.1", "providers": [ @@ -10813,6 +10901,12 @@ exports[`Registry Snapshots verify registry state 1`] = ` "novita", ], }, + { + "model": "qwen3.8-max", + "providers": [ + "scx", + ], + }, { "model": "sonar", "providers": [ @@ -10921,6 +11015,10 @@ exports[`Registry Snapshots verify registry state 1`] = ` "modelCount": 5, "provider": "perplexity", }, + { + "modelCount": 2, + "provider": "scx", + }, { "modelCount": 23, "provider": "vertex", @@ -10967,6 +11065,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` "gemma2-9b-it", "glm-4.6", "glm-4.7", + "glm-5.2", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", @@ -11034,6 +11133,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` "qwen3-coder-30b-a3b-instruct", "qwen3-next-80b-a3b-instruct", "qwen3-vl-235b-a22b-instruct", + "qwen3.8-max", "sonar", "sonar-deep-research", "sonar-pro", @@ -11049,9 +11149,9 @@ exports[`Registry Snapshots verify registry state 1`] = ` "claude-3.5-haiku:anthropic:*", ], "totalArchivedConfigs": 0, - "totalEndpoints": 329, - "totalModelProviderConfigs": 329, - "totalModelsWithPtb": 108, - "totalProviders": 21, + "totalEndpoints": 331, + "totalModelProviderConfigs": 331, + "totalModelsWithPtb": 110, + "totalProviders": 22, } `; diff --git a/packages/cost/models/authors/alibaba/index.ts b/packages/cost/models/authors/alibaba/index.ts index 405f6a179a..0aa0d466a5 100644 --- a/packages/cost/models/authors/alibaba/index.ts +++ b/packages/cost/models/authors/alibaba/index.ts @@ -8,19 +8,23 @@ import type { ModelConfig, ModelProviderConfig } from "../../types"; // Import models import { models as qwen25Models } from "./qwen2.5/models"; import { models as qwen3Models } from "./qwen3/models"; +import { models as qwen38Models } from "./qwen3.8/models"; // Import endpoints import { endpoints as qwen25Endpoints } from "./qwen2.5/endpoints"; import { endpoints as qwen3Endpoints } from "./qwen3/endpoints"; +import { endpoints as qwen38Endpoints } from "./qwen3.8/endpoints"; // Aggregate models export const alibabaModels = { ...qwen25Models, ...qwen3Models, + ...qwen38Models, } satisfies Record; // Aggregate endpoints export const alibabaEndpointConfig = { ...qwen25Endpoints, ...qwen3Endpoints, + ...qwen38Endpoints, } satisfies Record; diff --git a/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts b/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts new file mode 100644 index 0000000000..379ff4f6e9 --- /dev/null +++ b/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts @@ -0,0 +1,45 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { Qwen38ModelName } from "./models"; + +export const endpoints = { + "qwen3.8-max:scx": { + providerModelId: "Qwen3.8-Max", + provider: "scx", + author: "alibaba", + pricing: [ + { + threshold: 0, + input: 0.000001815, + output: 0.0000054461, + cacheMultipliers: { + cachedInput: 0.1157, // $0.21/M cache read vs $1.815/M input + }, + }, + ], + contextLength: 1_000_000, + maxCompletionTokens: 131_072, + // Qwen thinking models reject tool_choice "required" or an object, so + // tool_choice is intentionally left out. + supportedParameters: [ + "max_tokens", + "temperature", + "top_p", + "stop", + "frequency_penalty", + "presence_penalty", + "reasoning", + "tools", + "response_format", + ], + ptbEnabled: true, + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record< + `${Qwen38ModelName}:${ModelProviderName}` | Qwen38ModelName, + ModelProviderConfig + > +>; diff --git a/packages/cost/models/authors/alibaba/qwen3.8/models.ts b/packages/cost/models/authors/alibaba/qwen3.8/models.ts new file mode 100644 index 0000000000..d0c20f9bd0 --- /dev/null +++ b/packages/cost/models/authors/alibaba/qwen3.8/models.ts @@ -0,0 +1,17 @@ +import { ModelConfig } from "../../../types"; + +export const models = { + "qwen3.8-max": { + name: "Qwen3.8 Max", + author: "alibaba", + description: + "Flagship 2.4-trillion-parameter MoE model in the Qwen3.8 series with native visual understanding, excelling at coding, professional work, and long-horizon autonomous agent tasks.", + contextLength: 1_000_000, + maxOutputTokens: 131_072, + created: "2026-08-02T00:00:00.000Z", + modality: { inputs: ["text", "image"], outputs: ["text"] }, + tokenizer: "Qwen", + }, +} satisfies Record; + +export type Qwen38ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/zai/glm-5/endpoints.ts b/packages/cost/models/authors/zai/glm-5/endpoints.ts new file mode 100644 index 0000000000..a768cce4df --- /dev/null +++ b/packages/cost/models/authors/zai/glm-5/endpoints.ts @@ -0,0 +1,49 @@ +import { ModelProviderName } from "../../../providers"; +import type { ModelProviderConfig } from "../../../types"; +import { Glm5ModelName } from "./models"; + +export const endpoints = { + "glm-5.2:scx": { + providerModelId: "GLM-5.2", + provider: "scx", + author: "zai", + pricing: [ + { + threshold: 0, + input: 0.00000066, + output: 0.0000023106, + cacheMultipliers: { + cachedInput: 0.25, // $0.165/M cache read vs $0.66/M input + }, + }, + ], + quantization: "fp8", + contextLength: 1_000_000, + // SCX rejects max_tokens above 131072 even though the model card advertises 128k + maxCompletionTokens: 131_072, + supportedParameters: [ + "functions", + "structured_outputs", + "reasoning", + "tool_choice", + "tools", + "response_format", + "max_tokens", + "temperature", + "top_p", + "stop", + "frequency_penalty", + "presence_penalty", + "seed", + ], + ptbEnabled: true, + endpointConfigs: { + "*": {}, + }, + }, +} satisfies Partial< + Record< + `${Glm5ModelName}:${ModelProviderName}` | Glm5ModelName, + ModelProviderConfig + > +>; diff --git a/packages/cost/models/authors/zai/glm-5/models.ts b/packages/cost/models/authors/zai/glm-5/models.ts new file mode 100644 index 0000000000..f9c2dc2a85 --- /dev/null +++ b/packages/cost/models/authors/zai/glm-5/models.ts @@ -0,0 +1,17 @@ +import type { ModelConfig } from "../../../types"; + +export const models = { + "glm-5.2": { + name: "Zai GLM-5.2", + author: "zai", + description: + "GLM-5.2 is Zhipu AI's flagship model for long-horizon coding and agentic engineering tasks. It extends the GLM series to a 1M token context window and adds stronger multi-step reasoning and tool orchestration.", + contextLength: 1_000_000, + maxOutputTokens: 131_072, + created: "2026-06-13T00:00:00.000Z", + modality: { inputs: ["text"], outputs: ["text"] }, + tokenizer: "Zai", + }, +} satisfies Record; + +export type Glm5ModelName = keyof typeof models; diff --git a/packages/cost/models/authors/zai/index.ts b/packages/cost/models/authors/zai/index.ts index 4957ee5f06..c74349cf70 100644 --- a/packages/cost/models/authors/zai/index.ts +++ b/packages/cost/models/authors/zai/index.ts @@ -8,13 +8,17 @@ import type { ModelConfig, ModelProviderConfig } from "../../types"; // Import models and endpoints import { models as glm4Models } from "./glm-4/models"; import { endpoints as glm4Endpoints } from "./glm-4/endpoints"; +import { models as glm5Models } from "./glm-5/models"; +import { endpoints as glm5Endpoints } from "./glm-5/endpoints"; // Aggregate models export const zaiModels = { - ...glm4Models + ...glm4Models, + ...glm5Models } satisfies Record; // Aggregate endpoints export const zaiEndpointConfig = { - ...glm4Endpoints + ...glm4Endpoints, + ...glm5Endpoints } satisfies Record; diff --git a/packages/cost/models/provider-helpers.ts b/packages/cost/models/provider-helpers.ts index ae00f70ee3..4db7d2b2d9 100644 --- a/packages/cost/models/provider-helpers.ts +++ b/packages/cost/models/provider-helpers.ts @@ -62,6 +62,8 @@ export function heliconeProviderToModelProviderName( return "fireworks"; case "CANOPYWAVE": return "canopywave"; + case "SCX": + return "scx"; // new registry does not have case "LOCAL": case "HELICONE": @@ -138,6 +140,9 @@ export const dbProviderToProvider = ( if (provider === "canopywave" || provider === "Canopy Wave") { return "canopywave"; } + if (provider === "scx" || provider === "SCX.ai") { + return "scx"; + } if (provider === "novita" || provider === "Novita") { return "novita"; } diff --git a/packages/cost/models/providers/index.ts b/packages/cost/models/providers/index.ts index 963717d50d..e79dccff46 100644 --- a/packages/cost/models/providers/index.ts +++ b/packages/cost/models/providers/index.ts @@ -17,6 +17,7 @@ import { NovitaProvider } from "./novita"; import { OpenAIProvider } from "./openai"; import { OpenRouterProvider } from "./openrouter"; import { PerplexityProvider } from "./perplexity"; +import { ScxProvider } from "./scx"; import { VertexProvider } from "./vertex"; import { XAIProvider } from "./xai"; @@ -41,6 +42,7 @@ export const providers = { openai: new OpenAIProvider(), openrouter: new OpenRouterProvider(), perplexity: new PerplexityProvider(), + scx: new ScxProvider(), vertex: new VertexProvider(), xai: new XAIProvider() } as const; @@ -73,6 +75,7 @@ export const ResponsesAPIEnabledProviders: ModelProviderName[] = [ "chutes", "deepinfra", "deepseek", + "scx", // has known issues with returning structured JSONS // should be okay to enable, but its not stable enough to add without request diff --git a/packages/cost/models/providers/priorities.ts b/packages/cost/models/providers/priorities.ts index 38df67014c..cada1f0b9b 100644 --- a/packages/cost/models/providers/priorities.ts +++ b/packages/cost/models/providers/priorities.ts @@ -36,6 +36,7 @@ export const PROVIDER_PRIORITIES: Record = { novita: 4, perplexity: 4, + scx: 4, vertex: 4, xai: 4, diff --git a/packages/cost/models/providers/scx.ts b/packages/cost/models/providers/scx.ts new file mode 100644 index 0000000000..24535d77e7 --- /dev/null +++ b/packages/cost/models/providers/scx.ts @@ -0,0 +1,13 @@ +import { BaseProvider } from "./base"; + +export class ScxProvider extends BaseProvider { + readonly displayName = "SCX.ai"; + readonly baseUrl = "https://api.scx.ai/"; + readonly auth = "api-key" as const; + readonly pricingPages = ["https://scx.ai/pricing"]; + readonly modelPages = ["https://scx.ai/models"]; + + buildUrl(): string { + return `${this.baseUrl}v1/chat/completions`; + } +} diff --git a/packages/cost/providers/mappings.ts b/packages/cost/providers/mappings.ts index 7e856b54c5..776c6eead5 100644 --- a/packages/cost/providers/mappings.ts +++ b/packages/cost/providers/mappings.ts @@ -94,6 +94,9 @@ const cerebras = /^https:\/\/api\.cerebras\.ai/; // https://inference.canopywave.io const canopywave = /^https:\/\/inference\.canopywave\.io/; +// https://api.scx.ai +const scx = /^https:\/\/api\.scx\.ai/; + export const providersNames = [ "OPENAI", "ANTHROPIC", @@ -132,6 +135,7 @@ export const providersNames = [ "CEREBRAS", "BASETEN", "CANOPYWAVE", + "SCX", ] as const; export type ProviderName = (typeof providersNames)[number]; @@ -325,6 +329,11 @@ export const providers: { pattern: canopywave, provider: "CANOPYWAVE", costs: [], + }, + { + pattern: scx, + provider: "SCX", + costs: [], } ]; diff --git a/packages/cost/usage/getUsageProcessor.ts b/packages/cost/usage/getUsageProcessor.ts index 3dc612abf9..d8f0479b1f 100644 --- a/packages/cost/usage/getUsageProcessor.ts +++ b/packages/cost/usage/getUsageProcessor.ts @@ -27,6 +27,7 @@ export function getUsageProcessor( case "fireworks": case "cerebras": case "perplexity": + case "scx": return new OpenAIUsageProcessor(); case "anthropic": return new AnthropicUsageProcessor(); diff --git a/web/data/providers.ts b/web/data/providers.ts index 8a22235279..3311305bee 100644 --- a/web/data/providers.ts +++ b/web/data/providers.ts @@ -264,6 +264,17 @@ export const providers: Provider[] = [ apiKeyPlaceholder: "...", relevanceScore: 4, }, + { + id: "scx", + name: "SCX.ai", + logoUrl: "/assets/home/providers/scx.webp", + description: + "Configure your SCX.ai API keys for Australian sovereign, renewable-powered inference", + docsUrl: "https://docs.helicone.ai/getting-started/integration-methods", + apiKeyLabel: "SCX.ai API Key", + apiKeyPlaceholder: "...", + relevanceScore: 4, + }, { id: "chutes", name: "Chutes", diff --git a/web/public/assets/home/providers/scx.webp b/web/public/assets/home/providers/scx.webp new file mode 100644 index 0000000000000000000000000000000000000000..bfd7ca71bf86abb33a4137fdbf798a4a524fe123 GIT binary patch literal 7924 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 { beforeEach(() => { // Clear all mocks between tests @@ -5565,4 +5571,196 @@ describe("Alibaba Registry Tests", () => { }); }); + + describe("BYOK Tests - qwen3.8-max", () => { + describe("qwen3.8-max", () => { + it("should handle scx provider", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "Qwen3.8-Max", + data: createOpenAIMockResponse("Qwen3.8-Max"), + expects: scxAuthExpectations, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle tool calls with scx provider", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + request: { + body: { + messages: [{ role: "user", content: "What's the weather?" }], + tools: [ + { + type: "function", + function: { + name: "get_weather", + description: "Get current weather", + parameters: { + type: "object", + properties: { + location: { type: "string" }, + }, + required: ["location"], + }, + }, + }, + ], + temperature: 0.7, + max_tokens: 1000, + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "Qwen3.8-Max", + data: createOpenAIMockResponse("Qwen3.8-Max"), + expects: { + ...scxAuthExpectations, + bodyContains: ["tools", "get_weather"], + }, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle image input with scx provider", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + request: { + body: { + messages: [ + { + role: "user", + content: [ + { type: "text", text: "What is in this image?" }, + { + type: "image_url", + image_url: { url: "https://example.com/image.png" }, + }, + ], + }, + ], + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "Qwen3.8-Max", + data: createOpenAIMockResponse("Qwen3.8-Max"), + expects: { + ...scxAuthExpectations, + bodyContains: ["image_url"], + }, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle all supported parameters with scx provider", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + request: { + body: { + messages: [ + { role: "user", content: "Test comprehensive parameters" }, + ], + max_tokens: 1000, + temperature: 0.8, + top_p: 0.95, + stop: ["STOP"], + frequency_penalty: 0.2, + presence_penalty: 0.1, + response_format: { type: "text" }, + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "Qwen3.8-Max", + data: createOpenAIMockResponse("Qwen3.8-Max"), + expects: { + ...scxAuthExpectations, + bodyContains: [ + "max_tokens", + "temperature", + "top_p", + "stop", + "frequency_penalty", + "presence_penalty", + "response_format", + ], + }, + }, + ], + finalStatus: 200, + }, + })); + }); + }); + + describe("Error scenarios - qwen3.8-max with SCX Provider", () => { + it("should handle SCX provider failure", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 500, + errorMessage: "SCX service unavailable", + }, + ], + finalStatus: 500, + }, + })); + + it("should handle rate limiting from SCX", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 429, + errorMessage: "Rate limit exceeded", + }, + ], + finalStatus: 429, + }, + })); + + it("should handle authentication failure from SCX", () => + runGatewayTest({ + model: "qwen3.8-max/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 401, + errorMessage: "Invalid API key", + }, + ], + finalStatus: 401, + }, + })); + }); }); diff --git a/worker/test/ai-gateway/registry-zai.spec.ts b/worker/test/ai-gateway/registry-zai.spec.ts index 0fd8930b99..f8540fe20f 100644 --- a/worker/test/ai-gateway/registry-zai.spec.ts +++ b/worker/test/ai-gateway/registry-zai.spec.ts @@ -17,6 +17,13 @@ const canopywaveAuthExpectations = { }, }; +// Define auth expectations for SCX.ai provider +const scxAuthExpectations = { + headers: { + Authorization: /^Bearer /, + }, +}; + describe("Zai Registry Tests", () => { beforeEach(() => { // Clear all mocks between tests @@ -848,4 +855,263 @@ describe("Zai Registry Tests", () => { }, })); }); + + describe("BYOK Tests - glm-5.2", () => { + describe("glm-5.2", () => { + it("should handle scx provider", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", + data: createOpenAIMockResponse("GLM-5.2"), + expects: scxAuthExpectations, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle tool calls with scx provider", () => + runGatewayTest({ + model: "glm-5.2/scx", + request: { + body: { + messages: [{ role: "user", content: "What's the weather?" }], + tools: [ + { + type: "function", + function: { + name: "get_weather", + description: "Get current weather", + parameters: { + type: "object", + properties: { + location: { type: "string" }, + }, + required: ["location"], + }, + }, + }, + ], + tool_choice: "auto", + temperature: 0.7, + max_tokens: 1000, + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", + data: createOpenAIMockResponse("GLM-5.2"), + expects: { + ...scxAuthExpectations, + bodyContains: ["tools", "tool_choice", "get_weather"], + }, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle reasoning parameter with scx provider", () => + runGatewayTest({ + model: "glm-5.2/scx", + request: { + body: { + messages: [{ role: "user", content: "Solve this problem" }], + reasoning: { type: "step_by_step" }, + temperature: 0.7, + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", + data: createOpenAIMockResponse("GLM-5.2"), + expects: { + ...scxAuthExpectations, + bodyContains: ["reasoning", "step_by_step", "temperature"], + }, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle all supported parameters with scx provider", () => + runGatewayTest({ + model: "glm-5.2/scx", + request: { + body: { + messages: [ + { role: "user", content: "Test comprehensive parameters" }, + ], + max_tokens: 1000, + temperature: 0.8, + top_p: 0.95, + stop: ["STOP"], + frequency_penalty: 0.2, + presence_penalty: 0.1, + seed: 12345, + response_format: { type: "text" }, + }, + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", + data: createOpenAIMockResponse("GLM-5.2"), + expects: { + ...scxAuthExpectations, + bodyContains: [ + "max_tokens", + "temperature", + "top_p", + "stop", + "frequency_penalty", + "presence_penalty", + "seed", + "response_format", + ], + }, + }, + ], + finalStatus: 200, + }, + })); + }); + }); + + describe("Error scenarios - glm-5.2 with SCX Provider", () => { + it("should handle SCX provider failure", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 500, + errorMessage: "SCX service unavailable", + }, + ], + finalStatus: 500, + }, + })); + + it("should handle rate limiting from SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 429, + errorMessage: "Rate limit exceeded", + }, + ], + finalStatus: 429, + }, + })); + + it("should handle authentication failure from SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 401, + errorMessage: "Invalid API key", + }, + ], + finalStatus: 401, + }, + })); + + it("should handle model not found error from SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 404, + errorMessage: "Model not found", + }, + ], + finalStatus: 500, + }, + })); + + it("should handle timeout from SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "failure", + statusCode: 408, + errorMessage: "Request timeout", + }, + ], + finalStatus: 500, + }, + })); + }); + + describe("Provider validation - glm-5.2 with SCX", () => { + it("should handle provider model ID mapping correctly for SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", // Should map to the correct provider model ID + data: createOpenAIMockResponse("GLM-5.2"), + expects: scxAuthExpectations, + }, + ], + finalStatus: 200, + }, + })); + + it("should handle request body mapping for SCX", () => + runGatewayTest({ + model: "glm-5.2/scx", + request: { + bodyMapping: "NO_MAPPING", + }, + expected: { + providers: [ + { + url: "https://api.scx.ai/v1/chat/completions", + response: "success", + model: "GLM-5.2", + data: createOpenAIMockResponse("GLM-5.2"), + expects: { + ...scxAuthExpectations, + }, + }, + ], + finalStatus: 200, + }, + })); + }); }); diff --git a/worker/test/setup.ts b/worker/test/setup.ts index ff949da294..1e3d7ff00d 100644 --- a/worker/test/setup.ts +++ b/worker/test/setup.ts @@ -234,6 +234,15 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: isByokEnabled, }, + scx: { + org_id: "test-org-id", + provider_name: "scx", + decrypted_provider_key: "test-scx-api-key", + decrypted_provider_secret_key: null, + auth_type: "api_key", + config: null, + byok_enabled: isByokEnabled, + }, nebius: { org_id: "test-org-id", provider_name: "nebius", @@ -428,6 +437,15 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: true, }, + scx: { + org_id: "0afe3a6e-d095-4ec0-bc1e-2af6f57bd2a5", + provider_name: "scx", + decrypted_provider_key: "helicone-scx-api-key", + decrypted_provider_secret_key: null, + auth_type: "api_key", + config: null, + byok_enabled: true, + }, nebius: { org_id: "0afe3a6e-d095-4ec0-bc1e-2af6f57bd2a5", provider_name: "nebius", From ec7caa6539a0e3ea347edf043d4b13665de7519e Mon Sep 17 00:00:00 2001 From: bhuvan2134686 Date: Fri, 14 Aug 2026 13:35:39 +1000 Subject: [PATCH 2/3] Add SCX to the nightly gateway provider tests Co-Authored-By: Claude Opus 5 (1M context) --- e2e/tests/nightly/gateway.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/tests/nightly/gateway.test.ts b/e2e/tests/nightly/gateway.test.ts index 8a1182a0d5..04dbe0c0eb 100644 --- a/e2e/tests/nightly/gateway.test.ts +++ b/e2e/tests/nightly/gateway.test.ts @@ -79,6 +79,9 @@ describe('AI Gateway Tests', () => { 'perplexity': { model: 'sonar/perplexity', }, + 'scx': { + model: 'glm-5.2/scx', + }, 'vertex': { model: 'gemini-2.5-flash-lite/vertex', }, From fa3b2638105e669b410c8d7299d35a1b58d0a5d1 Mon Sep 17 00:00:00 2001 From: bhuvan2134686 Date: Fri, 14 Aug 2026 13:51:39 +1000 Subject: [PATCH 3/3] Rename provider id from scx to scx-ai Co-Authored-By: Claude Opus 5 (1M context) --- e2e/tests/nightly/gateway.test.ts | 4 +- .../registrySnapshots.test.ts.snap | 22 +++++------ .../authors/alibaba/qwen3.8/endpoints.ts | 4 +- .../models/authors/zai/glm-5/endpoints.ts | 4 +- packages/cost/models/provider-helpers.ts | 6 +-- packages/cost/models/providers/index.ts | 6 +-- packages/cost/models/providers/priorities.ts | 2 +- .../models/providers/{scx.ts => scx-ai.ts} | 2 +- packages/cost/providers/mappings.ts | 4 +- packages/cost/usage/getUsageProcessor.ts | 2 +- web/data/providers.ts | 4 +- .../home/providers/{scx.webp => scx-ai.webp} | Bin .../test/ai-gateway/registry-alibaba.spec.ts | 24 ++++++------ worker/test/ai-gateway/registry-zai.spec.ts | 36 +++++++++--------- worker/test/setup.ts | 12 +++--- 15 files changed, 66 insertions(+), 66 deletions(-) rename packages/cost/models/providers/{scx.ts => scx-ai.ts} (87%) rename web/public/assets/home/providers/{scx.webp => scx-ai.webp} (100%) diff --git a/e2e/tests/nightly/gateway.test.ts b/e2e/tests/nightly/gateway.test.ts index 04dbe0c0eb..12c7815468 100644 --- a/e2e/tests/nightly/gateway.test.ts +++ b/e2e/tests/nightly/gateway.test.ts @@ -79,8 +79,8 @@ describe('AI Gateway Tests', () => { 'perplexity': { model: 'sonar/perplexity', }, - 'scx': { - model: 'glm-5.2/scx', + 'scx-ai': { + model: 'glm-5.2/scx-ai', }, 'vertex': { model: 'gemini-2.5-flash-lite/vertex', diff --git a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap index c66572b6e8..4bd1a6e950 100644 --- a/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap +++ b/packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap @@ -300,7 +300,7 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` }, }, "alibaba/qwen3.8": { - "qwen3.8-max:scx": { + "qwen3.8-max:scx-ai": { "context": 1000000, "crossRegion": false, "maxTokens": 131072, @@ -316,7 +316,7 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` "tools", "top_p", ], - "provider": "scx", + "provider": "scx-ai", "ptbEnabled": true, "regions": [ "*", @@ -7317,7 +7317,7 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` }, }, "zai/glm-5": { - "glm-5.2:scx": { + "glm-5.2:scx-ai": { "context": 1000000, "crossRegion": false, "maxTokens": 131072, @@ -7337,7 +7337,7 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = ` "tools", "top_p", ], - "provider": "scx", + "provider": "scx-ai", "ptbEnabled": true, "regions": [ "*", @@ -7365,7 +7365,7 @@ exports[`Registry Snapshots model coverage snapshot 1`] = ` "openrouter", ], "alibaba/qwen3.8": [ - "scx", + "scx-ai", ], "anthropic/claude-3-haiku-20240307": [ "anthropic", @@ -7788,7 +7788,7 @@ exports[`Registry Snapshots model coverage snapshot 1`] = ` "novita", ], "zai/glm-5": [ - "scx", + "scx-ai", ], } `; @@ -7849,7 +7849,7 @@ exports[`Registry Snapshots pricing snapshot 1`] = ` ], }, "alibaba/qwen3.8": { - "scx": [ + "scx-ai": [ { "cacheMultipliers": { "cachedInput": 0.1157, @@ -10024,7 +10024,7 @@ exports[`Registry Snapshots pricing snapshot 1`] = ` ], }, "zai/glm-5": { - "scx": [ + "scx-ai": [ { "cacheMultipliers": { "cachedInput": 0.25, @@ -10363,7 +10363,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` { "model": "glm-5.2", "providers": [ - "scx", + "scx-ai", ], }, { @@ -10904,7 +10904,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` { "model": "qwen3.8-max", "providers": [ - "scx", + "scx-ai", ], }, { @@ -11017,7 +11017,7 @@ exports[`Registry Snapshots verify registry state 1`] = ` }, { "modelCount": 2, - "provider": "scx", + "provider": "scx-ai", }, { "modelCount": 23, diff --git a/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts b/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts index 379ff4f6e9..3e982542db 100644 --- a/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts +++ b/packages/cost/models/authors/alibaba/qwen3.8/endpoints.ts @@ -3,9 +3,9 @@ import type { ModelProviderConfig } from "../../../types"; import { Qwen38ModelName } from "./models"; export const endpoints = { - "qwen3.8-max:scx": { + "qwen3.8-max:scx-ai": { providerModelId: "Qwen3.8-Max", - provider: "scx", + provider: "scx-ai", author: "alibaba", pricing: [ { diff --git a/packages/cost/models/authors/zai/glm-5/endpoints.ts b/packages/cost/models/authors/zai/glm-5/endpoints.ts index a768cce4df..d2cee354d8 100644 --- a/packages/cost/models/authors/zai/glm-5/endpoints.ts +++ b/packages/cost/models/authors/zai/glm-5/endpoints.ts @@ -3,9 +3,9 @@ import type { ModelProviderConfig } from "../../../types"; import { Glm5ModelName } from "./models"; export const endpoints = { - "glm-5.2:scx": { + "glm-5.2:scx-ai": { providerModelId: "GLM-5.2", - provider: "scx", + provider: "scx-ai", author: "zai", pricing: [ { diff --git a/packages/cost/models/provider-helpers.ts b/packages/cost/models/provider-helpers.ts index 4db7d2b2d9..14c81facb5 100644 --- a/packages/cost/models/provider-helpers.ts +++ b/packages/cost/models/provider-helpers.ts @@ -63,7 +63,7 @@ export function heliconeProviderToModelProviderName( case "CANOPYWAVE": return "canopywave"; case "SCX": - return "scx"; + return "scx-ai"; // new registry does not have case "LOCAL": case "HELICONE": @@ -140,8 +140,8 @@ export const dbProviderToProvider = ( if (provider === "canopywave" || provider === "Canopy Wave") { return "canopywave"; } - if (provider === "scx" || provider === "SCX.ai") { - return "scx"; + if (provider === "scx-ai" || provider === "SCX.ai") { + return "scx-ai"; } if (provider === "novita" || provider === "Novita") { return "novita"; diff --git a/packages/cost/models/providers/index.ts b/packages/cost/models/providers/index.ts index e79dccff46..dfa3ee9293 100644 --- a/packages/cost/models/providers/index.ts +++ b/packages/cost/models/providers/index.ts @@ -17,7 +17,7 @@ import { NovitaProvider } from "./novita"; import { OpenAIProvider } from "./openai"; import { OpenRouterProvider } from "./openrouter"; import { PerplexityProvider } from "./perplexity"; -import { ScxProvider } from "./scx"; +import { ScxAiProvider } from "./scx-ai"; import { VertexProvider } from "./vertex"; import { XAIProvider } from "./xai"; @@ -42,7 +42,7 @@ export const providers = { openai: new OpenAIProvider(), openrouter: new OpenRouterProvider(), perplexity: new PerplexityProvider(), - scx: new ScxProvider(), + "scx-ai": new ScxAiProvider(), vertex: new VertexProvider(), xai: new XAIProvider() } as const; @@ -75,7 +75,7 @@ export const ResponsesAPIEnabledProviders: ModelProviderName[] = [ "chutes", "deepinfra", "deepseek", - "scx", + "scx-ai", // has known issues with returning structured JSONS // should be okay to enable, but its not stable enough to add without request diff --git a/packages/cost/models/providers/priorities.ts b/packages/cost/models/providers/priorities.ts index cada1f0b9b..ea1bea2eae 100644 --- a/packages/cost/models/providers/priorities.ts +++ b/packages/cost/models/providers/priorities.ts @@ -36,7 +36,7 @@ export const PROVIDER_PRIORITIES: Record = { novita: 4, perplexity: 4, - scx: 4, + "scx-ai": 4, vertex: 4, xai: 4, diff --git a/packages/cost/models/providers/scx.ts b/packages/cost/models/providers/scx-ai.ts similarity index 87% rename from packages/cost/models/providers/scx.ts rename to packages/cost/models/providers/scx-ai.ts index 24535d77e7..c11fd42386 100644 --- a/packages/cost/models/providers/scx.ts +++ b/packages/cost/models/providers/scx-ai.ts @@ -1,6 +1,6 @@ import { BaseProvider } from "./base"; -export class ScxProvider extends BaseProvider { +export class ScxAiProvider extends BaseProvider { readonly displayName = "SCX.ai"; readonly baseUrl = "https://api.scx.ai/"; readonly auth = "api-key" as const; diff --git a/packages/cost/providers/mappings.ts b/packages/cost/providers/mappings.ts index 776c6eead5..663c508569 100644 --- a/packages/cost/providers/mappings.ts +++ b/packages/cost/providers/mappings.ts @@ -95,7 +95,7 @@ const cerebras = /^https:\/\/api\.cerebras\.ai/; const canopywave = /^https:\/\/inference\.canopywave\.io/; // https://api.scx.ai -const scx = /^https:\/\/api\.scx\.ai/; +const scxAi = /^https:\/\/api\.scx\.ai/; export const providersNames = [ "OPENAI", @@ -331,7 +331,7 @@ export const providers: { costs: [], }, { - pattern: scx, + pattern: scxAi, provider: "SCX", costs: [], } diff --git a/packages/cost/usage/getUsageProcessor.ts b/packages/cost/usage/getUsageProcessor.ts index d8f0479b1f..349638a36a 100644 --- a/packages/cost/usage/getUsageProcessor.ts +++ b/packages/cost/usage/getUsageProcessor.ts @@ -27,7 +27,7 @@ export function getUsageProcessor( case "fireworks": case "cerebras": case "perplexity": - case "scx": + case "scx-ai": return new OpenAIUsageProcessor(); case "anthropic": return new AnthropicUsageProcessor(); diff --git a/web/data/providers.ts b/web/data/providers.ts index 3311305bee..ea44159c67 100644 --- a/web/data/providers.ts +++ b/web/data/providers.ts @@ -265,9 +265,9 @@ export const providers: Provider[] = [ relevanceScore: 4, }, { - id: "scx", + id: "scx-ai", name: "SCX.ai", - logoUrl: "/assets/home/providers/scx.webp", + logoUrl: "/assets/home/providers/scx-ai.webp", description: "Configure your SCX.ai API keys for Australian sovereign, renewable-powered inference", docsUrl: "https://docs.helicone.ai/getting-started/integration-methods", diff --git a/web/public/assets/home/providers/scx.webp b/web/public/assets/home/providers/scx-ai.webp similarity index 100% rename from web/public/assets/home/providers/scx.webp rename to web/public/assets/home/providers/scx-ai.webp diff --git a/worker/test/ai-gateway/registry-alibaba.spec.ts b/worker/test/ai-gateway/registry-alibaba.spec.ts index 82b1544376..cdbd345670 100644 --- a/worker/test/ai-gateway/registry-alibaba.spec.ts +++ b/worker/test/ai-gateway/registry-alibaba.spec.ts @@ -35,7 +35,7 @@ const canopywaveAuthExpectations = { }, }; -const scxAuthExpectations = { +const scxAiAuthExpectations = { headers: { Authorization: /^Bearer /, }, @@ -5576,7 +5576,7 @@ describe("Alibaba Registry Tests", () => { describe("qwen3.8-max", () => { it("should handle scx provider", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", expected: { providers: [ { @@ -5584,7 +5584,7 @@ describe("Alibaba Registry Tests", () => { response: "success", model: "Qwen3.8-Max", data: createOpenAIMockResponse("Qwen3.8-Max"), - expects: scxAuthExpectations, + expects: scxAiAuthExpectations, }, ], finalStatus: 200, @@ -5593,7 +5593,7 @@ describe("Alibaba Registry Tests", () => { it("should handle tool calls with scx provider", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", request: { body: { messages: [{ role: "user", content: "What's the weather?" }], @@ -5625,7 +5625,7 @@ describe("Alibaba Registry Tests", () => { model: "Qwen3.8-Max", data: createOpenAIMockResponse("Qwen3.8-Max"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: ["tools", "get_weather"], }, }, @@ -5636,7 +5636,7 @@ describe("Alibaba Registry Tests", () => { it("should handle image input with scx provider", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", request: { body: { messages: [ @@ -5661,7 +5661,7 @@ describe("Alibaba Registry Tests", () => { model: "Qwen3.8-Max", data: createOpenAIMockResponse("Qwen3.8-Max"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: ["image_url"], }, }, @@ -5672,7 +5672,7 @@ describe("Alibaba Registry Tests", () => { it("should handle all supported parameters with scx provider", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", request: { body: { messages: [ @@ -5695,7 +5695,7 @@ describe("Alibaba Registry Tests", () => { model: "Qwen3.8-Max", data: createOpenAIMockResponse("Qwen3.8-Max"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: [ "max_tokens", "temperature", @@ -5717,7 +5717,7 @@ describe("Alibaba Registry Tests", () => { describe("Error scenarios - qwen3.8-max with SCX Provider", () => { it("should handle SCX provider failure", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", expected: { providers: [ { @@ -5733,7 +5733,7 @@ describe("Alibaba Registry Tests", () => { it("should handle rate limiting from SCX", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", expected: { providers: [ { @@ -5749,7 +5749,7 @@ describe("Alibaba Registry Tests", () => { it("should handle authentication failure from SCX", () => runGatewayTest({ - model: "qwen3.8-max/scx", + model: "qwen3.8-max/scx-ai", expected: { providers: [ { diff --git a/worker/test/ai-gateway/registry-zai.spec.ts b/worker/test/ai-gateway/registry-zai.spec.ts index f8540fe20f..766d9fc004 100644 --- a/worker/test/ai-gateway/registry-zai.spec.ts +++ b/worker/test/ai-gateway/registry-zai.spec.ts @@ -18,7 +18,7 @@ const canopywaveAuthExpectations = { }; // Define auth expectations for SCX.ai provider -const scxAuthExpectations = { +const scxAiAuthExpectations = { headers: { Authorization: /^Bearer /, }, @@ -860,7 +860,7 @@ describe("Zai Registry Tests", () => { describe("glm-5.2", () => { it("should handle scx provider", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -868,7 +868,7 @@ describe("Zai Registry Tests", () => { response: "success", model: "GLM-5.2", data: createOpenAIMockResponse("GLM-5.2"), - expects: scxAuthExpectations, + expects: scxAiAuthExpectations, }, ], finalStatus: 200, @@ -877,7 +877,7 @@ describe("Zai Registry Tests", () => { it("should handle tool calls with scx provider", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", request: { body: { messages: [{ role: "user", content: "What's the weather?" }], @@ -910,7 +910,7 @@ describe("Zai Registry Tests", () => { model: "GLM-5.2", data: createOpenAIMockResponse("GLM-5.2"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: ["tools", "tool_choice", "get_weather"], }, }, @@ -921,7 +921,7 @@ describe("Zai Registry Tests", () => { it("should handle reasoning parameter with scx provider", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", request: { body: { messages: [{ role: "user", content: "Solve this problem" }], @@ -937,7 +937,7 @@ describe("Zai Registry Tests", () => { model: "GLM-5.2", data: createOpenAIMockResponse("GLM-5.2"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: ["reasoning", "step_by_step", "temperature"], }, }, @@ -948,7 +948,7 @@ describe("Zai Registry Tests", () => { it("should handle all supported parameters with scx provider", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", request: { body: { messages: [ @@ -972,7 +972,7 @@ describe("Zai Registry Tests", () => { model: "GLM-5.2", data: createOpenAIMockResponse("GLM-5.2"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, bodyContains: [ "max_tokens", "temperature", @@ -995,7 +995,7 @@ describe("Zai Registry Tests", () => { describe("Error scenarios - glm-5.2 with SCX Provider", () => { it("should handle SCX provider failure", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1011,7 +1011,7 @@ describe("Zai Registry Tests", () => { it("should handle rate limiting from SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1027,7 +1027,7 @@ describe("Zai Registry Tests", () => { it("should handle authentication failure from SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1043,7 +1043,7 @@ describe("Zai Registry Tests", () => { it("should handle model not found error from SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1059,7 +1059,7 @@ describe("Zai Registry Tests", () => { it("should handle timeout from SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1077,7 +1077,7 @@ describe("Zai Registry Tests", () => { describe("Provider validation - glm-5.2 with SCX", () => { it("should handle provider model ID mapping correctly for SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", expected: { providers: [ { @@ -1085,7 +1085,7 @@ describe("Zai Registry Tests", () => { response: "success", model: "GLM-5.2", // Should map to the correct provider model ID data: createOpenAIMockResponse("GLM-5.2"), - expects: scxAuthExpectations, + expects: scxAiAuthExpectations, }, ], finalStatus: 200, @@ -1094,7 +1094,7 @@ describe("Zai Registry Tests", () => { it("should handle request body mapping for SCX", () => runGatewayTest({ - model: "glm-5.2/scx", + model: "glm-5.2/scx-ai", request: { bodyMapping: "NO_MAPPING", }, @@ -1106,7 +1106,7 @@ describe("Zai Registry Tests", () => { model: "GLM-5.2", data: createOpenAIMockResponse("GLM-5.2"), expects: { - ...scxAuthExpectations, + ...scxAiAuthExpectations, }, }, ], diff --git a/worker/test/setup.ts b/worker/test/setup.ts index 1e3d7ff00d..b040fbb2a0 100644 --- a/worker/test/setup.ts +++ b/worker/test/setup.ts @@ -234,10 +234,10 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: isByokEnabled, }, - scx: { + "scx-ai": { org_id: "test-org-id", - provider_name: "scx", - decrypted_provider_key: "test-scx-api-key", + provider_name: "scx-ai", + decrypted_provider_key: "test-scx-ai-api-key", decrypted_provider_secret_key: null, auth_type: "api_key", config: null, @@ -437,10 +437,10 @@ vi.mock("@supabase/supabase-js", () => ({ config: null, byok_enabled: true, }, - scx: { + "scx-ai": { org_id: "0afe3a6e-d095-4ec0-bc1e-2af6f57bd2a5", - provider_name: "scx", - decrypted_provider_key: "helicone-scx-api-key", + provider_name: "scx-ai", + decrypted_provider_key: "helicone-scx-ai-api-key", decrypted_provider_secret_key: null, auth_type: "api_key", config: null,