@tonyclaw/agent-inspector 2.0.23 → 2.0.25

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 (41) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-DnYQtd8R.js → CompareDrawer-DVNvS0GQ.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-By3XtQhZ.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-B-3V95xT.js → ReplayDialog-Cw6bN-NR.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-CT8hLGLa.js → RequestAnatomy-DnIsirBE.js} +1 -1
  6. package/.output/public/assets/{ResponseView-DK3CYom0.js → ResponseView-ygsUs7FU.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-D3jJutjp.js → StreamingChunkSequence-DMUn8qXi.js} +1 -1
  8. package/.output/public/assets/_sessionId-DoqvnFx7.js +1 -0
  9. package/.output/public/assets/index-B0YcFeL-.js +1 -0
  10. package/.output/public/assets/index-DsiKfWCp.css +1 -0
  11. package/.output/public/assets/{main-hF_572U6.js → main-kBQ49n13.js} +2 -2
  12. package/.output/server/{_sessionId-BqhNE2M8.mjs → _sessionId-CYKaI68v.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-C4LQW4S1.mjs → CompareDrawer-CHk4p41X.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-BKInvawk.mjs → ProxyViewerContainer-Co9ENCKV.mjs} +276 -47
  15. package/.output/server/_ssr/{ReplayDialog-C3ZiRihL.mjs → ReplayDialog-BRs3fk8H.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-DlVAPIQZ.mjs → RequestAnatomy-Cb68EeZz.mjs} +2 -2
  17. package/.output/server/_ssr/{ResponseView-nMS0rkxu.mjs → ResponseView-DGdbfEjs.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-PDWqrNiO.mjs → StreamingChunkSequence-DptYgirK.mjs} +2 -2
  19. package/.output/server/_ssr/{index-Cd1zD-lo.mjs → index-Ef5mCuww.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-R9gm7_sh.mjs → router-Cvv7SJnd.mjs} +192 -15
  22. package/.output/server/{_tanstack-start-manifest_v-BnLVH_EX.mjs → _tanstack-start-manifest_v-DQoXxIFQ.mjs} +1 -1
  23. package/.output/server/index.mjs +56 -56
  24. package/package.json +1 -1
  25. package/src/components/ProxyViewer.tsx +11 -1
  26. package/src/components/ProxyViewerContainer.tsx +21 -15
  27. package/src/components/providers/ProviderForm.tsx +176 -16
  28. package/src/lib/export-logs.ts +54 -11
  29. package/src/lib/providerTestPrompt.ts +4 -4
  30. package/src/lib/utils.ts +46 -0
  31. package/src/mcp/toolHandlers.ts +58 -8
  32. package/src/proxy/logFinalizer.ts +79 -1
  33. package/src/proxy/logIndex.ts +6 -1
  34. package/src/proxy/schemas.ts +3 -0
  35. package/src/proxy/store.ts +79 -2
  36. package/src/routes/api/logs.stream.ts +5 -2
  37. package/src/routes/api/logs.ts +4 -1
  38. package/.output/public/assets/ProxyViewerContainer-BTYGkg36.js +0 -115
  39. package/.output/public/assets/_sessionId-DpdfAzbo.js +0 -1
  40. package/.output/public/assets/index-780zTVwp.css +0 -1
  41. package/.output/public/assets/index-BpJNbm9G.js +0 -1
@@ -50,7 +50,7 @@ import "../_libs/mimic-function.mjs";
50
50
  import "../_libs/semver.mjs";
51
51
  import "../_libs/uint8array-extras.mjs";
52
52
  const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%20role='img'%20aria-label='Agent%20Inspector'%3e%3crect%20width='64'%20height='64'%20rx='14'%20fill='%23111827'%20/%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='4.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20fill='%23f59e0b'%20d='M15%2036c0-11%207-18%2017-18s17%207%2017%2018c0%208-7%2013-17%2013s-17-5-17-13z'%20/%3e%3cpath%20d='M16%2031c-6-5-12-3-12%204%200%205%206%206%2011%202'%20/%3e%3cpath%20d='M48%2031c6-5%2012-3%2012%204%200%205-6%206-11%202'%20/%3e%3cpath%20d='M27%2019l-3-7'%20/%3e%3cpath%20d='M37%2019l3-7'%20/%3e%3cpath%20d='M19%2045l-6%209'%20/%3e%3cpath%20d='M27%2048l-3%209'%20/%3e%3cpath%20d='M37%2048l3%209'%20/%3e%3cpath%20d='M45%2045l6%209'%20/%3e%3c/g%3e%3cpath%20d='M14%2047l9-8%208%209c-5%203.5-12%203-17-1z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M18%2046l5-5%205%206c-3%201.6-7%201.4-10-1z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M23%2041v10'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.6'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='31'%20cy='48'%20r='1.8'%20fill='%23c2412d'%20/%3e%3cpath%20d='M24%2044l13-11%2014%2013c-8%205.8-18%205.3-27-2z'%20fill='%232f6b3f'%20opacity='.97'%20/%3e%3cpath%20d='M30%2043l7-7%209%209c-5%202.7-11%202.5-16-2z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M37%2036v14'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='2'%20stroke-linecap='round'%20/%3e%3cpath%20d='M30%2043c5-2%2011-1.5%2016%202'%20fill='none'%20stroke='%239fca78'%20stroke-width='1.8'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='51'%20cy='46'%20r='2'%20fill='%23c2412d'%20/%3e%3cpath%20d='M40%2050l8-8%207%208c-4.2%203.2-10%203-15%200z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M43%2049l5-5%204.5%205.5c-3%201.5-6%201.2-9.5-.5z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M48%2044v9'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.5'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='55'%20cy='50'%20r='1.7'%20fill='%23c2412d'%20/%3e%3ccircle%20cx='24'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='40'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='25'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3ccircle%20cx='39'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3c/svg%3e";
53
- const appCss = "/assets/index-780zTVwp.css";
53
+ const appCss = "/assets/index-DsiKfWCp.css";
54
54
  const Route$s = createRootRoute({
55
55
  head: () => ({
56
56
  meta: [
@@ -93,7 +93,7 @@ function RootDocument({ children }) {
93
93
  ] })
94
94
  ] });
95
95
  }
96
- const $$splitComponentImporter$1 = () => import("./index-Cd1zD-lo.mjs");
96
+ const $$splitComponentImporter$1 = () => import("./index-Ef5mCuww.mjs");
97
97
  const Route$r = createFileRoute("/")({
98
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
99
99
  });
@@ -136,7 +136,7 @@ function decodeSessionIdFromPath(encoded) {
136
136
  function getSessionPath(sessionId) {
137
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
138
138
  }
139
- const $$splitComponentImporter = () => import("../_sessionId-BqhNE2M8.mjs");
139
+ const $$splitComponentImporter = () => import("../_sessionId-CYKaI68v.mjs");
140
140
  const Route$q = createFileRoute("/session/$sessionId")({
141
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
142
142
  parseParams: (params) => ({
@@ -729,6 +729,9 @@ const CapturedLogSchema = object({
729
729
  clientProjectFolder: string().nullable().optional(),
730
730
  streamingChunks: StreamingChunksArraySchema.optional(),
731
731
  streamingChunksPath: string().nullable().optional(),
732
+ rawRequestBodyBytes: number().int().nonnegative().nullable().optional(),
733
+ responseTextBytes: number().int().nonnegative().nullable().optional(),
734
+ bodyContentMode: _enum(["full", "compact", "truncated"]).optional(),
732
735
  /** Error message from streaming response (e.g., SSE error event) */
733
736
  error: string().nullable().optional()
734
737
  });
@@ -927,6 +930,10 @@ async function findInIndex(id) {
927
930
  const index = await loadIndex();
928
931
  return index.entries[id] ?? null;
929
932
  }
933
+ async function listIndexEntries() {
934
+ const index = await loadIndex();
935
+ return Object.values(index.entries).sort((left, right) => left.id - right.id);
936
+ }
930
937
  async function indexFile(filePath, file) {
931
938
  const entries = {};
932
939
  let maxId = 0;
@@ -1341,6 +1348,26 @@ function addToCache(log) {
1341
1348
  function removeFromCache(id) {
1342
1349
  memoryCache.delete(id);
1343
1350
  }
1351
+ function textByteLength$1(value) {
1352
+ if (value === null) return null;
1353
+ return Buffer.byteLength(value, "utf8");
1354
+ }
1355
+ function compactLogForList(log) {
1356
+ return {
1357
+ ...log,
1358
+ rawRequestBody: null,
1359
+ responseText: null,
1360
+ rawHeaders: void 0,
1361
+ headers: void 0,
1362
+ streamingChunks: void 0,
1363
+ rawRequestBodyBytes: textByteLength$1(log.rawRequestBody),
1364
+ responseTextBytes: textByteLength$1(log.responseText),
1365
+ bodyContentMode: "compact"
1366
+ };
1367
+ }
1368
+ function prepareLogForList(log, includeBodies) {
1369
+ return includeBodies ? log : compactLogForList(log);
1370
+ }
1344
1371
  async function addTestLogEntry(entry) {
1345
1372
  const id = await getNextLogId();
1346
1373
  let streamingChunksPath = null;
@@ -1542,11 +1569,50 @@ async function visitPersistedLogFile(filePath, visitor) {
1542
1569
  logger.error("[store] Failed to read persisted log file:", filePath, String(err));
1543
1570
  }
1544
1571
  }
1572
+ function canUseIndexedListPage(options) {
1573
+ return options.sessionId === void 0 && options.model === void 0;
1574
+ }
1575
+ function hasUsableIndexOffset(entry) {
1576
+ return entry.byteOffset >= 0 && entry.byteLength > 0;
1577
+ }
1578
+ async function listLogsPageFromIndex(options, allowRebuild) {
1579
+ if (!canUseIndexedListPage(options)) return null;
1580
+ let entries = await listIndexEntries();
1581
+ if (entries.length === 0 && allowRebuild) {
1582
+ await rebuildIndex();
1583
+ entries = await listIndexEntries();
1584
+ }
1585
+ const pageStart = options.offset;
1586
+ const pageEnd = options.offset + options.limit;
1587
+ const pageEntries = entries.slice(pageStart, pageEnd);
1588
+ const hasIncompleteOffset = pageEntries.some((entry) => !hasUsableIndexOffset(entry));
1589
+ if (hasIncompleteOffset) {
1590
+ if (!allowRebuild) return null;
1591
+ await rebuildIndex();
1592
+ return await listLogsPageFromIndex(options, false);
1593
+ }
1594
+ const logs = [];
1595
+ const includeBodies = options.includeBodies !== false;
1596
+ for (const entry of pageEntries) {
1597
+ const log = await getLogById(entry.id);
1598
+ if (log === null) return null;
1599
+ logs.push(prepareLogForList(log, includeBodies));
1600
+ }
1601
+ return {
1602
+ logs,
1603
+ total: entries.length,
1604
+ offset: options.offset,
1605
+ limit: options.limit
1606
+ };
1607
+ }
1545
1608
  async function listLogsPage(options) {
1609
+ const indexedPage = await listLogsPageFromIndex(options, true);
1610
+ if (indexedPage !== null) return indexedPage;
1546
1611
  const seenIds = /* @__PURE__ */ new Set();
1547
1612
  const pageById = /* @__PURE__ */ new Map();
1548
1613
  const pageStart = options.offset;
1549
1614
  const pageEnd = options.offset + options.limit;
1615
+ const includeBodies = options.includeBodies !== false;
1550
1616
  let total = 0;
1551
1617
  const visitLog = (log) => {
1552
1618
  if (!matchesLogFilters(log, options.sessionId, options.model)) return;
@@ -1555,12 +1621,12 @@ async function listLogsPage(options) {
1555
1621
  const position = total;
1556
1622
  total += 1;
1557
1623
  if (position >= pageStart && position < pageEnd) {
1558
- pageById.set(log.id, log);
1624
+ pageById.set(log.id, prepareLogForList(log, includeBodies));
1559
1625
  }
1560
1626
  return;
1561
1627
  }
1562
1628
  if (pageById.has(log.id)) {
1563
- pageById.set(log.id, log);
1629
+ pageById.set(log.id, prepareLogForList(log, includeBodies));
1564
1630
  }
1565
1631
  };
1566
1632
  await visitPersistedLogs((log) => {
@@ -3851,6 +3917,14 @@ function stripOpenAIOrphanToolMessages(rawBody) {
3851
3917
  parsed.messages = kept;
3852
3918
  return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
3853
3919
  }
3920
+ const DEFAULT_MAX_FINALIZE_STREAM_BYTES = 20 * 1024 * 1024;
3921
+ function resolveMaxFinalizeStreamBytes() {
3922
+ const raw = process.env["AGENT_INSPECTOR_MAX_FINALIZE_STREAM_BYTES"];
3923
+ if (raw === void 0 || raw === "") return DEFAULT_MAX_FINALIZE_STREAM_BYTES;
3924
+ const parsed = Number(raw);
3925
+ if (!Number.isFinite(parsed) || parsed < 1) return DEFAULT_MAX_FINALIZE_STREAM_BYTES;
3926
+ return Math.floor(parsed);
3927
+ }
3854
3928
  function buildFileLogEntry(log, upstreamUrl) {
3855
3929
  return {
3856
3930
  timestamp: log.timestamp,
@@ -3933,6 +4007,37 @@ function cleanupPathForRawStream(rawStream) {
3933
4007
  return rawStream.path;
3934
4008
  }
3935
4009
  }
4010
+ function rawStreamByteLength(rawStream) {
4011
+ if (typeof rawStream === "string") {
4012
+ return Buffer.byteLength(rawStream, "utf8");
4013
+ }
4014
+ switch (rawStream.type) {
4015
+ case "memory":
4016
+ return Buffer.byteLength(rawStream.rawStream, "utf8");
4017
+ case "file":
4018
+ try {
4019
+ return statSync(rawStream.path).size;
4020
+ } catch {
4021
+ return null;
4022
+ }
4023
+ }
4024
+ }
4025
+ function getOversizedRawStream(rawStream) {
4026
+ const limit = resolveMaxFinalizeStreamBytes();
4027
+ const byteLength = rawStreamByteLength(rawStream);
4028
+ if (byteLength === null || byteLength <= limit) return null;
4029
+ return { byteLength, limit };
4030
+ }
4031
+ function formatBytes(bytes) {
4032
+ if (bytes < 1024) return `${bytes} bytes`;
4033
+ if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
4034
+ return `${Math.ceil(bytes / (1024 * 1024))} MB`;
4035
+ }
4036
+ function oversizedRawStreamText(oversized) {
4037
+ return `Streaming response omitted because the captured raw stream is ${formatBytes(
4038
+ oversized.byteLength
4039
+ )}, exceeding the finalize limit of ${formatBytes(oversized.limit)}. Increase AGENT_INSPECTOR_MAX_FINALIZE_STREAM_BYTES if this response must be parsed.`;
4040
+ }
3936
4041
  function readRawStream(rawStream) {
3937
4042
  if (typeof rawStream === "string") {
3938
4043
  return { rawStreamText: rawStream, cleanupRawStreamPath: null };
@@ -3959,6 +4064,18 @@ function finalizeStreaming(job, log) {
3959
4064
  cleanupPathForRawStream(job.rawStream)
3960
4065
  );
3961
4066
  }
4067
+ const oversized = getOversizedRawStream(job.rawStream);
4068
+ if (oversized !== null) {
4069
+ log.elapsedMs = job.elapsedMs;
4070
+ log.responseStatus = job.responseStatus;
4071
+ log.responseText = oversizedRawStreamText(oversized);
4072
+ return {
4073
+ log,
4074
+ upstreamUrl: job.upstreamUrl,
4075
+ error: null,
4076
+ cleanupRawStreamPath: cleanupPathForRawStream(job.rawStream)
4077
+ };
4078
+ }
3962
4079
  let rawStreamText = "";
3963
4080
  let cleanupRawStreamPath = null;
3964
4081
  try {
@@ -4003,6 +4120,16 @@ function finalizeStreamAbort(job, log) {
4003
4120
  try {
4004
4121
  log.elapsedMs = job.elapsedMs;
4005
4122
  if (job.hasChunks && formatHandler !== null) {
4123
+ const oversized = getOversizedRawStream(job.rawStream);
4124
+ if (oversized !== null) {
4125
+ log.responseText = `Client aborted. ${oversizedRawStreamText(oversized)}`;
4126
+ return {
4127
+ log,
4128
+ upstreamUrl: job.upstreamUrl,
4129
+ error: "Client aborted",
4130
+ cleanupRawStreamPath: cleanupPathForRawStream(job.rawStream)
4131
+ };
4132
+ }
4006
4133
  const raw = readRawStream(job.rawStream);
4007
4134
  rawStreamText = raw.rawStreamText;
4008
4135
  cleanupRawStreamPath = raw.cleanupRawStreamPath;
@@ -4756,6 +4883,7 @@ const LogsListResponseSchema = object({
4756
4883
  });
4757
4884
  const PAGINATION_DEFAULTS = { offset: 0, limit: 3 };
4758
4885
  const LIMIT_HARD_CAP = 5;
4886
+ const LOG_DETAIL_TEXT_LIMIT = 2e5;
4759
4887
  function clampLimit(input) {
4760
4888
  if (input === void 0) return PAGINATION_DEFAULTS.limit;
4761
4889
  if (!Number.isFinite(input) || input < 1) return 1;
@@ -4767,8 +4895,46 @@ function textJson(data) {
4767
4895
  function toolError(message) {
4768
4896
  return { content: [{ type: "text", text: message }], isError: true };
4769
4897
  }
4898
+ function textByteLength(value) {
4899
+ if (value === null) return null;
4900
+ return Buffer.byteLength(value, "utf8");
4901
+ }
4902
+ function hasBodyText(value, byteLength) {
4903
+ if (value !== null && value.length > 0) return true;
4904
+ return byteLength !== void 0 && byteLength !== null && byteLength > 0;
4905
+ }
4906
+ function truncateToolText(value) {
4907
+ if (value === null) return { value: null, byteLength: null, truncated: false };
4908
+ const byteLength = textByteLength(value);
4909
+ if (value.length <= LOG_DETAIL_TEXT_LIMIT) {
4910
+ return { value, byteLength, truncated: false };
4911
+ }
4912
+ const suffix = `
4913
+ [truncated by agent-inspector MCP: original ${byteLength ?? 0} bytes]`;
4914
+ return {
4915
+ value: `${value.slice(0, LOG_DETAIL_TEXT_LIMIT)}${suffix}`,
4916
+ byteLength,
4917
+ truncated: true
4918
+ };
4919
+ }
4920
+ function buildBoundedLogDetail(log) {
4921
+ const { streamingChunks: _chunks, rawRequestBody, responseText, ...rest } = log;
4922
+ const boundedRequest = truncateToolText(rawRequestBody);
4923
+ const boundedResponse = truncateToolText(responseText);
4924
+ return {
4925
+ ...rest,
4926
+ rawRequestBody: boundedRequest.value,
4927
+ responseText: boundedResponse.value,
4928
+ rawRequestBodyBytes: boundedRequest.byteLength,
4929
+ responseTextBytes: boundedResponse.byteLength,
4930
+ rawRequestBodyTruncated: boundedRequest.truncated,
4931
+ responseTextTruncated: boundedResponse.truncated
4932
+ };
4933
+ }
4770
4934
  function buildLogSummary(log) {
4771
4935
  const hasError = log.error !== null && log.error !== void 0 && log.error.length > 0 || log.responseStatus !== null && log.responseStatus !== void 0 && log.responseStatus >= 400;
4936
+ const rawRequestBodyBytes = log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody);
4937
+ const responseTextBytes = log.responseTextBytes ?? textByteLength(log.responseText);
4772
4938
  return {
4773
4939
  id: log.id,
4774
4940
  timestamp: log.timestamp,
@@ -4792,13 +4958,19 @@ function buildLogSummary(log) {
4792
4958
  lastUserMessagePreview: extractLastUserMessagePreview(log),
4793
4959
  responsePreview: extractResponsePreview(log),
4794
4960
  hasChunks: log.streamingChunks !== void 0 && log.streamingChunks !== null || log.streamingChunksPath !== null && log.streamingChunksPath !== void 0,
4795
- hasRawRequestBody: log.rawRequestBody !== null && log.rawRequestBody !== void 0
4961
+ hasRawRequestBody: hasBodyText(log.rawRequestBody, rawRequestBodyBytes),
4962
+ rawRequestBodyBytes,
4963
+ responseTextBytes
4796
4964
  };
4797
4965
  }
4798
4966
  async function listLogsImpl(callApi2, args) {
4799
4967
  const offset = args.offset ?? PAGINATION_DEFAULTS.offset;
4800
4968
  const limit = clampLimit(args.limit);
4801
- const params = new URLSearchParams({ offset: String(offset), limit: String(limit) });
4969
+ const params = new URLSearchParams({
4970
+ offset: String(offset),
4971
+ limit: String(limit),
4972
+ compact: "1"
4973
+ });
4802
4974
  if (args.sessionId !== void 0 && args.sessionId.length > 0) {
4803
4975
  params.set("sessionId", args.sessionId);
4804
4976
  }
@@ -4821,8 +4993,7 @@ async function getLogImpl(callApi2, id) {
4821
4993
  if (!parsed.success) {
4822
4994
  return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
4823
4995
  }
4824
- const { streamingChunks: _chunks, ...rest } = parsed.data;
4825
- return textJson(rest);
4996
+ return textJson(buildBoundedLogDetail(parsed.data));
4826
4997
  }
4827
4998
  async function getLogChunksImpl(callApi2, id) {
4828
4999
  const res = await callApi2(`/api/logs/${id}/chunks`);
@@ -5259,7 +5430,10 @@ const Route$k = createFileRoute("/api/logs")({
5259
5430
  const model = url.searchParams.get("model") ?? void 0;
5260
5431
  const offset = parseNonNegativeInt(url.searchParams.get("offset"), 0);
5261
5432
  const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
5262
- return Response.json(await listLogsPage({ sessionId, model, offset, limit }));
5433
+ const includeBodies = url.searchParams.get("compact") !== "1";
5434
+ return Response.json(
5435
+ await listLogsPage({ sessionId, model, offset, limit, includeBodies })
5436
+ );
5263
5437
  },
5264
5438
  DELETE: async ({ request }) => {
5265
5439
  let body = void 0;
@@ -5902,6 +6076,7 @@ const Route$d = createFileRoute("/api/logs/stream")({
5902
6076
  const url = new URL(request.url);
5903
6077
  const sessionId = url.searchParams.get("sessionId") ?? void 0;
5904
6078
  const model = url.searchParams.get("model") ?? void 0;
6079
+ const includeBodies = url.searchParams.get("compact") !== "1";
5905
6080
  let controllerRef = null;
5906
6081
  let cleanedUp = false;
5907
6082
  const unsubscribe = onLogUpdate((log) => {
@@ -5909,7 +6084,8 @@ const Route$d = createFileRoute("/api/logs/stream")({
5909
6084
  if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return;
5910
6085
  if (model !== void 0 && log.model !== model) return;
5911
6086
  try {
5912
- const data = `data: ${JSON.stringify({ type: "update", log })}
6087
+ const payloadLog = includeBodies ? log : compactLogForList(log);
6088
+ const data = `data: ${JSON.stringify({ type: "update", log: payloadLog })}
5913
6089
 
5914
6090
  `;
5915
6091
  controllerRef.enqueue(new TextEncoder().encode(data));
@@ -5942,7 +6118,8 @@ const Route$d = createFileRoute("/api/logs/stream")({
5942
6118
  sessionId,
5943
6119
  model,
5944
6120
  offset: 0,
5945
- limit: INITIAL_STREAM_LOG_LIMIT
6121
+ limit: INITIAL_STREAM_LOG_LIMIT,
6122
+ includeBodies
5946
6123
  });
5947
6124
  const logs = result.logs;
5948
6125
  const initData = `data: ${JSON.stringify({ type: "init", logs })}
@@ -6642,9 +6819,9 @@ function modeInstruction(mode) {
6642
6819
  function maxTokensForMode(mode) {
6643
6820
  switch (mode) {
6644
6821
  case "non-streaming":
6645
- return 1024;
6822
+ return 256;
6646
6823
  case "streaming":
6647
- return 384;
6824
+ return 256;
6648
6825
  }
6649
6826
  }
6650
6827
  function buildProviderTestUserPrompt(mode) {
@@ -6675,7 +6852,7 @@ function buildProviderTestRequestBody(model, mode) {
6675
6852
  };
6676
6853
  switch (mode) {
6677
6854
  case "non-streaming":
6678
- return base;
6855
+ return { ...base, stream: false };
6679
6856
  case "streaming":
6680
6857
  return { ...base, stream: true };
6681
6858
  }
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/knowledge/sessions/$sessionId/candidates"], "preloads": ["/assets/main-hF_572U6.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-BpJNbm9G.js", "/assets/ProxyViewerContainer-BTYGkg36.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DpdfAzbo.js", "/assets/ProxyViewerContainer-BTYGkg36.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.paths.ts" }, "/api/knowledge/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.ts", "children": ["/api/knowledge/candidates/$candidateId"] }, "/api/knowledge/project-context": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.project-context.ts" }, "/api/knowledge/search": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.search.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/model-metadata", "/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.scan.ts" }, "/api/knowledge/candidates/$candidateId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.ts", "children": ["/api/knowledge/candidates/$candidateId/promote"] }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/model-metadata": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.model-metadata.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/knowledge/candidates/$candidateId/promote": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.promote.ts" }, "/api/knowledge/sessions/$sessionId/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.sessions.$sessionId.candidates.ts" }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-hF_572U6.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/knowledge/sessions/$sessionId/candidates"], "preloads": ["/assets/main-kBQ49n13.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-B0YcFeL-.js", "/assets/ProxyViewerContainer-By3XtQhZ.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DoqvnFx7.js", "/assets/ProxyViewerContainer-By3XtQhZ.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.paths.ts" }, "/api/knowledge/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.ts", "children": ["/api/knowledge/candidates/$candidateId"] }, "/api/knowledge/project-context": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.project-context.ts" }, "/api/knowledge/search": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.search.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/model-metadata", "/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.scan.ts" }, "/api/knowledge/candidates/$candidateId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.ts", "children": ["/api/knowledge/candidates/$candidateId/promote"] }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/model-metadata": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.model-metadata.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/knowledge/candidates/$candidateId/promote": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.promote.ts" }, "/api/knowledge/sessions/$sessionId/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.sessions.$sessionId.candidates.ts" }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-kBQ49n13.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -38,100 +38,100 @@ const assets = {
38
38
  "/assets/alibaba-TTwafVwX.svg": {
39
39
  "type": "image/svg+xml",
40
40
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
41
- "mtime": "2026-06-29T10:57:03.958Z",
41
+ "mtime": "2026-06-29T13:24:14.325Z",
42
42
  "size": 5915,
43
43
  "path": "../public/assets/alibaba-TTwafVwX.svg"
44
44
  },
45
- "/assets/CompareDrawer-DnYQtd8R.js": {
45
+ "/assets/CompareDrawer-DVNvS0GQ.js": {
46
46
  "type": "text/javascript; charset=utf-8",
47
- "etag": '"49fe-b7mmBTYljsLKWenrZlAtgJH1LOY"',
48
- "mtime": "2026-06-29T10:57:03.958Z",
47
+ "etag": '"49fe-2PP252UHJD4ZMXt5VpoaibQADuU"',
48
+ "mtime": "2026-06-29T13:24:14.326Z",
49
49
  "size": 18942,
50
- "path": "../public/assets/CompareDrawer-DnYQtd8R.js"
50
+ "path": "../public/assets/CompareDrawer-DVNvS0GQ.js"
51
51
  },
52
- "/assets/index-BpJNbm9G.js": {
52
+ "/assets/index-B0YcFeL-.js": {
53
53
  "type": "text/javascript; charset=utf-8",
54
- "etag": '"74-5ny3SIFXNJjz8mYDJPqwGLMogII"',
55
- "mtime": "2026-06-29T10:57:03.958Z",
54
+ "etag": '"74-ohkGewLZlIYioQpffENsT1gm8EE"',
55
+ "mtime": "2026-06-29T13:24:14.325Z",
56
56
  "size": 116,
57
- "path": "../public/assets/index-BpJNbm9G.js"
58
- },
59
- "/assets/index-780zTVwp.css": {
60
- "type": "text/css; charset=utf-8",
61
- "etag": '"1a380-Lz+Shs13XyKSlh9PTaGE50w/BpU"',
62
- "mtime": "2026-06-29T10:57:03.958Z",
63
- "size": 107392,
64
- "path": "../public/assets/index-780zTVwp.css"
57
+ "path": "../public/assets/index-B0YcFeL-.js"
65
58
  },
66
59
  "/assets/minimax-BPMzvuL-.jpeg": {
67
60
  "type": "image/jpeg",
68
61
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
69
- "mtime": "2026-06-29T10:57:03.958Z",
62
+ "mtime": "2026-06-29T13:24:14.325Z",
70
63
  "size": 6918,
71
64
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
72
65
  },
73
- "/assets/ReplayDialog-B-3V95xT.js": {
74
- "type": "text/javascript; charset=utf-8",
75
- "etag": '"318c-1NLootFaQs7KDJvmrRBmp0zrGoY"',
76
- "mtime": "2026-06-29T10:57:03.958Z",
77
- "size": 12684,
78
- "path": "../public/assets/ReplayDialog-B-3V95xT.js"
66
+ "/assets/index-DsiKfWCp.css": {
67
+ "type": "text/css; charset=utf-8",
68
+ "etag": '"1a3d5-pnLJEAiyUUKlRw5OUnvwf1dCQeI"',
69
+ "mtime": "2026-06-29T13:24:14.325Z",
70
+ "size": 107477,
71
+ "path": "../public/assets/index-DsiKfWCp.css"
79
72
  },
80
- "/assets/main-hF_572U6.js": {
73
+ "/assets/main-kBQ49n13.js": {
81
74
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"51726-eooZ68MhIVAdbT2A97BnHJuW5lM"',
83
- "mtime": "2026-06-29T10:57:03.958Z",
75
+ "etag": '"51726-+FidFdV98/WDrHsP2x5s4MGlsZI"',
76
+ "mtime": "2026-06-29T13:24:14.325Z",
84
77
  "size": 333606,
85
- "path": "../public/assets/main-hF_572U6.js"
78
+ "path": "../public/assets/main-kBQ49n13.js"
86
79
  },
87
- "/assets/ResponseView-DK3CYom0.js": {
80
+ "/assets/ResponseView-ygsUs7FU.js": {
88
81
  "type": "text/javascript; charset=utf-8",
89
- "etag": '"30d0-Cy0g09LS9VDwp/DlETvauFmcBFs"',
90
- "mtime": "2026-06-29T10:57:03.958Z",
82
+ "etag": '"30d0-VoRHAUzRfGHjVqxMmhp9+gxQNvg"',
83
+ "mtime": "2026-06-29T13:24:14.326Z",
91
84
  "size": 12496,
92
- "path": "../public/assets/ResponseView-DK3CYom0.js"
85
+ "path": "../public/assets/ResponseView-ygsUs7FU.js"
93
86
  },
94
- "/assets/RequestAnatomy-CT8hLGLa.js": {
87
+ "/assets/ReplayDialog-Cw6bN-NR.js": {
95
88
  "type": "text/javascript; charset=utf-8",
96
- "etag": '"4485-oTcemFMGh2ben0T+njg79IE1+H8"',
97
- "mtime": "2026-06-29T10:57:03.958Z",
98
- "size": 17541,
99
- "path": "../public/assets/RequestAnatomy-CT8hLGLa.js"
89
+ "etag": '"318c-Ec8xMdTaVtWn+gQco0A8wJxm/Zw"',
90
+ "mtime": "2026-06-29T13:24:14.325Z",
91
+ "size": 12684,
92
+ "path": "../public/assets/ReplayDialog-Cw6bN-NR.js"
100
93
  },
101
- "/assets/StreamingChunkSequence-D3jJutjp.js": {
94
+ "/assets/StreamingChunkSequence-DMUn8qXi.js": {
102
95
  "type": "text/javascript; charset=utf-8",
103
- "etag": '"d57-SqKRwuSq9h9H7zwcDkj5Lqd+u/Y"',
104
- "mtime": "2026-06-29T10:57:03.958Z",
96
+ "etag": '"d57-sKePDdoc8LggANuuW66cotctSKo"',
97
+ "mtime": "2026-06-29T13:24:14.326Z",
105
98
  "size": 3415,
106
- "path": "../public/assets/StreamingChunkSequence-D3jJutjp.js"
99
+ "path": "../public/assets/StreamingChunkSequence-DMUn8qXi.js"
107
100
  },
108
- "/assets/_sessionId-DpdfAzbo.js": {
101
+ "/assets/_sessionId-DoqvnFx7.js": {
109
102
  "type": "text/javascript; charset=utf-8",
110
- "etag": '"d2-tBmz0AuKXPVen+wNzq7oGvD4J44"',
111
- "mtime": "2026-06-29T10:57:03.958Z",
103
+ "etag": '"d2-eM4ckKV5XpF8O96oypRgw/AMrPQ"',
104
+ "mtime": "2026-06-29T13:24:14.325Z",
112
105
  "size": 210,
113
- "path": "../public/assets/_sessionId-DpdfAzbo.js"
106
+ "path": "../public/assets/_sessionId-DoqvnFx7.js"
114
107
  },
115
- "/assets/qwen-CONDcHqt.png": {
116
- "type": "image/png",
117
- "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
118
- "mtime": "2026-06-29T10:57:03.958Z",
119
- "size": 357059,
120
- "path": "../public/assets/qwen-CONDcHqt.png"
108
+ "/assets/RequestAnatomy-DnIsirBE.js": {
109
+ "type": "text/javascript; charset=utf-8",
110
+ "etag": '"4485-FLpxtJEMMSiId/JKv/qeTF/xc4E"',
111
+ "mtime": "2026-06-29T13:24:14.326Z",
112
+ "size": 17541,
113
+ "path": "../public/assets/RequestAnatomy-DnIsirBE.js"
121
114
  },
122
115
  "/assets/zhipuai-BPNAnxo-.svg": {
123
116
  "type": "image/svg+xml",
124
117
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
125
- "mtime": "2026-06-29T10:57:03.958Z",
118
+ "mtime": "2026-06-29T13:24:14.325Z",
126
119
  "size": 11256,
127
120
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
128
121
  },
129
- "/assets/ProxyViewerContainer-BTYGkg36.js": {
122
+ "/assets/qwen-CONDcHqt.png": {
123
+ "type": "image/png",
124
+ "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
125
+ "mtime": "2026-06-29T13:24:14.325Z",
126
+ "size": 357059,
127
+ "path": "../public/assets/qwen-CONDcHqt.png"
128
+ },
129
+ "/assets/ProxyViewerContainer-By3XtQhZ.js": {
130
130
  "type": "text/javascript; charset=utf-8",
131
- "etag": '"adfba-Leh4MRDOCIBADBcckDDDPe8s/NM"',
132
- "mtime": "2026-06-29T10:57:03.958Z",
133
- "size": 712634,
134
- "path": "../public/assets/ProxyViewerContainer-BTYGkg36.js"
131
+ "etag": '"af111-Qj0fjruqs4EheKPZxm5LynPyIwI"',
132
+ "mtime": "2026-06-29T13:24:14.326Z",
133
+ "size": 717073,
134
+ "path": "../public/assets/ProxyViewerContainer-By3XtQhZ.js"
135
135
  }
136
136
  };
137
137
  function readAsset(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "2.0.23",
3
+ "version": "2.0.25",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
@@ -637,6 +637,7 @@ export function ProxyViewer({
637
637
  const { totalIn, totalOut } = useMemo(() => computeTokenSummary(logs), [logs]);
638
638
  const { providers } = useProviders();
639
639
  const [exporting, setExporting] = useState<ExportMode | null>(null);
640
+ const [exportError, setExportError] = useState<string | null>(null);
640
641
  const [comparePair, setComparePair] = useState<[CapturedLog, CapturedLog] | null>(null);
641
642
  const [crabEntrancePhase, setCrabEntrancePhase] = useState<"hidden" | "playing" | "done">(
642
643
  "hidden",
@@ -669,8 +670,12 @@ export function ProxyViewer({
669
670
  const handleExport = useCallback(
670
671
  async (mode: ExportMode) => {
671
672
  setExporting(mode);
673
+ setExportError(null);
672
674
  try {
673
- await exportLogsAsZip(logs, mode);
675
+ const result = await exportLogsAsZip(logs, mode);
676
+ if (!result.ok) {
677
+ setExportError(result.message);
678
+ }
674
679
  } finally {
675
680
  setExporting(null);
676
681
  }
@@ -908,6 +913,11 @@ export function ProxyViewer({
908
913
  Clear
909
914
  </button>
910
915
  </div>
916
+ {exportError !== null && (
917
+ <div className="mb-4 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
918
+ {exportError}
919
+ </div>
920
+ )}
911
921
  </div>
912
922
 
913
923
  {/* Log list */}