@tonyclaw/agent-inspector 2.1.15 → 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.
- package/.output/cli.js +57 -13
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-B9sLBHw5.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
- package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-D9I9W9n3.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-DGXK_Rii.js} +1 -1
- package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-df_JzwsS.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
- package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
- package/.output/public/assets/index-Blqvndcn.js +1 -0
- package/.output/public/assets/index-Cs79WRQj.css +1 -0
- package/.output/public/assets/{index-DjKt8XKe.js → index-Dqc2r1ea.js} +1 -1
- package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-D3QWQlSB.js} +1 -1
- package/.output/public/assets/{main-DpD1N0S8.js → main-ZhxhOCmF.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +255 -201
- package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +2732 -1849
- package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-HQfrxjO1.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-QSlSD11e.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +3 -3
- package/.output/server/_ssr/{index-47XVPghS.mjs → index-D_EzfXaN.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
- package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-__o2wrfO.mjs} +505 -136
- package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
- package/.output/server/index.mjs +63 -63
- package/README.md +57 -2
- package/package.json +1 -1
- package/src/cli/startupOutput.ts +16 -0
- package/src/cli.ts +61 -15
- package/src/components/ProxyViewer.tsx +535 -163
- package/src/components/ProxyViewerContainer.tsx +77 -67
- package/src/components/alerts/AlertsDialog.tsx +2 -1
- package/src/components/clients/ClientLogo.tsx +1 -1
- package/src/components/groups/GroupsDialog.tsx +19 -1
- package/src/components/providers/ProviderCard.tsx +133 -32
- package/src/components/providers/ProviderForm.tsx +61 -29
- package/src/components/providers/ProviderLogo.tsx +6 -1
- package/src/components/providers/ProvidersPanel.tsx +7 -7
- package/src/components/providers/SettingsDialog.tsx +13 -6
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
- package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
- package/src/components/proxy-viewer/ConversationHeader.tsx +153 -120
- package/src/components/proxy-viewer/LogEntry.tsx +130 -43
- package/src/components/proxy-viewer/LogEntryHeader.tsx +357 -354
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
- package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
- package/src/components/proxy-viewer/TurnGroup.tsx +33 -69
- package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
- package/src/components/proxy-viewer/bodyHydration.ts +56 -0
- package/src/components/proxy-viewer/viewerState.ts +0 -4
- package/src/components/ui/icon-trigger.ts +2 -0
- package/src/components/ui/transient-toast.tsx +104 -0
- package/src/lib/providerTestContract.ts +56 -10
- package/src/lib/providerTestPrompt.ts +23 -1
- package/src/lib/sessionInfoContract.ts +4 -0
- package/src/lib/upstreamUrl.ts +105 -1
- package/src/mcp/server.ts +71 -27
- package/src/mcp/toolHandlers.ts +24 -5
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
- package/src/proxy/sessionInfo.ts +31 -2
- package/src/proxy/sessionSupervisor.ts +20 -1
- package/src/proxy/store.ts +1 -1
- package/src/routes/api/providers.$providerId.test.log.ts +93 -14
- package/styles/globals.css +180 -180
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
- package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
- package/.output/public/assets/index-CI1-G8ua.js +0 -1
- package/.output/public/assets/index-DdhFqPsI.css +0 -1
|
@@ -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 {
|
|
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 =
|
|
21
|
-
const OPENAI_CHAT_COMPLETIONS_ENDPOINT =
|
|
22
|
-
const OPENAI_RESPONSES_ENDPOINT =
|
|
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<
|
|
@@ -107,7 +113,7 @@ const MINIMAX_MODELS = [
|
|
|
107
113
|
];
|
|
108
114
|
|
|
109
115
|
// Alibaba model options
|
|
110
|
-
const ALIBABA_MODELS = ["
|
|
116
|
+
const ALIBABA_MODELS = ["qwen3.6-plus", "qwen3.7-max"];
|
|
111
117
|
|
|
112
118
|
// ZhipuAI / Z.AI model options
|
|
113
119
|
const ZHIPU_MODELS = [
|
|
@@ -275,25 +281,45 @@ function stripEndpointSuffix(value: string | undefined, endpoint: string): strin
|
|
|
275
281
|
: trimmed;
|
|
276
282
|
}
|
|
277
283
|
|
|
278
|
-
function previewEndpointUrl(
|
|
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 :
|
|
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
|
|
291
|
-
if (
|
|
302
|
+
const preview = previewEndpointUrl(baseUrl, endpoint, providerName);
|
|
303
|
+
if (preview === null) return null;
|
|
292
304
|
|
|
293
305
|
return (
|
|
294
|
-
<div className="
|
|
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
|
}
|
|
@@ -656,7 +682,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
656
682
|
}
|
|
657
683
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
658
684
|
>
|
|
659
|
-
<option value=""
|
|
685
|
+
<option value="">Unspecified</option>
|
|
660
686
|
<option value="personal">个人 (Personal)</option>
|
|
661
687
|
<option value="company">公司 (Company)</option>
|
|
662
688
|
</select>
|
|
@@ -681,7 +707,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
681
707
|
<p className="text-xs text-destructive">{errors.modelMetadataUrl}</p>
|
|
682
708
|
)}
|
|
683
709
|
{modelMetadataUrl.trim() === "" && (
|
|
684
|
-
<div className="
|
|
710
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs text-muted-foreground">
|
|
685
711
|
Built-in limits cover DeepSeek v4 pro/flash, MiniMax M3/M2.7/M2.5/M2.1, and GLM coding
|
|
686
712
|
models. Use a registry URL for private or custom model limits.
|
|
687
713
|
</div>
|
|
@@ -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-
|
|
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>}
|
|
@@ -751,7 +777,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
751
777
|
ref={(el) => {
|
|
752
778
|
modelRowRefs.current[i] = el;
|
|
753
779
|
}}
|
|
754
|
-
className="space-y-2 rounded-
|
|
780
|
+
className="border border-border bg-card shadow-sm space-y-2 rounded-[8px] p-2"
|
|
755
781
|
>
|
|
756
782
|
<div className="flex items-center gap-2">
|
|
757
783
|
<div className="relative flex-1">
|
|
@@ -775,7 +801,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
775
801
|
<ChevronDown className="size-4" />
|
|
776
802
|
</button>
|
|
777
803
|
{openModelDropdown === i && (
|
|
778
|
-
<div className="absolute left-0 right-0 top-full
|
|
804
|
+
<div className="bg-popover text-popover-foreground absolute left-0 right-0 top-full z-50 mt-1 max-h-48 overflow-y-auto rounded-md border shadow-md">
|
|
779
805
|
{suggestedModels.map((opt) => (
|
|
780
806
|
<button
|
|
781
807
|
key={opt}
|
|
@@ -784,7 +810,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
784
810
|
updateModel(i, opt);
|
|
785
811
|
setOpenModelDropdown(null);
|
|
786
812
|
}}
|
|
787
|
-
className="w-full
|
|
813
|
+
className="w-full px-3 py-2 text-left text-sm transition-colors hover:bg-white/[0.06]"
|
|
788
814
|
>
|
|
789
815
|
{opt}
|
|
790
816
|
</button>
|
|
@@ -899,7 +925,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
899
925
|
</div>
|
|
900
926
|
|
|
901
927
|
<div className="space-y-2">
|
|
902
|
-
<div className="flex gap-1
|
|
928
|
+
<div className="border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1">
|
|
903
929
|
<TooltipProvider>
|
|
904
930
|
<Tooltip>
|
|
905
931
|
<TooltipTrigger asChild>
|
|
@@ -945,7 +971,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
945
971
|
<label htmlFor="provider-anthropic-base-url" className="text-sm font-medium">
|
|
946
972
|
Anthropic Base URL
|
|
947
973
|
</label>
|
|
948
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
974
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
949
975
|
<input
|
|
950
976
|
id="provider-anthropic-base-url"
|
|
951
977
|
type="text"
|
|
@@ -955,16 +981,20 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
955
981
|
setAnthropicBaseUrl(e.target.value);
|
|
956
982
|
}}
|
|
957
983
|
placeholder="https://api.anthropic.com"
|
|
958
|
-
className="min-w-0 flex-1 bg-
|
|
984
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
959
985
|
/>
|
|
960
|
-
<span className="shrink-0 select-none border-l bg-
|
|
986
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
961
987
|
{ANTHROPIC_MESSAGES_ENDPOINT}
|
|
962
988
|
</span>
|
|
963
989
|
</div>
|
|
964
990
|
{errors.anthropicBaseUrl !== undefined && (
|
|
965
991
|
<p className="text-xs text-destructive">{errors.anthropicBaseUrl}</p>
|
|
966
992
|
)}
|
|
967
|
-
<EndpointUrlPreview
|
|
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.
|
|
@@ -978,7 +1008,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
978
1008
|
<label htmlFor="provider-openai-base-url" className="text-sm font-medium">
|
|
979
1009
|
OpenAI Chat Base URL
|
|
980
1010
|
</label>
|
|
981
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
1011
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
982
1012
|
<input
|
|
983
1013
|
id="provider-openai-base-url"
|
|
984
1014
|
type="text"
|
|
@@ -988,9 +1018,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
988
1018
|
setOpenaiBaseUrl(e.target.value);
|
|
989
1019
|
}}
|
|
990
1020
|
placeholder="https://api.openai.com"
|
|
991
|
-
className="min-w-0 flex-1 bg-
|
|
1021
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
992
1022
|
/>
|
|
993
|
-
<span className="shrink-0 select-none border-l bg-
|
|
1023
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
994
1024
|
{OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
995
1025
|
</span>
|
|
996
1026
|
</div>
|
|
@@ -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
|
|
@@ -1010,7 +1041,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1010
1041
|
<label htmlFor="provider-openai-responses-base-url" className="text-sm font-medium">
|
|
1011
1042
|
OpenAI Responses Base URL
|
|
1012
1043
|
</label>
|
|
1013
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
1044
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
1014
1045
|
<input
|
|
1015
1046
|
id="provider-openai-responses-base-url"
|
|
1016
1047
|
type="text"
|
|
@@ -1020,9 +1051,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1020
1051
|
setOpenaiResponsesBaseUrl(e.target.value);
|
|
1021
1052
|
}}
|
|
1022
1053
|
placeholder="https://api.openai.com"
|
|
1023
|
-
className="min-w-0 flex-1 bg-
|
|
1054
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
1024
1055
|
/>
|
|
1025
|
-
<span className="shrink-0 select-none border-l bg-
|
|
1056
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
1026
1057
|
{OPENAI_RESPONSES_ENDPOINT}
|
|
1027
1058
|
</span>
|
|
1028
1059
|
</div>
|
|
@@ -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
|
|
@@ -1058,7 +1090,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1058
1090
|
</p>
|
|
1059
1091
|
</div>
|
|
1060
1092
|
|
|
1061
|
-
<div className="
|
|
1093
|
+
<div className="border border-border bg-background/95 shadow-sm sticky bottom-0 flex justify-end gap-2 rounded-[8px] px-3 py-2">
|
|
1062
1094
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
|
1063
1095
|
Cancel
|
|
1064
1096
|
</Button>
|
|
@@ -26,12 +26,17 @@ const PROVIDER_MAP: Record<string, Provider> = {
|
|
|
26
26
|
"deepseek-": "deepseek",
|
|
27
27
|
MiniMax: "minimax",
|
|
28
28
|
qwen: "qwen",
|
|
29
|
-
"glm-": "zhipuai",
|
|
30
29
|
};
|
|
31
30
|
|
|
31
|
+
function isGlmModel(modelLower: string): boolean {
|
|
32
|
+
const normalized = modelLower.replace(/[_\s]+/g, "-");
|
|
33
|
+
return normalized.split(/[/:@]/).some((segment) => segment.startsWith("glm-"));
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function detectProvider(model: string | null): Provider {
|
|
33
37
|
if (model === null) return "unknown";
|
|
34
38
|
const modelLower = model.toLowerCase();
|
|
39
|
+
if (isGlmModel(modelLower)) return "zhipuai";
|
|
35
40
|
for (const [prefix, provider] of Object.entries(PROVIDER_MAP)) {
|
|
36
41
|
if (modelLower.startsWith(prefix.toLowerCase())) {
|
|
37
42
|
return provider;
|
|
@@ -521,7 +521,7 @@ export function ProvidersPanel({
|
|
|
521
521
|
if (showForm || editingProvider) {
|
|
522
522
|
return (
|
|
523
523
|
<div className="space-y-4">
|
|
524
|
-
<div className="
|
|
524
|
+
<div className="border border-border bg-background/95 shadow-sm sticky top-0 z-10 flex items-center justify-between rounded-[8px] px-3 py-2">
|
|
525
525
|
<h3 className="text-lg font-medium">
|
|
526
526
|
{editingProvider ? "Edit Provider" : "Add New Provider"}
|
|
527
527
|
</h3>
|
|
@@ -540,7 +540,7 @@ export function ProvidersPanel({
|
|
|
540
540
|
|
|
541
541
|
return (
|
|
542
542
|
<div className="space-y-4">
|
|
543
|
-
<div className="
|
|
543
|
+
<div className="border border-border bg-background/95 shadow-sm sticky top-0 z-10 flex items-center justify-between rounded-[8px] px-3 py-2">
|
|
544
544
|
<h3 className="text-lg font-medium">Providers</h3>
|
|
545
545
|
<div className="flex items-center gap-2">
|
|
546
546
|
<TooltipProvider>
|
|
@@ -608,7 +608,7 @@ export function ProvidersPanel({
|
|
|
608
608
|
</div>
|
|
609
609
|
|
|
610
610
|
{configPath !== null && (
|
|
611
|
-
<div className="
|
|
611
|
+
<div className="border border-border bg-card shadow-sm flex items-center gap-2 rounded-[8px] px-3 py-2 text-xs text-muted-foreground">
|
|
612
612
|
<span className="shrink-0">Config:</span>
|
|
613
613
|
<span className="font-mono truncate" title={configPath}>
|
|
614
614
|
{configPath}
|
|
@@ -631,7 +631,7 @@ export function ProvidersPanel({
|
|
|
631
631
|
}
|
|
632
632
|
>
|
|
633
633
|
{configPathCopied ? (
|
|
634
|
-
<Check className="size-3 text-
|
|
634
|
+
<Check className="size-3 text-cyan-300" />
|
|
635
635
|
) : (
|
|
636
636
|
<Copy className="size-3" />
|
|
637
637
|
)}
|
|
@@ -644,14 +644,14 @@ export function ProvidersPanel({
|
|
|
644
644
|
)}
|
|
645
645
|
|
|
646
646
|
{error !== null && (
|
|
647
|
-
<div className="
|
|
647
|
+
<div className="border border-border bg-card shadow-sm flex items-center gap-2 rounded-[8px] px-3 py-2 text-sm text-destructive">
|
|
648
648
|
<AlertCircle className="size-4 shrink-0" />
|
|
649
649
|
{error}
|
|
650
650
|
</div>
|
|
651
651
|
)}
|
|
652
652
|
|
|
653
653
|
{providers.length === 0 ? (
|
|
654
|
-
<div className="
|
|
654
|
+
<div className="border border-border bg-card shadow-sm space-y-3 rounded-[8px] py-12 text-center">
|
|
655
655
|
<p className="text-sm text-muted-foreground">No providers configured yet.</p>
|
|
656
656
|
<Button onClick={() => setShowForm(true)} variant="outline" size="sm">
|
|
657
657
|
<Plus className="size-4" />
|
|
@@ -660,7 +660,7 @@ export function ProvidersPanel({
|
|
|
660
660
|
</div>
|
|
661
661
|
) : (
|
|
662
662
|
<div className="space-y-3">
|
|
663
|
-
<div className="flex gap-1
|
|
663
|
+
<div className="border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1">
|
|
664
664
|
{(["all", "personal", "company"] as const).map((tab) => (
|
|
665
665
|
<TooltipProvider key={tab}>
|
|
666
666
|
<Tooltip>
|
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
|
5
5
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
|
|
6
6
|
import { Button } from "../ui/button";
|
|
7
|
+
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
7
8
|
import { ProvidersPanel } from "./ProvidersPanel";
|
|
8
9
|
import { useProviders } from "../../lib/useProviders";
|
|
9
10
|
import { useStripConfig } from "../../lib/useStripConfig";
|
|
@@ -61,7 +62,13 @@ export function SettingsDialog(): JSX.Element {
|
|
|
61
62
|
return (
|
|
62
63
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
63
64
|
<DialogTrigger asChild>
|
|
64
|
-
<Button
|
|
65
|
+
<Button
|
|
66
|
+
variant="ghost"
|
|
67
|
+
size="icon"
|
|
68
|
+
className={INSPECTOR_ICON_TRIGGER_CLASS}
|
|
69
|
+
aria-label="Settings"
|
|
70
|
+
title="Settings"
|
|
71
|
+
>
|
|
65
72
|
<Settings className="size-4" />
|
|
66
73
|
<span className="sr-only">Settings</span>
|
|
67
74
|
</Button>
|
|
@@ -226,25 +233,25 @@ function StorageSettingsTab(): JSX.Element {
|
|
|
226
233
|
copiedId={copiedId}
|
|
227
234
|
onCopy={handleCopy}
|
|
228
235
|
/>
|
|
229
|
-
<div className="
|
|
236
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
230
237
|
<div className="font-medium">In-memory logs</div>
|
|
231
238
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
232
239
|
{stats.memoryCount.toLocaleString()} loaded
|
|
233
240
|
</div>
|
|
234
241
|
</div>
|
|
235
|
-
<div className="
|
|
242
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
236
243
|
<div className="font-medium">Log files</div>
|
|
237
244
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
238
245
|
{stats.logFileCount.toLocaleString()} files / {formatBytes(stats.logBytes)}
|
|
239
246
|
</div>
|
|
240
247
|
</div>
|
|
241
|
-
<div className="
|
|
248
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
242
249
|
<div className="font-medium">Streaming chunks</div>
|
|
243
250
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
244
251
|
{stats.chunkFileCount.toLocaleString()} files / {formatBytes(stats.chunkBytes)}
|
|
245
252
|
</div>
|
|
246
253
|
</div>
|
|
247
|
-
<div className="
|
|
254
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
248
255
|
<div className="font-medium">Session archives</div>
|
|
249
256
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
250
257
|
{stats.sessionArchiveFileCount.toLocaleString()} files /{" "}
|
|
@@ -274,7 +281,7 @@ function CopyableSetupValue({
|
|
|
274
281
|
}): JSX.Element {
|
|
275
282
|
const copied = copiedId === id;
|
|
276
283
|
return (
|
|
277
|
-
<div className="
|
|
284
|
+
<div className="border border-border bg-muted/40 min-w-0 rounded-md px-3 py-2">
|
|
278
285
|
<div className="mb-1 text-xs font-medium text-muted-foreground">{label}</div>
|
|
279
286
|
<div className="flex min-w-0 items-center gap-2">
|
|
280
287
|
<code className="min-w-0 flex-1 truncate font-mono text-xs text-foreground">{value}</code>
|
|
@@ -392,7 +392,7 @@ function CandidateItem({
|
|
|
392
392
|
|
|
393
393
|
if (editing) {
|
|
394
394
|
return (
|
|
395
|
-
<div className="
|
|
395
|
+
<div className="border border-border bg-card shadow-sm rounded-[8px] px-2.5 py-2">
|
|
396
396
|
<div className="grid gap-2">
|
|
397
397
|
<div className="flex flex-wrap items-center gap-2">
|
|
398
398
|
<select
|
|
@@ -404,7 +404,7 @@ function CandidateItem({
|
|
|
404
404
|
);
|
|
405
405
|
if (parsed.success) setDraftType(parsed.data);
|
|
406
406
|
}}
|
|
407
|
-
className="
|
|
407
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 rounded-md px-2 text-xs"
|
|
408
408
|
aria-label="Candidate type"
|
|
409
409
|
>
|
|
410
410
|
<option value="episode">episode</option>
|
|
@@ -416,7 +416,7 @@ function CandidateItem({
|
|
|
416
416
|
value={draftTitle}
|
|
417
417
|
disabled={isUpdating}
|
|
418
418
|
onChange={(event) => setDraftTitle(event.currentTarget.value)}
|
|
419
|
-
className="h-7 min-w-[220px] flex-1 rounded-md
|
|
419
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 min-w-[220px] flex-1 rounded-md px-2 text-xs"
|
|
420
420
|
aria-label="Candidate title"
|
|
421
421
|
/>
|
|
422
422
|
</div>
|
|
@@ -424,14 +424,14 @@ function CandidateItem({
|
|
|
424
424
|
value={draftContent}
|
|
425
425
|
disabled={isUpdating}
|
|
426
426
|
onChange={(event) => setDraftContent(event.currentTarget.value)}
|
|
427
|
-
className="min-h-28 rounded-md
|
|
427
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground min-h-28 rounded-md px-2 py-1.5 font-mono text-[11px] leading-relaxed"
|
|
428
428
|
aria-label="Candidate content"
|
|
429
429
|
/>
|
|
430
430
|
<input
|
|
431
431
|
value={draftTags}
|
|
432
432
|
disabled={isUpdating}
|
|
433
433
|
onChange={(event) => setDraftTags(event.currentTarget.value)}
|
|
434
|
-
className="
|
|
434
|
+
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 rounded-md px-2 text-xs"
|
|
435
435
|
aria-label="Candidate tags"
|
|
436
436
|
/>
|
|
437
437
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -794,7 +794,7 @@ export function AgentTraceSummary({
|
|
|
794
794
|
if (logs.length === 0) return null;
|
|
795
795
|
|
|
796
796
|
return (
|
|
797
|
-
<section className="
|
|
797
|
+
<section className="border border-border bg-card shadow-sm mb-2 rounded-[8px] px-3 py-2">
|
|
798
798
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 text-xs">
|
|
799
799
|
{showRollupMetrics && (
|
|
800
800
|
<span className="inline-flex items-center gap-1.5 font-semibold text-foreground">
|
|
@@ -8,7 +8,7 @@ import { TurnGroupList } from "./TurnGroupList";
|
|
|
8
8
|
import { AgentTraceSummary } from "./AgentTraceSummary";
|
|
9
9
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
10
10
|
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest } from "./logFocus";
|
|
11
|
-
import { buildTurnGroups
|
|
11
|
+
import { buildTurnGroups } from "./viewerState";
|
|
12
12
|
|
|
13
13
|
export type ConversationGroupProps = {
|
|
14
14
|
group: ConversationGroupData;
|
|
@@ -25,14 +25,18 @@ export type ConversationGroupProps = {
|
|
|
25
25
|
/** Callback to open CompareDrawer with a log and its immediate predecessor. */
|
|
26
26
|
onCompareWithPrevious: (log: CapturedLog) => void;
|
|
27
27
|
comparisonPredecessors: Map<number, CapturedLog>;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/** When true, the route-level session context already shows rollup metrics. */
|
|
31
|
-
hasPinnedSessionContext?: boolean;
|
|
28
|
+
/** Initial expansion state. Single-session pages use this to show logs under the session header. */
|
|
29
|
+
defaultExpanded?: boolean;
|
|
32
30
|
/** Controls whether timestamps render as compact local time or full ISO strings. */
|
|
33
31
|
timeDisplayFormat: TimeDisplayFormat;
|
|
34
32
|
/** Clear all logs that belong to this group. */
|
|
35
33
|
onClearGroup: (ids: number[]) => void;
|
|
34
|
+
/** Show IDE identity in the session header. */
|
|
35
|
+
showClientIdentity?: boolean;
|
|
36
|
+
/** Show process/project metadata in the session header. */
|
|
37
|
+
showProcessMetadata?: boolean;
|
|
38
|
+
/** Show client PID/project metadata on individual log rows. */
|
|
39
|
+
showLogClientMetadata?: boolean;
|
|
36
40
|
/** Optional controlled expansion state used by virtualized lists. */
|
|
37
41
|
expanded?: boolean;
|
|
38
42
|
/** Optional controlled expansion setter used by virtualized lists. */
|
|
@@ -104,13 +108,15 @@ export const ConversationGroup = memo(function ({
|
|
|
104
108
|
onCompareWithPrevious,
|
|
105
109
|
comparisonPredecessors,
|
|
106
110
|
onClearGroup,
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
showClientIdentity = true,
|
|
112
|
+
showProcessMetadata = true,
|
|
113
|
+
showLogClientMetadata = true,
|
|
114
|
+
defaultExpanded = false,
|
|
109
115
|
timeDisplayFormat,
|
|
110
116
|
expanded: controlledExpanded,
|
|
111
117
|
onExpandedChange,
|
|
112
118
|
}: ConversationGroupProps): JSX.Element {
|
|
113
|
-
const [internalExpanded, setInternalExpanded] = useState(
|
|
119
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
114
120
|
const expanded = controlledExpanded ?? internalExpanded;
|
|
115
121
|
const setExpanded = useCallback(
|
|
116
122
|
(nextExpanded: boolean) => {
|
|
@@ -122,12 +128,17 @@ export const ConversationGroup = memo(function ({
|
|
|
122
128
|
},
|
|
123
129
|
[onExpandedChange],
|
|
124
130
|
);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!defaultExpanded || controlledExpanded !== undefined) return;
|
|
134
|
+
setInternalExpanded(true);
|
|
135
|
+
}, [controlledExpanded, defaultExpanded]);
|
|
136
|
+
|
|
125
137
|
const stats = useMemo(() => computeStats(group.logs), [group.logs]);
|
|
126
138
|
const providers = useMemo(() => collectProviders(group.logs), [group.logs]);
|
|
127
139
|
const startTime = group.logs[0]?.timestamp ?? new Date().toISOString();
|
|
128
140
|
const endTime = group.logs[group.logs.length - 1]?.timestamp ?? new Date().toISOString();
|
|
129
141
|
const isLoading = group.logs.some((log) => log.responseStatus === null);
|
|
130
|
-
const showTraceRollupMetrics = standalone && !hasPinnedSessionContext;
|
|
131
142
|
const clientPid = useMemo(() => getFirstClientPid(group.logs), [group.logs]);
|
|
132
143
|
const clientProjectFolder = useMemo(() => getFirstClientProjectFolder(group.logs), [group.logs]);
|
|
133
144
|
|
|
@@ -136,7 +147,6 @@ export const ConversationGroup = memo(function ({
|
|
|
136
147
|
|
|
137
148
|
useEffect(() => {
|
|
138
149
|
const handleLogFocusRequest = (event: Event): void => {
|
|
139
|
-
if (standalone) return;
|
|
140
150
|
const request = readLogFocusRequest(event);
|
|
141
151
|
if (request === null) return;
|
|
142
152
|
if (!group.logs.some((log) => log.id === request.logId)) return;
|
|
@@ -147,37 +157,37 @@ export const ConversationGroup = memo(function ({
|
|
|
147
157
|
return () => {
|
|
148
158
|
window.removeEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
149
159
|
};
|
|
150
|
-
}, [group.logs, setExpanded
|
|
160
|
+
}, [group.logs, setExpanded]);
|
|
151
161
|
|
|
152
162
|
return (
|
|
153
163
|
<div className="mb-2">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
<ConversationHeader
|
|
165
|
+
conversationId={group.conversationId}
|
|
166
|
+
startTime={startTime}
|
|
167
|
+
endTime={endTime}
|
|
168
|
+
totalCalls={group.logs.length}
|
|
169
|
+
totalInputTokens={stats.totalInputTokens}
|
|
170
|
+
totalOutputTokens={stats.totalOutputTokens}
|
|
171
|
+
expanded={expanded}
|
|
172
|
+
onToggle={() => setExpanded(!expanded)}
|
|
173
|
+
providers={providers}
|
|
174
|
+
isLoading={isLoading}
|
|
175
|
+
userAgent={group.logs[0]?.userAgent ?? null}
|
|
176
|
+
clientPid={clientPid}
|
|
177
|
+
clientProjectFolder={clientProjectFolder}
|
|
178
|
+
showClientIdentity={showClientIdentity}
|
|
179
|
+
showProcessMetadata={showProcessMetadata}
|
|
180
|
+
timeDisplayFormat={timeDisplayFormat}
|
|
181
|
+
onClear={() => onClearGroup(group.logs.map((l) => l.id))}
|
|
182
|
+
/>
|
|
173
183
|
|
|
174
|
-
{
|
|
184
|
+
{expanded && (
|
|
175
185
|
<div>
|
|
176
186
|
<AgentTraceSummary
|
|
177
187
|
logs={group.logs}
|
|
178
188
|
scopeId={group.conversationId}
|
|
179
189
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
180
|
-
showRollupMetrics={
|
|
190
|
+
showRollupMetrics={false}
|
|
181
191
|
timeDisplayFormat={timeDisplayFormat}
|
|
182
192
|
/>
|
|
183
193
|
<TurnGroupList
|
|
@@ -189,6 +199,7 @@ export const ConversationGroup = memo(function ({
|
|
|
189
199
|
cacheTrends={cacheTrends}
|
|
190
200
|
onCompareWithPrevious={onCompareWithPrevious}
|
|
191
201
|
comparisonPredecessors={comparisonPredecessors}
|
|
202
|
+
showLogClientMetadata={showLogClientMetadata}
|
|
192
203
|
/>
|
|
193
204
|
</div>
|
|
194
205
|
)}
|