@tonyclaw/agent-inspector 2.0.20 → 2.0.22

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 (52) hide show
  1. package/.output/cli.js +10 -0
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-CUqYtaH3.js → CompareDrawer-NEdpe1wl.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-Dwhi4Q1c.js +115 -0
  5. package/.output/public/assets/{ReplayDialog-CPXNEqVg.js → ReplayDialog-DijzJwaQ.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-qkj8QgkY.js → RequestAnatomy-CYbosGYy.js} +1 -1
  7. package/.output/public/assets/{ResponseView-BPNLZpuw.js → ResponseView-B3dPM63X.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-DGs-YFXI.js → StreamingChunkSequence-DYDx3xRY.js} +1 -1
  9. package/.output/public/assets/_sessionId-b1KDnByv.js +1 -0
  10. package/.output/public/assets/index-780zTVwp.css +1 -0
  11. package/.output/public/assets/index-CUyRKgII.js +1 -0
  12. package/.output/public/assets/{main-j3vS8rmP.js → main-N-sjHJUM.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +27 -27
  14. package/.output/server/{_sessionId-Bc5Nohhe.mjs → _sessionId-BR46y_bO.mjs} +2 -2
  15. package/.output/server/_ssr/{CompareDrawer-D0UewydM.mjs → CompareDrawer-DDxpmdPs.mjs} +3 -3
  16. package/.output/server/_ssr/{ProxyViewerContainer-BEUWt2eX.mjs → ProxyViewerContainer-B1zAefzz.mjs} +304 -55
  17. package/.output/server/_ssr/{ReplayDialog-zaN-xMOw.mjs → ReplayDialog-DleMFvMx.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-BMomYged.mjs → RequestAnatomy-B-P9tC3D.mjs} +3 -3
  19. package/.output/server/_ssr/{ResponseView-DSoHCyAM.mjs → ResponseView-DWfGn0i3.mjs} +3 -3
  20. package/.output/server/_ssr/{StreamingChunkSequence-pt3pZOOL.mjs → StreamingChunkSequence-C8kFF6xJ.mjs} +3 -3
  21. package/.output/server/_ssr/{index-BCTKxcgV.mjs → index-DzENi2RO.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DpaDa5q5.mjs → router-Dy1VEjxy.mjs} +434 -80
  24. package/.output/server/{_tanstack-start-manifest_v-EsubwO0A.mjs → _tanstack-start-manifest_v-CyzuAD8A.mjs} +1 -1
  25. package/.output/server/index.mjs +55 -55
  26. package/package.json +1 -1
  27. package/src/cli/templates/codex-skill-onboard.ts +5 -0
  28. package/src/cli/templates/skill-onboard.ts +5 -0
  29. package/src/components/OnboardingBanner.tsx +4 -0
  30. package/src/components/ProxyViewer.tsx +5 -0
  31. package/src/components/providers/ProviderCard.tsx +178 -38
  32. package/src/components/providers/ProviderForm.tsx +107 -16
  33. package/src/components/providers/ProvidersPanel.tsx +21 -3
  34. package/src/components/providers/SettingsDialog.tsx +76 -2
  35. package/src/lib/providerModelMetadata.ts +67 -3
  36. package/src/lib/providerTestContract.ts +13 -0
  37. package/src/lib/runtimeConfig.ts +8 -0
  38. package/src/lib/useOnboarding.ts +3 -0
  39. package/src/lib/useStripConfig.ts +22 -0
  40. package/src/proxy/config.ts +4 -0
  41. package/src/proxy/formats/index.ts +1 -0
  42. package/src/proxy/formats/openai/index.ts +1 -0
  43. package/src/proxy/formats/openai/zhipuProvider.ts +31 -0
  44. package/src/proxy/providerImporters.ts +47 -0
  45. package/src/proxy/providers.ts +55 -0
  46. package/src/proxy/upstream.ts +25 -0
  47. package/src/routes/api/config.ts +7 -0
  48. package/src/routes/api/providers.$providerId.test.log.ts +28 -12
  49. package/.output/public/assets/ProxyViewerContainer-CX0-R0nn.js +0 -114
  50. package/.output/public/assets/_sessionId-CXtbrFTj.js +0 -1
  51. package/.output/public/assets/index-DrppF281.js +0 -1
  52. package/.output/public/assets/index-DynnYt7S.css +0 -1
@@ -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);
@@ -1,6 +1,7 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { z } from "zod";
3
3
  import {
4
+ MAX_PROVIDER_TEST_TIMEOUT_SECONDS,
4
5
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
5
6
  TimeDisplayFormatSchema,
6
7
  } from "../../lib/runtimeConfig";
@@ -17,6 +18,12 @@ const RuntimeConfigPatchSchema = z
17
18
  .min(0)
18
19
  .max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS)
19
20
  .optional(),
21
+ providerTestTimeoutSeconds: z
22
+ .number()
23
+ .int()
24
+ .min(1)
25
+ .max(MAX_PROVIDER_TEST_TIMEOUT_SECONDS)
26
+ .optional(),
20
27
  timeDisplayFormat: TimeDisplayFormatSchema.optional(),
21
28
  })
22
29
  .strict()
@@ -13,6 +13,22 @@ function hasSuccessField(result: ProviderTestState): result is TestResult {
13
13
  return Object.prototype.hasOwnProperty.call(result, "success");
14
14
  }
15
15
 
16
+ function getResultResponseStatus(result: TestResult): number {
17
+ if (result.debug?.responseStatus !== undefined) {
18
+ return result.debug.responseStatus;
19
+ }
20
+
21
+ return result.success ? 200 : 500;
22
+ }
23
+
24
+ function getResultResponseText(result: TestResult): string {
25
+ return result.rawResponse ?? result.debug?.responseBody ?? JSON.stringify(result);
26
+ }
27
+
28
+ function getResultHeaders(result: TestResult): Record<string, string> {
29
+ return result.requestHeaders ?? result.debug?.requestHeaders ?? {};
30
+ }
31
+
16
32
  async function logModelResults(
17
33
  displayName: string,
18
34
  providerName: string,
@@ -42,8 +58,8 @@ async function logModelResults(
42
58
  model: nonStreamingResult.model ?? displayName,
43
59
  sessionId: null,
44
60
  rawRequestBody: requestBody,
45
- responseStatus: nonStreamingResult.success ? 200 : 500,
46
- responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
61
+ responseStatus: getResultResponseStatus(nonStreamingResult),
62
+ responseText: getResultResponseText(nonStreamingResult),
47
63
  inputTokens: nonStreamingResult.inputTokens ?? null,
48
64
  outputTokens: nonStreamingResult.outputTokens ?? null,
49
65
  cacheCreationInputTokens: nonStreamingResult.cacheCreationInputTokens ?? null,
@@ -55,7 +71,7 @@ async function logModelResults(
55
71
  apiFormat: "anthropic",
56
72
  isTest: true,
57
73
  providerName,
58
- headers: captureFullDetails ? (nonStreamingResult.requestHeaders ?? {}) : undefined,
74
+ headers: captureFullDetails ? getResultHeaders(nonStreamingResult) : undefined,
59
75
  });
60
76
 
61
77
  const streamingRequestBody = JSON.stringify(
@@ -69,8 +85,8 @@ async function logModelResults(
69
85
  model: streamingResult.model ?? displayName,
70
86
  sessionId: null,
71
87
  rawRequestBody: streamingRequestBody,
72
- responseStatus: streamingResult.success ? 200 : 500,
73
- responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
88
+ responseStatus: getResultResponseStatus(streamingResult),
89
+ responseText: getResultResponseText(streamingResult),
74
90
  inputTokens: streamingResult.inputTokens ?? null,
75
91
  outputTokens: streamingResult.outputTokens ?? null,
76
92
  cacheCreationInputTokens: streamingResult.cacheCreationInputTokens ?? null,
@@ -83,7 +99,7 @@ async function logModelResults(
83
99
  apiFormat: "anthropic",
84
100
  isTest: true,
85
101
  providerName,
86
- headers: captureFullDetails ? (streamingResult.requestHeaders ?? {}) : undefined,
102
+ headers: captureFullDetails ? getResultHeaders(streamingResult) : undefined,
87
103
  });
88
104
  }
89
105
  }
@@ -104,8 +120,8 @@ async function logModelResults(
104
120
  model: nonStreamingResult.model ?? displayName,
105
121
  sessionId: null,
106
122
  rawRequestBody: requestBody,
107
- responseStatus: nonStreamingResult.success ? 200 : 500,
108
- responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
123
+ responseStatus: getResultResponseStatus(nonStreamingResult),
124
+ responseText: getResultResponseText(nonStreamingResult),
109
125
  inputTokens: nonStreamingResult.inputTokens ?? null,
110
126
  outputTokens: nonStreamingResult.outputTokens ?? null,
111
127
  cacheCreationInputTokens: null,
@@ -117,7 +133,7 @@ async function logModelResults(
117
133
  apiFormat: "openai",
118
134
  isTest: true,
119
135
  providerName,
120
- headers: captureFullDetails ? (nonStreamingResult.requestHeaders ?? {}) : undefined,
136
+ headers: captureFullDetails ? getResultHeaders(nonStreamingResult) : undefined,
121
137
  });
122
138
 
123
139
  const streamingRequestBody = JSON.stringify(
@@ -131,8 +147,8 @@ async function logModelResults(
131
147
  model: streamingResult.model ?? displayName,
132
148
  sessionId: null,
133
149
  rawRequestBody: streamingRequestBody,
134
- responseStatus: streamingResult.success ? 200 : 500,
135
- responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
150
+ responseStatus: getResultResponseStatus(streamingResult),
151
+ responseText: getResultResponseText(streamingResult),
136
152
  inputTokens: streamingResult.inputTokens ?? null,
137
153
  outputTokens: streamingResult.outputTokens ?? null,
138
154
  cacheCreationInputTokens: null,
@@ -145,7 +161,7 @@ async function logModelResults(
145
161
  apiFormat: "openai",
146
162
  isTest: true,
147
163
  providerName,
148
- headers: captureFullDetails ? (streamingResult.requestHeaders ?? {}) : undefined,
164
+ headers: captureFullDetails ? getResultHeaders(streamingResult) : undefined,
149
165
  });
150
166
  }
151
167
  }