@tonyclaw/agent-inspector 2.0.22 → 2.0.24

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 (37) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-NEdpe1wl.js → CompareDrawer-DrY-gAzy.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-CiLvRNie.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-DijzJwaQ.js → ReplayDialog-CFyw6Vu4.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-CYbosGYy.js → RequestAnatomy-DHGJ9py7.js} +1 -1
  6. package/.output/public/assets/{ResponseView-B3dPM63X.js → ResponseView-CB_3yc08.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-DYDx3xRY.js → StreamingChunkSequence-CqhHwgDn.js} +1 -1
  8. package/.output/public/assets/_sessionId-CgG9CQ9r.js +1 -0
  9. package/.output/public/assets/index-CViEXkqa.js +1 -0
  10. package/.output/public/assets/{main-N-sjHJUM.js → main-CVw7-JyE.js} +2 -2
  11. package/.output/server/{_sessionId-BR46y_bO.mjs → _sessionId-V33sBWBC.mjs} +2 -2
  12. package/.output/server/_ssr/{CompareDrawer-DDxpmdPs.mjs → CompareDrawer-C_Fk9joM.mjs} +2 -2
  13. package/.output/server/_ssr/{ProxyViewerContainer-B1zAefzz.mjs → ProxyViewerContainer-CyFKksQa.mjs} +75 -32
  14. package/.output/server/_ssr/{ReplayDialog-DleMFvMx.mjs → ReplayDialog-BpQHN7O2.mjs} +3 -3
  15. package/.output/server/_ssr/{RequestAnatomy-B-P9tC3D.mjs → RequestAnatomy-CuhmKAma.mjs} +2 -2
  16. package/.output/server/_ssr/{ResponseView-DWfGn0i3.mjs → ResponseView-CO6LyBmj.mjs} +2 -2
  17. package/.output/server/_ssr/{StreamingChunkSequence-C8kFF6xJ.mjs → StreamingChunkSequence-DLeVetNR.mjs} +2 -2
  18. package/.output/server/_ssr/{index-DzENi2RO.mjs → index-DWUKl9Bf.mjs} +2 -2
  19. package/.output/server/_ssr/index.mjs +2 -2
  20. package/.output/server/_ssr/{router-Dy1VEjxy.mjs → router-BrK_DcTS.mjs} +337 -86
  21. package/.output/server/{_tanstack-start-manifest_v-CyzuAD8A.mjs → _tanstack-start-manifest_v-BjW2XeWE.mjs} +1 -1
  22. package/.output/server/index.mjs +57 -57
  23. package/package.json +1 -1
  24. package/src/components/ProxyViewer.tsx +11 -1
  25. package/src/components/ProxyViewerContainer.tsx +21 -15
  26. package/src/lib/export-logs.ts +54 -11
  27. package/src/lib/providerTestPrompt.ts +4 -4
  28. package/src/mcp/toolHandlers.ts +58 -8
  29. package/src/proxy/logFinalizer.ts +79 -1
  30. package/src/proxy/logIndex.ts +6 -1
  31. package/src/proxy/schemas.ts +3 -0
  32. package/src/proxy/store.ts +275 -135
  33. package/src/routes/api/logs.stream.ts +6 -3
  34. package/src/routes/api/logs.ts +4 -1
  35. package/.output/public/assets/ProxyViewerContainer-Dwhi4Q1c.js +0 -115
  36. package/.output/public/assets/_sessionId-b1KDnByv.js +0 -1
  37. package/.output/public/assets/index-CUyRKgII.js +0 -1
@@ -1,5 +1,12 @@
1
- import { readFileSync, existsSync, createReadStream, readdirSync, statSync } from "node:fs";
2
- import { open, readFile, unlink, writeFile } from "node:fs/promises";
1
+ import {
2
+ existsSync,
3
+ createReadStream,
4
+ createWriteStream,
5
+ readdirSync,
6
+ statSync,
7
+ type WriteStream,
8
+ } from "node:fs";
9
+ import { open, unlink, rename } from "node:fs/promises";
3
10
  import { readdir as readdirCallback } from "node:fs/promises";
4
11
  import { createInterface } from "node:readline";
5
12
  import { Buffer } from "node:buffer";
@@ -18,7 +25,9 @@ import {
18
25
  flushIndex,
19
26
  getNextLogId,
20
27
  getCurrentLogFile,
28
+ listIndexEntries,
21
29
  rebuildIndex,
30
+ type LogIndexEntry,
22
31
  } from "./logIndex";
23
32
  import { writeChunks } from "./chunkStorage";
24
33
  import type { CapturedLog } from "./schemas";
@@ -68,6 +77,7 @@ export type ListLogsPageOptions = {
68
77
  model?: string;
69
78
  offset: number;
70
79
  limit: number;
80
+ includeBodies?: boolean;
71
81
  };
72
82
 
73
83
  export type ListLogsPageResult = {
@@ -109,6 +119,29 @@ function removeFromCache(id: number): void {
109
119
  memoryCache.delete(id);
110
120
  }
111
121
 
122
+ function textByteLength(value: string | null): number | null {
123
+ if (value === null) return null;
124
+ return Buffer.byteLength(value, "utf8");
125
+ }
126
+
127
+ export function compactLogForList(log: CapturedLog): CapturedLog {
128
+ return {
129
+ ...log,
130
+ rawRequestBody: null,
131
+ responseText: null,
132
+ rawHeaders: undefined,
133
+ headers: undefined,
134
+ streamingChunks: undefined,
135
+ rawRequestBodyBytes: textByteLength(log.rawRequestBody),
136
+ responseTextBytes: textByteLength(log.responseText),
137
+ bodyContentMode: "compact",
138
+ };
139
+ }
140
+
141
+ function prepareLogForList(log: CapturedLog, includeBodies: boolean): CapturedLog {
142
+ return includeBodies ? log : compactLogForList(log);
143
+ }
144
+
112
145
  /**
113
146
  * Add a test log entry to the in-memory store and persistent log file.
114
147
  * This is used by the provider test endpoint to seed the provider-test session.
@@ -273,33 +306,7 @@ export async function getLogById(id: number): Promise<CapturedLog | null> {
273
306
  return null;
274
307
  }
275
308
 
276
- // Fallback: scan file for matching ID (for entries written before index rebuild)
277
- const content = readFileSync(filePath, "utf-8");
278
- const lines = content.split("\n");
279
-
280
- let lastMatch: CapturedLog | null = null;
281
- for (const line of lines) {
282
- if (line.trim() === "") continue;
283
- try {
284
- const parsed: unknown = JSON.parse(line);
285
- if (typeof parsed === "object" && parsed !== null) {
286
- const desc = Object.getOwnPropertyDescriptor(parsed, "id");
287
- if (desc !== undefined && typeof desc.value === "number" && desc.value === id) {
288
- const result = CapturedLogSchema.safeParse(parsed);
289
- if (result.success) {
290
- lastMatch = normalizeLogSession(result.data);
291
- if (result.data.responseStatus !== null) {
292
- addToCache(lastMatch);
293
- observeSessionLog(lastMatch);
294
- return lastMatch;
295
- }
296
- }
297
- }
298
- }
299
- } catch {
300
- // Skip malformed lines
301
- }
302
- }
309
+ const lastMatch = await scanLogFileForId(filePath, id);
303
310
  if (lastMatch !== null) {
304
311
  addToCache(lastMatch);
305
312
  observeSessionLog(lastMatch);
@@ -312,6 +319,37 @@ export async function getLogById(id: number): Promise<CapturedLog | null> {
312
319
  return null;
313
320
  }
314
321
 
322
+ async function scanLogFileForId(filePath: string, id: number): Promise<CapturedLog | null> {
323
+ let lastMatch: CapturedLog | null = null;
324
+ const fileStream = createInterface({
325
+ input: createReadStream(filePath),
326
+ crlfDelay: Infinity,
327
+ });
328
+
329
+ for await (const line of fileStream) {
330
+ if (line.trim() === "") continue;
331
+ try {
332
+ const parsed: unknown = JSON.parse(line);
333
+ if (typeof parsed === "object" && parsed !== null) {
334
+ const desc = Object.getOwnPropertyDescriptor(parsed, "id");
335
+ if (desc !== undefined && typeof desc.value === "number" && desc.value === id) {
336
+ const result = CapturedLogSchema.safeParse(parsed);
337
+ if (result.success) {
338
+ lastMatch = normalizeLogSession(result.data);
339
+ if (result.data.responseStatus !== null) {
340
+ return lastMatch;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ } catch {
346
+ // Skip malformed lines
347
+ }
348
+ }
349
+
350
+ return lastMatch;
351
+ }
352
+
315
353
  export function getFilteredLogs(sessionId?: string, model?: string): CapturedLog[] {
316
354
  // Cache maintains insertion order (sorted by ID since logs are added in ID order)
317
355
  // Use spread instead of Array.from for slightly better performance
@@ -326,43 +364,26 @@ function matchesLogFilters(log: CapturedLog, sessionId?: string, model?: string)
326
364
  return true;
327
365
  }
328
366
 
329
- function requestedWindowFitsMemory(
330
- filteredMemoryCount: number,
331
- offset: number,
332
- limit: number,
333
- ): boolean {
334
- return offset + limit <= filteredMemoryCount;
335
- }
336
-
337
- function paginateLogs(logs: CapturedLog[], offset: number, limit: number): CapturedLog[] {
338
- return logs.slice(offset, offset + limit);
339
- }
367
+ type PersistedLogVisitor = (log: CapturedLog) => void;
340
368
 
341
- async function readPersistedLogsById(): Promise<Map<number, CapturedLog>> {
342
- const byId = new Map<number, CapturedLog>();
369
+ async function visitPersistedLogs(visitor: PersistedLogVisitor): Promise<void> {
343
370
  const logDir = resolveLogDir();
344
- if (!existsSync(logDir)) return byId;
371
+ if (!existsSync(logDir)) return;
345
372
 
346
373
  try {
347
374
  const entries = await readdirCallback(logDir);
348
375
  const files = entries.filter((file) => file.endsWith(".jsonl")).sort();
349
376
  for (const file of files) {
350
- await readPersistedLogFile(join(logDir, file), byId);
377
+ await visitPersistedLogFile(join(logDir, file), visitor);
351
378
  }
352
379
  } catch (err) {
353
380
  logger.error("[store] Failed to list persisted logs:", String(err));
354
381
  }
355
-
356
- for (const log of memoryCache.values()) {
357
- byId.set(log.id, log);
358
- }
359
-
360
- return byId;
361
382
  }
362
383
 
363
- async function readPersistedLogFile(
384
+ async function visitPersistedLogFile(
364
385
  filePath: string,
365
- byId: Map<number, CapturedLog>,
386
+ visitor: PersistedLogVisitor,
366
387
  ): Promise<void> {
367
388
  if (!existsSync(filePath)) return;
368
389
 
@@ -379,8 +400,7 @@ async function readPersistedLogFile(
379
400
  const result = CapturedLogSchema.safeParse(parsed);
380
401
  if (result.success) {
381
402
  const log = normalizeLogSession(result.data);
382
- byId.set(log.id, log);
383
- observeSessionLog(log);
403
+ visitor(log);
384
404
  }
385
405
  } catch {
386
406
  // Skip malformed lines.
@@ -391,25 +411,96 @@ async function readPersistedLogFile(
391
411
  }
392
412
  }
393
413
 
414
+ function canUseIndexedListPage(options: ListLogsPageOptions): boolean {
415
+ return options.sessionId === undefined && options.model === undefined;
416
+ }
417
+
418
+ function hasUsableIndexOffset(entry: LogIndexEntry): boolean {
419
+ return entry.byteOffset >= 0 && entry.byteLength > 0;
420
+ }
421
+
422
+ async function listLogsPageFromIndex(
423
+ options: ListLogsPageOptions,
424
+ allowRebuild: boolean,
425
+ ): Promise<ListLogsPageResult | null> {
426
+ if (!canUseIndexedListPage(options)) return null;
427
+
428
+ let entries = await listIndexEntries();
429
+ if (entries.length === 0 && allowRebuild) {
430
+ await rebuildIndex();
431
+ entries = await listIndexEntries();
432
+ }
433
+
434
+ const pageStart = options.offset;
435
+ const pageEnd = options.offset + options.limit;
436
+ const pageEntries = entries.slice(pageStart, pageEnd);
437
+ const hasIncompleteOffset = pageEntries.some((entry) => !hasUsableIndexOffset(entry));
438
+
439
+ if (hasIncompleteOffset) {
440
+ if (!allowRebuild) return null;
441
+ await rebuildIndex();
442
+ return await listLogsPageFromIndex(options, false);
443
+ }
444
+
445
+ const logs: CapturedLog[] = [];
446
+ const includeBodies = options.includeBodies !== false;
447
+ for (const entry of pageEntries) {
448
+ const log = await getLogById(entry.id);
449
+ if (log === null) return null;
450
+ logs.push(prepareLogForList(log, includeBodies));
451
+ }
452
+
453
+ return {
454
+ logs,
455
+ total: entries.length,
456
+ offset: options.offset,
457
+ limit: options.limit,
458
+ };
459
+ }
460
+
394
461
  export async function listLogsPage(options: ListLogsPageOptions): Promise<ListLogsPageResult> {
395
- const memoryLogs = getFilteredLogs(options.sessionId, options.model);
396
- if (requestedWindowFitsMemory(memoryLogs.length, options.offset, options.limit)) {
397
- return {
398
- logs: paginateLogs(memoryLogs, options.offset, options.limit),
399
- total: memoryLogs.length,
400
- offset: options.offset,
401
- limit: options.limit,
402
- };
462
+ const indexedPage = await listLogsPageFromIndex(options, true);
463
+ if (indexedPage !== null) return indexedPage;
464
+
465
+ const seenIds = new Set<number>();
466
+ const pageById = new Map<number, CapturedLog>();
467
+ const pageStart = options.offset;
468
+ const pageEnd = options.offset + options.limit;
469
+ const includeBodies = options.includeBodies !== false;
470
+ let total = 0;
471
+
472
+ const visitLog = (log: CapturedLog): void => {
473
+ if (!matchesLogFilters(log, options.sessionId, options.model)) return;
474
+
475
+ if (!seenIds.has(log.id)) {
476
+ seenIds.add(log.id);
477
+ const position = total;
478
+ total += 1;
479
+ if (position >= pageStart && position < pageEnd) {
480
+ pageById.set(log.id, prepareLogForList(log, includeBodies));
481
+ }
482
+ return;
483
+ }
484
+
485
+ if (pageById.has(log.id)) {
486
+ pageById.set(log.id, prepareLogForList(log, includeBodies));
487
+ }
488
+ };
489
+
490
+ await visitPersistedLogs((log) => {
491
+ observeSessionLog(log);
492
+ visitLog(log);
493
+ });
494
+
495
+ for (const log of memoryCache.values()) {
496
+ visitLog(log);
403
497
  }
404
498
 
405
- const persistedById = await readPersistedLogsById();
406
- const persistedLogs = [...persistedById.values()]
407
- .filter((log) => matchesLogFilters(log, options.sessionId, options.model))
408
- .sort((left, right) => left.id - right.id);
499
+ const logs = [...pageById.values()].sort((left, right) => left.id - right.id);
409
500
 
410
501
  return {
411
- logs: paginateLogs(persistedLogs, options.offset, options.limit),
412
- total: persistedLogs.length,
502
+ logs,
503
+ total,
413
504
  offset: options.offset,
414
505
  limit: options.limit,
415
506
  };
@@ -492,65 +583,41 @@ export async function loadLogsIntoMemory(): Promise<void> {
492
583
  const logDir = resolveLogDir();
493
584
  if (!existsSync(logDir)) return;
494
585
 
495
- // lastCompletedById: id -> CapturedLog (overwrite on each occurrence)
496
- const lastCompletedById = new Map<number, CapturedLog>();
497
-
498
- try {
499
- const entries = await readdirCallback(logDir);
500
- const filesWithExt = entries.filter((f: string) => f.endsWith(".jsonl")).sort();
501
- for (const file of filesWithExt) {
502
- const filePath = join(logDir, file);
503
- await loadLogFile(filePath, lastCompletedById);
504
- }
505
- } catch (err) {
506
- logger.error("[store] Failed to read log directory:", String(err));
507
- }
586
+ const recentCompletedById = new Map<number, CapturedLog>();
587
+ await visitPersistedLogs((log) => {
588
+ rememberRecentCompletedLog(log, recentCompletedById);
589
+ });
508
590
 
509
591
  // Sort by id descending, keep only MAX_MEMORY_CACHE newest completed entries
510
- const sorted = [...lastCompletedById.values()].sort((a, b) => b.id - a.id);
511
- for (let i = 0; i < Math.min(sorted.length, MAX_MEMORY_CACHE); i++) {
512
- const entry = sorted[i];
513
- if (entry !== undefined) addToCache(entry);
592
+ const sorted = [...recentCompletedById.values()].sort((a, b) => b.id - a.id);
593
+ for (const entry of sorted) {
594
+ addToCache(entry);
514
595
  }
515
596
 
516
597
  rebuildSessionRegistry(memoryCache.values());
517
598
  }
518
599
 
519
- async function loadLogFile(
520
- filePath: string,
521
- lastCompletedById: Map<number, CapturedLog>,
522
- ): Promise<void> {
523
- if (!existsSync(filePath)) return;
524
-
525
- try {
526
- const fileStream = createInterface({
527
- input: createReadStream(filePath),
528
- crlfDelay: Infinity,
529
- });
600
+ function rememberRecentCompletedLog(
601
+ log: CapturedLog,
602
+ recentCompletedById: Map<number, CapturedLog>,
603
+ ): void {
604
+ if (log.responseStatus === null) return;
605
+ recentCompletedById.set(log.id, log);
606
+ while (recentCompletedById.size > MAX_MEMORY_CACHE) {
607
+ const oldestId = findSmallestLogId(recentCompletedById);
608
+ if (oldestId === null) return;
609
+ recentCompletedById.delete(oldestId);
610
+ }
611
+ }
530
612
 
531
- for await (const line of fileStream) {
532
- if (line.trim() === "") continue;
533
- try {
534
- const parsed: unknown = JSON.parse(line);
535
- if (typeof parsed === "object" && parsed !== null) {
536
- const desc = Object.getOwnPropertyDescriptor(parsed, "id");
537
- if (desc !== undefined && typeof desc.value === "number") {
538
- const statusDesc = Object.getOwnPropertyDescriptor(parsed, "responseStatus");
539
- if (statusDesc !== undefined && statusDesc.value !== null) {
540
- const result = CapturedLogSchema.safeParse(parsed);
541
- if (result.success) {
542
- lastCompletedById.set(desc.value, result.data);
543
- }
544
- }
545
- }
546
- }
547
- } catch {
548
- // Skip malformed lines
549
- }
613
+ function findSmallestLogId(logsById: ReadonlyMap<number, CapturedLog>): number | null {
614
+ let smallestId: number | null = null;
615
+ for (const id of logsById.keys()) {
616
+ if (smallestId === null || id < smallestId) {
617
+ smallestId = id;
550
618
  }
551
- } catch (err) {
552
- logger.error("[store] Failed to load logs into memory:", String(err));
553
619
  }
620
+ return smallestId;
554
621
  }
555
622
 
556
623
  export function clearAllLogs(): { cleared: number } {
@@ -632,40 +699,113 @@ type RewriteLogFileResult = {
632
699
  removedIds: Set<number>;
633
700
  };
634
701
 
702
+ function tempRewritePath(filePath: string): string {
703
+ return `${filePath}.rewrite-${String(process.pid)}-${String(Date.now())}.tmp`;
704
+ }
705
+
706
+ async function removeTempFile(filePath: string): Promise<void> {
707
+ try {
708
+ await unlink(filePath);
709
+ } catch {
710
+ // Best-effort cleanup for temp rewrite files.
711
+ }
712
+ }
713
+
714
+ function waitForWriterDrain(writer: WriteStream): Promise<boolean> {
715
+ return new Promise((resolve) => {
716
+ const cleanup = (): void => {
717
+ writer.off("drain", onDrain);
718
+ writer.off("error", onError);
719
+ };
720
+ const onDrain = (): void => {
721
+ cleanup();
722
+ resolve(true);
723
+ };
724
+ const onError = (): void => {
725
+ cleanup();
726
+ resolve(false);
727
+ };
728
+ writer.once("drain", onDrain);
729
+ writer.once("error", onError);
730
+ });
731
+ }
732
+
733
+ async function writeKeptLogLine(writer: WriteStream, line: string): Promise<boolean> {
734
+ if (writer.write(`${line}\n`, "utf-8")) return true;
735
+ return await waitForWriterDrain(writer);
736
+ }
737
+
738
+ function finishWriter(writer: WriteStream): Promise<boolean> {
739
+ return new Promise((resolve) => {
740
+ const cleanup = (): void => {
741
+ writer.off("finish", onFinish);
742
+ writer.off("error", onError);
743
+ };
744
+ const onFinish = (): void => {
745
+ cleanup();
746
+ resolve(true);
747
+ };
748
+ const onError = (): void => {
749
+ cleanup();
750
+ resolve(false);
751
+ };
752
+ writer.once("finish", onFinish);
753
+ writer.once("error", onError);
754
+ writer.end();
755
+ });
756
+ }
757
+
635
758
  async function rewriteLogFileWithoutIds(
636
759
  filePath: string,
637
760
  ids: ReadonlySet<number>,
638
761
  ): Promise<RewriteLogFileResult> {
639
- let content: string;
762
+ const tempPath = tempRewritePath(filePath);
763
+ const writer = createWriteStream(tempPath, { encoding: "utf-8" });
764
+ const removedIds = new Set<number>();
765
+ let keptLineCount = 0;
766
+ let writeOk = true;
767
+
640
768
  try {
641
- content = await readFile(filePath, "utf-8");
769
+ const fileStream = createInterface({
770
+ input: createReadStream(filePath),
771
+ crlfDelay: Infinity,
772
+ });
773
+
774
+ for await (const line of fileStream) {
775
+ if (line === "") continue;
776
+ const id = readLogIdFromLine(line);
777
+ if (id !== null && ids.has(id)) {
778
+ removedIds.add(id);
779
+ } else {
780
+ keptLineCount += 1;
781
+ writeOk = await writeKeptLogLine(writer, line);
782
+ if (!writeOk) break;
783
+ }
784
+ }
642
785
  } catch (err) {
643
- logger.warn("[store] Failed to read log file for deletion:", filePath, String(err));
644
- return { rewritten: false, removedIds: new Set() };
786
+ logger.warn("[store] Failed to stream log file for deletion:", filePath, String(err));
787
+ writeOk = false;
645
788
  }
646
789
 
647
- const lines = content.split("\n");
648
- const keptLines: string[] = [];
649
- const removedIds = new Set<number>();
650
- for (const line of lines) {
651
- if (line === "") continue;
652
- const id = readLogIdFromLine(line);
653
- if (id !== null && ids.has(id)) {
654
- removedIds.add(id);
655
- } else {
656
- keptLines.push(line);
657
- }
790
+ if (!(await finishWriter(writer)) || !writeOk) {
791
+ await removeTempFile(tempPath);
792
+ return { rewritten: false, removedIds: new Set() };
658
793
  }
659
794
 
660
- if (removedIds.size === 0) return { rewritten: false, removedIds };
795
+ if (removedIds.size === 0) {
796
+ await removeTempFile(tempPath);
797
+ return { rewritten: false, removedIds };
798
+ }
661
799
 
662
800
  try {
663
- if (keptLines.length === 0) {
801
+ if (keptLineCount === 0) {
802
+ await removeTempFile(tempPath);
664
803
  await unlink(filePath);
665
804
  } else {
666
- await writeFile(filePath, `${keptLines.join("\n")}\n`, "utf-8");
805
+ await rename(tempPath, filePath);
667
806
  }
668
807
  } catch (err) {
808
+ await removeTempFile(tempPath);
669
809
  logger.warn("[store] Failed to rewrite log file after deletion:", filePath, String(err));
670
810
  return { rewritten: false, removedIds: new Set() };
671
811
  }
@@ -1,8 +1,8 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
- import { onLogUpdate, getLogSessionId, listLogsPage } from "../../proxy/store";
2
+ import { compactLogForList, onLogUpdate, getLogSessionId, listLogsPage } from "../../proxy/store";
3
3
  import { type CapturedLog } from "../../proxy/schemas";
4
4
 
5
- const INITIAL_STREAM_LOG_LIMIT = 500;
5
+ const INITIAL_STREAM_LOG_LIMIT = 100;
6
6
 
7
7
  export const Route = createFileRoute("/api/logs/stream")({
8
8
  server: {
@@ -11,6 +11,7 @@ export const Route = createFileRoute("/api/logs/stream")({
11
11
  const url = new URL(request.url);
12
12
  const sessionId = url.searchParams.get("sessionId") ?? undefined;
13
13
  const model = url.searchParams.get("model") ?? undefined;
14
+ const includeBodies = url.searchParams.get("compact") !== "1";
14
15
 
15
16
  let controllerRef: ReadableStreamDefaultController<Uint8Array> | null = null;
16
17
  let cleanedUp = false;
@@ -21,7 +22,8 @@ export const Route = createFileRoute("/api/logs/stream")({
21
22
  if (sessionId !== undefined && getLogSessionId(log) !== sessionId) return;
22
23
  if (model !== undefined && log.model !== model) return;
23
24
  try {
24
- const data = `data: ${JSON.stringify({ type: "update", log })}\n\n`;
25
+ const payloadLog = includeBodies ? log : compactLogForList(log);
26
+ const data = `data: ${JSON.stringify({ type: "update", log: payloadLog })}\n\n`;
25
27
  controllerRef.enqueue(new TextEncoder().encode(data));
26
28
  } catch {
27
29
  cleanup();
@@ -59,6 +61,7 @@ export const Route = createFileRoute("/api/logs/stream")({
59
61
  model,
60
62
  offset: 0,
61
63
  limit: INITIAL_STREAM_LOG_LIMIT,
64
+ includeBodies,
62
65
  });
63
66
  const logs = result.logs;
64
67
  const initData = `data: ${JSON.stringify({ type: "init", logs })}\n\n`;
@@ -49,8 +49,11 @@ export const Route = createFileRoute("/api/logs")({
49
49
  const model = url.searchParams.get("model") ?? undefined;
50
50
  const offset = parseNonNegativeInt(url.searchParams.get("offset"), 0);
51
51
  const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
52
+ const includeBodies = url.searchParams.get("compact") !== "1";
52
53
 
53
- return Response.json(await listLogsPage({ sessionId, model, offset, limit }));
54
+ return Response.json(
55
+ await listLogsPage({ sessionId, model, offset, limit, includeBodies }),
56
+ );
54
57
  },
55
58
  DELETE: async ({ request }: { request: Request }) => {
56
59
  let body: z.infer<typeof DeleteBodySchema> = undefined;