@tonyclaw/agent-inspector 2.1.16 → 2.1.17

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 (51) hide show
  1. package/.output/cli.js +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-Ccrrcx1j.js → CompareDrawer-B9sLBHw5.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-C7axhr-l.js → ReplayDialog-D9I9W9n3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-WOptg8j_.js → RequestAnatomy-DGXK_Rii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-CSAcxh_M.js → ResponseView-df_JzwsS.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-DJS5KhPx.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
  9. package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
  10. package/.output/public/assets/index-Blqvndcn.js +1 -0
  11. package/.output/public/assets/index-Cs79WRQj.css +1 -0
  12. package/.output/public/assets/{index-DMPNh46t.js → index-Dqc2r1ea.js} +1 -1
  13. package/.output/public/assets/{json-viewer-inX5QSa3.js → json-viewer-D3QWQlSB.js} +1 -1
  14. package/.output/public/assets/{main-aCAKYGSD.js → main-ZhxhOCmF.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +220 -202
  16. package/.output/server/{_sessionId-C0fhvQnn.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-CO-Ti5sg.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-N4J7uBCX.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +1858 -1396
  19. package/.output/server/_ssr/{ReplayDialog-J1VxC0In.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-DBH-F3rq.mjs → RequestAnatomy-HQfrxjO1.mjs} +2 -2
  21. package/.output/server/_ssr/{ResponseView-DLFwV7Hd.mjs → ResponseView-QSlSD11e.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BywmuND0.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +2 -2
  23. package/.output/server/_ssr/{index-CStymIc0.mjs → index-D_EzfXaN.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-DlQu0N1x.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
  26. package/.output/server/_ssr/{router-CQ4Zxhmr.mjs → router-__o2wrfO.mjs} +398 -95
  27. package/.output/server/{_tanstack-start-manifest_v-BHeJBGfo.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
  28. package/.output/server/index.mjs +73 -73
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +395 -64
  34. package/src/components/providers/ProviderCard.tsx +132 -31
  35. package/src/components/providers/ProviderForm.tsx +43 -11
  36. package/src/components/providers/ProvidersPanel.tsx +1 -1
  37. package/src/components/proxy-viewer/ConversationHeader.tsx +133 -112
  38. package/src/components/proxy-viewer/LogEntry.tsx +109 -26
  39. package/src/components/proxy-viewer/LogEntryHeader.tsx +339 -329
  40. package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
  41. package/src/components/proxy-viewer/TurnGroup.tsx +22 -63
  42. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  43. package/src/lib/providerTestContract.ts +56 -10
  44. package/src/lib/providerTestPrompt.ts +23 -1
  45. package/src/lib/upstreamUrl.ts +105 -1
  46. package/src/mcp/toolHandlers.ts +1 -1
  47. package/src/routes/api/providers.$providerId.test.log.ts +85 -10
  48. package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +0 -106
  49. package/.output/public/assets/_sessionId-DUSalzKH.js +0 -1
  50. package/.output/public/assets/index-BPpA21dY.css +0 -1
  51. package/.output/public/assets/index-Bt0Az2I2.js +0 -1
@@ -31,7 +31,15 @@ import {
31
31
  providerHasContextMetadata,
32
32
  } from "../../lib/providerModelMetadata";
33
33
  import { formatContextWindowTokens } from "../../lib/utils";
34
+ import {
35
+ PATH_V1_CHAT_COMPLETIONS,
36
+ PATH_V1_MESSAGES,
37
+ PATH_V1_RESPONSES,
38
+ previewUpstreamUrl,
39
+ type UpstreamUrlDiagnostic,
40
+ } from "../../lib/upstreamUrl";
34
41
  import type {
42
+ ProviderFormatTestResults,
35
43
  ProviderTestErrorType as ErrorType,
36
44
  ProviderTestResult as TestResult,
37
45
  ProviderTestResults as TestResults,
@@ -65,8 +73,6 @@ function isNotConfiguredState(result: ProviderTestState): result is { notConfigu
65
73
  return Object.prototype.hasOwnProperty.call(result, "notConfigured");
66
74
  }
67
75
 
68
- type ProviderFormatTestResults = TestResults["anthropic"];
69
-
70
76
  function hasText(value: string | undefined): value is string {
71
77
  return value !== undefined && value.trim() !== "";
72
78
  }
@@ -206,12 +212,12 @@ function ProviderFormatTestStatus({
206
212
  results: ProviderFormatTestResults;
207
213
  }): JSX.Element {
208
214
  return (
209
- <div className="flex min-w-0 flex-col gap-2">
210
- <div className="flex min-w-0 flex-col gap-1">
215
+ <div className="grid min-w-0 gap-2 sm:grid-cols-2">
216
+ <div className="min-w-0 rounded-md border border-border/60 bg-muted/20 px-2 py-1.5">
211
217
  <span className="text-[10px] text-muted-foreground">Non-stream</span>
212
218
  <TestStatus result={results.nonStreaming} />
213
219
  </div>
214
- <div className="flex min-w-0 flex-col gap-1">
220
+ <div className="min-w-0 rounded-md border border-border/60 bg-muted/20 px-2 py-1.5">
215
221
  <span className="text-[10px] text-muted-foreground">Stream</span>
216
222
  <TestStatus result={results.streaming} />
217
223
  </div>
@@ -219,6 +225,56 @@ function ProviderFormatTestStatus({
219
225
  );
220
226
  }
221
227
 
228
+ function EndpointDiagnosticLine({
229
+ diagnostic,
230
+ }: {
231
+ diagnostic: UpstreamUrlDiagnostic;
232
+ }): JSX.Element {
233
+ return (
234
+ <div className={diagnostic.severity === "warning" ? "text-amber-200" : "text-muted-foreground"}>
235
+ {diagnostic.message}
236
+ </div>
237
+ );
238
+ }
239
+
240
+ function ProviderEndpointSummary({
241
+ label,
242
+ baseUrl,
243
+ endpoint,
244
+ providerName,
245
+ children,
246
+ }: {
247
+ label: string;
248
+ baseUrl: string;
249
+ endpoint: string;
250
+ providerName: string;
251
+ children?: ReactNode;
252
+ }): JSX.Element {
253
+ const preview = previewUpstreamUrl(baseUrl, endpoint, { providerName });
254
+
255
+ return (
256
+ <div className="flex min-w-0 flex-col gap-2 border-t border-border/60 pt-2 first:border-t-0 first:pt-0">
257
+ <div className="grid min-w-0 gap-1 text-xs sm:grid-cols-[104px_minmax(0,1fr)]">
258
+ <span className="font-medium text-muted-foreground">{label}</span>
259
+ <code className="min-w-0 break-all font-mono text-foreground/80">{baseUrl}</code>
260
+ <span className="text-[10px] uppercase text-muted-foreground/80">Final</span>
261
+ <code className="min-w-0 break-all font-mono text-foreground">{preview.finalUrl}</code>
262
+ </div>
263
+ {preview.diagnostics.length > 0 && (
264
+ <div className="space-y-1 text-xs">
265
+ {preview.diagnostics.map((diagnostic) => (
266
+ <EndpointDiagnosticLine
267
+ key={`${diagnostic.severity}:${diagnostic.message}`}
268
+ diagnostic={diagnostic}
269
+ />
270
+ ))}
271
+ </div>
272
+ )}
273
+ {children}
274
+ </div>
275
+ );
276
+ }
277
+
222
278
  function getErrorIcon(type: ErrorType): JSX.Element {
223
279
  const iconProps = { className: "size-3", strokeWidth: 2 };
224
280
  switch (type) {
@@ -314,8 +370,8 @@ function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
314
370
  <TooltipProvider>
315
371
  <Tooltip>
316
372
  <TooltipTrigger asChild>
317
- <div className="flex items-center gap-1 text-xs text-green-600 shrink-0">
318
- <CheckCircle className="size-3" />
373
+ <div className="flex items-center gap-1 text-xs text-cyan-100 shrink-0">
374
+ <CheckCircle className="size-3 text-cyan-300" />
319
375
  <span>Connected</span>
320
376
  {tokenParts.length > 0 && (
321
377
  <span className="text-muted-foreground">({displayTokens})</span>
@@ -440,6 +496,30 @@ export function ProviderCard({
440
496
  const canRefreshMetadata =
441
497
  provider.modelMetadataUrl !== undefined && provider.modelMetadataUrl.trim() !== "";
442
498
  const metadataImportLabel = canRefreshMetadata ? "Refresh" : "Import defaults";
499
+ const providerSourceBadge =
500
+ provider.source === "company" ? (
501
+ <TooltipProvider>
502
+ <Tooltip>
503
+ <TooltipTrigger asChild>
504
+ <span className="shrink-0 rounded border border-cyan-300/20 bg-cyan-300/[0.08] px-1.5 py-0.5 text-xs text-cyan-100">
505
+ Company
506
+ </span>
507
+ </TooltipTrigger>
508
+ <TooltipContent>Company-provided API key</TooltipContent>
509
+ </Tooltip>
510
+ </TooltipProvider>
511
+ ) : provider.source === "personal" ? (
512
+ <TooltipProvider>
513
+ <Tooltip>
514
+ <TooltipTrigger asChild>
515
+ <span className="shrink-0 rounded border border-violet-300/20 bg-violet-300/[0.08] px-1.5 py-0.5 text-xs text-violet-100">
516
+ Personal
517
+ </span>
518
+ </TooltipTrigger>
519
+ <TooltipContent>Your personal API key</TooltipContent>
520
+ </Tooltip>
521
+ </TooltipProvider>
522
+ ) : null;
443
523
 
444
524
  return (
445
525
  <div
@@ -448,7 +528,8 @@ export function ProviderCard({
448
528
  <div className="flex items-start justify-between gap-2">
449
529
  <div className="flex items-center gap-2 min-w-0">
450
530
  <span className="font-medium truncate">{provider.name}</span>
451
- {provider.source === "company" && (
531
+ {providerSourceBadge}
532
+ {providerSourceBadge === null && provider.source === "company" && (
452
533
  <TooltipProvider>
453
534
  <Tooltip>
454
535
  <TooltipTrigger asChild>
@@ -460,7 +541,7 @@ export function ProviderCard({
460
541
  </Tooltip>
461
542
  </TooltipProvider>
462
543
  )}
463
- {provider.source === "personal" && (
544
+ {providerSourceBadge === null && provider.source === "personal" && (
464
545
  <TooltipProvider>
465
546
  <Tooltip>
466
547
  <TooltipTrigger asChild>
@@ -575,39 +656,47 @@ export function ProviderCard({
575
656
  className="text-muted-foreground hover:text-foreground transition-colors p-1"
576
657
  aria-label="Copy API key"
577
658
  >
578
- {copied ? <Check className="size-4 text-green-500" /> : <Copy className="size-4" />}
659
+ {copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
579
660
  </button>
580
661
  </div>
581
662
 
582
663
  {provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl !== "" && (
583
- <div className="flex min-w-0 flex-col gap-2">
584
- <div className="min-w-0 text-xs text-muted-foreground">
585
- <span className="font-medium">Anthropic:</span>{" "}
586
- <span className="truncate">{provider.anthropicBaseUrl}</span>
587
- </div>
664
+ <ProviderEndpointSummary
665
+ label="Anthropic"
666
+ baseUrl={provider.anthropicBaseUrl}
667
+ endpoint={PATH_V1_MESSAGES}
668
+ providerName={provider.name}
669
+ >
588
670
  {testResults !== undefined && (
589
671
  <ProviderFormatTestStatus results={testResults.anthropic} />
590
672
  )}
591
- </div>
673
+ </ProviderEndpointSummary>
592
674
  )}
593
675
 
594
676
  {provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl !== "" && (
595
- <div className="flex min-w-0 flex-col gap-2">
596
- <div className="min-w-0 text-xs text-muted-foreground">
597
- <span className="font-medium">OpenAI Chat:</span>{" "}
598
- <span className="truncate">{provider.openaiBaseUrl}</span>
599
- </div>
600
- {testResults !== undefined && <ProviderFormatTestStatus results={testResults.openai} />}
601
- </div>
677
+ <ProviderEndpointSummary
678
+ label="OpenAI Chat"
679
+ baseUrl={provider.openaiBaseUrl}
680
+ endpoint={PATH_V1_CHAT_COMPLETIONS}
681
+ providerName={provider.name}
682
+ >
683
+ {testResults !== undefined && (
684
+ <ProviderFormatTestStatus results={testResults.openaiChat} />
685
+ )}
686
+ </ProviderEndpointSummary>
602
687
  )}
603
688
 
604
689
  {provider.openaiResponsesBaseUrl !== undefined && provider.openaiResponsesBaseUrl !== "" && (
605
- <div className="flex min-w-0 flex-col gap-2">
606
- <div className="min-w-0 text-xs text-muted-foreground">
607
- <span className="font-medium">OpenAI Responses:</span>{" "}
608
- <span className="truncate">{provider.openaiResponsesBaseUrl}</span>
609
- </div>
610
- </div>
690
+ <ProviderEndpointSummary
691
+ label="OpenAI Responses"
692
+ baseUrl={provider.openaiResponsesBaseUrl}
693
+ endpoint={PATH_V1_RESPONSES}
694
+ providerName={provider.name}
695
+ >
696
+ {testResults !== undefined && (
697
+ <ProviderFormatTestStatus results={testResults.openaiResponses} />
698
+ )}
699
+ </ProviderEndpointSummary>
611
700
  )}
612
701
 
613
702
  {testResults?.testedAt !== undefined && (
@@ -644,9 +733,15 @@ export function ProviderCard({
644
733
  )}
645
734
  {provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl !== "" && (
646
735
  <th className="text-left py-1 px-2 font-medium text-muted-foreground">
647
- OpenAI
736
+ Chat
648
737
  </th>
649
738
  )}
739
+ {provider.openaiResponsesBaseUrl !== undefined &&
740
+ provider.openaiResponsesBaseUrl !== "" && (
741
+ <th className="text-left py-1 px-2 font-medium text-muted-foreground">
742
+ Responses
743
+ </th>
744
+ )}
650
745
  </tr>
651
746
  </thead>
652
747
  <tbody>
@@ -661,9 +756,15 @@ export function ProviderCard({
661
756
  )}
662
757
  {provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl !== "" && (
663
758
  <td className="py-1 px-2 align-top">
664
- <ProviderFormatTestStatus results={modelResult.openai} />
759
+ <ProviderFormatTestStatus results={modelResult.openaiChat} />
665
760
  </td>
666
761
  )}
762
+ {provider.openaiResponsesBaseUrl !== undefined &&
763
+ provider.openaiResponsesBaseUrl !== "" && (
764
+ <td className="py-1 px-2 align-top">
765
+ <ProviderFormatTestStatus results={modelResult.openaiResponses} />
766
+ </td>
767
+ )}
667
768
  </tr>
668
769
  ))}
669
770
  </tbody>
@@ -6,7 +6,13 @@ import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
6
6
  import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
7
7
  import { copyTextToClipboard } from "../../lib/clipboard";
8
8
  import { maskApiKey } from "../../lib/mask";
9
- import { buildUpstreamUrl } from "../../lib/upstreamUrl";
9
+ import {
10
+ PATH_V1_CHAT_COMPLETIONS,
11
+ PATH_V1_MESSAGES,
12
+ PATH_V1_RESPONSES,
13
+ previewUpstreamUrl,
14
+ type UpstreamUrlPreview,
15
+ } from "../../lib/upstreamUrl";
10
16
  import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
11
17
 
12
18
  const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
@@ -17,9 +23,9 @@ const ZHIPU_CODING_PROVIDER_KEYWORDS = [
17
23
  "z.ai coding",
18
24
  "zai coding",
19
25
  ];
20
- const ANTHROPIC_MESSAGES_ENDPOINT = "/v1/messages";
21
- const OPENAI_CHAT_COMPLETIONS_ENDPOINT = "/v1/chat/completions";
22
- const OPENAI_RESPONSES_ENDPOINT = "/v1/responses";
26
+ const ANTHROPIC_MESSAGES_ENDPOINT = PATH_V1_MESSAGES;
27
+ const OPENAI_CHAT_COMPLETIONS_ENDPOINT = PATH_V1_CHAT_COMPLETIONS;
28
+ const OPENAI_RESPONSES_ENDPOINT = PATH_V1_RESPONSES;
23
29
 
24
30
  // Known provider presets - maps provider name keywords to their API URLs
25
31
  const KNOWN_PROVIDER_PRESETS: Record<
@@ -275,25 +281,45 @@ function stripEndpointSuffix(value: string | undefined, endpoint: string): strin
275
281
  : trimmed;
276
282
  }
277
283
 
278
- function previewEndpointUrl(baseUrl: string, endpoint: string): string | null {
284
+ function previewEndpointUrl(
285
+ baseUrl: string,
286
+ endpoint: string,
287
+ providerName: string,
288
+ ): UpstreamUrlPreview | null {
279
289
  const trimmed = baseUrl.trim();
280
- return trimmed === "" ? null : buildUpstreamUrl(trimmed, endpoint);
290
+ return trimmed === "" ? null : previewUpstreamUrl(trimmed, endpoint, { providerName });
281
291
  }
282
292
 
283
293
  function EndpointUrlPreview({
284
294
  baseUrl,
285
295
  endpoint,
296
+ providerName,
286
297
  }: {
287
298
  baseUrl: string;
288
299
  endpoint: string;
300
+ providerName: string;
289
301
  }): JSX.Element | null {
290
- const finalUrl = previewEndpointUrl(baseUrl, endpoint);
291
- if (finalUrl === null) return null;
302
+ const preview = previewEndpointUrl(baseUrl, endpoint, providerName);
303
+ if (preview === null) return null;
292
304
 
293
305
  return (
294
306
  <div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs">
295
307
  <div className="text-muted-foreground">Final URL</div>
296
- <div className="break-all font-mono text-foreground">{finalUrl}</div>
308
+ <div className="break-all font-mono text-foreground">{preview.finalUrl}</div>
309
+ {preview.diagnostics.length > 0 && (
310
+ <div className="mt-2 space-y-1">
311
+ {preview.diagnostics.map((diagnostic) => (
312
+ <div
313
+ key={`${diagnostic.severity}:${diagnostic.message}`}
314
+ className={
315
+ diagnostic.severity === "warning" ? "text-amber-200" : "text-muted-foreground"
316
+ }
317
+ >
318
+ {diagnostic.message}
319
+ </div>
320
+ ))}
321
+ </div>
322
+ )}
297
323
  </div>
298
324
  );
299
325
  }
@@ -729,7 +755,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
729
755
  className="text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0"
730
756
  aria-label="Copy API key"
731
757
  >
732
- {copied ? <Check className="size-4 text-green-500" /> : <Copy className="size-4" />}
758
+ {copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
733
759
  </button>
734
760
  </div>
735
761
  {errors.apiKey !== undefined && <p className="text-xs text-destructive">{errors.apiKey}</p>}
@@ -964,7 +990,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
964
990
  {errors.anthropicBaseUrl !== undefined && (
965
991
  <p className="text-xs text-destructive">{errors.anthropicBaseUrl}</p>
966
992
  )}
967
- <EndpointUrlPreview baseUrl={anthropicBaseUrl} endpoint={ANTHROPIC_MESSAGES_ENDPOINT} />
993
+ <EndpointUrlPreview
994
+ baseUrl={anthropicBaseUrl}
995
+ endpoint={ANTHROPIC_MESSAGES_ENDPOINT}
996
+ providerName={name}
997
+ />
968
998
  <p className="text-xs text-muted-foreground">
969
999
  Anthropic-compatible base URL. Leave empty if this provider does not support Anthropic
970
1000
  format.
@@ -1000,6 +1030,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
1000
1030
  <EndpointUrlPreview
1001
1031
  baseUrl={openaiBaseUrl}
1002
1032
  endpoint={OPENAI_CHAT_COMPLETIONS_ENDPOINT}
1033
+ providerName={name}
1003
1034
  />
1004
1035
  <p className="text-xs text-muted-foreground">
1005
1036
  OpenAI Chat Completions-compatible base URL. Leave empty if this provider only
@@ -1032,6 +1063,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
1032
1063
  <EndpointUrlPreview
1033
1064
  baseUrl={openaiResponsesBaseUrl}
1034
1065
  endpoint={OPENAI_RESPONSES_ENDPOINT}
1066
+ providerName={name}
1035
1067
  />
1036
1068
  <p className="text-xs text-muted-foreground">
1037
1069
  Optional Responses-compatible base URL. Set only when the provider supports the
@@ -631,7 +631,7 @@ export function ProvidersPanel({
631
631
  }
632
632
  >
633
633
  {configPathCopied ? (
634
- <Check className="size-3 text-green-500" />
634
+ <Check className="size-3 text-cyan-300" />
635
635
  ) : (
636
636
  <Copy className="size-3" />
637
637
  )}