@tonyclaw/agent-inspector 2.0.19 → 2.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-D-90PIPw.js → CompareDrawer-ztKMd-FE.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-BT8Ilsbs.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-DzlLYoTO.js → ReplayDialog-RNhfLSsW.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-0hbyf6oa.js → RequestAnatomy-fsUI5HhH.js} +1 -1
  6. package/.output/public/assets/{ResponseView-CKe0tNqr.js → ResponseView-B3YTDBBX.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-enhfdeNh.js → StreamingChunkSequence-CbGqQ0mO.js} +1 -1
  8. package/.output/public/assets/_sessionId-C9cpyfLX.js +1 -0
  9. package/.output/public/assets/index-0mTTpvA2.js +1 -0
  10. package/.output/public/assets/index-DynnYt7S.css +1 -0
  11. package/.output/public/assets/{main-o9-hg8g1.js → main-lTQKpxiU.js} +2 -2
  12. package/.output/server/{_sessionId-F8lm6PIb.mjs → _sessionId-DsF6LFJG.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-Cf1yfwfd.mjs → CompareDrawer-DgdZcMpg.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-Bmu9867L.mjs → ProxyViewerContainer-DaFW1IVu.mjs} +99 -23
  15. package/.output/server/_ssr/{ReplayDialog-DxZfLBOe.mjs → ReplayDialog-BOaTX1ir.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-CbdERGZd.mjs → RequestAnatomy-C-2_dejd.mjs} +2 -2
  17. package/.output/server/_ssr/{ResponseView-CvLd6qxI.mjs → ResponseView-BtRVhgzy.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-B_lkRB6o.mjs → StreamingChunkSequence-DZaerowX.mjs} +2 -2
  19. package/.output/server/_ssr/{index-ejrSItfz.mjs → index-B3_fgwFy.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-SbCnbDc2.mjs → router-CDOnHAWy.mjs} +191 -6
  22. package/.output/server/{_tanstack-start-manifest_v-yx8ftqWz.mjs → _tanstack-start-manifest_v-Brrx1NEa.mjs} +1 -1
  23. package/.output/server/index.mjs +61 -61
  24. package/package.json +1 -1
  25. package/src/components/providers/ProviderForm.tsx +107 -16
  26. package/src/components/ui/scroll-area.tsx +2 -2
  27. package/src/lib/providerModelMetadata.ts +67 -3
  28. package/src/proxy/formats/index.ts +1 -0
  29. package/src/proxy/formats/openai/index.ts +1 -0
  30. package/src/proxy/formats/openai/zhipuProvider.ts +31 -0
  31. package/src/proxy/providerImporters.ts +47 -0
  32. package/src/proxy/providers.ts +55 -0
  33. package/src/proxy/upstream.ts +25 -0
  34. package/.output/public/assets/ProxyViewerContainer-CxRDWacg.js +0 -114
  35. package/.output/public/assets/_sessionId-CJ-SusES.js +0 -1
  36. package/.output/public/assets/index-BbFsmQRC.js +0 -1
  37. package/.output/public/assets/index-BfBgrkgU.css +0 -1
@@ -5,23 +5,78 @@ import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
5
5
  import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
6
6
  import { maskApiKey } from "../../lib/mask";
7
7
 
8
+ const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
9
+ const ZHIPU_CODING_PROVIDER_KEYWORDS = [
10
+ "zhipu coding",
11
+ "glm coding",
12
+ "bigmodel coding",
13
+ "z.ai coding",
14
+ "zai coding",
15
+ ];
16
+
8
17
  // Known provider presets - maps provider name keywords to their API URLs
9
18
  const KNOWN_PROVIDER_PRESETS: Record<
10
19
  string,
11
- { format: "anthropic" | "openai"; baseUrl: string; apiDocsUrl?: string }
20
+ { anthropicBaseUrl?: string; openaiBaseUrl?: string; apiDocsUrl?: string }
12
21
  > = {
13
22
  deepseek: {
14
- format: "openai",
15
- baseUrl: "https://api.deepseek.com",
23
+ openaiBaseUrl: "https://api.deepseek.com",
16
24
  },
17
25
  minimax: {
18
- format: "anthropic",
19
- baseUrl: "https://api.minimaxi.com/anthropic",
26
+ anthropicBaseUrl: "https://api.minimaxi.com/anthropic",
20
27
  apiDocsUrl: "https://platform.minimaxi.com/docs/api-reference/api-overview",
21
28
  },
29
+ "zhipu coding": {
30
+ anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
31
+ openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
32
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
33
+ },
34
+ "glm coding": {
35
+ anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
36
+ openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
37
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
38
+ },
39
+ "bigmodel coding": {
40
+ anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
41
+ openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
42
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
43
+ },
44
+ "z.ai coding": {
45
+ anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
46
+ openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
47
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
48
+ },
49
+ "zai coding": {
50
+ anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
51
+ openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
52
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
53
+ },
54
+ zhipu: {
55
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
56
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
57
+ },
58
+ zhipuai: {
59
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
60
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
61
+ },
62
+ bigmodel: {
63
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
64
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
65
+ },
66
+ "z.ai": {
67
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
68
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
69
+ },
70
+ zai: {
71
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
72
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
73
+ },
74
+ glm: {
75
+ openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
76
+ apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
77
+ },
22
78
  alibaba: {
23
- format: "openai",
24
- baseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
79
+ openaiBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
25
80
  },
26
81
  };
27
82
 
@@ -40,6 +95,22 @@ const MINIMAX_MODELS = [
40
95
  // Alibaba model options
41
96
  const ALIBABA_MODELS = ["glm-5", "glm-5.1", "glm-5.2", "qwen3.6-plus", "qwen3.7-max"];
42
97
 
98
+ // ZhipuAI / Z.AI model options
99
+ const ZHIPU_MODELS = [
100
+ "glm-5.2",
101
+ "glm-5.1",
102
+ "glm-5",
103
+ "glm-5-turbo",
104
+ "glm-4.7",
105
+ "glm-4.7-flashx",
106
+ "glm-4.6",
107
+ "glm-4.5",
108
+ "glm-4.5-air",
109
+ "glm-4.5-x",
110
+ "glm-4.5-airx",
111
+ "glm-4.5-flash",
112
+ ];
113
+
43
114
  type ProviderFormProps = {
44
115
  provider?: ProviderConfig;
45
116
  onSubmit: (data: {
@@ -151,6 +222,16 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
151
222
  const isMiniMax = name.toLowerCase().includes("minimax");
152
223
  // Check if Alibaba is detected
153
224
  const isAlibaba = name.toLowerCase().includes("alibaba");
225
+ const isZhipu = [...ZHIPU_PROVIDER_KEYWORDS, ...ZHIPU_CODING_PROVIDER_KEYWORDS].some((keyword) =>
226
+ name.toLowerCase().includes(keyword),
227
+ );
228
+ const suggestedModels = isMiniMax
229
+ ? MINIMAX_MODELS
230
+ : isZhipu
231
+ ? ZHIPU_MODELS
232
+ : isAlibaba
233
+ ? ALIBABA_MODELS
234
+ : [];
154
235
  const hasContextMetadata =
155
236
  provider?.modelMetadata?.some((metadata) => metadata.contextWindow !== undefined) ?? false;
156
237
 
@@ -182,10 +263,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
182
263
  const lowerName = name.toLowerCase();
183
264
  for (const [keyword, preset] of Object.entries(KNOWN_PROVIDER_PRESETS)) {
184
265
  if (lowerName.includes(keyword)) {
185
- if (preset.format === "anthropic" && !manualAnthropicUrlOverride) {
186
- setAnthropicBaseUrl(preset.baseUrl);
187
- } else if (preset.format === "openai" && !manualOpenaiUrlOverride) {
188
- setOpenaiBaseUrl(preset.baseUrl);
266
+ if (preset.anthropicBaseUrl !== undefined && !manualAnthropicUrlOverride) {
267
+ setAnthropicBaseUrl(preset.anthropicBaseUrl);
268
+ }
269
+ if (preset.openaiBaseUrl !== undefined && !manualOpenaiUrlOverride) {
270
+ setOpenaiBaseUrl(preset.openaiBaseUrl);
189
271
  }
190
272
  if (preset.apiDocsUrl !== undefined && !apiDocsUrl) {
191
273
  setApiDocsUrl(preset.apiDocsUrl);
@@ -198,6 +280,15 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
198
280
  if (keyword === "alibaba" && models.length === 1 && models[0] === "") {
199
281
  setModels([ALIBABA_MODELS[0] ?? ""]);
200
282
  }
283
+ // For ZhipuAI, auto-select the first model if not already set
284
+ if (
285
+ (ZHIPU_PROVIDER_KEYWORDS.includes(keyword) ||
286
+ ZHIPU_CODING_PROVIDER_KEYWORDS.includes(keyword)) &&
287
+ models.length === 1 &&
288
+ models[0] === ""
289
+ ) {
290
+ setModels([ZHIPU_MODELS[0] ?? ""]);
291
+ }
201
292
  break;
202
293
  }
203
294
  }
@@ -396,8 +487,8 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
396
487
  )}
397
488
  {modelMetadataUrl.trim() === "" && (
398
489
  <div className="rounded-md border border-border bg-muted/20 px-3 py-2 text-xs text-muted-foreground">
399
- Built-in limits cover DeepSeek v4 pro/flash and MiniMax M3/M2.7/M2.5/M2.1. Use a
400
- registry URL for private or custom model limits.
490
+ Built-in limits cover DeepSeek v4 pro/flash, MiniMax M3/M2.7/M2.5/M2.1, and GLM coding
491
+ models. Use a registry URL for private or custom model limits.
401
492
  </div>
402
493
  )}
403
494
  {provider !== undefined && !hasContextMetadata && modelMetadataUrl.trim() === "" && (
@@ -472,11 +563,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
472
563
  value={m}
473
564
  onChange={(e) => updateModel(i, e.target.value)}
474
565
  placeholder={
475
- isMiniMax || isAlibaba ? "Type or select a model..." : "Model name"
566
+ suggestedModels.length > 0 ? "Type or select a model..." : "Model name"
476
567
  }
477
568
  className="w-full rounded-md border border-input bg-background px-4 py-3 pr-8 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
478
569
  />
479
- {(isMiniMax || isAlibaba) && (
570
+ {suggestedModels.length > 0 && (
480
571
  <>
481
572
  <button
482
573
  type="button"
@@ -488,7 +579,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
488
579
  </button>
489
580
  {openModelDropdown === i && (
490
581
  <div className="absolute left-0 right-0 top-full mt-1 z-50 bg-popover border border-border rounded-md shadow-md max-h-48 overflow-y-auto">
491
- {(isMiniMax ? MINIMAX_MODELS : ALIBABA_MODELS).map((opt) => (
582
+ {suggestedModels.map((opt) => (
492
583
  <button
493
584
  key={opt}
494
585
  type="button"
@@ -11,12 +11,12 @@ function ScrollArea({
11
11
  return (
12
12
  <ScrollAreaPrimitive.Root
13
13
  data-slot="scroll-area"
14
- className={cn("relative", className)}
14
+ className={cn("relative overflow-hidden", className)}
15
15
  {...props}
16
16
  >
17
17
  <ScrollAreaPrimitive.Viewport
18
18
  data-slot="scroll-area-viewport"
19
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
19
+ className="focus-visible:ring-ring/50 size-full max-h-[inherit] rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
20
20
  >
21
21
  {children}
22
22
  </ScrollAreaPrimitive.Viewport>
@@ -47,6 +47,10 @@ export const HF_MINIMAX_M3_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M3";
47
47
  export const HF_MINIMAX_M2_7_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.7";
48
48
  export const HF_MINIMAX_M2_5_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.5";
49
49
  export const HF_MINIMAX_M2_1_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.1";
50
+ export const ZHIPU_GLM_4_5_URL = "https://docs.bigmodel.cn/cn/guide/models/text/glm-4.5";
51
+ export const ZHIPU_GLM_4_6_URL = "https://docs.bigmodel.cn/cn/guide/models/text/glm-4.6";
52
+ export const ZHIPU_GLM_4_7_URL = "https://docs.bigmodel.cn/cn/guide/models/text/glm-4.7";
53
+ export const ZHIPU_GLM_5_TURBO_URL = "https://docs.bigmodel.cn/cn/guide/models/text/glm-5-turbo";
50
54
  export const HF_GLM_5_URL = "https://huggingface.co/zai-org/GLM-5";
51
55
  export const HF_GLM_5_1_URL = "https://huggingface.co/zai-org/GLM-5.1";
52
56
  export const HF_GLM_5_2_URL = "https://huggingface.co/zai-org/GLM-5.2";
@@ -124,10 +128,63 @@ export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
124
128
  ],
125
129
  },
126
130
  {
127
- name: "GLM",
128
- aliases: ["glm", "zai", "zhipu", "bigmodel"],
129
- baseUrls: [],
131
+ name: "ZhipuAI",
132
+ aliases: ["glm", "zai", "zhipu", "zhipuai", "bigmodel"],
133
+ baseUrls: [
134
+ "https://open.bigmodel.cn/api/paas/v4",
135
+ "https://open.bigmodel.cn/api/coding/paas/v4",
136
+ "https://open.bigmodel.cn/api/anthropic",
137
+ ],
130
138
  models: [
139
+ {
140
+ id: "glm-4.5",
141
+ aliases: ["GLM-4.5"],
142
+ contextWindow: 131_072,
143
+ outputLimit: 98_304,
144
+ sourceUrl: ZHIPU_GLM_4_5_URL,
145
+ },
146
+ {
147
+ id: "glm-4.5-air",
148
+ aliases: ["GLM-4.5-Air"],
149
+ contextWindow: 131_072,
150
+ outputLimit: 98_304,
151
+ sourceUrl: ZHIPU_GLM_4_5_URL,
152
+ },
153
+ {
154
+ id: "glm-4.5-x",
155
+ aliases: ["GLM-4.5-X"],
156
+ contextWindow: 131_072,
157
+ outputLimit: 98_304,
158
+ sourceUrl: ZHIPU_GLM_4_5_URL,
159
+ },
160
+ {
161
+ id: "glm-4.5-airx",
162
+ aliases: ["GLM-4.5-AirX"],
163
+ contextWindow: 131_072,
164
+ outputLimit: 98_304,
165
+ sourceUrl: ZHIPU_GLM_4_5_URL,
166
+ },
167
+ {
168
+ id: "glm-4.5-flash",
169
+ aliases: ["GLM-4.5-Flash"],
170
+ contextWindow: 131_072,
171
+ outputLimit: 98_304,
172
+ sourceUrl: ZHIPU_GLM_4_5_URL,
173
+ },
174
+ {
175
+ id: "glm-4.6",
176
+ aliases: ["GLM-4.6"],
177
+ contextWindow: 202_752,
178
+ outputLimit: 65_536,
179
+ sourceUrl: ZHIPU_GLM_4_6_URL,
180
+ },
181
+ {
182
+ id: "glm-4.7",
183
+ aliases: ["GLM-4.7"],
184
+ contextWindow: 202_752,
185
+ outputLimit: 131_072,
186
+ sourceUrl: ZHIPU_GLM_4_7_URL,
187
+ },
131
188
  {
132
189
  id: "glm-5",
133
190
  aliases: ["GLM-5"],
@@ -146,6 +203,13 @@ export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
146
203
  contextWindow: 1_048_576,
147
204
  sourceUrl: HF_GLM_5_2_URL,
148
205
  },
206
+ {
207
+ id: "glm-5-turbo",
208
+ aliases: ["GLM-5-Turbo"],
209
+ contextWindow: 202_752,
210
+ outputLimit: 131_072,
211
+ sourceUrl: ZHIPU_GLM_5_TURBO_URL,
212
+ },
149
213
  ],
150
214
  },
151
215
  ],
@@ -5,6 +5,7 @@ import "./openai/index";
5
5
  // Import providers to trigger self-registration
6
6
  import "./anthropic/anthropicProvider";
7
7
  import "./openai/provider";
8
+ import "./openai/zhipuProvider";
8
9
  import "./openai/alibabaProvider";
9
10
 
10
11
  export type { FormatHandler, ParsedRequest } from "./handler";
@@ -22,4 +22,5 @@ export { extractOpenAIStream } from "./stream";
22
22
 
23
23
  // Providers
24
24
  export { openaiProvider } from "./provider";
25
+ export { zhipuProvider } from "./zhipuProvider";
25
26
  export { alibabaProvider } from "./alibabaProvider";
@@ -0,0 +1,31 @@
1
+ import type { CapturedLog } from "../../schemas";
2
+ import type { ProviderProtocol } from "../protocol";
3
+ import { registry } from "../providers";
4
+ import { extractOpenAIStream } from "./stream";
5
+
6
+ const DEFAULT_ZHIPU_UPSTREAM = "https://open.bigmodel.cn/api/paas/v4";
7
+ const ZHIPU_MODEL_PREFIXES = ["glm-", "charglm-", "cogview-", "embedding-", "rerank-"];
8
+
9
+ export const zhipuProvider: ProviderProtocol = {
10
+ name: "zhipu",
11
+
12
+ matches(model: string): boolean {
13
+ const normalized = model.toLowerCase().replace(/\s+/g, "-");
14
+ return ZHIPU_MODEL_PREFIXES.some((prefix) => normalized.startsWith(prefix));
15
+ },
16
+
17
+ getUpstreamBase(_isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string {
18
+ return providerConfig?.baseUrl ?? DEFAULT_ZHIPU_UPSTREAM;
19
+ },
20
+
21
+ extractStream(
22
+ raw: string,
23
+ log: CapturedLog,
24
+ fallbackModel?: string,
25
+ collectChunks?: boolean,
26
+ ): string {
27
+ return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
28
+ },
29
+ };
30
+
31
+ registry.register(zhipuProvider);
@@ -17,6 +17,10 @@ export type ExternalProvider = {
17
17
  alreadyExists: boolean;
18
18
  };
19
19
 
20
+ const ZHIPU_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
21
+ const ZHIPU_CODING_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
22
+ const ZHIPU_CODING_ANTHROPIC_BASE_URL = "https://open.bigmodel.cn/api/anthropic";
23
+
20
24
  function readJsonSafe(filePath: string): Record<string, unknown> | null {
21
25
  try {
22
26
  if (!existsSync(filePath)) return null;
@@ -51,6 +55,9 @@ const KNOWN_PROVIDER_NAMES: Record<string, string> = {
51
55
  azure: "Azure",
52
56
  moonshot: "Moonshot",
53
57
  zhipu: "ZhipuAI",
58
+ zhipuai: "ZhipuAI",
59
+ bigmodel: "ZhipuAI",
60
+ zai: "Z.AI",
54
61
  qwen: "Qwen",
55
62
  baichuan: "Baichuan",
56
63
  iflytek: "iFlytek",
@@ -399,6 +406,46 @@ function detectMiMoCodeProviders(): ExternalProvider[] {
399
406
  openaiBaseUrl: "https://api.together.xyz",
400
407
  format: "openai",
401
408
  },
409
+ zhipu: {
410
+ anthropicBaseUrl: "",
411
+ openaiBaseUrl: ZHIPU_OPENAI_BASE_URL,
412
+ format: "openai",
413
+ },
414
+ zhipuai: {
415
+ anthropicBaseUrl: "",
416
+ openaiBaseUrl: ZHIPU_OPENAI_BASE_URL,
417
+ format: "openai",
418
+ },
419
+ bigmodel: {
420
+ anthropicBaseUrl: "",
421
+ openaiBaseUrl: ZHIPU_OPENAI_BASE_URL,
422
+ format: "openai",
423
+ },
424
+ zai: {
425
+ anthropicBaseUrl: "",
426
+ openaiBaseUrl: ZHIPU_OPENAI_BASE_URL,
427
+ format: "openai",
428
+ },
429
+ glm: {
430
+ anthropicBaseUrl: ZHIPU_CODING_ANTHROPIC_BASE_URL,
431
+ openaiBaseUrl: ZHIPU_CODING_OPENAI_BASE_URL,
432
+ format: "anthropic",
433
+ },
434
+ "glm-coding-plan": {
435
+ anthropicBaseUrl: ZHIPU_CODING_ANTHROPIC_BASE_URL,
436
+ openaiBaseUrl: ZHIPU_CODING_OPENAI_BASE_URL,
437
+ format: "anthropic",
438
+ },
439
+ "zhipu-coding-plan": {
440
+ anthropicBaseUrl: ZHIPU_CODING_ANTHROPIC_BASE_URL,
441
+ openaiBaseUrl: ZHIPU_CODING_OPENAI_BASE_URL,
442
+ format: "anthropic",
443
+ },
444
+ "zai-coding-plan": {
445
+ anthropicBaseUrl: ZHIPU_CODING_ANTHROPIC_BASE_URL,
446
+ openaiBaseUrl: ZHIPU_CODING_OPENAI_BASE_URL,
447
+ format: "anthropic",
448
+ },
402
449
  };
403
450
 
404
451
  for (const [key, cred] of Object.entries(auth)) {
@@ -665,6 +665,56 @@ function normalizeModelName(name: string): string {
665
665
  return name.toLowerCase().replace(/\s+/g, "-");
666
666
  }
667
667
 
668
+ const ZHIPU_MODEL_PREFIXES = ["glm-", "charglm-", "cogview-", "embedding-", "rerank-"];
669
+ const ZHIPU_PROVIDER_ALIASES = ["zhipu", "zhipuai", "bigmodel", "zai", "glm"];
670
+ const ZHIPU_BASE_URL_MARKERS = [
671
+ "open.bigmodel.cn/api/paas/v4",
672
+ "open.bigmodel.cn/api/coding/paas/v4",
673
+ "open.bigmodel.cn/api/anthropic",
674
+ ];
675
+
676
+ function normalizeProviderAlias(name: string): string {
677
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
678
+ }
679
+
680
+ function providerRoutingUrls(provider: ProviderConfig): string[] {
681
+ const urls: string[] = [];
682
+ if (provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.trim() !== "") {
683
+ urls.push(provider.anthropicBaseUrl);
684
+ }
685
+ if (provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.trim() !== "") {
686
+ urls.push(provider.openaiBaseUrl);
687
+ }
688
+ if (provider.baseUrl !== undefined && provider.baseUrl.trim() !== "") {
689
+ urls.push(provider.baseUrl);
690
+ }
691
+ return urls;
692
+ }
693
+
694
+ function isZhipuProvider(provider: ProviderConfig): boolean {
695
+ const normalizedName = normalizeProviderAlias(provider.name);
696
+ const nameMatches = ZHIPU_PROVIDER_ALIASES.some((alias) => {
697
+ const normalizedAlias = normalizeProviderAlias(alias);
698
+ return normalizedName.includes(normalizedAlias) || normalizedAlias.includes(normalizedName);
699
+ });
700
+ if (nameMatches) return true;
701
+
702
+ return providerRoutingUrls(provider).some((url) => {
703
+ const normalizedUrl = url.toLowerCase().replace(/\/+$/, "");
704
+ return ZHIPU_BASE_URL_MARKERS.some((marker) => normalizedUrl.includes(marker));
705
+ });
706
+ }
707
+
708
+ function modelMatchesKnownProviderPrefix(
709
+ modelNormalized: string,
710
+ provider: ProviderConfig,
711
+ ): boolean {
712
+ if (isZhipuProvider(provider)) {
713
+ return ZHIPU_MODEL_PREFIXES.some((prefix) => modelNormalized.startsWith(prefix));
714
+ }
715
+ return false;
716
+ }
717
+
668
718
  /**
669
719
  * Finds a provider by model name using three strategies:
670
720
  * 1. Case-insensitive prefix match against "{provider.name}-" (e.g., "deepseek-" matches "deepseek-*")
@@ -683,6 +733,11 @@ export function findProviderByModel(model: string): ProviderConfig | null {
683
733
  if (modelLower.startsWith(providerPrefix)) {
684
734
  return provider;
685
735
  }
736
+ // Strategy 1b: known model-family prefixes for providers whose public
737
+ // model names do not start with the provider brand, e.g. ZhipuAI -> glm-*.
738
+ if (modelMatchesKnownProviderPrefix(modelNormalized, provider)) {
739
+ return provider;
740
+ }
686
741
  // Strategy 2: match against provider.models array with normalization
687
742
  const modelList = provider.models ?? (provider.model !== undefined ? [provider.model] : []);
688
743
  for (const m of modelList) {
@@ -60,6 +60,10 @@ export function selectUpstreamBase(route: ApiRoute, provider: ProviderConfig | n
60
60
  export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string): string {
61
61
  const base = upstreamBase.endsWith("/") ? upstreamBase.slice(0, -1) : upstreamBase;
62
62
 
63
+ if (isZhipuVersionedOpenAIBase(base) && isVersionedChatCompletionsPath(normalizedPath)) {
64
+ return base + normalizedPath.slice(3);
65
+ }
66
+
63
67
  // Many OpenAI-compatible base URLs already include /v1. Avoid producing /v1/v1.
64
68
  if (base.endsWith("/v1") && normalizedPath.startsWith("/v1/")) {
65
69
  return base + normalizedPath.slice(3);
@@ -67,6 +71,27 @@ export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string):
67
71
  return base + normalizedPath;
68
72
  }
69
73
 
74
+ function isVersionedChatCompletionsPath(normalizedPath: string): boolean {
75
+ return (
76
+ normalizedPath === PATH_V1_CHAT_COMPLETIONS ||
77
+ normalizedPath.startsWith(`${PATH_V1_CHAT_COMPLETIONS}?`) ||
78
+ normalizedPath.startsWith(`${PATH_V1_CHAT_COMPLETIONS}/`)
79
+ );
80
+ }
81
+
82
+ function isZhipuVersionedOpenAIBase(base: string): boolean {
83
+ try {
84
+ const parsed = new URL(base);
85
+ const path = parsed.pathname.replace(/\/+$/, "");
86
+ return (
87
+ parsed.hostname === "open.bigmodel.cn" &&
88
+ (path === "/api/paas/v4" || path === "/api/coding/paas/v4")
89
+ );
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
70
95
  export function setUpstreamHost(headers: Headers, upstreamBase: string): void {
71
96
  try {
72
97
  headers.set(HEADER_HOST, new URL(upstreamBase).host);