@tonyclaw/agent-inspector 2.0.2 → 2.0.3

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 (42) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Bp7_x-5N.js → CompareDrawer-D5A4bTfV.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-Da0jpBkp.js +101 -0
  4. package/.output/public/assets/{ReplayDialog-DFHCd0yx.js → ReplayDialog-CxUk_TF0.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-ehyrskxt.js → RequestAnatomy-DIlzjgjJ.js} +1 -1
  6. package/.output/public/assets/ResponseView-DQCuKJ1G.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-Bjs4Lqwn.js → StreamingChunkSequence-DHk4SGGL.js} +1 -1
  8. package/.output/public/assets/_sessionId-dY1TTl7N.js +1 -0
  9. package/.output/public/assets/index-D7wwbwly.css +1 -0
  10. package/.output/public/assets/index-FqQZbfl2.js +1 -0
  11. package/.output/public/assets/{json-viewer-6uV_YXws.js → json-viewer-BbU0n8eM.js} +1 -1
  12. package/.output/public/assets/{main-FSGUGtEL.js → main-CZT_F-gu.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +8 -8
  14. package/.output/server/{_sessionId-_bf9vUww.mjs → _sessionId-B-s9P7fJ.mjs} +2 -2
  15. package/.output/server/_ssr/{CompareDrawer-DIth2DQM.mjs → CompareDrawer-C08L3UOO.mjs} +4 -4
  16. package/.output/server/_ssr/{ProxyViewerContainer-249bTH-T.mjs → ProxyViewerContainer-CMWl3Ijy.mjs} +399 -49
  17. package/.output/server/_ssr/{ReplayDialog-C1aGx0y1.mjs → ReplayDialog-CPDo9_G5.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-D2bCiEJn.mjs → RequestAnatomy-D9wt_K1E.mjs} +3 -3
  19. package/.output/server/_ssr/{ResponseView-DP6k4Xs_.mjs → ResponseView-DXaL7nY3.mjs} +4 -4
  20. package/.output/server/_ssr/{StreamingChunkSequence-HyXZV-b5.mjs → StreamingChunkSequence-B_hudZyb.mjs} +3 -3
  21. package/.output/server/_ssr/{index-Bt47f9pn.mjs → index-CuE_BN86.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-Co-YRwUP.mjs → json-viewer-Ci6kkjde.mjs} +2 -2
  24. package/.output/server/_ssr/{router-to_OJirX.mjs → router-BemxgIg7.mjs} +24 -93
  25. package/.output/server/{_tanstack-start-manifest_v-Bd-2YRWo.mjs → _tanstack-start-manifest_v--L1_b4sd.mjs} +1 -1
  26. package/.output/server/index.mjs +67 -67
  27. package/README.md +5 -2
  28. package/package.json +1 -1
  29. package/src/components/proxy-viewer/AgentTraceSummary.tsx +218 -0
  30. package/src/components/proxy-viewer/ConversationGroup.tsx +6 -0
  31. package/src/components/proxy-viewer/ToolTraceEvents.tsx +33 -0
  32. package/src/components/proxy-viewer/TurnGroup.tsx +11 -1
  33. package/src/components/proxy-viewer/viewerState.ts +177 -0
  34. package/src/proxy/chunkStorage.ts +3 -4
  35. package/src/proxy/logger.ts +8 -15
  36. package/src/proxy/store.ts +8 -16
  37. package/src/routes/api/providers.$providerId.test.log.ts +0 -79
  38. package/.output/public/assets/ProxyViewerContainer-USuxPy-K.js +0 -101
  39. package/.output/public/assets/ResponseView-BNGyc8e_.js +0 -1
  40. package/.output/public/assets/_sessionId-D_SeK_qp.js +0 -1
  41. package/.output/public/assets/index-BGGOWR7A.js +0 -1
  42. package/.output/public/assets/index-CIL46Z2y.css +0 -1
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, d as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, g as getSessionPath, e as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-to_OJirX.mjs";
2
+ import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as RuntimeConfigSchema, r as requestFormatForPath, d as createPendingProviderTestResults, P as ProviderTestResultsSchema, e as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, g as getSessionPath, f as ProviderConfigSchema, K as KnowledgeCandidateSchema, s as stripClaudeCodeBillingHeader, c as safeGetOwnProperty, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-BemxgIg7.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -9,7 +9,7 @@ import { R as Root, T as Trigger$2, C as Content, a as Close, b as Title, P as P
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
11
11
  import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
12
- import { C as Check, X, P as Plus, D as Download, S as Settings, A as ArrowLeft, a as Copy, b as ChevronDown, U as Upload, c as Scan, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, T as Trash2, i as TriangleAlert, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, m as Minus, n as CircleCheckBig, O as OctagonAlert, W as Wrench, G as Globe, F as FileTerminal, o as Radio, p as ChevronsUp, q as ChevronsDown, r as FileDiff, H as History, s as RotateCcw, t as GitCompareArrows, u as CircleQuestionMark, v as Server, w as Gauge, x as Lock, y as Wifi, z as WifiOff, B as ArrowUp, I as ArrowDown, J as Rows3, K as Columns2 } from "../_libs/lucide-react.mjs";
12
+ import { C as Check, X, P as Plus, D as Download, S as Settings, A as ArrowLeft, a as Copy, b as ChevronDown, U as Upload, c as Scan, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, T as Trash2, W as Wrench, i as TriangleAlert, B as Brain, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, m as Minus, n as CircleCheckBig, O as OctagonAlert, G as Globe, F as FileTerminal, o as Radio, p as ChevronsUp, q as ChevronsDown, r as FileDiff, H as History, s as RotateCcw, t as GitCompareArrows, u as CircleQuestionMark, v as Server, w as Gauge, x as Lock, y as Wifi, z as WifiOff, I as ArrowUp, J as ArrowDown, K as Rows3, N as Columns2 } from "../_libs/lucide-react.mjs";
13
13
  import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
14
14
  import { P as Provider, R as Root3, T as Trigger$1, a as Portal$1, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
15
15
  import { R as Root2$1, L as List, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
@@ -17,7 +17,7 @@ import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
17
17
  const ApiErrorSchema = object({
18
18
  error: string()
19
19
  });
20
- async function parseJsonResponse(response, schema) {
20
+ async function parseJsonResponse$1(response, schema) {
21
21
  const data = await response.json();
22
22
  return schema.parse(data);
23
23
  }
@@ -36,7 +36,7 @@ async function fetchJson(input, schema, init, errorFallback) {
36
36
  const fallback = errorFallback?.(response) ?? `Request failed with status ${response.status}`;
37
37
  throw new Error(await readApiError(response, fallback));
38
38
  }
39
- return parseJsonResponse(response, schema);
39
+ return parseJsonResponse$1(response, schema);
40
40
  }
41
41
  const STRIP_CONFIG_SWR_KEY = "/api/config";
42
42
  async function fetcher$2(url) {
@@ -275,7 +275,7 @@ function getStatusCategory(status) {
275
275
  if (status >= 500) return "server_error";
276
276
  return "pending";
277
277
  }
278
- const version = "2.0.2";
278
+ const version = "2.0.3";
279
279
  const packageJson = {
280
280
  version
281
281
  };
@@ -1343,27 +1343,27 @@ function useCopyFeedback(text) {
1343
1343
  return { copied, copy };
1344
1344
  }
1345
1345
  const LazyCompareDrawer = reactExports.lazy(
1346
- () => import("./CompareDrawer-DIth2DQM.mjs").then((m) => ({ default: m.CompareDrawer }))
1346
+ () => import("./CompareDrawer-C08L3UOO.mjs").then((m) => ({ default: m.CompareDrawer }))
1347
1347
  );
1348
1348
  const LazyReplayDialog = reactExports.lazy(
1349
- () => import("./ReplayDialog-C1aGx0y1.mjs").then((m) => ({ default: m.ReplayDialog }))
1349
+ () => import("./ReplayDialog-CPDo9_G5.mjs").then((m) => ({ default: m.ReplayDialog }))
1350
1350
  );
1351
1351
  const LazyRequestAnatomy = reactExports.lazy(
1352
- () => import("./RequestAnatomy-D2bCiEJn.mjs").then((m) => ({ default: m.RequestAnatomy }))
1352
+ () => import("./RequestAnatomy-D9wt_K1E.mjs").then((m) => ({ default: m.RequestAnatomy }))
1353
1353
  );
1354
1354
  const LazyResponseView = reactExports.lazy(
1355
- () => import("./ResponseView-DP6k4Xs_.mjs").then((m) => ({ default: m.ResponseView }))
1355
+ () => import("./ResponseView-DXaL7nY3.mjs").then((m) => ({ default: m.ResponseView }))
1356
1356
  );
1357
1357
  const LazyStreamingChunkSequence = reactExports.lazy(
1358
- () => import("./StreamingChunkSequence-HyXZV-b5.mjs").then((m) => ({
1358
+ () => import("./StreamingChunkSequence-B_hudZyb.mjs").then((m) => ({
1359
1359
  default: m.StreamingChunkSequence
1360
1360
  }))
1361
1361
  );
1362
1362
  const LazyJsonViewer = reactExports.lazy(
1363
- () => import("./json-viewer-Co-YRwUP.mjs").then((m) => ({ default: m.JsonViewer }))
1363
+ () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewer }))
1364
1364
  );
1365
1365
  const LazyJsonViewerFromString = reactExports.lazy(
1366
- () => import("./json-viewer-Co-YRwUP.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1366
+ () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1367
1367
  );
1368
1368
  const HIGHLIGHT_DURATION_MS = 1200;
1369
1369
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1757,7 +1757,7 @@ const STATUS_BADGE_CLASSES = {
1757
1757
  server_error: "bg-rose-500/15 text-rose-400 border-rose-500/25",
1758
1758
  pending: "bg-muted text-muted-foreground border-border"
1759
1759
  };
1760
- function formatElapsed$1(ms) {
1760
+ function formatElapsed$2(ms) {
1761
1761
  if (ms < 1e3) return `${ms}ms`;
1762
1762
  return `${(ms / 1e3).toFixed(1)}s`;
1763
1763
  }
@@ -1849,12 +1849,12 @@ const LogEntryHeader = reactExports.memo(function({
1849
1849
  ),
1850
1850
  children: [
1851
1851
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1852
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) }),
1852
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$2(log.elapsedMs) }),
1853
1853
  isSlowResponse && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
1854
1854
  ]
1855
1855
  }
1856
1856
  ) }),
1857
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$1(log.elapsedMs)} exceeds ${formatElapsed$1(
1857
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$2(log.elapsedMs)} exceeds ${formatElapsed$2(
1858
1858
  slowResponseThresholdSeconds * 1e3
1859
1859
  )}` : "Elapsed response time" })
1860
1860
  ] }),
@@ -2764,6 +2764,29 @@ function ThreadConnector({
2764
2764
  ) })
2765
2765
  ] });
2766
2766
  }
2767
+ function ToolTraceEvents({ events }) {
2768
+ if (events.length === 0) return null;
2769
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mb-2 grid gap-1.5", children: events.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
2770
+ "div",
2771
+ {
2772
+ className: "flex min-w-0 items-center gap-2 rounded-md border border-amber-500/20 bg-amber-500/5 px-2.5 py-1.5 text-xs",
2773
+ children: [
2774
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 shrink-0 text-amber-400" }),
2775
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-semibold text-amber-300", children: event.name }),
2776
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground", children: [
2777
+ "#",
2778
+ event.logId
2779
+ ] }),
2780
+ event.argumentsPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2781
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground/60" }),
2782
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-muted-foreground", children: event.argumentsPreview })
2783
+ ] })
2784
+ ]
2785
+ },
2786
+ event.id
2787
+ )) });
2788
+ }
2789
+ const PREVIEW_LIMIT = 180;
2767
2790
  function shouldRenderConversationContent(standalone, expanded) {
2768
2791
  return standalone || expanded;
2769
2792
  }
@@ -2801,7 +2824,135 @@ function buildValidPredecessors(groups) {
2801
2824
  }
2802
2825
  return predecessors;
2803
2826
  }
2804
- function formatElapsed(ms) {
2827
+ function parseJsonResponse(responseText) {
2828
+ if (responseText === null) return null;
2829
+ try {
2830
+ const parsed = JSON.parse(responseText);
2831
+ if (typeof parsed === "string") {
2832
+ return JSON.parse(parsed);
2833
+ }
2834
+ return parsed;
2835
+ } catch {
2836
+ return null;
2837
+ }
2838
+ }
2839
+ function previewValue(value) {
2840
+ if (value === void 0 || value === null) return null;
2841
+ const raw = typeof value === "string" ? value : JSON.stringify(value);
2842
+ if (raw === void 0) return null;
2843
+ const normalized = raw.replace(/\s+/g, " ").trim();
2844
+ if (normalized.length === 0) return null;
2845
+ return normalized.length > PREVIEW_LIMIT ? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...` : normalized;
2846
+ }
2847
+ function extractAnthropicToolTraceEvents(log) {
2848
+ const parsed = parseJsonResponse(log.responseText);
2849
+ const content = safeGetOwnProperty(parsed, "content");
2850
+ if (!Array.isArray(content)) return [];
2851
+ const events = [];
2852
+ for (const block of content) {
2853
+ const type = safeGetOwnProperty(block, "type");
2854
+ if (type !== "tool_use") continue;
2855
+ const name = safeGetOwnProperty(block, "name");
2856
+ if (typeof name !== "string" || name.length === 0) continue;
2857
+ events.push({
2858
+ id: `${String(log.id)}-anthropic-tool-${String(events.length)}`,
2859
+ logId: log.id,
2860
+ index: events.length,
2861
+ provider: "anthropic",
2862
+ name,
2863
+ argumentsPreview: previewValue(safeGetOwnProperty(block, "input"))
2864
+ });
2865
+ }
2866
+ return events;
2867
+ }
2868
+ function extractOpenAIToolTraceEvents(log) {
2869
+ const parsed = parseJsonResponse(log.responseText);
2870
+ const choices = safeGetOwnProperty(parsed, "choices");
2871
+ if (!Array.isArray(choices)) return [];
2872
+ const events = [];
2873
+ for (const choice of choices) {
2874
+ const message = safeGetOwnProperty(choice, "message");
2875
+ const toolCalls = safeGetOwnProperty(message, "tool_calls");
2876
+ if (!Array.isArray(toolCalls)) continue;
2877
+ for (const call of toolCalls) {
2878
+ const fn = safeGetOwnProperty(call, "function");
2879
+ const name = safeGetOwnProperty(fn, "name");
2880
+ if (typeof name !== "string" || name.length === 0) continue;
2881
+ events.push({
2882
+ id: `${String(log.id)}-openai-tool-${String(events.length)}`,
2883
+ logId: log.id,
2884
+ index: events.length,
2885
+ provider: "openai",
2886
+ name,
2887
+ argumentsPreview: previewValue(safeGetOwnProperty(fn, "arguments"))
2888
+ });
2889
+ }
2890
+ }
2891
+ return events;
2892
+ }
2893
+ function extractToolTraceEvents(log) {
2894
+ const format = resolveLogFormat(log);
2895
+ switch (format) {
2896
+ case "anthropic":
2897
+ return extractAnthropicToolTraceEvents(log);
2898
+ case "openai":
2899
+ return extractOpenAIToolTraceEvents(log);
2900
+ case "unknown":
2901
+ return [];
2902
+ }
2903
+ }
2904
+ function buildTraceSummary(logs, slowResponseThresholdSeconds, knowledgeCandidateCount = 0) {
2905
+ let failedCallCount = 0;
2906
+ let pendingCallCount = 0;
2907
+ let slowCallCount = 0;
2908
+ let totalInputTokens = 0;
2909
+ let totalOutputTokens = 0;
2910
+ let totalCacheCreationInputTokens = 0;
2911
+ let totalCacheReadInputTokens = 0;
2912
+ let totalElapsedMs = 0;
2913
+ let maxElapsedMs = null;
2914
+ let toolCallCount = 0;
2915
+ for (const log of logs) {
2916
+ if (log.responseStatus === null) {
2917
+ pendingCallCount += 1;
2918
+ } else if (log.responseStatus >= 400) {
2919
+ failedCallCount += 1;
2920
+ }
2921
+ if (log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3) {
2922
+ slowCallCount += 1;
2923
+ }
2924
+ if (log.inputTokens !== null) totalInputTokens += log.inputTokens;
2925
+ if (log.outputTokens !== null) totalOutputTokens += log.outputTokens;
2926
+ if (log.cacheCreationInputTokens !== null) {
2927
+ totalCacheCreationInputTokens += log.cacheCreationInputTokens;
2928
+ }
2929
+ if (log.cacheReadInputTokens !== null) {
2930
+ totalCacheReadInputTokens += log.cacheReadInputTokens;
2931
+ }
2932
+ if (log.elapsedMs !== null) {
2933
+ totalElapsedMs += log.elapsedMs;
2934
+ maxElapsedMs = maxElapsedMs === null ? log.elapsedMs : Math.max(maxElapsedMs, log.elapsedMs);
2935
+ }
2936
+ toolCallCount += extractToolTraceEvents(log).length;
2937
+ }
2938
+ return {
2939
+ llmCallCount: logs.length,
2940
+ toolCallCount,
2941
+ failedCallCount,
2942
+ pendingCallCount,
2943
+ slowCallCount,
2944
+ totalInputTokens,
2945
+ totalOutputTokens,
2946
+ totalCacheCreationInputTokens,
2947
+ totalCacheReadInputTokens,
2948
+ totalElapsedMs,
2949
+ maxElapsedMs,
2950
+ startedAt: logs[0]?.timestamp ?? null,
2951
+ endedAt: logs[logs.length - 1]?.timestamp ?? null,
2952
+ knowledgeCandidateCount
2953
+ };
2954
+ }
2955
+ function formatElapsed$1(ms) {
2805
2956
  if (ms < 1e3) return `${ms}ms`;
2806
2957
  return `${(ms / 1e3).toFixed(1)}s`;
2807
2958
  }
@@ -2871,6 +3022,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2871
3022
  const EndCrab = reactExports.useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
2872
3023
  const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
2873
3024
  const aggregateIsSlow = aggregate.maxElapsed !== null && slowResponseThresholdSeconds > 0 && aggregate.maxElapsed > slowResponseThresholdSeconds * 1e3;
3025
+ const toolEventsByLogId = reactExports.useMemo(() => {
3026
+ const events = /* @__PURE__ */ new Map();
3027
+ for (const entry of entries) {
3028
+ const extracted = extractToolTraceEvents(entry.log);
3029
+ if (extracted.length > 0) events.set(entry.log.id, extracted);
3030
+ }
3031
+ return events;
3032
+ }, [entries]);
2874
3033
  const [layoutVersion, setLayoutVersion] = reactExports.useState(0);
2875
3034
  const containerRef = reactExports.useRef(null);
2876
3035
  reactExports.useEffect(() => {
@@ -3001,14 +3160,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3001
3160
  ),
3002
3161
  children: [
3003
3162
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3004
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.maxElapsed) }),
3163
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(aggregate.maxElapsed) }),
3005
3164
  aggregateIsSlow && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
3006
3165
  ]
3007
3166
  }
3008
3167
  ) }),
3009
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed(
3168
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed$1(
3010
3169
  aggregate.maxElapsed
3011
- )} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3170
+ )} exceeds ${formatElapsed$1(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3012
3171
  ] }) }),
3013
3172
  aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3014
3173
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
@@ -3052,23 +3211,204 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3052
3211
  onToggle: toggleCollapse
3053
3212
  }
3054
3213
  ),
3055
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3056
- LogEntry,
3057
- {
3058
- log,
3059
- viewMode,
3060
- strip,
3061
- slowResponseThresholdSeconds,
3062
- cacheTrend: cacheTrends?.get(log.id) ?? null,
3063
- onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3064
- }
3065
- ) })
3214
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: [
3215
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3216
+ LogEntry,
3217
+ {
3218
+ log,
3219
+ viewMode,
3220
+ strip,
3221
+ slowResponseThresholdSeconds,
3222
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
3223
+ onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3224
+ }
3225
+ ),
3226
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ToolTraceEvents, { events: toolEventsByLogId.get(log.id) ?? [] })
3227
+ ] })
3066
3228
  ] }, log.id);
3067
3229
  })
3068
3230
  )
3069
3231
  }
3070
3232
  );
3071
3233
  });
3234
+ const CandidateResponseSchema = object({
3235
+ candidates: array(KnowledgeCandidateSchema)
3236
+ });
3237
+ function formatElapsed(ms) {
3238
+ if (ms === null) return "-";
3239
+ if (ms < 1e3) return `${String(ms)}ms`;
3240
+ return `${(ms / 1e3).toFixed(1)}s`;
3241
+ }
3242
+ function formatTimeRange$1(startedAt, endedAt) {
3243
+ if (startedAt === null || endedAt === null) return null;
3244
+ const format = (iso) => new Date(iso).toLocaleTimeString([], {
3245
+ hour: "2-digit",
3246
+ minute: "2-digit",
3247
+ second: "2-digit"
3248
+ });
3249
+ return `${format(startedAt)} - ${format(endedAt)}`;
3250
+ }
3251
+ function scrollToLog(logId) {
3252
+ const target = document.getElementById(`log-${String(logId)}`);
3253
+ if (!(target instanceof HTMLElement)) return;
3254
+ target.scrollIntoView({ block: "center", behavior: "smooth" });
3255
+ target.focus({ preventScroll: true });
3256
+ if (target.getAttribute("data-nav-action") === "expand") {
3257
+ target.click();
3258
+ }
3259
+ }
3260
+ function CandidateList({ candidates }) {
3261
+ if (candidates.length === 0) return null;
3262
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 grid gap-1.5", children: candidates.map((candidate) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3263
+ "div",
3264
+ {
3265
+ className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2",
3266
+ children: [
3267
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
3268
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 px-1.5 text-[10px] font-mono", children: candidate.type }),
3269
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", title: candidate.title, children: candidate.title }),
3270
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 font-mono text-[10px] text-muted-foreground", children: candidate.status })
3271
+ ] }),
3272
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: candidate.logIds.map((logId) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3273
+ "button",
3274
+ {
3275
+ type: "button",
3276
+ onClick: () => scrollToLog(logId),
3277
+ className: "rounded border border-border px-1.5 py-0.5 font-mono text-[10px] text-blue-400 transition-colors hover:bg-muted hover:text-blue-300",
3278
+ children: [
3279
+ "#",
3280
+ logId
3281
+ ]
3282
+ },
3283
+ logId
3284
+ )) })
3285
+ ]
3286
+ },
3287
+ candidate.id
3288
+ )) });
3289
+ }
3290
+ function AgentTraceSummary({
3291
+ logs,
3292
+ scopeId,
3293
+ slowResponseThresholdSeconds
3294
+ }) {
3295
+ const [candidates, setCandidates] = reactExports.useState([]);
3296
+ const [candidateState, setCandidateState] = reactExports.useState({
3297
+ status: "idle",
3298
+ error: null
3299
+ });
3300
+ const summary = reactExports.useMemo(
3301
+ () => buildTraceSummary(logs, slowResponseThresholdSeconds, candidates.length),
3302
+ [candidates.length, logs, slowResponseThresholdSeconds]
3303
+ );
3304
+ const timeRange = reactExports.useMemo(
3305
+ () => formatTimeRange$1(summary.startedAt, summary.endedAt),
3306
+ [summary.endedAt, summary.startedAt]
3307
+ );
3308
+ const createCandidates = reactExports.useCallback(() => {
3309
+ if (logs.length === 0 || candidateState.status === "loading") return;
3310
+ setCandidateState({ status: "loading", error: null });
3311
+ void (async () => {
3312
+ try {
3313
+ const response = await fetch(
3314
+ `/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
3315
+ { method: "POST" }
3316
+ );
3317
+ if (!response.ok) {
3318
+ const message = await readApiError(
3319
+ response,
3320
+ `Candidate generation failed with ${String(response.status)}`
3321
+ );
3322
+ setCandidateState({ status: "failed", error: message });
3323
+ return;
3324
+ }
3325
+ const parsed = await parseJsonResponse$1(response, CandidateResponseSchema);
3326
+ setCandidates(parsed.candidates);
3327
+ setCandidateState({ status: "ready", error: null });
3328
+ } catch (error) {
3329
+ setCandidateState({
3330
+ status: "failed",
3331
+ error: error instanceof Error ? error.message : "Candidate response was invalid"
3332
+ });
3333
+ }
3334
+ })();
3335
+ }, [candidateState.status, logs.length, scopeId]);
3336
+ if (logs.length === 0) return null;
3337
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-2 rounded-lg border border-border bg-muted/10 px-3 py-2", children: [
3338
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs", children: [
3339
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-foreground", children: [
3340
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3.5 text-blue-400" }),
3341
+ summary.llmCallCount,
3342
+ " LLM"
3343
+ ] }),
3344
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3345
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 text-amber-400" }),
3346
+ summary.toolCallCount,
3347
+ " tools"
3348
+ ] }),
3349
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3350
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-emerald-400" }),
3351
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3352
+ formatTokens(summary.totalInputTokens),
3353
+ " / ",
3354
+ formatTokens(summary.totalOutputTokens)
3355
+ ] })
3356
+ ] }),
3357
+ (summary.totalCacheCreationInputTokens > 0 || summary.totalCacheReadInputTokens > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3358
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-purple-400" }),
3359
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3360
+ "+",
3361
+ formatTokens(summary.totalCacheCreationInputTokens),
3362
+ " / ~",
3363
+ formatTokens(summary.totalCacheReadInputTokens)
3364
+ ] })
3365
+ ] }),
3366
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3367
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3.5" }),
3368
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: formatElapsed(summary.totalElapsedMs) }),
3369
+ summary.maxElapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground/70", children: [
3370
+ "max ",
3371
+ formatElapsed(summary.maxElapsedMs)
3372
+ ] })
3373
+ ] }),
3374
+ timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground/70", children: timeRange }),
3375
+ (summary.failedCallCount > 0 || summary.pendingCallCount > 0 || summary.slowCallCount > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-amber-400", children: [
3376
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
3377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3378
+ summary.failedCallCount,
3379
+ " fail / ",
3380
+ summary.pendingCallCount,
3381
+ " pending /",
3382
+ " ",
3383
+ summary.slowCallCount,
3384
+ " slow"
3385
+ ] })
3386
+ ] }),
3387
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3388
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "h-6 px-2 text-[10px] font-mono", children: [
3389
+ summary.knowledgeCandidateCount,
3390
+ " memory"
3391
+ ] }),
3392
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3393
+ Button,
3394
+ {
3395
+ type: "button",
3396
+ variant: "outline",
3397
+ size: "sm",
3398
+ className: "h-7 gap-1.5 px-2 text-xs",
3399
+ onClick: createCandidates,
3400
+ disabled: candidateState.status === "loading",
3401
+ children: [
3402
+ candidateState.status === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { className: "size-3.5" }),
3403
+ "Candidate"
3404
+ ]
3405
+ }
3406
+ )
3407
+ ] }),
3408
+ candidateState.status === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-xs text-destructive", children: candidateState.error }),
3409
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CandidateList, { candidates })
3410
+ ] });
3411
+ }
3072
3412
  function computeStats(logs) {
3073
3413
  let totalInput = 0;
3074
3414
  let totalOutput = 0;
@@ -3115,20 +3455,30 @@ const ConversationGroup = reactExports.memo(function({
3115
3455
  onClear: () => onClearGroup(group.logs.map((l) => l.id))
3116
3456
  }
3117
3457
  ),
3118
- shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3119
- TurnGroup,
3120
- {
3121
- entries: tg.entries,
3122
- viewMode,
3123
- strip,
3124
- slowResponseThresholdSeconds,
3125
- cacheTrends,
3126
- onCompareWithPrevious,
3127
- comparisonPredecessors,
3128
- turnIndex: tg.turnIndex
3129
- },
3130
- tg.turnIndex
3131
- )) })
3458
+ shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3459
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3460
+ AgentTraceSummary,
3461
+ {
3462
+ logs: group.logs,
3463
+ scopeId: group.conversationId,
3464
+ slowResponseThresholdSeconds
3465
+ }
3466
+ ),
3467
+ turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3468
+ TurnGroup,
3469
+ {
3470
+ entries: tg.entries,
3471
+ viewMode,
3472
+ strip,
3473
+ slowResponseThresholdSeconds,
3474
+ cacheTrends,
3475
+ onCompareWithPrevious,
3476
+ comparisonPredecessors,
3477
+ turnIndex: tg.turnIndex
3478
+ },
3479
+ tg.turnIndex
3480
+ ))
3481
+ ] })
3132
3482
  ] });
3133
3483
  });
3134
3484
  function CrabLogo({ className }) {
@@ -4480,7 +4830,7 @@ function ProvidersPanel({
4480
4830
  try {
4481
4831
  const res = await fetch("/api/config/paths");
4482
4832
  if (res.ok) {
4483
- const data = await parseJsonResponse(res, ConfigPathsResponseSchema);
4833
+ const data = await parseJsonResponse$1(res, ConfigPathsResponseSchema);
4484
4834
  setConfigPath(data.providerConfig);
4485
4835
  }
4486
4836
  } catch {
@@ -4559,7 +4909,7 @@ function ProvidersPanel({
4559
4909
  signal: controller.signal
4560
4910
  });
4561
4911
  if (res.ok) {
4562
- const results = await parseJsonResponse(res, ProviderTestResultsSchema);
4912
+ const results = await parseJsonResponse$1(res, ProviderTestResultsSchema);
4563
4913
  updateTestResults(providerId, results);
4564
4914
  await persistProviderTestLog(providerId, results);
4565
4915
  } else {
@@ -4599,7 +4949,7 @@ function ProvidersPanel({
4599
4949
  setError(await readApiError(res, "Failed to add provider"));
4600
4950
  return;
4601
4951
  }
4602
- const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
4952
+ const newProvider = await parseJsonResponse$1(res, ProviderConfigSchema);
4603
4953
  setShowForm(false);
4604
4954
  triggerHighlight(newProvider.id);
4605
4955
  refreshProviders();
@@ -4622,7 +4972,7 @@ function ProvidersPanel({
4622
4972
  setError(await readApiError(res, "Failed to update provider"));
4623
4973
  return;
4624
4974
  }
4625
- const updated = await parseJsonResponse(res, ProviderConfigSchema);
4975
+ const updated = await parseJsonResponse$1(res, ProviderConfigSchema);
4626
4976
  setEditingProvider(void 0);
4627
4977
  triggerHighlight(updated.id);
4628
4978
  refreshProviders();
@@ -4697,7 +5047,7 @@ function ProvidersPanel({
4697
5047
  headers: { "Content-Type": "application/json" },
4698
5048
  body: JSON.stringify(text)
4699
5049
  });
4700
- const data = await parseJsonResponse(res, ImportResponseSchema);
5050
+ const data = await parseJsonResponse$1(res, ImportResponseSchema);
4701
5051
  if (res.ok && data.imported !== void 0 && data.imported > 0) {
4702
5052
  refreshProviders();
4703
5053
  setError(null);
@@ -1,10 +1,10 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { D as Dialog, b as DialogContent, d as DialogHeader, e as DialogTitle, T as Tabs, h as TabsList, i as TabsTrigger, j as TabsContent, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent, o as Button } from "./ProxyViewerContainer-249bTH-T.mjs";
3
- import { ResponseView } from "./ResponseView-DP6k4Xs_.mjs";
4
- import "./router-to_OJirX.mjs";
2
+ import { D as Dialog, b as DialogContent, d as DialogHeader, e as DialogTitle, T as Tabs, h as TabsList, i as TabsTrigger, j as TabsContent, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent, o as Button } from "./ProxyViewerContainer-CMWl3Ijy.mjs";
3
+ import { ResponseView } from "./ResponseView-DXaL7nY3.mjs";
4
+ import "./router-BemxgIg7.mjs";
5
5
  import "../_libs/modelcontextprotocol__server.mjs";
6
6
  import "../_libs/jszip.mjs";
7
- import "./json-viewer-Co-YRwUP.mjs";
7
+ import "./json-viewer-Ci6kkjde.mjs";
8
8
  import { s as RotateCcw } from "../_libs/lucide-react.mjs";
9
9
  import { d as object, c as boolean, n as number, b as string } from "../_libs/zod.mjs";
10
10
  import "../_libs/swr.mjs";
@@ -1,9 +1,9 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { k as TooltipProvider, f as formatTokens, c as cn, l as Tooltip, m as TooltipTrigger, n as TooltipContent } from "./ProxyViewerContainer-249bTH-T.mjs";
3
- import "./router-to_OJirX.mjs";
2
+ import { k as TooltipProvider, f as formatTokens, c as cn, l as Tooltip, m as TooltipTrigger, n as TooltipContent } from "./ProxyViewerContainer-CMWl3Ijy.mjs";
3
+ import "./router-BemxgIg7.mjs";
4
4
  import "../_libs/modelcontextprotocol__server.mjs";
5
5
  import "../_libs/jszip.mjs";
6
- import { Q as Info } from "../_libs/lucide-react.mjs";
6
+ import { V as Info } from "../_libs/lucide-react.mjs";
7
7
  import "../_libs/swr.mjs";
8
8
  import "../_libs/use-sync-external-store.mjs";
9
9
  import "../_libs/dequal.mjs";
@@ -1,10 +1,10 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { g as getLogFormatAdapter, f as formatTokens, c as cn, p as getStatusCategory, B as Badge, s as safeJsonValue } from "./ProxyViewerContainer-249bTH-T.mjs";
3
- import { JsonViewer } from "./json-viewer-Co-YRwUP.mjs";
4
- import "./router-to_OJirX.mjs";
2
+ import { g as getLogFormatAdapter, f as formatTokens, c as cn, p as getStatusCategory, B as Badge, s as safeJsonValue } from "./ProxyViewerContainer-CMWl3Ijy.mjs";
3
+ import { JsonViewer } from "./json-viewer-Ci6kkjde.mjs";
4
+ import "./router-BemxgIg7.mjs";
5
5
  import "../_libs/modelcontextprotocol__server.mjs";
6
6
  import "../_libs/jszip.mjs";
7
- import { Z as Zap, i as TriangleAlert, V as CircleStop, Y as Brain, b as ChevronDown, f as ChevronRight, _ as Terminal } from "../_libs/lucide-react.mjs";
7
+ import { Z as Zap, i as TriangleAlert, Y as CircleStop, B as Brain, b as ChevronDown, f as ChevronRight, _ as Terminal } from "../_libs/lucide-react.mjs";
8
8
  import { M as Markdown } from "../_libs/react-markdown.mjs";
9
9
  import { R as Root } from "../_libs/radix-ui__react-separator.mjs";
10
10
  import { R as Root$1, C as CollapsibleTrigger$1, a as CollapsibleContent$1 } from "../_libs/radix-ui__react-collapsible.mjs";