Skip to content

Commit 44121e5

Browse files
colegottdankclaude
andcommitted
feat: add GLM (ZhipuAI) support to playground
- Add ZhipuAI provider for direct GLM API access - Add glm-4.6:zai and glm-4.7:zai endpoints - Add provider model ID to canonical model ID mapping - Update playground to resolve provider-specific model IDs (e.g., "zai-glm-4.7") to canonical IDs (e.g., "glm-4.7") - Update model registry to include providerModelIdMap in response This allows users to open requests made with provider-specific model IDs (like Cerebras's "zai-glm-4.7") in the playground without falling back to gpt-4o-mini. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d7d8b4b commit 44121e5

File tree

12 files changed

+235
-22
lines changed

12 files changed

+235
-22
lines changed

packages/__tests__/cost/__snapshots__/registrySnapshots.test.ts.snap

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6866,6 +6866,27 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = `
68666866
"*",
68676867
],
68686868
},
6869+
"glm-4.6:zai": {
6870+
"context": 128000,
6871+
"crossRegion": false,
6872+
"maxTokens": 4096,
6873+
"modelId": "glm-4-plus",
6874+
"parameters": [
6875+
"functions",
6876+
"max_tokens",
6877+
"response_format",
6878+
"stop",
6879+
"temperature",
6880+
"tool_choice",
6881+
"tools",
6882+
"top_p",
6883+
],
6884+
"provider": "zai",
6885+
"ptbEnabled": true,
6886+
"regions": [
6887+
"*",
6888+
],
6889+
},
68696890
"glm-4.7:baseten": {
68706891
"context": 200000,
68716892
"crossRegion": false,
@@ -7016,6 +7037,27 @@ exports[`Registry Snapshots endpoint configurations snapshot 1`] = `
70167037
"*",
70177038
],
70187039
},
7040+
"glm-4.7:zai": {
7041+
"context": 128000,
7042+
"crossRegion": false,
7043+
"maxTokens": 4096,
7044+
"modelId": "glm-4-plus",
7045+
"parameters": [
7046+
"functions",
7047+
"max_tokens",
7048+
"response_format",
7049+
"stop",
7050+
"temperature",
7051+
"tool_choice",
7052+
"tools",
7053+
"top_p",
7054+
],
7055+
"provider": "zai",
7056+
"ptbEnabled": true,
7057+
"regions": [
7058+
"*",
7059+
],
7060+
},
70197061
},
70207062
}
70217063
`;
@@ -7440,6 +7482,8 @@ exports[`Registry Snapshots model coverage snapshot 1`] = `
74407482
"fireworks",
74417483
"novita",
74427484
"novita",
7485+
"zai",
7486+
"zai",
74437487
],
74447488
}
74457489
`;
@@ -9572,6 +9616,13 @@ exports[`Registry Snapshots pricing snapshot 1`] = `
95729616
"threshold": 0,
95739617
},
95749618
],
9619+
"zai": [
9620+
{
9621+
"input": 0.00000714,
9622+
"output": 0.00000714,
9623+
"threshold": 0,
9624+
},
9625+
],
95759626
},
95769627
}
95779628
`;
@@ -9877,6 +9928,7 @@ exports[`Registry Snapshots verify registry state 1`] = `
98779928
"baseten",
98789929
"canopywave",
98799930
"novita",
9931+
"zai",
98809932
],
98819933
},
98829934
{
@@ -9887,6 +9939,7 @@ exports[`Registry Snapshots verify registry state 1`] = `
98879939
"cerebras",
98889940
"fireworks",
98899941
"novita",
9942+
"zai",
98909943
],
98919944
},
98929945
{
@@ -10522,6 +10575,10 @@ exports[`Registry Snapshots verify registry state 1`] = `
1052210575
"modelCount": 8,
1052310576
"provider": "xai",
1052410577
},
10578+
{
10579+
"modelCount": 2,
10580+
"provider": "zai",
10581+
},
1052510582
],
1052610583
"ptbEnabledModels": [
1052710584
"chatgpt-4o-latest",
@@ -10639,9 +10696,9 @@ exports[`Registry Snapshots verify registry state 1`] = `
1063910696
"claude-3.5-haiku:anthropic:*",
1064010697
],
1064110698
"totalArchivedConfigs": 0,
10642-
"totalEndpoints": 317,
10643-
"totalModelProviderConfigs": 317,
10699+
"totalEndpoints": 319,
10700+
"totalModelProviderConfigs": 319,
1064410701
"totalModelsWithPtb": 105,
10645-
"totalProviders": 21,
10702+
"totalProviders": 22,
1064610703
}
1064710704
`;

packages/cost/models/authors/zai/glm-4/endpoints.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,62 @@ export const endpoints = {
313313
"*": {},
314314
},
315315
},
316+
"glm-4.6:zai": {
317+
providerModelId: "glm-4-plus",
318+
provider: "zai",
319+
author: "zai",
320+
pricing: [
321+
{
322+
threshold: 0,
323+
input: 0.00000714, // 0.05 CNY/1K tokens ≈ $0.00714/1K
324+
output: 0.00000714,
325+
},
326+
],
327+
contextLength: 128_000,
328+
maxCompletionTokens: 4_096,
329+
supportedParameters: [
330+
"functions",
331+
"tool_choice",
332+
"tools",
333+
"response_format",
334+
"max_tokens",
335+
"temperature",
336+
"top_p",
337+
"stop",
338+
],
339+
ptbEnabled: true,
340+
endpointConfigs: {
341+
"*": {},
342+
},
343+
},
344+
"glm-4.7:zai": {
345+
providerModelId: "glm-4-plus",
346+
provider: "zai",
347+
author: "zai",
348+
pricing: [
349+
{
350+
threshold: 0,
351+
input: 0.00000714, // 0.05 CNY/1K tokens ≈ $0.00714/1K
352+
output: 0.00000714,
353+
},
354+
],
355+
contextLength: 128_000,
356+
maxCompletionTokens: 4_096,
357+
supportedParameters: [
358+
"functions",
359+
"tool_choice",
360+
"tools",
361+
"response_format",
362+
"max_tokens",
363+
"temperature",
364+
"top_p",
365+
"stop",
366+
],
367+
ptbEnabled: true,
368+
endpointConfigs: {
369+
"*": {},
370+
},
371+
},
316372
} satisfies Partial<
317373
Record<
318374
`${ZaiModelName}:${ModelProviderName}` | ZaiModelName,

packages/cost/models/build-indexes.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export interface ModelIndexes {
6565
providerModelIdToConfig: Map<string, ModelProviderConfig>;
6666
providerModelIdAliasToConfig: Map<string, ModelProviderConfig>;
6767
modelToArchivedEndpointConfigs: Map<string, ModelProviderConfig>;
68+
// Reverse lookup: provider model ID -> canonical model ID (without needing to know the provider)
69+
providerModelIdToCanonicalModelId: Map<string, ModelName>;
6870
}
6971

7072
export function buildIndexes(
@@ -92,6 +94,7 @@ export function buildIndexes(
9294
new Map();
9395
const modelToArchivedEndpointConfigs: Map<string, ModelProviderConfig> =
9496
new Map();
97+
const providerModelIdToCanonicalModelId: Map<string, ModelName> = new Map();
9598

9699
for (const [configKey, config] of Object.entries(modelProviderConfigs)) {
97100
const typedConfigKey = configKey as ModelProviderConfigId;
@@ -115,6 +118,15 @@ export function buildIndexes(
115118
}
116119
}
117120

121+
// Store reverse lookup: providerModelId -> canonical model ID
122+
// This allows finding the canonical model from any provider model ID
123+
providerModelIdToCanonicalModelId.set(config.providerModelId, modelName);
124+
if (config.providerModelIdAliases) {
125+
for (const alias of config.providerModelIdAliases) {
126+
providerModelIdToCanonicalModelId.set(alias, modelName);
127+
}
128+
}
129+
118130
// Track provider to models mapping
119131
if (!providerToModels.has(provider)) {
120132
providerToModels.set(provider, new Set());
@@ -219,5 +231,6 @@ export function buildIndexes(
219231
providerModelIdToConfig,
220232
providerModelIdAliasToConfig,
221233
modelToArchivedEndpointConfigs,
234+
providerModelIdToCanonicalModelId,
222235
};
223236
}

packages/cost/models/providers/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { OpenRouterProvider } from "./openrouter";
1919
import { PerplexityProvider } from "./perplexity";
2020
import { VertexProvider } from "./vertex";
2121
import { XAIProvider } from "./xai";
22+
import { ZaiProvider } from "./zai";
2223

2324
// Create singleton instances (stateless, so safe to share)
2425
export const providers = {
@@ -42,7 +43,8 @@ export const providers = {
4243
openrouter: new OpenRouterProvider(),
4344
perplexity: new PerplexityProvider(),
4445
vertex: new VertexProvider(),
45-
xai: new XAIProvider()
46+
xai: new XAIProvider(),
47+
zai: new ZaiProvider(),
4648
} as const;
4749

4850
export type ModelProviderName = keyof typeof providers;
@@ -87,6 +89,7 @@ export const ResponsesAPIEnabledProviders: ModelProviderName[] = [
8789
"xai",
8890
"baseten",
8991
"fireworks",
92+
"zai",
9093

9194
// anthropic and chat completions provider
9295
"vertex"

packages/cost/models/providers/priorities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const PROVIDER_PRIORITIES: Record<ModelProviderName, number> = {
3838
perplexity: 4,
3939
vertex: 4,
4040
xai: 4,
41+
zai: 4,
4142

4243
// Priority 5: Secondary primary providers
4344
"google-ai-studio": 5,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BaseProvider } from "./base";
2+
import type { Endpoint, RequestParams } from "../types";
3+
4+
export class ZaiProvider extends BaseProvider {
5+
readonly displayName = "ZhipuAI";
6+
readonly baseUrl = "https://open.bigmodel.cn/";
7+
readonly auth = "api-key" as const;
8+
readonly pricingPages = ["https://open.bigmodel.cn/pricing"];
9+
readonly modelPages = ["https://open.bigmodel.cn/dev/howuse/model"];
10+
11+
buildUrl(endpoint: Endpoint, requestParams: RequestParams): string {
12+
return `${this.baseUrl}api/paas/v4/chat/completions`;
13+
}
14+
}

packages/cost/models/registry.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,25 @@ function getModelProviderConfigByVersion(
276276
return ok(archivedConfig || null);
277277
}
278278

279+
/**
280+
* Find the canonical model ID from a provider model ID.
281+
* This is useful when a request uses a provider-specific model ID (e.g., "zai-glm-4.7")
282+
* and we need to find the canonical model ID (e.g., "glm-4.7").
283+
*
284+
* @param providerModelId - The provider-specific model ID (e.g., "zai-glm-4.7", "accounts/fireworks/models/glm-4p7")
285+
* @returns The canonical model ID if found, null otherwise
286+
*/
287+
function findCanonicalModelId(providerModelId: string): string | null {
288+
// First check if it's already a canonical model ID
289+
if (allModels[providerModelId as keyof typeof allModels]) {
290+
return providerModelId;
291+
}
292+
293+
// Look up in the reverse mapping
294+
const canonical = indexes.providerModelIdToCanonicalModelId.get(providerModelId);
295+
return canonical ?? null;
296+
}
297+
279298
export const registry = {
280299
getAllModelIds,
281300
getAllModelsWithIds,
@@ -294,4 +313,5 @@ export const registry = {
294313
getModelProviderEntry,
295314
getModelProviderConfigByVersion,
296315
getAuthorByModel,
316+
findCanonicalModelId,
297317
};

packages/cost/usage/getUsageProcessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function getUsageProcessor(
2727
case "fireworks":
2828
case "cerebras":
2929
case "perplexity":
30+
case "zai":
3031
return new OpenAIUsageProcessor();
3132
case "anthropic":
3233
return new AnthropicUsageProcessor();

valhalla/jawn/src/controllers/public/modelRegistryController.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ interface ModelRegistryResponse {
7373
authors: string[];
7474
capabilities: ModelCapability[];
7575
};
76+
// Map of provider model IDs to canonical model IDs
77+
// Useful for resolving provider-specific model names (e.g., "zai-glm-4.7" -> "glm-4.7")
78+
providerModelIdMap?: Record<string, string>;
7679
}
7780

7881
@Route("/v1/public/model-registry")
@@ -400,6 +403,7 @@ export class ModelRegistryController extends Controller {
400403
const availableProviders = new Set<string>();
401404
const availableAuthors = new Set<string>();
402405
const availableCapabilities = new Set<ModelCapability>();
406+
const providerModelIdMap: Record<string, string> = {};
403407

404408
models.forEach((model) => {
405409
availableAuthors.add(model.author);
@@ -421,6 +425,10 @@ export class ModelRegistryController extends Controller {
421425
) {
422426
availableCapabilities.add("caching");
423427
}
428+
// Build provider model ID to canonical model ID mapping
429+
if (ep.endpoint?.providerModelId) {
430+
providerModelIdMap[ep.endpoint.providerModelId] = model.id;
431+
}
424432
});
425433
});
426434
const providersWithDisplayNames = Array.from(availableProviders)
@@ -439,11 +447,13 @@ export class ModelRegistryController extends Controller {
439447
authors: Array.from(availableAuthors).sort(),
440448
capabilities: Array.from(availableCapabilities).sort(),
441449
},
450+
providerModelIdMap,
442451
});
443452
} catch (error) {
444453
console.error("Error fetching model registry:", error);
445454
this.setStatus(500);
446455
return err("Internal server error while fetching model registry");
447456
}
448457
}
458+
449459
}

web/components/templates/playground/components/PlaygroundHeader.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ const PlaygroundHeader = ({
8585
onDismissUnsupportedModelWarning,
8686
}: PlaygroundHeaderProps) => {
8787
const [modelListOpen, setModelListOpen] = useState<boolean>(false);
88-
const { data: playgroundModels, isLoading: modelsLoading } =
88+
const { data: modelRegistryData, isLoading: modelsLoading } =
8989
useModelRegistry();
90+
const playgroundModels = modelRegistryData?.models ?? [];
9091

9192
// Get display name for selected model
92-
const selectedModelData = playgroundModels?.find(
93+
const selectedModelData = playgroundModels.find(
9394
(m) => m.id === selectedModel,
9495
);
9596
const displayName =

0 commit comments

Comments
 (0)