@tonyclaw/agent-inspector 2.0.39 → 2.0.41
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/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DCg6S2cl.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-DsU6QETm.js +115 -0
- package/.output/public/assets/{ReplayDialog-B58TbTtY.js → ReplayDialog-ClVgjSDE.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-d4P6JNiP.js → RequestAnatomy-BhqvLQp9.js} +1 -1
- package/.output/public/assets/ResponseView-BZAJoK6B.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-CaORmoKX.js +1 -0
- package/.output/public/assets/_sessionId-BHqywvM3.js +1 -0
- package/.output/public/assets/index-B_LPYuM0.js +1 -0
- package/.output/public/assets/{main-DK9PoBjx.js → main-CI3HFEcV.js} +2 -2
- package/.output/server/{_sessionId-DjarbXpT.mjs → _sessionId-DQ0ljHQ8.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-C5Z6AlUk.mjs → CompareDrawer-nkVa9epn.mjs} +4 -3
- package/.output/server/_ssr/{ProxyViewerContainer-4pXsBywn.mjs → ProxyViewerContainer-CXA7iH3H.mjs} +140 -45
- package/.output/server/_ssr/{ReplayDialog-w1vc1p5e.mjs → ReplayDialog-CK71-ucq.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-2HaBhsNV.mjs → RequestAnatomy-DDCUJJ3X.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-SO_Y_W85.mjs → ResponseView-C6ZA7V3B.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-CXEa2DkO.mjs → StreamingChunkSequence-DW5v_o6G.mjs} +2 -2
- package/.output/server/_ssr/{index-DuCGt4a2.mjs → index-Ckex98yq.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D0BaSzkZ.mjs → router-ChJIVv7-.mjs} +434 -6
- package/.output/server/{_tanstack-start-manifest_v-6N4SODyd.mjs → _tanstack-start-manifest_v-CJ4__weU.mjs} +1 -1
- package/.output/server/index.mjs +62 -62
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +5 -2
- package/src/components/providers/ProviderCard.tsx +6 -3
- package/src/components/providers/ProviderForm.tsx +6 -3
- package/src/components/providers/ProvidersPanel.tsx +6 -1
- package/src/components/providers/SettingsDialog.tsx +5 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
- package/src/components/proxy-viewer/LogEntry.tsx +23 -6
- package/src/components/proxy-viewer/LogEntryHeader.tsx +20 -0
- package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
- package/src/components/ui/json-viewer.tsx +7 -2
- package/src/contracts/log.ts +1 -0
- package/src/lib/clipboard.ts +67 -0
- package/src/proxy/logFinalizer.ts +58 -0
- package/src/proxy/logSearch.ts +1 -0
- package/src/proxy/toolSchemaWarnings.ts +515 -0
- package/.output/public/assets/CompareDrawer-Jfe6M6DQ.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-CQKV0Bqq.js +0 -115
- package/.output/public/assets/ResponseView-Bum1a3tG.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-CjBDp5E9.js +0 -1
- package/.output/public/assets/_sessionId-BupV2gtf.js +0 -1
- package/.output/public/assets/index-DHSww5em.js +0 -1
package/.output/server/index.mjs
CHANGED
|
@@ -35,103 +35,103 @@ const headers = ((m) => function headersRouteRule(event) {
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
const assets = {
|
|
38
|
+
"/assets/CompareDrawer-DCg6S2cl.js": {
|
|
39
|
+
"type": "text/javascript; charset=utf-8",
|
|
40
|
+
"etag": '"49e8-k5Sysa3YIYWHG3hgZOKGI6S/VSQ"',
|
|
41
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
42
|
+
"size": 18920,
|
|
43
|
+
"path": "../public/assets/CompareDrawer-DCg6S2cl.js"
|
|
44
|
+
},
|
|
38
45
|
"/assets/alibaba-TTwafVwX.svg": {
|
|
39
46
|
"type": "image/svg+xml",
|
|
40
47
|
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
41
|
-
"mtime": "2026-07-
|
|
48
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
42
49
|
"size": 5915,
|
|
43
50
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
51
|
},
|
|
45
|
-
"/assets/
|
|
46
|
-
"type": "
|
|
47
|
-
"etag": '"
|
|
48
|
-
"mtime": "2026-07-
|
|
49
|
-
"size":
|
|
50
|
-
"path": "../public/assets/
|
|
52
|
+
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
53
|
+
"type": "image/jpeg",
|
|
54
|
+
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
55
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
56
|
+
"size": 6918,
|
|
57
|
+
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
51
58
|
},
|
|
52
|
-
"/assets/
|
|
59
|
+
"/assets/RequestAnatomy-BhqvLQp9.js": {
|
|
53
60
|
"type": "text/javascript; charset=utf-8",
|
|
54
|
-
"etag": '"
|
|
55
|
-
"mtime": "2026-07-
|
|
56
|
-
"size":
|
|
57
|
-
"path": "../public/assets/
|
|
61
|
+
"etag": '"4485-e5cA5GSbVksNziulI0+GKBLNU8I"',
|
|
62
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
63
|
+
"size": 17541,
|
|
64
|
+
"path": "../public/assets/RequestAnatomy-BhqvLQp9.js"
|
|
58
65
|
},
|
|
59
|
-
"/assets/ReplayDialog-
|
|
66
|
+
"/assets/ReplayDialog-ClVgjSDE.js": {
|
|
60
67
|
"type": "text/javascript; charset=utf-8",
|
|
61
|
-
"etag": '"318c-
|
|
62
|
-
"mtime": "2026-07-
|
|
68
|
+
"etag": '"318c-LpwaR4Dkb9DojATSYhkNfnmy2zI"',
|
|
69
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
63
70
|
"size": 12684,
|
|
64
|
-
"path": "../public/assets/ReplayDialog-
|
|
71
|
+
"path": "../public/assets/ReplayDialog-ClVgjSDE.js"
|
|
65
72
|
},
|
|
66
|
-
"/assets/index-
|
|
73
|
+
"/assets/index-B_LPYuM0.js": {
|
|
67
74
|
"type": "text/javascript; charset=utf-8",
|
|
68
|
-
"etag": '"74-
|
|
69
|
-
"mtime": "2026-07-
|
|
75
|
+
"etag": '"74-nFohMzCw3y6hRBAH/I/1aAKk9qA"',
|
|
76
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
70
77
|
"size": 116,
|
|
71
|
-
"path": "../public/assets/index-
|
|
72
|
-
},
|
|
73
|
-
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
74
|
-
"type": "image/jpeg",
|
|
75
|
-
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
76
|
-
"mtime": "2026-07-01T08:15:03.700Z",
|
|
77
|
-
"size": 6918,
|
|
78
|
-
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
78
|
+
"path": "../public/assets/index-B_LPYuM0.js"
|
|
79
79
|
},
|
|
80
|
-
"/assets/
|
|
81
|
-
"type": "text/
|
|
82
|
-
"etag": '"
|
|
83
|
-
"mtime": "2026-07-
|
|
84
|
-
"size":
|
|
85
|
-
"path": "../public/assets/
|
|
80
|
+
"/assets/index-BfGJEb-2.css": {
|
|
81
|
+
"type": "text/css; charset=utf-8",
|
|
82
|
+
"etag": '"1acc9-JrjZs7fIRGuBr66cqP9miLHcLKk"',
|
|
83
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
84
|
+
"size": 109769,
|
|
85
|
+
"path": "../public/assets/index-BfGJEb-2.css"
|
|
86
86
|
},
|
|
87
|
-
"/assets/
|
|
87
|
+
"/assets/StreamingChunkSequence-CaORmoKX.js": {
|
|
88
88
|
"type": "text/javascript; charset=utf-8",
|
|
89
|
-
"etag": '"
|
|
90
|
-
"mtime": "2026-07-
|
|
91
|
-
"size":
|
|
92
|
-
"path": "../public/assets/
|
|
89
|
+
"etag": '"d5c-CFWA5sGEVjRZL7Tc8f3pZV/+3pw"',
|
|
90
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
91
|
+
"size": 3420,
|
|
92
|
+
"path": "../public/assets/StreamingChunkSequence-CaORmoKX.js"
|
|
93
93
|
},
|
|
94
|
-
"/assets/
|
|
94
|
+
"/assets/ResponseView-BZAJoK6B.js": {
|
|
95
95
|
"type": "text/javascript; charset=utf-8",
|
|
96
|
-
"etag": '"
|
|
97
|
-
"mtime": "2026-07-
|
|
98
|
-
"size":
|
|
99
|
-
"path": "../public/assets/
|
|
96
|
+
"etag": '"30d1-1lX2qKD6ndedO8OL4mCAFqTi2c4"',
|
|
97
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
98
|
+
"size": 12497,
|
|
99
|
+
"path": "../public/assets/ResponseView-BZAJoK6B.js"
|
|
100
100
|
},
|
|
101
101
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
102
102
|
"type": "image/svg+xml",
|
|
103
103
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
104
|
-
"mtime": "2026-07-
|
|
104
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
105
105
|
"size": 11256,
|
|
106
106
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
107
107
|
},
|
|
108
|
-
"/assets/
|
|
108
|
+
"/assets/_sessionId-BHqywvM3.js": {
|
|
109
|
+
"type": "text/javascript; charset=utf-8",
|
|
110
|
+
"etag": '"d2-aRnjHQXX3+PkuHFc37DXAEd2jHY"',
|
|
111
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
112
|
+
"size": 210,
|
|
113
|
+
"path": "../public/assets/_sessionId-BHqywvM3.js"
|
|
114
|
+
},
|
|
115
|
+
"/assets/main-CI3HFEcV.js": {
|
|
109
116
|
"type": "text/javascript; charset=utf-8",
|
|
110
|
-
"etag": '"
|
|
111
|
-
"mtime": "2026-07-
|
|
112
|
-
"size":
|
|
113
|
-
"path": "../public/assets/
|
|
117
|
+
"etag": '"517d9-uRZNMUg8U43tHnhzFFtGrUV430s"',
|
|
118
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
119
|
+
"size": 333785,
|
|
120
|
+
"path": "../public/assets/main-CI3HFEcV.js"
|
|
114
121
|
},
|
|
115
122
|
"/assets/qwen-CONDcHqt.png": {
|
|
116
123
|
"type": "image/png",
|
|
117
124
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
118
|
-
"mtime": "2026-07-
|
|
125
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
119
126
|
"size": 357059,
|
|
120
127
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
121
128
|
},
|
|
122
|
-
"/assets/
|
|
123
|
-
"type": "text/javascript; charset=utf-8",
|
|
124
|
-
"etag": '"d2-yDl+GeqeLZbuzelLqReyuecyYVA"',
|
|
125
|
-
"mtime": "2026-07-01T08:15:03.700Z",
|
|
126
|
-
"size": 210,
|
|
127
|
-
"path": "../public/assets/_sessionId-BupV2gtf.js"
|
|
128
|
-
},
|
|
129
|
-
"/assets/ProxyViewerContainer-CQKV0Bqq.js": {
|
|
129
|
+
"/assets/ProxyViewerContainer-DsU6QETm.js": {
|
|
130
130
|
"type": "text/javascript; charset=utf-8",
|
|
131
|
-
"etag": '"
|
|
132
|
-
"mtime": "2026-07-
|
|
133
|
-
"size":
|
|
134
|
-
"path": "../public/assets/ProxyViewerContainer-
|
|
131
|
+
"etag": '"b645c-NZxv8v6zEqGaTkLxqNhFQyxqtsc"',
|
|
132
|
+
"mtime": "2026-07-02T06:48:19.896Z",
|
|
133
|
+
"size": 746588,
|
|
134
|
+
"path": "../public/assets/ProxyViewerContainer-DsU6QETm.js"
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
137
|
function readAsset(id) {
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import type { CapturedLog } from "../contracts";
|
|
15
15
|
import { exportLogsAsZip, type ExportMode } from "../lib/export-logs";
|
|
16
|
+
import { copyTextToClipboard } from "../lib/clipboard";
|
|
16
17
|
import type { CaptureMode, TimeDisplayFormat } from "../lib/runtimeConfig";
|
|
17
18
|
import { formatTimestampRange } from "../lib/timeDisplay";
|
|
18
19
|
import { cn, formatContextWindowTokens, formatTokens } from "../lib/utils";
|
|
@@ -276,7 +277,8 @@ function CopyableCommand({ command }: { command: string }): JSX.Element {
|
|
|
276
277
|
const [copied, setCopied] = useState(false);
|
|
277
278
|
|
|
278
279
|
const handleCopy = useCallback(() => {
|
|
279
|
-
void
|
|
280
|
+
void copyTextToClipboard(command).then((success) => {
|
|
281
|
+
if (!success) return;
|
|
280
282
|
setCopied(true);
|
|
281
283
|
setTimeout(() => setCopied(false), 2000);
|
|
282
284
|
});
|
|
@@ -557,7 +559,8 @@ function SessionContextBar({
|
|
|
557
559
|
const userAgent = useMemo(() => getFirstUserAgent(logs), [logs]);
|
|
558
560
|
|
|
559
561
|
const handleCopyLink = useCallback(() => {
|
|
560
|
-
void
|
|
562
|
+
void copyTextToClipboard(window.location.href).then((success) => {
|
|
563
|
+
if (!success) return;
|
|
561
564
|
setCopied(true);
|
|
562
565
|
setTimeout(() => setCopied(false), 2000);
|
|
563
566
|
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ChevronRight,
|
|
25
25
|
} from "lucide-react";
|
|
26
26
|
import type { ProviderConfig } from "../../proxy/providers";
|
|
27
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
27
28
|
import { maskApiKey } from "../../lib/mask";
|
|
28
29
|
import {
|
|
29
30
|
findProviderModelMetadata,
|
|
@@ -422,9 +423,11 @@ export function ProviderCard({
|
|
|
422
423
|
const [showModelResults, setShowModelResults] = useState(false);
|
|
423
424
|
|
|
424
425
|
function handleCopy() {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
426
|
+
void copyTextToClipboard(provider.apiKey).then((success) => {
|
|
427
|
+
if (!success) return;
|
|
428
|
+
setCopied(true);
|
|
429
|
+
setTimeout(() => setCopied(false), 2000);
|
|
430
|
+
});
|
|
428
431
|
}
|
|
429
432
|
|
|
430
433
|
// Get docs URL: use provider.apiDocsUrl if configured, otherwise check KNOWN_PROVIDER_DOCS
|
|
@@ -4,6 +4,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
|
|
4
4
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
5
5
|
import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
|
|
6
6
|
import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
|
|
7
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
7
8
|
import { maskApiKey } from "../../lib/mask";
|
|
8
9
|
import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
|
|
9
10
|
|
|
@@ -316,9 +317,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
316
317
|
provider?.modelMetadata?.some((metadata) => metadata.contextWindow !== undefined) ?? false;
|
|
317
318
|
|
|
318
319
|
function handleCopy() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
void copyTextToClipboard(apiKey).then((success) => {
|
|
321
|
+
if (!success) return;
|
|
322
|
+
setCopied(true);
|
|
323
|
+
setTimeout(() => setCopied(false), 2000);
|
|
324
|
+
});
|
|
322
325
|
}
|
|
323
326
|
|
|
324
327
|
useEffect(() => {
|
|
@@ -8,6 +8,7 @@ import { ConfirmDialog } from "../ui/confirm-dialog";
|
|
|
8
8
|
import { ProviderCard } from "./ProviderCard";
|
|
9
9
|
import { ProviderForm } from "./ProviderForm";
|
|
10
10
|
import { parseJsonResponse, readApiError } from "../../lib/apiClient";
|
|
11
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
11
12
|
import {
|
|
12
13
|
ProviderConfigSchema,
|
|
13
14
|
type ProviderConfig,
|
|
@@ -614,12 +615,16 @@ export function ProvidersPanel({
|
|
|
614
615
|
<button
|
|
615
616
|
type="button"
|
|
616
617
|
onClick={() => {
|
|
617
|
-
void
|
|
618
|
+
void copyTextToClipboard(configPath).then((success) => {
|
|
619
|
+
if (!success) return;
|
|
618
620
|
setConfigPathCopied(true);
|
|
619
621
|
setTimeout(() => setConfigPathCopied(false), 2000);
|
|
620
622
|
});
|
|
621
623
|
}}
|
|
622
624
|
className="shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors"
|
|
625
|
+
aria-label={
|
|
626
|
+
configPathCopied ? "Copied config file path" : "Copy config file path"
|
|
627
|
+
}
|
|
623
628
|
>
|
|
624
629
|
{configPathCopied ? (
|
|
625
630
|
<Check className="size-3 text-green-500" />
|
|
@@ -7,6 +7,7 @@ import { Button } from "../ui/button";
|
|
|
7
7
|
import { ProvidersPanel } from "./ProvidersPanel";
|
|
8
8
|
import { useProviders } from "../../lib/useProviders";
|
|
9
9
|
import { useStripConfig } from "../../lib/useStripConfig";
|
|
10
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
10
11
|
import {
|
|
11
12
|
MAX_PROVIDER_TEST_TIMEOUT_SECONDS,
|
|
12
13
|
MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
@@ -171,7 +172,8 @@ function StorageSettingsTab(): JSX.Element {
|
|
|
171
172
|
}, [refresh]);
|
|
172
173
|
|
|
173
174
|
const handleCopy = useCallback((id: string, value: string) => {
|
|
174
|
-
void
|
|
175
|
+
void copyTextToClipboard(value).then((success) => {
|
|
176
|
+
if (!success) return;
|
|
175
177
|
setCopiedId(id);
|
|
176
178
|
setTimeout(() => setCopiedId(null), 1600);
|
|
177
179
|
});
|
|
@@ -314,7 +316,8 @@ function OnboardingSettingsTab(): JSX.Element {
|
|
|
314
316
|
);
|
|
315
317
|
|
|
316
318
|
const handleCopy = useCallback((id: string, value: string) => {
|
|
317
|
-
void
|
|
319
|
+
void copyTextToClipboard(value).then((success) => {
|
|
320
|
+
if (!success) return;
|
|
318
321
|
setCopiedId(id);
|
|
319
322
|
setTimeout(() => setCopiedId(null), 1600);
|
|
320
323
|
});
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
X,
|
|
14
14
|
} from "lucide-react";
|
|
15
15
|
import { cn, formatTokens } from "../../lib/utils";
|
|
16
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
16
17
|
import type { CapturedLog } from "../../contracts";
|
|
17
18
|
import {
|
|
18
19
|
type DiffOp,
|
|
@@ -656,7 +657,8 @@ export function CompareDrawer({ left, right, onClose }: CompareDrawerProps): JSX
|
|
|
656
657
|
const [copiedPath, setCopiedPath] = useState<string | null>(null);
|
|
657
658
|
const copyResetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
658
659
|
const onCopyPath = (path: string) => {
|
|
659
|
-
void
|
|
660
|
+
void copyTextToClipboard(path).then((success) => {
|
|
661
|
+
if (!success) return;
|
|
660
662
|
setCopiedPath(path);
|
|
661
663
|
if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
|
|
662
664
|
copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { GitCompareArrows } from "lucide-react";
|
|
1
|
+
import { AlertTriangle, GitCompareArrows } from "lucide-react";
|
|
2
2
|
import { Suspense, type JSX } from "react";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState, memo } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
4
|
import type { CapturedLog } from "../../contracts";
|
|
5
5
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
6
6
|
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
|
|
@@ -295,6 +295,7 @@ export const LogEntry = memo(function ({
|
|
|
295
295
|
() => parseRequestTools(requestExpansion.parsedData),
|
|
296
296
|
[requestExpansion.parsedData],
|
|
297
297
|
);
|
|
298
|
+
const warnings = log.warnings ?? [];
|
|
298
299
|
const anatomyPaths = useMemo(() => {
|
|
299
300
|
if (anatomySegments === null) return undefined;
|
|
300
301
|
return new Set(anatomySegments.map((s) => s.path));
|
|
@@ -307,9 +308,12 @@ export const LogEntry = memo(function ({
|
|
|
307
308
|
},
|
|
308
309
|
});
|
|
309
310
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
const handleToggleExpanded = useCallback(() => {
|
|
312
|
+
const nextExpanded = !expanded;
|
|
313
|
+
if (nextExpanded) {
|
|
314
|
+
setActiveTab(anatomySegments !== null ? "anatomy" : "request");
|
|
315
|
+
}
|
|
316
|
+
setExpanded(nextExpanded);
|
|
313
317
|
}, [anatomySegments, expanded]);
|
|
314
318
|
|
|
315
319
|
useEffect(() => {
|
|
@@ -335,7 +339,7 @@ export const LogEntry = memo(function ({
|
|
|
335
339
|
messageCount={requestAnalysis.messageCount}
|
|
336
340
|
toolCount={requestAnalysis.toolCount}
|
|
337
341
|
expanded={expanded}
|
|
338
|
-
onToggle={
|
|
342
|
+
onToggle={handleToggleExpanded}
|
|
339
343
|
cacheTrend={cacheTrend}
|
|
340
344
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
341
345
|
timeDisplayFormat={timeDisplayFormat}
|
|
@@ -392,6 +396,19 @@ export const LogEntry = memo(function ({
|
|
|
392
396
|
<TabsContent value="request">
|
|
393
397
|
{activeTab === "request" && (
|
|
394
398
|
<div className="px-4 pt-1 pb-3">
|
|
399
|
+
{warnings.length > 0 && (
|
|
400
|
+
<div className="mb-3 rounded border border-amber-500/30 bg-amber-500/10 p-3 text-xs">
|
|
401
|
+
<div className="flex items-center gap-2 font-semibold text-amber-300">
|
|
402
|
+
<AlertTriangle className="size-3.5" />
|
|
403
|
+
Tool schema warnings
|
|
404
|
+
</div>
|
|
405
|
+
<ul className="mt-2 space-y-1 text-muted-foreground">
|
|
406
|
+
{warnings.map((warning) => (
|
|
407
|
+
<li key={warning}>{warning}</li>
|
|
408
|
+
))}
|
|
409
|
+
</ul>
|
|
410
|
+
</div>
|
|
411
|
+
)}
|
|
395
412
|
<RequestTools
|
|
396
413
|
summary={requestTools}
|
|
397
414
|
calledToolNames={responseAnalysis.toolNames ?? []}
|
|
@@ -159,6 +159,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
159
159
|
const firstChunkLabel = firstChunkMs === null ? null : formatElapsed(firstChunkMs);
|
|
160
160
|
const tokenRateLabel =
|
|
161
161
|
tokensPerSecond === null || tokensPerSecond <= 0 ? null : formatTokenRate(tokensPerSecond);
|
|
162
|
+
const warningCount = log.warnings?.length ?? 0;
|
|
162
163
|
|
|
163
164
|
return (
|
|
164
165
|
<TooltipProvider>
|
|
@@ -229,6 +230,25 @@ export const LogEntryHeader = memo(function ({
|
|
|
229
230
|
</Badge>
|
|
230
231
|
)}
|
|
231
232
|
|
|
233
|
+
{warningCount > 0 && (
|
|
234
|
+
<Tooltip>
|
|
235
|
+
<TooltipTrigger asChild>
|
|
236
|
+
<Badge
|
|
237
|
+
variant="outline"
|
|
238
|
+
className="text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums gap-1 bg-amber-500/15 text-amber-400 border-amber-500/25"
|
|
239
|
+
>
|
|
240
|
+
<AlertTriangle className="size-3" aria-label="Tool schema warnings" />
|
|
241
|
+
{warningCount}
|
|
242
|
+
</Badge>
|
|
243
|
+
</TooltipTrigger>
|
|
244
|
+
<TooltipContent>
|
|
245
|
+
{warningCount === 1
|
|
246
|
+
? "1 tool schema warning"
|
|
247
|
+
: `${warningCount} tool schema warnings`}
|
|
248
|
+
</TooltipContent>
|
|
249
|
+
</Tooltip>
|
|
250
|
+
)}
|
|
251
|
+
|
|
232
252
|
{/* Elapsed time */}
|
|
233
253
|
{log.elapsedMs !== null && (
|
|
234
254
|
<Tooltip>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState, type MouseEvent } from "react";
|
|
2
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Clipboard write with a 2s "Copied!" indicator. Returns null-safe
|
|
@@ -23,7 +24,8 @@ export function useCopyFeedback(text: string | null): {
|
|
|
23
24
|
(event: MouseEvent) => {
|
|
24
25
|
event.stopPropagation();
|
|
25
26
|
if (text === null) return;
|
|
26
|
-
void
|
|
27
|
+
void copyTextToClipboard(text).then((success) => {
|
|
28
|
+
if (!success) return;
|
|
27
29
|
setCopied(true);
|
|
28
30
|
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
29
31
|
timerRef.current = setTimeout(() => setCopied(false), 2000);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Check, ChevronDown, ChevronRight, ChevronsDown, Copy } from "lucide-react";
|
|
2
2
|
import { type JSX, memo, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
4
5
|
import { cn } from "../../lib/utils";
|
|
5
6
|
import { Button } from "./button";
|
|
6
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
@@ -134,7 +135,8 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
134
135
|
|
|
135
136
|
function handleCopy(e: React.MouseEvent): void {
|
|
136
137
|
e.stopPropagation();
|
|
137
|
-
void
|
|
138
|
+
void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
|
|
139
|
+
if (!success) return;
|
|
138
140
|
setCopied(true);
|
|
139
141
|
setTimeout(() => {
|
|
140
142
|
setCopied(false);
|
|
@@ -147,6 +149,7 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
147
149
|
type="button"
|
|
148
150
|
onClick={handleCopy}
|
|
149
151
|
className="inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors"
|
|
152
|
+
aria-label={copied ? "Copied JSON" : "Copy JSON"}
|
|
150
153
|
title="Copy JSON"
|
|
151
154
|
>
|
|
152
155
|
{copied ? <Check className="size-3 text-green-500" /> : <Copy className="size-3" />}
|
|
@@ -159,7 +162,8 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
159
162
|
|
|
160
163
|
function handleCopy(e: React.MouseEvent): void {
|
|
161
164
|
e.stopPropagation();
|
|
162
|
-
void
|
|
165
|
+
void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
|
|
166
|
+
if (!success) return;
|
|
163
167
|
setCopied(true);
|
|
164
168
|
setTimeout(() => {
|
|
165
169
|
setCopied(false);
|
|
@@ -172,6 +176,7 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
172
176
|
type="button"
|
|
173
177
|
onClick={handleCopy}
|
|
174
178
|
className="opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0"
|
|
179
|
+
aria-label={copied ? "Copied JSON value" : "Copy JSON value"}
|
|
175
180
|
title="Copy to clipboard"
|
|
176
181
|
>
|
|
177
182
|
{copied ? (
|
package/src/contracts/log.ts
CHANGED
|
@@ -58,6 +58,7 @@ export const CapturedLogSchema = z.object({
|
|
|
58
58
|
rawRequestBodyBytes: z.number().int().nonnegative().nullable().optional(),
|
|
59
59
|
responseTextBytes: z.number().int().nonnegative().nullable().optional(),
|
|
60
60
|
bodyContentMode: z.enum(["full", "compact", "truncated"]).optional(),
|
|
61
|
+
warnings: z.array(z.string()).optional(),
|
|
61
62
|
/** Error message from streaming response (e.g., SSE error event) */
|
|
62
63
|
error: z.string().nullable().optional(),
|
|
63
64
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function restoreSelection(selection: Selection | null, ranges: readonly Range[]): void {
|
|
2
|
+
if (selection === null) return;
|
|
3
|
+
selection.removeAllRanges();
|
|
4
|
+
for (const range of ranges) {
|
|
5
|
+
selection.addRange(range);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readSelectionRanges(selection: Selection | null): Range[] {
|
|
10
|
+
if (selection === null) return [];
|
|
11
|
+
const ranges: Range[] = [];
|
|
12
|
+
for (let index = 0; index < selection.rangeCount; index++) {
|
|
13
|
+
ranges.push(selection.getRangeAt(index));
|
|
14
|
+
}
|
|
15
|
+
return ranges;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function copyWithTextareaFallback(text: string): boolean {
|
|
19
|
+
if (typeof document === "undefined") return false;
|
|
20
|
+
|
|
21
|
+
const activeElement = document.activeElement;
|
|
22
|
+
const selection = document.getSelection();
|
|
23
|
+
const ranges = readSelectionRanges(selection);
|
|
24
|
+
const textarea = document.createElement("textarea");
|
|
25
|
+
|
|
26
|
+
textarea.value = text;
|
|
27
|
+
textarea.setAttribute("readonly", "");
|
|
28
|
+
textarea.style.position = "fixed";
|
|
29
|
+
textarea.style.top = "0";
|
|
30
|
+
textarea.style.left = "-9999px";
|
|
31
|
+
textarea.style.opacity = "0";
|
|
32
|
+
|
|
33
|
+
document.body.appendChild(textarea);
|
|
34
|
+
textarea.focus();
|
|
35
|
+
textarea.select();
|
|
36
|
+
textarea.setSelectionRange(0, text.length);
|
|
37
|
+
|
|
38
|
+
let copied = false;
|
|
39
|
+
try {
|
|
40
|
+
copied = document.execCommand("copy");
|
|
41
|
+
} catch {
|
|
42
|
+
// Keep the false default.
|
|
43
|
+
} finally {
|
|
44
|
+
document.body.removeChild(textarea);
|
|
45
|
+
restoreSelection(selection, ranges);
|
|
46
|
+
if (typeof HTMLElement !== "undefined" && activeElement instanceof HTMLElement) {
|
|
47
|
+
activeElement.focus({ preventScroll: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return copied;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|
55
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
56
|
+
if (clipboard !== undefined) {
|
|
57
|
+
try {
|
|
58
|
+
await clipboard.writeText(text);
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
// Fall back below. Some embedded browsers expose Clipboard API but reject
|
|
62
|
+
// writes outside a secure context or without an explicit permission grant.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return copyWithTextareaFallback(text);
|
|
67
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { CapturedLog } from "./schemas";
|
|
|
9
9
|
import { getSessionProcess, isSessionProcessAvailable } from "./sessionProcess";
|
|
10
10
|
import { finalizeLogUpdate } from "./store";
|
|
11
11
|
import { computeTokensPerSecond } from "./streamTiming";
|
|
12
|
+
import { analyzeToolSchemaWarnings } from "./toolSchemaWarnings";
|
|
12
13
|
|
|
13
14
|
type BaseFinalizeLogJob = {
|
|
14
15
|
log: CapturedLog;
|
|
@@ -91,6 +92,7 @@ export function buildFileLogEntry(log: CapturedLog, upstreamUrl: string): Record
|
|
|
91
92
|
clientProjectFolder: log.clientProjectFolder,
|
|
92
93
|
streamingChunks: log.streamingChunks,
|
|
93
94
|
streamingChunksPath: log.streamingChunksPath,
|
|
95
|
+
warnings: log.warnings,
|
|
94
96
|
error: log.error,
|
|
95
97
|
};
|
|
96
98
|
}
|
|
@@ -152,6 +154,57 @@ function applyOptionalStreamingTiming(log: CapturedLog, job: FinalizeLogJob): vo
|
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
type ToolSchemaWarningAnalysisJob = {
|
|
158
|
+
log: CapturedLog;
|
|
159
|
+
upstreamUrl: string;
|
|
160
|
+
error: string | null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
let toolSchemaWarningQueue: Promise<void> = Promise.resolve();
|
|
164
|
+
|
|
165
|
+
function deferToolSchemaWarningAnalysis(): Promise<void> {
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
setTimeout(resolve, 0);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function withToolSchemaWarnings(log: CapturedLog): CapturedLog | null {
|
|
172
|
+
const warnings = analyzeToolSchemaWarnings({
|
|
173
|
+
rawRequestBody: log.rawRequestBody,
|
|
174
|
+
responseText: log.responseText,
|
|
175
|
+
apiFormat: log.apiFormat,
|
|
176
|
+
});
|
|
177
|
+
if (warnings.length === 0) return null;
|
|
178
|
+
const mergedWarnings = Array.from(new Set([...(log.warnings ?? []), ...warnings]));
|
|
179
|
+
if (mergedWarnings.length === (log.warnings?.length ?? 0)) return null;
|
|
180
|
+
return { ...log, warnings: mergedWarnings };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob): Promise<void> {
|
|
184
|
+
await deferToolSchemaWarningAnalysis();
|
|
185
|
+
const updatedLog = withToolSchemaWarnings(job.log);
|
|
186
|
+
if (updatedLog === null) return;
|
|
187
|
+
|
|
188
|
+
appendLogEntry({ ...buildFileLogEntry(updatedLog, job.upstreamUrl), error: job.error });
|
|
189
|
+
finalizeLogUpdate(updatedLog);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function scheduleToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob): void {
|
|
193
|
+
toolSchemaWarningQueue = toolSchemaWarningQueue
|
|
194
|
+
.catch(() => undefined)
|
|
195
|
+
.then(() => runToolSchemaWarningAnalysis(job))
|
|
196
|
+
.catch((err: unknown) => {
|
|
197
|
+
logger.warn(
|
|
198
|
+
`[logFinalizer] Tool schema warning analysis failed for log #${job.log.id}:`,
|
|
199
|
+
errorMessage(err),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function flushToolSchemaWarningAnalysis(): Promise<void> {
|
|
205
|
+
await toolSchemaWarningQueue;
|
|
206
|
+
}
|
|
207
|
+
|
|
155
208
|
function finalizeNonStreaming(
|
|
156
209
|
job: FinalizeNonStreamingLogJob,
|
|
157
210
|
log: CapturedLog,
|
|
@@ -384,6 +437,11 @@ export function buildFinalizeLogResult(job: FinalizeLogJob): FinalizeLogResult {
|
|
|
384
437
|
export function commitFinalizeLogResult(result: FinalizeLogResult): void {
|
|
385
438
|
appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
|
|
386
439
|
finalizeLogUpdate(result.log);
|
|
440
|
+
scheduleToolSchemaWarningAnalysis({
|
|
441
|
+
log: cloneLog(result.log),
|
|
442
|
+
upstreamUrl: result.upstreamUrl,
|
|
443
|
+
error: result.error,
|
|
444
|
+
});
|
|
387
445
|
if (result.cleanupRawStreamPath !== undefined && result.cleanupRawStreamPath !== null) {
|
|
388
446
|
try {
|
|
389
447
|
rmSync(result.cleanupRawStreamPath, { force: true });
|
package/src/proxy/logSearch.ts
CHANGED
|
@@ -70,6 +70,7 @@ function searchableFields(log: CapturedLog): string[] {
|
|
|
70
70
|
optionalText(log.clientCwd),
|
|
71
71
|
optionalText(log.clientProjectFolder),
|
|
72
72
|
optionalText(log.error),
|
|
73
|
+
...(log.warnings ?? []),
|
|
73
74
|
].filter((value): value is string => value !== null && value.length > 0);
|
|
74
75
|
}
|
|
75
76
|
|