@tonyclaw/agent-inspector 2.0.18 → 2.0.20

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 (73) hide show
  1. package/.output/cli.js +137 -8
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-CepU-dLW.js → CompareDrawer-CUqYtaH3.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-CX0-R0nn.js +114 -0
  5. package/.output/public/assets/ReplayDialog-CPXNEqVg.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-C_C6ZU3T.js → RequestAnatomy-qkj8QgkY.js} +1 -1
  7. package/.output/public/assets/ResponseView-BPNLZpuw.js +1 -0
  8. package/.output/public/assets/StreamingChunkSequence-DGs-YFXI.js +1 -0
  9. package/.output/public/assets/_sessionId-CXtbrFTj.js +1 -0
  10. package/.output/public/assets/index-DrppF281.js +1 -0
  11. package/.output/public/assets/index-DynnYt7S.css +1 -0
  12. package/.output/public/assets/{main-BRXsrxtv.js → main-j3vS8rmP.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +141 -118
  14. package/.output/server/_libs/tanstack__react-virtual.mjs +13 -2
  15. package/.output/server/_libs/tanstack__virtual-core.mjs +24 -1
  16. package/.output/server/{_sessionId-B8Bbfs-v.mjs → _sessionId-Bc5Nohhe.mjs} +3 -2
  17. package/.output/server/_ssr/{CompareDrawer-TgGZLf1J.mjs → CompareDrawer-D0UewydM.mjs} +4 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-B_ppaNmQ.mjs → ProxyViewerContainer-BEUWt2eX.mjs} +351 -76
  19. package/.output/server/_ssr/{ReplayDialog-BXlajnMC.mjs → ReplayDialog-zaN-xMOw.mjs} +199 -17
  20. package/.output/server/_ssr/{RequestAnatomy-CCrx09-i.mjs → RequestAnatomy-BMomYged.mjs} +4 -3
  21. package/.output/server/_ssr/{ResponseView-BxtMJrov.mjs → ResponseView-DSoHCyAM.mjs} +4 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BlpdSPb8.mjs → StreamingChunkSequence-pt3pZOOL.mjs} +4 -3
  23. package/.output/server/_ssr/{index-BeTMNSJy.mjs → index-BCTKxcgV.mjs} +3 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{router-Ddl1tuU7.mjs → router-DpaDa5q5.mjs} +872 -232
  26. package/.output/server/{_tanstack-start-manifest_v-Bvdz_U0f.mjs → _tanstack-start-manifest_v-EsubwO0A.mjs} +1 -1
  27. package/.output/server/index.mjs +51 -51
  28. package/README.md +11 -109
  29. package/package.json +2 -1
  30. package/src/cli/doctor.ts +150 -5
  31. package/src/components/OnboardingBanner.tsx +13 -13
  32. package/src/components/ProxyViewer.tsx +55 -38
  33. package/src/components/ProxyViewerContainer.tsx +5 -0
  34. package/src/components/providers/SettingsDialog.tsx +113 -2
  35. package/src/components/proxy-viewer/ConversationGroup.tsx +20 -3
  36. package/src/components/proxy-viewer/ConversationGroupList.tsx +144 -0
  37. package/src/components/proxy-viewer/ConversationHeader.tsx +0 -2
  38. package/src/components/proxy-viewer/ReplayDialog.tsx +200 -13
  39. package/src/components/proxy-viewer/index.ts +1 -1
  40. package/src/components/ui/scroll-area.tsx +2 -2
  41. package/src/knowledge/openclawClient.ts +0 -8
  42. package/src/knowledge/types.ts +0 -2
  43. package/src/lib/export-logs.ts +72 -5
  44. package/src/lib/providerTestContract.ts +0 -1
  45. package/src/lib/useProviders.ts +1 -1
  46. package/src/mcp/server.ts +14 -6
  47. package/src/mcp/toolHandlers.ts +21 -4
  48. package/src/proxy/chunkStorage.ts +2 -4
  49. package/src/proxy/dataDir.ts +3 -3
  50. package/src/proxy/formats/anthropic/index.ts +0 -1
  51. package/src/proxy/formats/anthropic/schemas.ts +0 -2
  52. package/src/proxy/handler.ts +41 -29
  53. package/src/proxy/logFinalizer.ts +93 -11
  54. package/src/proxy/logger.ts +35 -10
  55. package/src/proxy/providers.ts +12 -5
  56. package/src/proxy/rawStreamCapture.ts +85 -0
  57. package/src/proxy/schemas.ts +1 -51
  58. package/src/proxy/sessionRuntime.ts +0 -9
  59. package/src/proxy/socketTracker.ts +15 -8
  60. package/src/proxy/store.ts +414 -13
  61. package/src/routes/api/logs.$id.replay.ts +66 -1
  62. package/src/routes/api/logs.stream.ts +11 -3
  63. package/src/routes/api/logs.ts +51 -17
  64. package/src/routes/api/providers.$providerId.ts +10 -3
  65. package/src/routes/api/providers.ts +6 -3
  66. package/.output/public/assets/ProxyViewerContainer-BG7rc33d.js +0 -114
  67. package/.output/public/assets/ReplayDialog-DK6SPv8X.js +0 -1
  68. package/.output/public/assets/ResponseView-Bqpra4Zw.js +0 -1
  69. package/.output/public/assets/StreamingChunkSequence-CdPbpprq.js +0 -1
  70. package/.output/public/assets/_sessionId-CsNARHj5.js +0 -1
  71. package/.output/public/assets/index-37NfO0zx.js +0 -1
  72. package/.output/public/assets/index-D_nZj9Vt.css +0 -1
  73. package/src/components/ui/json-expansion-button.tsx +0 -56
@@ -1,8 +1,9 @@
1
1
  import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts } from "../_libs/tanstack__react-router.mjs";
2
2
  import { j as jsxRuntimeExports } from "../_libs/react.mjs";
3
3
  import { S as SWRConfig } from "../_libs/swr.mjs";
4
- import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, cpSync, rmSync } from "node:fs";
5
- import fs, { mkdir, appendFile, readFile, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, readdirSync, statSync, createReadStream, cpSync, rmSync } from "node:fs";
5
+ import fs, { mkdir, appendFile, readFile, writeFile, readdir, open, stat, unlink } from "node:fs/promises";
6
+ import { createInterface } from "node:readline";
6
7
  import { Buffer } from "node:buffer";
7
8
  import path, { join, isAbsolute, dirname } from "node:path";
8
9
  import { execFile, exec, spawn } from "node:child_process";
@@ -11,6 +12,7 @@ import { C as Conf } from "../_libs/conf.mjs";
11
12
  import { randomUUID } from "crypto";
12
13
  import { promisify } from "node:util";
13
14
  import { Worker } from "node:worker_threads";
15
+ import { randomUUID as randomUUID$1 } from "node:crypto";
14
16
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport } from "../_libs/modelcontextprotocol__server.mjs";
15
17
  import { homedir } from "node:os";
16
18
  import { d as object, b as string, a as array, _ as _enum, u as union, n as number, c as boolean, e as unknown, l as literal, r as record, g as discriminatedUnion, h as _null, k as lazy } from "../_libs/zod.mjs";
@@ -31,7 +33,6 @@ import "../_libs/isbot.mjs";
31
33
  import "../_libs/use-sync-external-store.mjs";
32
34
  import "../_libs/dequal.mjs";
33
35
  import "node:process";
34
- import "node:crypto";
35
36
  import "node:assert";
36
37
  import "../_libs/dot-prop.mjs";
37
38
  import "../_libs/env-paths.mjs";
@@ -49,7 +50,7 @@ import "../_libs/mimic-function.mjs";
49
50
  import "../_libs/semver.mjs";
50
51
  import "../_libs/uint8array-extras.mjs";
51
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";
52
- const appCss = "/assets/index-D_nZj9Vt.css";
53
+ const appCss = "/assets/index-DynnYt7S.css";
53
54
  const Route$s = createRootRoute({
54
55
  head: () => ({
55
56
  meta: [
@@ -92,7 +93,7 @@ function RootDocument({ children }) {
92
93
  ] })
93
94
  ] });
94
95
  }
95
- const $$splitComponentImporter$1 = () => import("./index-BeTMNSJy.mjs");
96
+ const $$splitComponentImporter$1 = () => import("./index-BCTKxcgV.mjs");
96
97
  const Route$r = createFileRoute("/")({
97
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
98
99
  });
@@ -135,7 +136,7 @@ function decodeSessionIdFromPath(encoded) {
135
136
  function getSessionPath(sessionId) {
136
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
137
138
  }
138
- const $$splitComponentImporter = () => import("../_sessionId-B8Bbfs-v.mjs");
139
+ const $$splitComponentImporter = () => import("../_sessionId-Bc5Nohhe.mjs");
139
140
  const Route$q = createFileRoute("/session/$sessionId")({
140
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
141
142
  parseParams: (params) => ({
@@ -188,6 +189,10 @@ function migrateLegacyDataDirs() {
188
189
  }
189
190
  }
190
191
  const RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
192
+ function writeStderrFallback(message, error) {
193
+ process.stderr.write(`${message} ${String(error)}
194
+ `);
195
+ }
191
196
  function resolveLogDir() {
192
197
  const logDirEnv = process.env["LOG_DIR"];
193
198
  if (logDirEnv !== void 0 && logDirEnv !== "") {
@@ -228,7 +233,7 @@ async function initLogger() {
228
233
  }
229
234
  }
230
235
  } catch (err) {
231
- console.error("[logger] Failed to initialize log directory:", err);
236
+ writeStderrFallback("[logger] Failed to initialize log directory:", err);
232
237
  }
233
238
  }
234
239
  let loggerInitialized = false;
@@ -245,7 +250,7 @@ async function writeAppLog(message) {
245
250
  await appendFile(logPath, `[${timestamp}] ${message}
246
251
  `, "utf-8");
247
252
  } catch (err) {
248
- console.error(`[logger] Failed to write to ${logPath}:`, err);
253
+ writeStderrFallback(`[logger] Failed to write to ${logPath}:`, err);
249
254
  }
250
255
  }
251
256
  const logger = {
@@ -269,12 +274,27 @@ const logger = {
269
274
  const MAX_BUFFER_SIZE = 1e3;
270
275
  let writeBuffer = [];
271
276
  let writeQueue = Promise.resolve();
277
+ let logWriteLock = Promise.resolve();
278
+ async function runWithLogWriteLock(fn) {
279
+ const previousLock = logWriteLock;
280
+ let releaseLock2 = () => {
281
+ };
282
+ logWriteLock = new Promise((resolve) => {
283
+ releaseLock2 = resolve;
284
+ });
285
+ await previousLock;
286
+ try {
287
+ return await fn();
288
+ } finally {
289
+ releaseLock2();
290
+ }
291
+ }
272
292
  function drainBuffer() {
273
293
  const toWrite = writeBuffer.join("");
274
294
  writeBuffer = [];
275
295
  return toWrite;
276
296
  }
277
- async function flushWriteBuffer() {
297
+ async function flushWriteBufferUnlocked() {
278
298
  let toWrite;
279
299
  {
280
300
  toWrite = drainBuffer();
@@ -286,24 +306,31 @@ async function flushWriteBuffer() {
286
306
  await appendFile(filePath, toWrite, "utf-8");
287
307
  } catch (err) {
288
308
  writeBuffer.unshift(toWrite);
289
- console.error("[logger] Failed to flush write buffer, re-queued:", err);
309
+ writeStderrFallback("[logger] Failed to flush write buffer, re-queued:", err);
290
310
  }
291
311
  }
292
312
  function appendLogEntry(entry) {
293
313
  const line = JSON.stringify(entry) + "\n";
294
314
  writeBuffer.push(line);
295
315
  if (writeBuffer.length >= MAX_BUFFER_SIZE) {
296
- writeQueue = writeQueue.then(() => flushWriteBuffer());
316
+ writeQueue = writeQueue.then(() => runWithLogWriteLock(flushWriteBufferUnlocked));
297
317
  } else if (writeBuffer.length === 1) {
298
- writeQueue = writeQueue.then(() => flushWriteBuffer());
318
+ writeQueue = writeQueue.then(() => runWithLogWriteLock(flushWriteBufferUnlocked));
299
319
  }
300
320
  }
301
321
  async function flushLogBuffer() {
302
322
  await writeQueue;
303
323
  if (writeBuffer.length > 0) {
304
- await flushWriteBuffer();
324
+ await runWithLogWriteLock(flushWriteBufferUnlocked);
305
325
  }
306
326
  }
327
+ async function runWithFlushedLogWriteLock(fn) {
328
+ await flushLogBuffer();
329
+ return await runWithLogWriteLock(async () => {
330
+ await flushWriteBufferUnlocked();
331
+ return await fn();
332
+ });
333
+ }
307
334
  process.on("exit", () => {
308
335
  if (writeBuffer.length > 0) {
309
336
  const toWrite = drainBuffer();
@@ -320,127 +347,6 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
320
347
  void flushLogBuffer().then(() => process.exit(0));
321
348
  });
322
349
  }
323
- const INDEX_VERSION = 1;
324
- const INDEX_FILE = "logs.idx";
325
- function getIndexPath() {
326
- return join(resolveLogDir(), INDEX_FILE);
327
- }
328
- let cachedIndex = null;
329
- function createEmptyIndex() {
330
- return {
331
- version: INDEX_VERSION,
332
- entries: {},
333
- maxId: 0
334
- };
335
- }
336
- function isLogIndex(obj) {
337
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
338
- const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
339
- const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
340
- const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
341
- return versionDesc !== void 0 && typeof versionDesc.value === "number" && entriesDesc !== void 0 && typeof entriesDesc.value === "object" && entriesDesc.value !== null && maxIdDesc !== void 0 && typeof maxIdDesc.value === "number";
342
- }
343
- async function loadIndex() {
344
- if (cachedIndex !== null) return cachedIndex;
345
- const indexPath = getIndexPath();
346
- if (!existsSync(indexPath)) {
347
- cachedIndex = createEmptyIndex();
348
- return cachedIndex;
349
- }
350
- try {
351
- const content = await readFile(indexPath, "utf-8");
352
- const parsed = JSON.parse(content);
353
- if (isLogIndex(parsed)) {
354
- cachedIndex = parsed;
355
- } else {
356
- cachedIndex = createEmptyIndex();
357
- }
358
- return cachedIndex;
359
- } catch (err) {
360
- logger.error("[logIndex] Failed to load index:", String(err));
361
- cachedIndex = createEmptyIndex();
362
- return cachedIndex;
363
- }
364
- }
365
- async function saveIndex(index) {
366
- const indexPath = getIndexPath();
367
- const dir = dirname(indexPath);
368
- try {
369
- await mkdir(dir, { recursive: true });
370
- } catch {
371
- }
372
- try {
373
- await writeFile(indexPath, JSON.stringify(index), "utf-8");
374
- } catch (err) {
375
- logger.error("[logIndex] Failed to save index:", String(err));
376
- }
377
- }
378
- async function addToIndex(id, file, byteOffset, byteLength) {
379
- const index = await loadIndex();
380
- index.entries[id] = { id, file, byteOffset, byteLength };
381
- if (id > index.maxId) {
382
- index.maxId = id;
383
- }
384
- scheduleIndexFlush();
385
- }
386
- let indexFlushScheduled = false;
387
- async function flushIndexAsync() {
388
- indexFlushScheduled = false;
389
- const index = await loadIndex();
390
- await saveIndex(index);
391
- }
392
- function scheduleIndexFlush() {
393
- if (indexFlushScheduled) return;
394
- indexFlushScheduled = true;
395
- setTimeout(() => {
396
- void flushIndexAsync();
397
- }, 1e3);
398
- }
399
- async function findInIndex(id) {
400
- const index = await loadIndex();
401
- return index.entries[id] ?? null;
402
- }
403
- let idGenerationPromise = null;
404
- let releaseLock = null;
405
- async function acquireLock() {
406
- if (releaseLock === null) {
407
- idGenerationPromise = new Promise((resolve) => {
408
- releaseLock = resolve;
409
- });
410
- } else {
411
- await idGenerationPromise;
412
- idGenerationPromise = new Promise((resolve) => {
413
- releaseLock = resolve;
414
- });
415
- }
416
- }
417
- function releaseLockFn() {
418
- if (releaseLock) {
419
- const resolve = releaseLock;
420
- releaseLock = null;
421
- idGenerationPromise = null;
422
- resolve();
423
- }
424
- }
425
- async function getNextLogId() {
426
- await acquireLock();
427
- try {
428
- const index = await loadIndex();
429
- const nextId = index.maxId + 1;
430
- index.maxId = nextId;
431
- cachedIndex = index;
432
- return nextId;
433
- } finally {
434
- releaseLockFn();
435
- }
436
- }
437
- function getCurrentLogFile() {
438
- const now = /* @__PURE__ */ new Date();
439
- const yyyy = now.getUTCFullYear();
440
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
441
- const dd = String(now.getUTCDate()).padStart(2, "0");
442
- return `${yyyy}-${mm}-${dd}.jsonl`;
443
- }
444
350
  const JsonValueSchema = lazy(
445
351
  () => union([
446
352
  string(),
@@ -454,14 +360,6 @@ const JsonValueSchema = lazy(
454
360
  function trustAsJsonValue(value) {
455
361
  return value;
456
362
  }
457
- function isPlainRecord(val) {
458
- return typeof val === "object" && val !== null && !Array.isArray(val);
459
- }
460
- function safeGetOwnProperty(obj, key) {
461
- if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return void 0;
462
- const desc = Object.getOwnPropertyDescriptor(obj, key);
463
- return desc?.value;
464
- }
465
363
  const CacheControl = object({
466
364
  type: string(),
467
365
  ttl: string().optional(),
@@ -823,6 +721,7 @@ const CapturedLogSchema = object({
823
721
  headers: record(string(), string()).optional(),
824
722
  apiFormat: _enum(["anthropic", "openai", "unknown"]).default("unknown"),
825
723
  isTest: boolean().optional().default(false),
724
+ replayOfLogId: number().nullable().optional(),
826
725
  providerName: string().nullable().optional(),
827
726
  clientPort: number().nullable().optional(),
828
727
  clientPid: number().nullable().optional(),
@@ -913,7 +812,7 @@ function writeChunks(logId, chunks, truncated) {
913
812
  try {
914
813
  renameSync(tempPath, targetPath);
915
814
  } catch (err) {
916
- console.warn("[chunkStorage] Rename failed, falling back to copy+delete:", err);
815
+ logger.warn("[chunkStorage] Rename failed, falling back to copy+delete:", String(err));
917
816
  try {
918
817
  copyFileSync(tempPath, targetPath);
919
818
  unlinkSync(tempPath);
@@ -937,6 +836,193 @@ function readChunks(path2) {
937
836
  return null;
938
837
  }
939
838
  }
839
+ const INDEX_VERSION = 1;
840
+ const INDEX_FILE = "logs.idx";
841
+ function getIndexPath() {
842
+ return join(resolveLogDir(), INDEX_FILE);
843
+ }
844
+ let cachedIndex = null;
845
+ function createEmptyIndex() {
846
+ return {
847
+ version: INDEX_VERSION,
848
+ entries: {},
849
+ maxId: 0
850
+ };
851
+ }
852
+ function isLogIndex(obj) {
853
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
854
+ const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
855
+ const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
856
+ const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
857
+ return versionDesc !== void 0 && typeof versionDesc.value === "number" && entriesDesc !== void 0 && typeof entriesDesc.value === "object" && entriesDesc.value !== null && maxIdDesc !== void 0 && typeof maxIdDesc.value === "number";
858
+ }
859
+ async function loadIndex() {
860
+ if (cachedIndex !== null) return cachedIndex;
861
+ const indexPath = getIndexPath();
862
+ if (!existsSync(indexPath)) {
863
+ cachedIndex = createEmptyIndex();
864
+ return cachedIndex;
865
+ }
866
+ try {
867
+ const content = await readFile(indexPath, "utf-8");
868
+ const parsed = JSON.parse(content);
869
+ if (isLogIndex(parsed)) {
870
+ cachedIndex = parsed;
871
+ } else {
872
+ cachedIndex = createEmptyIndex();
873
+ }
874
+ return cachedIndex;
875
+ } catch (err) {
876
+ logger.error("[logIndex] Failed to load index:", String(err));
877
+ cachedIndex = createEmptyIndex();
878
+ return cachedIndex;
879
+ }
880
+ }
881
+ async function saveIndex(index) {
882
+ const indexPath = getIndexPath();
883
+ const dir = dirname(indexPath);
884
+ try {
885
+ await mkdir(dir, { recursive: true });
886
+ } catch {
887
+ }
888
+ try {
889
+ await writeFile(indexPath, JSON.stringify(index), "utf-8");
890
+ } catch (err) {
891
+ logger.error("[logIndex] Failed to save index:", String(err));
892
+ }
893
+ }
894
+ async function addToIndex(id, file, byteOffset, byteLength) {
895
+ const index = await loadIndex();
896
+ index.entries[id] = { id, file, byteOffset, byteLength };
897
+ if (id > index.maxId) {
898
+ index.maxId = id;
899
+ }
900
+ scheduleIndexFlush();
901
+ }
902
+ let indexFlushScheduled = false;
903
+ let indexFlushTimeout = null;
904
+ async function flushIndexAsync() {
905
+ indexFlushScheduled = false;
906
+ indexFlushTimeout = null;
907
+ const index = await loadIndex();
908
+ await saveIndex(index);
909
+ }
910
+ function scheduleIndexFlush() {
911
+ if (indexFlushScheduled) return;
912
+ indexFlushScheduled = true;
913
+ indexFlushTimeout = setTimeout(() => {
914
+ void flushIndexAsync();
915
+ }, 1e3);
916
+ }
917
+ async function flushIndex() {
918
+ if (indexFlushTimeout !== null) {
919
+ clearTimeout(indexFlushTimeout);
920
+ indexFlushTimeout = null;
921
+ }
922
+ indexFlushScheduled = false;
923
+ const index = await loadIndex();
924
+ await saveIndex(index);
925
+ }
926
+ async function findInIndex(id) {
927
+ const index = await loadIndex();
928
+ return index.entries[id] ?? null;
929
+ }
930
+ async function indexFile(filePath, file) {
931
+ const entries = {};
932
+ let maxId = 0;
933
+ let byteOffset = 0;
934
+ const fileStream = createInterface({
935
+ input: createReadStream(filePath),
936
+ crlfDelay: Infinity
937
+ });
938
+ for await (const line of fileStream) {
939
+ const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
940
+ if (line.trim() !== "") {
941
+ try {
942
+ const entry = JSON.parse(line);
943
+ if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
944
+ const idDesc = Object.getOwnPropertyDescriptor(entry, "id");
945
+ if (idDesc !== void 0 && typeof idDesc.value === "number") {
946
+ const entryId = idDesc.value;
947
+ maxId = Math.max(maxId, entryId);
948
+ entries[entryId] = {
949
+ id: entryId,
950
+ file,
951
+ byteOffset,
952
+ byteLength: lineBytes
953
+ };
954
+ }
955
+ }
956
+ } catch {
957
+ }
958
+ }
959
+ byteOffset += lineBytes;
960
+ }
961
+ return { entries, maxId };
962
+ }
963
+ async function rebuildIndex() {
964
+ const logDir = resolveLogDir();
965
+ const newIndex = createEmptyIndex();
966
+ if (!existsSync(logDir)) {
967
+ cachedIndex = newIndex;
968
+ await saveIndex(newIndex);
969
+ return newIndex;
970
+ }
971
+ const files = (await readdir(logDir)).filter((f) => f.endsWith(".jsonl")).sort();
972
+ const CONCURRENCY = 4;
973
+ for (let i = 0; i < files.length; i += CONCURRENCY) {
974
+ const batch = files.slice(i, i + CONCURRENCY);
975
+ const results = await Promise.all(batch.map((file) => indexFile(join(logDir, file), file)));
976
+ for (const result of results) {
977
+ Object.assign(newIndex.entries, result.entries);
978
+ newIndex.maxId = Math.max(newIndex.maxId, result.maxId);
979
+ }
980
+ }
981
+ cachedIndex = newIndex;
982
+ await saveIndex(newIndex);
983
+ return newIndex;
984
+ }
985
+ let idGenerationPromise = null;
986
+ let releaseLock = null;
987
+ async function acquireLock() {
988
+ if (releaseLock === null) {
989
+ idGenerationPromise = new Promise((resolve) => {
990
+ releaseLock = resolve;
991
+ });
992
+ } else {
993
+ await idGenerationPromise;
994
+ idGenerationPromise = new Promise((resolve) => {
995
+ releaseLock = resolve;
996
+ });
997
+ }
998
+ }
999
+ function releaseLockFn() {
1000
+ if (releaseLock) {
1001
+ const resolve = releaseLock;
1002
+ releaseLock = null;
1003
+ idGenerationPromise = null;
1004
+ resolve();
1005
+ }
1006
+ }
1007
+ async function getNextLogId() {
1008
+ await acquireLock();
1009
+ try {
1010
+ const index = await loadIndex();
1011
+ const nextId = index.maxId + 1;
1012
+ index.maxId = nextId;
1013
+ cachedIndex = index;
1014
+ return nextId;
1015
+ } finally {
1016
+ releaseLockFn();
1017
+ }
1018
+ }
1019
+ function getCurrentLogFile() {
1020
+ const now = /* @__PURE__ */ new Date();
1021
+ const yyyy = now.getUTCFullYear();
1022
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
1023
+ const dd = String(now.getUTCDate()).padStart(2, "0");
1024
+ return `${yyyy}-${mm}-${dd}.jsonl`;
1025
+ }
940
1026
  const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
941
1027
  const MAX_RESTARTS = 3;
942
1028
  const _processes = /* @__PURE__ */ new Map();
@@ -1275,9 +1361,11 @@ async function addTestLogEntry(entry) {
1275
1361
  sessionId: session.id,
1276
1362
  streamingChunksPath
1277
1363
  };
1278
- const logFile = getCurrentLogFile();
1279
- appendLogEntry(log);
1280
- await addToIndex(id, logFile, -1, -1);
1364
+ await runWithLogWriteLock(async () => {
1365
+ const logFile = getCurrentLogFile();
1366
+ appendLogEntry(log);
1367
+ await addToIndex(id, logFile, -1, -1);
1368
+ });
1281
1369
  addToCache(log);
1282
1370
  observeSessionLog(log, session.source);
1283
1371
  emitLogUpdate(log);
@@ -1320,9 +1408,11 @@ async function createLog(method, path2, requestBody, headers, clientInfo, rawHea
1320
1408
  clientProjectFolder: clientInfo?.projectFolder ?? null,
1321
1409
  streamingChunksPath: null
1322
1410
  };
1323
- const logFile = getCurrentLogFile();
1324
- appendLogEntry(log);
1325
- await addToIndex(id, logFile, -1, -1);
1411
+ await runWithLogWriteLock(async () => {
1412
+ const logFile = getCurrentLogFile();
1413
+ appendLogEntry(log);
1414
+ await addToIndex(id, logFile, -1, -1);
1415
+ });
1326
1416
  addToCache(log);
1327
1417
  markSessionStarted(log, session.source);
1328
1418
  emitLogUpdate(log);
@@ -1404,11 +1494,81 @@ async function getLogById(id) {
1404
1494
  }
1405
1495
  function getFilteredLogs(sessionId, model) {
1406
1496
  return [...memoryCache.values()].filter((l) => {
1407
- if (sessionId !== void 0 && getLogSessionId(l) !== sessionId) return false;
1408
- if (model !== void 0 && l.model !== model) return false;
1409
- return true;
1497
+ return matchesLogFilters(l, sessionId, model);
1410
1498
  });
1411
1499
  }
1500
+ function matchesLogFilters(log, sessionId, model) {
1501
+ if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return false;
1502
+ if (model !== void 0 && log.model !== model) return false;
1503
+ return true;
1504
+ }
1505
+ function requestedWindowFitsMemory(filteredMemoryCount, offset, limit) {
1506
+ return offset + limit <= filteredMemoryCount;
1507
+ }
1508
+ function paginateLogs(logs, offset, limit) {
1509
+ return logs.slice(offset, offset + limit);
1510
+ }
1511
+ async function readPersistedLogsById() {
1512
+ const byId = /* @__PURE__ */ new Map();
1513
+ const logDir = resolveLogDir();
1514
+ if (!existsSync(logDir)) return byId;
1515
+ try {
1516
+ const entries = await readdir(logDir);
1517
+ const files = entries.filter((file) => file.endsWith(".jsonl")).sort();
1518
+ for (const file of files) {
1519
+ await readPersistedLogFile(join(logDir, file), byId);
1520
+ }
1521
+ } catch (err) {
1522
+ logger.error("[store] Failed to list persisted logs:", String(err));
1523
+ }
1524
+ for (const log of memoryCache.values()) {
1525
+ byId.set(log.id, log);
1526
+ }
1527
+ return byId;
1528
+ }
1529
+ async function readPersistedLogFile(filePath, byId) {
1530
+ if (!existsSync(filePath)) return;
1531
+ try {
1532
+ const fileStream = createInterface({
1533
+ input: createReadStream(filePath),
1534
+ crlfDelay: Infinity
1535
+ });
1536
+ for await (const line of fileStream) {
1537
+ if (line.trim() === "") continue;
1538
+ try {
1539
+ const parsed = JSON.parse(line);
1540
+ const result = CapturedLogSchema.safeParse(parsed);
1541
+ if (result.success) {
1542
+ const log = normalizeLogSession(result.data);
1543
+ byId.set(log.id, log);
1544
+ observeSessionLog(log);
1545
+ }
1546
+ } catch {
1547
+ }
1548
+ }
1549
+ } catch (err) {
1550
+ logger.error("[store] Failed to read persisted log file:", filePath, String(err));
1551
+ }
1552
+ }
1553
+ async function listLogsPage(options) {
1554
+ const memoryLogs = getFilteredLogs(options.sessionId, options.model);
1555
+ if (requestedWindowFitsMemory(memoryLogs.length, options.offset, options.limit)) {
1556
+ return {
1557
+ logs: paginateLogs(memoryLogs, options.offset, options.limit),
1558
+ total: memoryLogs.length,
1559
+ offset: options.offset,
1560
+ limit: options.limit
1561
+ };
1562
+ }
1563
+ const persistedById = await readPersistedLogsById();
1564
+ const persistedLogs = [...persistedById.values()].filter((log) => matchesLogFilters(log, options.sessionId, options.model)).sort((left, right) => left.id - right.id);
1565
+ return {
1566
+ logs: paginateLogs(persistedLogs, options.offset, options.limit),
1567
+ total: persistedLogs.length,
1568
+ offset: options.offset,
1569
+ limit: options.limit
1570
+ };
1571
+ }
1412
1572
  function getSessions() {
1413
1573
  return getSessionIds();
1414
1574
  }
@@ -1419,12 +1579,216 @@ function getModels() {
1419
1579
  }
1420
1580
  return [...set];
1421
1581
  }
1582
+ function getReplayLogsForSource(sourceLogId) {
1583
+ return [...memoryCache.values()].filter((log) => log.replayOfLogId === sourceLogId);
1584
+ }
1585
+ function collectDirectoryStats(dir, shouldCount) {
1586
+ if (!existsSync(dir)) return { fileCount: 0, bytes: 0 };
1587
+ let fileCount = 0;
1588
+ let bytes = 0;
1589
+ const visit = (currentDir, depth) => {
1590
+ if (depth > 8) return;
1591
+ let entries;
1592
+ try {
1593
+ entries = readdirSync(currentDir);
1594
+ } catch {
1595
+ return;
1596
+ }
1597
+ for (const entry of entries) {
1598
+ const path2 = join(currentDir, entry);
1599
+ try {
1600
+ const stats = statSync(path2);
1601
+ if (stats.isDirectory()) {
1602
+ visit(path2, depth + 1);
1603
+ } else if (shouldCount(entry)) {
1604
+ fileCount += 1;
1605
+ bytes += stats.size;
1606
+ }
1607
+ } catch {
1608
+ }
1609
+ }
1610
+ };
1611
+ visit(dir, 0);
1612
+ return { fileCount, bytes };
1613
+ }
1614
+ function getLogStorageStats() {
1615
+ const logDir = resolveLogDir();
1616
+ const chunkDir = getChunksDir();
1617
+ const logStats = collectDirectoryStats(logDir, (fileName) => fileName.endsWith(".jsonl"));
1618
+ const chunkStats = collectDirectoryStats(chunkDir, (fileName) => fileName.endsWith(".json"));
1619
+ return {
1620
+ memoryCount: memoryCache.size,
1621
+ logDir,
1622
+ logFileCount: logStats.fileCount,
1623
+ logBytes: logStats.bytes,
1624
+ chunkDir,
1625
+ chunkFileCount: chunkStats.fileCount,
1626
+ chunkBytes: chunkStats.bytes
1627
+ };
1628
+ }
1422
1629
  function clearAllLogs() {
1423
1630
  const count = memoryCache.size;
1424
1631
  memoryCache.clear();
1425
1632
  clearSessionRegistry();
1426
1633
  return { cleared: count };
1427
1634
  }
1635
+ async function deleteMatchingFiles(dir, shouldDelete) {
1636
+ if (!existsSync(dir)) return 0;
1637
+ let deleted = 0;
1638
+ let entries;
1639
+ try {
1640
+ entries = await readdir(dir);
1641
+ } catch (err) {
1642
+ logger.warn("[store] Failed to list log storage directory:", String(err));
1643
+ return deleted;
1644
+ }
1645
+ for (const entry of entries) {
1646
+ if (!shouldDelete(entry)) continue;
1647
+ try {
1648
+ await unlink(join(dir, entry));
1649
+ deleted += 1;
1650
+ } catch (err) {
1651
+ logger.warn("[store] Failed to delete log storage file:", entry, String(err));
1652
+ }
1653
+ }
1654
+ return deleted;
1655
+ }
1656
+ async function clearPersistedLogStorage() {
1657
+ return await runWithFlushedLogWriteLock(async () => {
1658
+ await flushIndex();
1659
+ const result = clearAllLogs();
1660
+ const [logFilesDeleted, chunkFilesDeleted] = await Promise.all([
1661
+ deleteMatchingFiles(
1662
+ resolveLogDir(),
1663
+ (fileName) => fileName.endsWith(".jsonl") || fileName === "logs.idx"
1664
+ ),
1665
+ deleteMatchingFiles(
1666
+ getChunksDir(),
1667
+ (fileName) => fileName.endsWith(".json") || fileName.startsWith(".") && fileName.endsWith(".tmp")
1668
+ )
1669
+ ]);
1670
+ await rebuildIndex();
1671
+ return {
1672
+ cleared: result.cleared,
1673
+ logFilesDeleted,
1674
+ chunkFilesDeleted
1675
+ };
1676
+ });
1677
+ }
1678
+ function readLogIdFromLine(line) {
1679
+ if (line.trim() === "") return null;
1680
+ try {
1681
+ const parsed = JSON.parse(line);
1682
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1683
+ const idDesc = Object.getOwnPropertyDescriptor(parsed, "id");
1684
+ if (idDesc === void 0 || typeof idDesc.value !== "number") return null;
1685
+ return idDesc.value;
1686
+ } catch {
1687
+ return null;
1688
+ }
1689
+ }
1690
+ async function rewriteLogFileWithoutIds(filePath, ids) {
1691
+ let content;
1692
+ try {
1693
+ content = await readFile(filePath, "utf-8");
1694
+ } catch (err) {
1695
+ logger.warn("[store] Failed to read log file for deletion:", filePath, String(err));
1696
+ return { rewritten: false, removedIds: /* @__PURE__ */ new Set() };
1697
+ }
1698
+ const lines = content.split("\n");
1699
+ const keptLines = [];
1700
+ const removedIds = /* @__PURE__ */ new Set();
1701
+ for (const line of lines) {
1702
+ if (line === "") continue;
1703
+ const id = readLogIdFromLine(line);
1704
+ if (id !== null && ids.has(id)) {
1705
+ removedIds.add(id);
1706
+ } else {
1707
+ keptLines.push(line);
1708
+ }
1709
+ }
1710
+ if (removedIds.size === 0) return { rewritten: false, removedIds };
1711
+ try {
1712
+ if (keptLines.length === 0) {
1713
+ await unlink(filePath);
1714
+ } else {
1715
+ await writeFile(filePath, `${keptLines.join("\n")}
1716
+ `, "utf-8");
1717
+ }
1718
+ } catch (err) {
1719
+ logger.warn("[store] Failed to rewrite log file after deletion:", filePath, String(err));
1720
+ return { rewritten: false, removedIds: /* @__PURE__ */ new Set() };
1721
+ }
1722
+ return { rewritten: true, removedIds };
1723
+ }
1724
+ async function rewriteLogFilesWithoutIds(ids) {
1725
+ const logDir = resolveLogDir();
1726
+ if (!existsSync(logDir)) return { logFilesRewritten: 0, removedIds: /* @__PURE__ */ new Set() };
1727
+ let entries;
1728
+ try {
1729
+ entries = await readdir(logDir);
1730
+ } catch (err) {
1731
+ logger.warn("[store] Failed to list log directory for deletion:", String(err));
1732
+ return { logFilesRewritten: 0, removedIds: /* @__PURE__ */ new Set() };
1733
+ }
1734
+ let logFilesRewritten = 0;
1735
+ const removedIds = /* @__PURE__ */ new Set();
1736
+ for (const entry of entries) {
1737
+ if (!entry.endsWith(".jsonl")) continue;
1738
+ const result = await rewriteLogFileWithoutIds(join(logDir, entry), ids);
1739
+ if (result.rewritten) logFilesRewritten += 1;
1740
+ for (const removedId of result.removedIds) {
1741
+ removedIds.add(removedId);
1742
+ }
1743
+ }
1744
+ return { logFilesRewritten, removedIds };
1745
+ }
1746
+ async function deleteChunkFilesByIds(ids) {
1747
+ const chunkDir = getChunksDir();
1748
+ if (!existsSync(chunkDir)) return 0;
1749
+ let deleted = 0;
1750
+ for (const id of ids) {
1751
+ const candidates2 = [`${String(id)}.json`, `.${String(id)}.tmp`];
1752
+ for (const candidate of candidates2) {
1753
+ try {
1754
+ await unlink(join(chunkDir, candidate));
1755
+ deleted += 1;
1756
+ } catch {
1757
+ }
1758
+ }
1759
+ }
1760
+ return deleted;
1761
+ }
1762
+ async function clearPersistedLogsByIds(ids) {
1763
+ const uniqueIds = new Set(ids);
1764
+ if (uniqueIds.size === 0) {
1765
+ return { cleared: 0, logFilesRewritten: 0, chunkFilesDeleted: 0 };
1766
+ }
1767
+ return await runWithFlushedLogWriteLock(async () => {
1768
+ await flushIndex();
1769
+ const memoryRemovedIds = /* @__PURE__ */ new Set();
1770
+ for (const id of uniqueIds) {
1771
+ if (memoryCache.has(id)) memoryRemovedIds.add(id);
1772
+ }
1773
+ const result = clearLogsByIds([...uniqueIds]);
1774
+ const [rewriteResult, chunkFilesDeleted] = await Promise.all([
1775
+ rewriteLogFilesWithoutIds(uniqueIds),
1776
+ deleteChunkFilesByIds(uniqueIds)
1777
+ ]);
1778
+ await rebuildIndex();
1779
+ const clearedIds = new Set(rewriteResult.removedIds);
1780
+ if (result.cleared > 0) {
1781
+ for (const id of memoryRemovedIds) {
1782
+ clearedIds.add(id);
1783
+ }
1784
+ }
1785
+ return {
1786
+ cleared: clearedIds.size,
1787
+ logFilesRewritten: rewriteResult.logFilesRewritten,
1788
+ chunkFilesDeleted
1789
+ };
1790
+ });
1791
+ }
1428
1792
  function clearLogsByIds(ids) {
1429
1793
  const unique2 = /* @__PURE__ */ new Set();
1430
1794
  for (const id of ids) {
@@ -2063,6 +2427,10 @@ const alibabaProvider = {
2063
2427
  }
2064
2428
  };
2065
2429
  registry.register(alibabaProvider);
2430
+ function maskApiKey(apiKey) {
2431
+ if (apiKey.length <= 8) return "••••••••";
2432
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
2433
+ }
2066
2434
  const ProviderModelMetadataSchema = object({
2067
2435
  model: string().min(1),
2068
2436
  contextWindow: number().int().positive().optional(),
@@ -2629,6 +2997,15 @@ function getProvider(id) {
2629
2997
  const providers = getProviders();
2630
2998
  return providers.find((p) => p.id === id);
2631
2999
  }
3000
+ function redactProvider(provider) {
3001
+ return {
3002
+ ...provider,
3003
+ apiKey: maskApiKey(provider.apiKey)
3004
+ };
3005
+ }
3006
+ function redactProviders(providers) {
3007
+ return providers.map(redactProvider);
3008
+ }
2632
3009
  function normalizeApiKey(apiKey) {
2633
3010
  return apiKey.replace(/^Bearer\s+/i, "").trim();
2634
3011
  }
@@ -2795,10 +3172,6 @@ function importProviders(json) {
2795
3172
  }
2796
3173
  return { imported, errors };
2797
3174
  }
2798
- function maskApiKey(apiKey) {
2799
- if (apiKey.length <= 8) return "••••••••";
2800
- return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
2801
- }
2802
3175
  function getModelUsageName(model, providerName) {
2803
3176
  if (providerName !== void 0 && providerName !== "" && providerName.toLowerCase().includes("minimax")) {
2804
3177
  return model.replace(/ /g, "-");
@@ -2840,6 +3213,10 @@ const execFileAsync = promisify(execFile);
2840
3213
  const cache = /* @__PURE__ */ new Map();
2841
3214
  const CACHE_TTL_MS = 5 * 60 * 1e3;
2842
3215
  const MAX_CACHE_SIZE = 200;
3216
+ const RemotePortSchema = number().int().min(1).max(65535);
3217
+ const SocketSchema = object({
3218
+ remotePort: RemotePortSchema.nullish()
3219
+ }).passthrough();
2843
3220
  const inflight = /* @__PURE__ */ new Map();
2844
3221
  function evictCacheIfNeeded() {
2845
3222
  while (cache.size > MAX_CACHE_SIZE) {
@@ -2861,10 +3238,13 @@ function setCache(port, info) {
2861
3238
  evictCacheIfNeeded();
2862
3239
  }
2863
3240
  function extractRemotePort(request) {
2864
- const socket = request.socket;
2865
- const remotePort = socket?.remotePort;
2866
- if (remotePort !== void 0 && remotePort !== null) return remotePort;
2867
- return null;
3241
+ const descriptor = Object.getOwnPropertyDescriptor(request, "socket");
3242
+ if (descriptor === void 0 || descriptor.get !== void 0) return null;
3243
+ const parsed = SocketSchema.safeParse(descriptor.value);
3244
+ if (!parsed.success) return null;
3245
+ const remotePort = parsed.data.remotePort;
3246
+ if (remotePort === void 0 || remotePort === null) return null;
3247
+ return remotePort;
2868
3248
  }
2869
3249
  async function lookupClientInfo(port) {
2870
3250
  const platform = process.platform;
@@ -3139,6 +3519,14 @@ function toPersistedConfig(value) {
3139
3519
  timeDisplayFormat: value.timeDisplayFormat
3140
3520
  };
3141
3521
  }
3522
+ function isPlainRecord(val) {
3523
+ return typeof val === "object" && val !== null && !Array.isArray(val);
3524
+ }
3525
+ function safeGetOwnProperty(obj, key) {
3526
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return void 0;
3527
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
3528
+ return desc?.value;
3529
+ }
3142
3530
  const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
3143
3531
  function isClaudeCodeBillingHeaderBlock(text) {
3144
3532
  const trimmed = text.trimStart();
@@ -3304,14 +3692,14 @@ function persistStreamingChunks(log) {
3304
3692
  log.streamingChunksPath = chunkPath;
3305
3693
  }
3306
3694
  }
3307
- function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err) {
3695
+ function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err, cleanupRawStreamPath = null) {
3308
3696
  const message = errorMessage$2(err);
3309
3697
  logger.error(`[logFinalizer] Failed to finalize log #${log.id}:`, message);
3310
3698
  log.responseStatus = log.responseStatus ?? fallbackStatus;
3311
3699
  log.responseText = log.responseText ?? fallbackResponseText;
3312
3700
  log.elapsedMs = job.elapsedMs;
3313
3701
  log.error = message;
3314
- return { log, upstreamUrl: job.upstreamUrl, error: message };
3702
+ return { log, upstreamUrl: job.upstreamUrl, error: message, cleanupRawStreamPath };
3315
3703
  }
3316
3704
  function finalizeNonStreaming(job, log) {
3317
3705
  const formatHandler = formatForPath(log.path);
@@ -3332,36 +3720,90 @@ function finalizeNonStreaming(job, log) {
3332
3720
  return finalizeWithError(job, log, job.responseStatus, job.responseBody, err);
3333
3721
  }
3334
3722
  }
3723
+ function cleanupPathForRawStream(rawStream) {
3724
+ if (typeof rawStream === "string") return null;
3725
+ switch (rawStream.type) {
3726
+ case "memory":
3727
+ return null;
3728
+ case "file":
3729
+ return rawStream.path;
3730
+ }
3731
+ }
3732
+ function readRawStream(rawStream) {
3733
+ if (typeof rawStream === "string") {
3734
+ return { rawStreamText: rawStream, cleanupRawStreamPath: null };
3735
+ }
3736
+ switch (rawStream.type) {
3737
+ case "memory":
3738
+ return { rawStreamText: rawStream.rawStream, cleanupRawStreamPath: null };
3739
+ case "file":
3740
+ return {
3741
+ rawStreamText: readFileSync(rawStream.path, "utf-8"),
3742
+ cleanupRawStreamPath: rawStream.path
3743
+ };
3744
+ }
3745
+ }
3335
3746
  function finalizeStreaming(job, log) {
3336
3747
  const formatHandler = formatForPath(log.path);
3337
3748
  if (formatHandler === null) {
3338
- return finalizeWithError(job, log, job.responseStatus, job.rawStream, "Unsupported format");
3749
+ return finalizeWithError(
3750
+ job,
3751
+ log,
3752
+ job.responseStatus,
3753
+ "Streaming response unavailable",
3754
+ "Unsupported format",
3755
+ cleanupPathForRawStream(job.rawStream)
3756
+ );
3339
3757
  }
3758
+ let rawStreamText = "";
3759
+ let cleanupRawStreamPath = null;
3340
3760
  try {
3761
+ const raw = readRawStream(job.rawStream);
3762
+ rawStreamText = raw.rawStreamText;
3763
+ cleanupRawStreamPath = raw.cleanupRawStreamPath;
3341
3764
  log.elapsedMs = job.elapsedMs;
3342
3765
  log.responseStatus = job.responseStatus;
3343
3766
  log.responseText = formatHandler.extractStream(
3344
- job.rawStream,
3767
+ rawStreamText,
3345
3768
  log,
3346
3769
  log.model ?? void 0,
3347
3770
  job.collectStreamingChunks
3348
3771
  );
3349
3772
  persistStreamingChunks(log);
3350
- return { log, upstreamUrl: job.upstreamUrl, error: null };
3773
+ return { log, upstreamUrl: job.upstreamUrl, error: null, cleanupRawStreamPath };
3351
3774
  } catch (err) {
3352
- return finalizeWithError(job, log, job.responseStatus, job.rawStream, err);
3775
+ return finalizeWithError(
3776
+ job,
3777
+ log,
3778
+ job.responseStatus,
3779
+ rawStreamText === "" ? "Streaming response unavailable" : rawStreamText,
3780
+ err,
3781
+ cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream)
3782
+ );
3353
3783
  }
3354
3784
  }
3355
3785
  function finalizeStreamAbort(job, log) {
3356
3786
  const formatHandler = formatForPath(log.path);
3357
3787
  if (formatHandler === null && job.hasChunks) {
3358
- return finalizeWithError(job, log, 499, "Client aborted", "Unsupported format");
3788
+ return finalizeWithError(
3789
+ job,
3790
+ log,
3791
+ 499,
3792
+ "Client aborted",
3793
+ "Unsupported format",
3794
+ cleanupPathForRawStream(job.rawStream)
3795
+ );
3359
3796
  }
3797
+ let rawStreamText = "";
3798
+ let cleanupRawStreamPath = null;
3360
3799
  try {
3361
3800
  log.elapsedMs = job.elapsedMs;
3362
3801
  if (job.hasChunks && formatHandler !== null) {
3802
+ const raw = readRawStream(job.rawStream);
3803
+ rawStreamText = raw.rawStreamText;
3804
+ cleanupRawStreamPath = raw.cleanupRawStreamPath;
3363
3805
  log.responseText = formatHandler.extractStream(
3364
- job.rawStream,
3806
+ rawStreamText,
3365
3807
  log,
3366
3808
  log.model ?? void 0,
3367
3809
  job.collectStreamingChunks
@@ -3370,9 +3812,16 @@ function finalizeStreamAbort(job, log) {
3370
3812
  } else {
3371
3813
  log.responseText = "Client aborted";
3372
3814
  }
3373
- return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted" };
3815
+ return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted", cleanupRawStreamPath };
3374
3816
  } catch (err) {
3375
- return finalizeWithError(job, log, 499, "Client aborted", err);
3817
+ return finalizeWithError(
3818
+ job,
3819
+ log,
3820
+ 499,
3821
+ rawStreamText === "" ? "Client aborted" : rawStreamText,
3822
+ err,
3823
+ cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream)
3824
+ );
3376
3825
  }
3377
3826
  }
3378
3827
  function buildFinalizeLogResult(job) {
@@ -3389,6 +3838,17 @@ function buildFinalizeLogResult(job) {
3389
3838
  function commitFinalizeLogResult(result) {
3390
3839
  appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
3391
3840
  finalizeLogUpdate(result.log);
3841
+ if (result.cleanupRawStreamPath !== void 0 && result.cleanupRawStreamPath !== null) {
3842
+ try {
3843
+ rmSync(result.cleanupRawStreamPath, { force: true });
3844
+ } catch (err) {
3845
+ logger.warn(
3846
+ "[logFinalizer] Failed to remove raw stream temp file:",
3847
+ result.cleanupRawStreamPath,
3848
+ errorMessage$2(err)
3849
+ );
3850
+ }
3851
+ }
3392
3852
  }
3393
3853
  const RUNTIME = (() => {
3394
3854
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
@@ -3511,6 +3971,63 @@ function enqueueFinalizeLogJob(job) {
3511
3971
  () => executeFinalizeLogJob(job)
3512
3972
  );
3513
3973
  }
3974
+ const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
3975
+ function resolveMemoryLimitBytes() {
3976
+ const raw = process.env["AGENT_INSPECTOR_STREAM_MEMORY_LIMIT_BYTES"];
3977
+ if (raw === void 0 || raw === "") return DEFAULT_MEMORY_LIMIT_BYTES;
3978
+ const parsed = Number(raw);
3979
+ if (!Number.isInteger(parsed) || parsed < 0) return DEFAULT_MEMORY_LIMIT_BYTES;
3980
+ return parsed;
3981
+ }
3982
+ function getRawStreamDir() {
3983
+ return join(getDataDir(), "raw-streams");
3984
+ }
3985
+ function createRawStreamPath() {
3986
+ return join(getRawStreamDir(), `${Date.now()}-${randomUUID$1()}.stream.tmp`);
3987
+ }
3988
+ class RawStreamCapture {
3989
+ memoryLimitBytes;
3990
+ chunks = [];
3991
+ byteLength = 0;
3992
+ filePath = null;
3993
+ sawChunk = false;
3994
+ constructor(memoryLimitBytes = resolveMemoryLimitBytes()) {
3995
+ this.memoryLimitBytes = memoryLimitBytes;
3996
+ }
3997
+ get hasChunks() {
3998
+ return this.sawChunk;
3999
+ }
4000
+ async append(text) {
4001
+ if (text === "") return;
4002
+ this.sawChunk = true;
4003
+ const nextBytes = Buffer.byteLength(text, "utf-8");
4004
+ if (this.filePath === null && this.byteLength + nextBytes <= this.memoryLimitBytes) {
4005
+ this.chunks.push(text);
4006
+ this.byteLength += nextBytes;
4007
+ return;
4008
+ }
4009
+ if (this.filePath === null) {
4010
+ await this.spillToFile();
4011
+ }
4012
+ if (this.filePath !== null) {
4013
+ await appendFile(this.filePath, text, "utf-8");
4014
+ this.byteLength += nextBytes;
4015
+ }
4016
+ }
4017
+ snapshot() {
4018
+ if (this.filePath !== null) {
4019
+ return { type: "file", path: this.filePath };
4020
+ }
4021
+ return { type: "memory", rawStream: this.chunks.join("") };
4022
+ }
4023
+ async spillToFile() {
4024
+ const path2 = createRawStreamPath();
4025
+ await mkdir(getRawStreamDir(), { recursive: true });
4026
+ await writeFile(path2, this.chunks.join(""), "utf-8");
4027
+ this.filePath = path2;
4028
+ this.chunks = [];
4029
+ }
4030
+ }
3514
4031
  function describeApiRoute(apiPath) {
3515
4032
  const endpointPath = apiPath.split("?")[0] ?? "";
3516
4033
  const isChatCompletions = endpointPath === PATH_CHAT_COMPLETIONS || endpointPath === PATH_V1_CHAT_COMPLETIONS;
@@ -3619,25 +4136,47 @@ function handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstre
3619
4136
  function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, collectStreamingChunks) {
3620
4137
  log.streaming = true;
3621
4138
  log.responseStatus = upstreamRes.status;
3622
- const chunks = [];
4139
+ const capture = new RawStreamCapture();
3623
4140
  const decoder = new TextDecoder();
4141
+ let finalizationScheduled = false;
4142
+ const scheduleStreamingFinalization = (type) => {
4143
+ if (finalizationScheduled) return;
4144
+ finalizationScheduled = true;
4145
+ const elapsedMs = Date.now() - startTime;
4146
+ const rawStream = capture.snapshot();
4147
+ switch (type) {
4148
+ case "streaming":
4149
+ scheduleLogFinalization({
4150
+ type: "streaming",
4151
+ log,
4152
+ upstreamUrl,
4153
+ elapsedMs,
4154
+ responseStatus: upstreamRes.status,
4155
+ rawStream,
4156
+ collectStreamingChunks
4157
+ });
4158
+ return;
4159
+ case "stream-abort":
4160
+ scheduleLogFinalization({
4161
+ type: "stream-abort",
4162
+ log,
4163
+ upstreamUrl,
4164
+ elapsedMs,
4165
+ rawStream,
4166
+ hasChunks: capture.hasChunks,
4167
+ collectStreamingChunks
4168
+ });
4169
+ return;
4170
+ }
4171
+ };
3624
4172
  const transform = new TransformStream({
3625
- transform(chunk, controller) {
4173
+ async transform(chunk, controller) {
3626
4174
  controller.enqueue(chunk);
3627
- chunks.push(decoder.decode(chunk, { stream: true }));
4175
+ await capture.append(decoder.decode(chunk, { stream: true }));
3628
4176
  },
3629
- flush() {
3630
- const full = chunks.join("");
3631
- const elapsedMs = Date.now() - startTime;
3632
- scheduleLogFinalization({
3633
- type: "streaming",
3634
- log,
3635
- upstreamUrl,
3636
- elapsedMs,
3637
- responseStatus: upstreamRes.status,
3638
- rawStream: full,
3639
- collectStreamingChunks
3640
- });
4177
+ async flush() {
4178
+ await capture.append(decoder.decode());
4179
+ scheduleStreamingFinalization("streaming");
3641
4180
  }
3642
4181
  });
3643
4182
  if (upstreamRes.body === null) {
@@ -3647,18 +4186,7 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
3647
4186
  req.signal?.addEventListener("abort", () => {
3648
4187
  if (log.responseText === null) {
3649
4188
  logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
3650
- const elapsedMs = Date.now() - startTime;
3651
- const full = chunks.join("");
3652
- const hasChunks = chunks.length > 0;
3653
- scheduleLogFinalization({
3654
- type: "stream-abort",
3655
- log,
3656
- upstreamUrl,
3657
- elapsedMs,
3658
- rawStream: full,
3659
- hasChunks,
3660
- collectStreamingChunks
3661
- });
4189
+ scheduleStreamingFinalization("stream-abort");
3662
4190
  }
3663
4191
  });
3664
4192
  const responseHeaders = new Headers(upstreamRes.headers);
@@ -3813,8 +4341,11 @@ const ProviderInputSchema = object({
3813
4341
  const Route$n = createFileRoute("/api/providers")({
3814
4342
  server: {
3815
4343
  handlers: {
3816
- GET: () => {
3817
- return Response.json(getProviders());
4344
+ GET: ({ request }) => {
4345
+ const url = new URL(request.url);
4346
+ const includeSecrets = url.searchParams.get("includeSecrets") === "1";
4347
+ const providers = getProviders();
4348
+ return Response.json(includeSecrets ? providers : redactProviders(providers));
3818
4349
  },
3819
4350
  POST: async ({ request }) => {
3820
4351
  const parsed = ProviderInputSchema.safeParse(await request.json());
@@ -4089,13 +4620,18 @@ async function listModelsImpl(callApi2) {
4089
4620
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
4090
4621
  return textJson(await res.json());
4091
4622
  }
4092
- async function listProvidersImpl(callApi2) {
4093
- const res = await callApi2("/api/providers");
4623
+ function includeSecretsQuery(includeSecrets) {
4624
+ return includeSecrets === true ? "?includeSecrets=1" : "";
4625
+ }
4626
+ async function listProvidersImpl(callApi2, args = {}) {
4627
+ const res = await callApi2(`/api/providers${includeSecretsQuery(args.includeSecrets)}`);
4094
4628
  if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
4095
4629
  return textJson(await res.json());
4096
4630
  }
4097
- async function getProviderImpl(callApi2, id) {
4098
- const res = await callApi2(`/api/providers/${encodeURIComponent(id)}`);
4631
+ async function getProviderImpl(callApi2, id, args = {}) {
4632
+ const res = await callApi2(
4633
+ `/api/providers/${encodeURIComponent(id)}${includeSecretsQuery(args.includeSecrets)}`
4634
+ );
4099
4635
  if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
4100
4636
  return textJson(await res.json());
4101
4637
  }
@@ -4318,21 +4854,24 @@ function registerTools(server) {
4318
4854
  "inspector_list_providers",
4319
4855
  {
4320
4856
  title: "List configured LLM providers",
4321
- description: "Returns the full ProviderConfig array, including apiKey in PLAINTEXT. MCP is localhost-only any process that can call /api/mcp can also read <dataDir>/providers.json directly, so the apiKey is not redacted. Do not expose this MCP server to non-trusted local processes.",
4322
- inputSchema: object({})
4857
+ description: "Returns the configured ProviderConfig array with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs plaintext local provider keys; this exposes secrets from <dataDir>/providers.json.",
4858
+ inputSchema: object({
4859
+ includeSecrets: boolean().optional().describe("Return plaintext provider API keys. Exposes local secrets when true.")
4860
+ })
4323
4861
  },
4324
- () => safeCall(() => listProvidersImpl(callApi))
4862
+ (args) => safeCall(() => listProvidersImpl(callApi, args))
4325
4863
  );
4326
4864
  server.registerTool(
4327
4865
  "inspector_get_provider",
4328
4866
  {
4329
4867
  title: "Get a single provider by id",
4330
- description: "Returns the full ProviderConfig for the given id, including apiKey in PLAINTEXT (same posture as inspector_list_providers).",
4868
+ description: "Returns one ProviderConfig with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs the plaintext local provider key.",
4331
4869
  inputSchema: object({
4332
- id: string().describe("The provider id.")
4870
+ id: string().describe("The provider id."),
4871
+ includeSecrets: boolean().optional().describe("Return the plaintext provider API key. Exposes a local secret when true.")
4333
4872
  })
4334
4873
  },
4335
- ({ id }) => safeCall(() => getProviderImpl(callApi, id))
4874
+ ({ id, includeSecrets }) => safeCall(() => getProviderImpl(callApi, id, { includeSecrets }))
4336
4875
  );
4337
4876
  server.registerTool(
4338
4877
  "inspector_replay_log",
@@ -4468,23 +5007,40 @@ const Route$l = createFileRoute("/api/mcp")({
4468
5007
  const DeleteBodySchema = object({
4469
5008
  ids: array(number().int().positive()).optional()
4470
5009
  }).optional();
5010
+ function parseNonNegativeInt(value, fallback) {
5011
+ if (value === null) return fallback;
5012
+ const parsed = Number(value);
5013
+ if (!Number.isInteger(parsed) || parsed < 0) return fallback;
5014
+ return parsed;
5015
+ }
5016
+ function parsePositiveInt(value, fallback) {
5017
+ if (value === null) return fallback;
5018
+ const parsed = Number(value);
5019
+ if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5020
+ return parsed;
5021
+ }
4471
5022
  const Route$k = createFileRoute("/api/logs")({
4472
5023
  server: {
4473
5024
  handlers: {
4474
- GET: ({ request }) => {
5025
+ GET: async ({ request }) => {
4475
5026
  const url = new URL(request.url);
5027
+ if (url.searchParams.get("stats") === "1") {
5028
+ return Response.json(getLogStorageStats());
5029
+ }
5030
+ const replayOf = url.searchParams.get("replayOf");
5031
+ if (replayOf !== null) {
5032
+ const id = Number(replayOf);
5033
+ if (!Number.isInteger(id) || id <= 0) {
5034
+ return Response.json({ error: "Invalid replayOf log ID" }, { status: 400 });
5035
+ }
5036
+ const logs = getReplayLogsForSource(id);
5037
+ return Response.json({ logs, total: logs.length, offset: 0, limit: logs.length });
5038
+ }
4476
5039
  const sessionId = url.searchParams.get("sessionId") ?? void 0;
4477
5040
  const model = url.searchParams.get("model") ?? void 0;
4478
- const offset = Number(url.searchParams.get("offset") ?? 0);
4479
- const limit = Number(url.searchParams.get("limit") ?? 50);
4480
- const allLogs = getFilteredLogs(sessionId, model);
4481
- const paginatedLogs = allLogs.slice(offset, offset + limit);
4482
- return Response.json({
4483
- logs: paginatedLogs,
4484
- total: allLogs.length,
4485
- offset,
4486
- limit
4487
- });
5041
+ const offset = parseNonNegativeInt(url.searchParams.get("offset"), 0);
5042
+ const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
5043
+ return Response.json(await listLogsPage({ sessionId, model, offset, limit }));
4488
5044
  },
4489
5045
  DELETE: async ({ request }) => {
4490
5046
  let body = void 0;
@@ -4505,11 +5061,21 @@ const Route$k = createFileRoute("/api/logs")({
4505
5061
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
4506
5062
  }
4507
5063
  if (body?.ids !== void 0 && body.ids.length > 0) {
4508
- const result2 = clearLogsByIds(body.ids);
4509
- return Response.json({ success: true, cleared: result2.cleared });
5064
+ const result2 = await clearPersistedLogsByIds(body.ids);
5065
+ return Response.json({
5066
+ success: true,
5067
+ cleared: result2.cleared,
5068
+ logFilesRewritten: result2.logFilesRewritten,
5069
+ chunkFilesDeleted: result2.chunkFilesDeleted
5070
+ });
4510
5071
  }
4511
- const result = clearAllLogs();
4512
- return Response.json({ success: true, cleared: result.cleared });
5072
+ const result = await clearPersistedLogStorage();
5073
+ return Response.json({
5074
+ success: true,
5075
+ cleared: result.cleared,
5076
+ logFilesDeleted: result.logFilesDeleted,
5077
+ chunkFilesDeleted: result.chunkFilesDeleted
5078
+ });
4513
5079
  }
4514
5080
  }
4515
5081
  }
@@ -5031,13 +5597,15 @@ const ProviderUpdateSchema = object({
5031
5597
  const Route$e = createFileRoute("/api/providers/$providerId")({
5032
5598
  server: {
5033
5599
  handlers: {
5034
- GET: ({ params }) => {
5600
+ GET: ({ params, request }) => {
5601
+ const url = new URL(request.url);
5602
+ const includeSecrets = url.searchParams.get("includeSecrets") === "1";
5035
5603
  const providers = getProviders();
5036
5604
  const provider = providers.find((p) => p.id === params.providerId);
5037
5605
  if (!provider) {
5038
5606
  return Response.json({ error: "Provider not found" }, { status: 404 });
5039
5607
  }
5040
- return Response.json(provider);
5608
+ return Response.json(includeSecrets ? provider : redactProvider(provider));
5041
5609
  },
5042
5610
  PUT: async ({ params, request }) => {
5043
5611
  const parsed = ProviderUpdateSchema.safeParse(await request.json());
@@ -5060,6 +5628,7 @@ const Route$e = createFileRoute("/api/providers/$providerId")({
5060
5628
  }
5061
5629
  }
5062
5630
  });
5631
+ const INITIAL_STREAM_LOG_LIMIT = 500;
5063
5632
  const Route$d = createFileRoute("/api/logs/stream")({
5064
5633
  server: {
5065
5634
  handlers: {
@@ -5101,9 +5670,15 @@ const Route$d = createFileRoute("/api/logs/stream")({
5101
5670
  }, 3e4);
5102
5671
  request.signal.addEventListener("abort", cleanup, { once: true });
5103
5672
  const stream = new ReadableStream({
5104
- start(controller) {
5673
+ async start(controller) {
5105
5674
  controllerRef = controller;
5106
- const logs = getFilteredLogs(sessionId, model);
5675
+ const result = await listLogsPage({
5676
+ sessionId,
5677
+ model,
5678
+ offset: 0,
5679
+ limit: INITIAL_STREAM_LOG_LIMIT
5680
+ });
5681
+ const logs = result.logs;
5107
5682
  const initData = `data: ${JSON.stringify({ type: "init", logs })}
5108
5683
 
5109
5684
  `;
@@ -6517,6 +7092,37 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6517
7092
  const mockLog = { ...log };
6518
7093
  const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
6519
7094
  const tokens = formatHandler.extractTokens(responseText);
7095
+ const savedReplayLog = await addTestLogEntry({
7096
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7097
+ method: log.method,
7098
+ path: log.path,
7099
+ model,
7100
+ sessionId: log.sessionId,
7101
+ rawRequestBody: modifiedBody,
7102
+ responseStatus: upstreamRes.status,
7103
+ responseText,
7104
+ inputTokens: tokens.inputTokens ?? null,
7105
+ outputTokens: tokens.outputTokens ?? null,
7106
+ cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
7107
+ cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
7108
+ elapsedMs,
7109
+ streaming: true,
7110
+ userAgent: log.userAgent,
7111
+ origin: log.origin,
7112
+ rawHeaders: log.rawHeaders,
7113
+ headers: log.headers,
7114
+ apiFormat: log.apiFormat,
7115
+ isTest: true,
7116
+ replayOfLogId: log.id,
7117
+ providerName: log.providerName,
7118
+ clientPort: log.clientPort,
7119
+ clientPid: log.clientPid,
7120
+ clientCwd: log.clientCwd,
7121
+ clientProjectFolder: log.clientProjectFolder,
7122
+ streamingChunks: void 0,
7123
+ streamingChunksPath: null,
7124
+ error: null
7125
+ });
6520
7126
  return Response.json({
6521
7127
  success: true,
6522
7128
  responseStatus: upstreamRes.status,
@@ -6524,11 +7130,43 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6524
7130
  inputTokens: tokens.inputTokens ?? void 0,
6525
7131
  outputTokens: tokens.outputTokens ?? void 0,
6526
7132
  elapsedMs,
6527
- streaming: true
7133
+ streaming: true,
7134
+ replayLogId: savedReplayLog.id
6528
7135
  });
6529
7136
  } else {
6530
7137
  const responseText = await upstreamRes.text();
6531
7138
  const tokens = formatHandler.extractTokens(responseText);
7139
+ const savedReplayLog = await addTestLogEntry({
7140
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7141
+ method: log.method,
7142
+ path: log.path,
7143
+ model,
7144
+ sessionId: log.sessionId,
7145
+ rawRequestBody: modifiedBody,
7146
+ responseStatus: upstreamRes.status,
7147
+ responseText,
7148
+ inputTokens: tokens.inputTokens ?? null,
7149
+ outputTokens: tokens.outputTokens ?? null,
7150
+ cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
7151
+ cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
7152
+ elapsedMs,
7153
+ streaming: false,
7154
+ userAgent: log.userAgent,
7155
+ origin: log.origin,
7156
+ rawHeaders: log.rawHeaders,
7157
+ headers: log.headers,
7158
+ apiFormat: log.apiFormat,
7159
+ isTest: true,
7160
+ replayOfLogId: log.id,
7161
+ providerName: log.providerName,
7162
+ clientPort: log.clientPort,
7163
+ clientPid: log.clientPid,
7164
+ clientCwd: log.clientCwd,
7165
+ clientProjectFolder: log.clientProjectFolder,
7166
+ streamingChunks: void 0,
7167
+ streamingChunksPath: null,
7168
+ error: null
7169
+ });
6532
7170
  return Response.json({
6533
7171
  success: true,
6534
7172
  responseStatus: upstreamRes.status,
@@ -6536,7 +7174,8 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6536
7174
  inputTokens: tokens.inputTokens ?? void 0,
6537
7175
  outputTokens: tokens.outputTokens ?? void 0,
6538
7176
  elapsedMs,
6539
- streaming: false
7177
+ streaming: false,
7178
+ replayLogId: savedReplayLog.id
6540
7179
  });
6541
7180
  }
6542
7181
  }
@@ -7220,8 +7859,9 @@ export {
7220
7859
  ProviderTestResultsSchema as j,
7221
7860
  createFailedProviderTestResults as k,
7222
7861
  resolveProviderContextWindow as l,
7223
- isPlainRecord as m,
7224
- router as n,
7862
+ maskApiKey as m,
7863
+ isPlainRecord as n,
7864
+ router as o,
7225
7865
  parseOpenAIResponse as p,
7226
7866
  requestFormatForPath as r,
7227
7867
  safeGetOwnProperty as s