@tonyclaw/agent-inspector 3.0.26 → 3.0.28

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 (86) hide show
  1. package/.output/cli.js +80 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-SZRUDQc9.js → CompareDrawer-TghZr5t1.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-BJvwOrSi.js → ProxyViewerContainer-fShBAZjj.js} +201 -201
  5. package/.output/public/assets/{ReplayDialog-DgfmqiM3.js → ReplayDialog-CZMApaF4.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CS6c7Qfn.js → RequestAnatomy-DNWccR7O.js} +1 -1
  7. package/.output/public/assets/{ResponseView-C0Ri_5gm.js → ResponseView-d25MdFhY.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-7dYcHWyZ.js → StreamingChunkSequence-DIFZEnWe.js} +1 -1
  9. package/.output/public/assets/{_sessionId-D-V84cgw.js → _sessionId-DRfvuE2k.js} +1 -1
  10. package/.output/public/assets/{index-DgFWCxOs.js → index-B3aAuqWq.js} +1 -1
  11. package/.output/public/assets/index-BsxAGqMA.css +1 -0
  12. package/.output/public/assets/{index-DrVJsks2.js → index-D4HkS7Ct.js} +1 -1
  13. package/.output/public/assets/{index-CSIL7-MK.js → index-DzARL2Fi.js} +3 -3
  14. package/.output/public/assets/{json-viewer-C9Ji3gYN.js → json-viewer-wuJSKaed.js} +1 -1
  15. package/.output/server/_libs/lucide-react.mjs +39 -39
  16. package/.output/server/{_sessionId-BQ6YQOO1.mjs → _sessionId-BJTNiP_O.mjs} +4 -2
  17. package/.output/server/_ssr/{CompareDrawer-CqNzIeMR.mjs → CompareDrawer-BMRH3N97.mjs} +5 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-CMC7Vsbw.mjs → ProxyViewerContainer-AFwcHAIR.mjs} +330 -223
  19. package/.output/server/_ssr/{ReplayDialog-D9p_LaTr.mjs → ReplayDialog-CbWvRHiN.mjs} +6 -4
  20. package/.output/server/_ssr/{RequestAnatomy-B4ge3qNZ.mjs → RequestAnatomy-C4kcPyqN.mjs} +4 -2
  21. package/.output/server/_ssr/{ResponseView-BCqMKApI.mjs → ResponseView-D8VYt-gZ.mjs} +5 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BA4zTLE4.mjs → StreamingChunkSequence-BUUo6vU0.mjs} +4 -2
  23. package/.output/server/_ssr/{index-CzxhDjlq.mjs → index-DTwMEEXO.mjs} +4 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-B3qV6iEJ.mjs → json-viewer-BqF1pSQD.mjs} +5 -3
  26. package/.output/server/_ssr/{router-CZe_R31M.mjs → router-C8BOns7y.mjs} +839 -368
  27. package/.output/server/{_tanstack-start-manifest_v-B50VTLrH.mjs → _tanstack-start-manifest_v-BBf2nRhb.mjs} +1 -1
  28. package/.output/server/index.mjs +70 -70
  29. package/README.md +8 -0
  30. package/package.json +4 -5
  31. package/src/cli.ts +43 -9
  32. package/src/components/ProxyViewer.tsx +57 -1
  33. package/src/components/clients/ClientLogo.tsx +43 -4
  34. package/src/components/inspector-pet/InspectorPet.tsx +0 -7
  35. package/src/components/providers/ProviderCard.tsx +3 -36
  36. package/src/components/providers/ProviderForm.tsx +13 -12
  37. package/src/components/providers/ProvidersPanel.tsx +2 -2
  38. package/src/components/providers/SettingsDialog.tsx +3 -3
  39. package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
  40. package/src/components/proxy-viewer/ConversationGroupList.tsx +233 -152
  41. package/src/components/proxy-viewer/ConversationHeader.tsx +29 -13
  42. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
  43. package/src/contracts/anthropic.ts +0 -5
  44. package/src/contracts/index.ts +0 -4
  45. package/src/contracts/log.ts +0 -1
  46. package/src/contracts/openai.ts +0 -3
  47. package/src/knowledge/openclawClient.ts +22 -9
  48. package/src/knowledge/openclawGatewayClient.ts +20 -13
  49. package/src/lib/alertContract.ts +0 -3
  50. package/src/lib/ecosystemContract.ts +0 -3
  51. package/src/lib/export-logs.ts +1 -4
  52. package/src/lib/jsonFile.ts +46 -0
  53. package/src/lib/safeFetch.ts +100 -0
  54. package/src/lib/ssrfGuard.ts +133 -0
  55. package/src/lib/stopReason.ts +1 -4
  56. package/src/lib/unknown.ts +27 -0
  57. package/src/lib/useProviders.ts +1 -1
  58. package/src/mcp/mode.ts +3 -9
  59. package/src/mcp/server.ts +5 -14
  60. package/src/mcp/toolHandlers.ts +45 -54
  61. package/src/proxy/constants.ts +17 -0
  62. package/src/proxy/evidenceAnalysis.ts +116 -117
  63. package/src/proxy/formats/anthropic/handler.ts +5 -1
  64. package/src/proxy/formats/anthropic/schemas.ts +1 -7
  65. package/src/proxy/formats/index.ts +1 -1
  66. package/src/proxy/formats/openai/schemas.ts +0 -3
  67. package/src/proxy/formats/registry.ts +0 -3
  68. package/src/proxy/groupEvidenceExporter.ts +4 -8
  69. package/src/proxy/groupStore.ts +3 -10
  70. package/src/proxy/handler.ts +91 -12
  71. package/src/proxy/identityProxy.ts +74 -1
  72. package/src/proxy/logImporter.ts +82 -12
  73. package/src/proxy/logIndex.ts +1 -4
  74. package/src/proxy/providerSecretStore.ts +177 -0
  75. package/src/proxy/providers.ts +84 -11
  76. package/src/proxy/runFailures.ts +4 -10
  77. package/src/proxy/runStore.ts +4 -11
  78. package/src/proxy/schemas.ts +1 -8
  79. package/src/proxy/sqliteLogIndex.ts +0 -16
  80. package/src/proxy/store.ts +1 -5
  81. package/src/proxy/toolSchemaWarnings.ts +1 -9
  82. package/src/routes/api/logs.$id.replay.ts +17 -5
  83. package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
  84. package/src/routes/api/providers.$providerId.ts +18 -5
  85. package/src/routes/api/providers.ts +18 -7
  86. package/.output/public/assets/index-CmeF5XXS.css +0 -1
@@ -1,17 +1,19 @@
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 { r as reactExports, 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, createReadStream, readdirSync, statSync, rmSync, cpSync, createWriteStream } from "node:fs";
4
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, createReadStream, readdirSync, statSync, rmSync, cpSync, chmodSync, createWriteStream } from "node:fs";
5
5
  import fs, { mkdir, appendFile, writeFile, readdir, open, readFile, stat, unlink, rm, rename } from "node:fs/promises";
6
6
  import { createInterface } from "node:readline";
7
7
  import { Buffer } from "node:buffer";
8
8
  import path, { basename, join, dirname, isAbsolute, resolve, relative } from "node:path";
9
- import { randomUUID, createHash } from "node:crypto";
9
+ import { randomUUID, timingSafeEqual, createHash, createDecipheriv, randomBytes, createCipheriv } from "node:crypto";
10
10
  import { spawn, execFile } from "node:child_process";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { C as Conf } from "../_libs/conf.mjs";
13
13
  import { randomUUID as randomUUID$1 } from "crypto";
14
14
  import { promisify } from "node:util";
15
+ import { lookup } from "node:dns/promises";
16
+ import { isIP } from "node:net";
15
17
  import { Worker } from "node:worker_threads";
16
18
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport, R as ResourceTemplate } from "../_libs/modelcontextprotocol__server.mjs";
17
19
  import { J as JSZip } from "../_libs/jszip.mjs";
@@ -64,7 +66,7 @@ import "../_libs/immediate.mjs";
64
66
  import "../_libs/setimmediate.mjs";
65
67
  import "../_libs/pako.mjs";
66
68
  const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20role='img'%20aria-label='Agent%20Inspector'%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%20%3e%3cpath%20d='M5%2013%20C5%209%208%207%2012%207%20C16%207%2019%209%2019%2013%20C19%2016%2016%2018%2012%2018%20C8%2018%205%2016%205%2013%20Z'%20/%3e%3cpath%20d='M5%2011%20C3.5%209.5%201.5%2010%202%2012.5%20C2.5%2014%204%2013.5%205%2012.5'%20/%3e%3cpath%20d='M19%2011%20C20.5%209.5%2022.5%2010%2022%2012.5%20C21.5%2014%2020%2013.5%2019%2012.5'%20/%3e%3cline%20x1='10'%20y1='7'%20x2='9.5'%20y2='5'%20/%3e%3cline%20x1='14'%20y1='7'%20x2='14.5'%20y2='5'%20/%3e%3cline%20x1='6.5'%20y1='16'%20x2='4.5'%20y2='19.5'%20/%3e%3cline%20x1='9'%20y1='17.5'%20x2='8'%20y2='20.5'%20/%3e%3cline%20x1='15'%20y1='17.5'%20x2='16'%20y2='20.5'%20/%3e%3cline%20x1='17.5'%20y1='16'%20x2='19.5'%20y2='19.5'%20/%3e%3c/g%3e%3ccircle%20cx='9.5'%20cy='4.5'%20r='0.9'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='14.5'%20cy='4.5'%20r='0.9'%20fill='%23f59e0b'%20/%3e%3c/svg%3e";
67
- const appCss = "/assets/index-CmeF5XXS.css";
69
+ const appCss = "/assets/index-BsxAGqMA.css";
68
70
  const CHUNK_LOAD_RELOAD_AT_KEY = "agent-inspector.chunkLoadReloadAt";
69
71
  const CHUNK_LOAD_RELOAD_WINDOW_MS = 15e3;
70
72
  const CHUNK_LOAD_FAILURE_PATTERNS = [
@@ -370,7 +372,7 @@ function RootDocument({ children }) {
370
372
  ] })
371
373
  ] });
372
374
  }
373
- const $$splitComponentImporter$1 = () => import("./index-CzxhDjlq.mjs");
375
+ const $$splitComponentImporter$1 = () => import("./index-DTwMEEXO.mjs");
374
376
  const Route$L = createFileRoute("/")({
375
377
  ssr: false,
376
378
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
@@ -414,7 +416,7 @@ function decodeSessionIdFromPath(encoded) {
414
416
  function getSessionPath(sessionId) {
415
417
  return `/session/${encodeSessionIdForPath(sessionId)}`;
416
418
  }
417
- const $$splitComponentImporter = () => import("../_sessionId-BQ6YQOO1.mjs");
419
+ const $$splitComponentImporter = () => import("../_sessionId-BJTNiP_O.mjs");
418
420
  const Route$K = createFileRoute("/session/$sessionId")({
419
421
  ssr: false,
420
422
  component: lazyRouteComponent($$splitComponentImporter, "component"),
@@ -1192,6 +1194,18 @@ function readChunks(path2) {
1192
1194
  return null;
1193
1195
  }
1194
1196
  }
1197
+ function isRecord(value) {
1198
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1199
+ }
1200
+ function readProperty$5(value, name2) {
1201
+ if (!isRecord(value)) return void 0;
1202
+ const descriptor = Object.getOwnPropertyDescriptor(value, name2);
1203
+ return descriptor === void 0 ? void 0 : descriptor.value;
1204
+ }
1205
+ function textByteLength(value) {
1206
+ if (value === null) return null;
1207
+ return new TextEncoder().encode(value).byteLength;
1208
+ }
1195
1209
  const SQLITE_INDEX_FILE = "inspector.sqlite";
1196
1210
  const SQLITE_PACKAGE_NAME$1 = "better-sqlite3";
1197
1211
  const BUN_SQLITE_MODULE_NAME$1 = "bun:sqlite";
@@ -1328,7 +1342,7 @@ function objectFromUnknown$1(value) {
1328
1342
  if (typeof value === "object" || typeof value === "function") return value;
1329
1343
  return null;
1330
1344
  }
1331
- function readProperty$5(value, name2) {
1345
+ function readProperty$4(value, name2) {
1332
1346
  let current = objectFromUnknown$1(value);
1333
1347
  while (current !== null) {
1334
1348
  const desc = Object.getOwnPropertyDescriptor(current, name2);
@@ -1342,7 +1356,7 @@ function readProperty$5(value, name2) {
1342
1356
  return void 0;
1343
1357
  }
1344
1358
  function readFunction$1(value, name2) {
1345
- const property = readProperty$5(value, name2);
1359
+ const property = readProperty$4(value, name2);
1346
1360
  return typeof property === "function" ? property : null;
1347
1361
  }
1348
1362
  function callMethod$1(target, methodName, args) {
@@ -1384,7 +1398,7 @@ function execSql$1(db, sql) {
1384
1398
  return result !== void 0;
1385
1399
  }
1386
1400
  function sqliteConstructorFromModule$1(moduleValue, exportName) {
1387
- const exportedValue = readProperty$5(moduleValue, exportName);
1401
+ const exportedValue = readProperty$4(moduleValue, exportName);
1388
1402
  if (typeof exportedValue === "function") {
1389
1403
  return (path2) => Reflect.construct(exportedValue, [path2]);
1390
1404
  }
@@ -1527,11 +1541,11 @@ function entryParams(entry) {
1527
1541
  ];
1528
1542
  }
1529
1543
  function readNumber$1(row, name2) {
1530
- const value = readProperty$5(row, name2);
1544
+ const value = readProperty$4(row, name2);
1531
1545
  return typeof value === "number" && Number.isFinite(value) ? value : null;
1532
1546
  }
1533
1547
  function readString$2(row, name2) {
1534
- const value = readProperty$5(row, name2);
1548
+ const value = readProperty$4(row, name2);
1535
1549
  return typeof value === "string" ? value : null;
1536
1550
  }
1537
1551
  function readBoolean(row, name2) {
@@ -1716,9 +1730,6 @@ function getIndexPath() {
1716
1730
  }
1717
1731
  let cachedIndex = null;
1718
1732
  let sqliteBackfillScheduledMaxId = 0;
1719
- function textByteLength$2(value) {
1720
- return value === null ? null : Buffer.byteLength(value, "utf8");
1721
- }
1722
1733
  function createLogIndexEntryMetadata(log) {
1723
1734
  return {
1724
1735
  sessionId: log.sessionId,
@@ -1751,8 +1762,8 @@ function createLogIndexEntryMetadata(log) {
1751
1762
  clientCwd: log.clientCwd ?? null,
1752
1763
  clientProjectFolder: log.clientProjectFolder ?? null,
1753
1764
  streamingChunksPath: log.streamingChunksPath ?? null,
1754
- rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength$2(log.rawRequestBody),
1755
- responseTextBytes: log.responseTextBytes ?? textByteLength$2(log.responseText),
1765
+ rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
1766
+ responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
1756
1767
  warnings: log.warnings,
1757
1768
  error: log.error ?? null
1758
1769
  }
@@ -1932,7 +1943,7 @@ function readStringArrayField(entry, field) {
1932
1943
  function readTextByteLength(entry, bytesField, textField) {
1933
1944
  const explicitBytes = readNumberOrNullField(entry, bytesField);
1934
1945
  if (explicitBytes !== null && explicitBytes >= 0) return explicitBytes;
1935
- return textByteLength$2(readStringOrNullField(entry, textField));
1946
+ return textByteLength(readStringOrNullField(entry, textField));
1936
1947
  }
1937
1948
  function readLogIndexSummary(entry, id) {
1938
1949
  return {
@@ -2284,9 +2295,18 @@ const HEADER_CONTENT_ENCODING = "content-encoding";
2284
2295
  const HEADER_CONTENT_LENGTH = "content-length";
2285
2296
  const HEADER_HOST = "host";
2286
2297
  const AUTH_HEADER_X_API_KEY = "x-api-key";
2298
+ const SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
2299
+ HEADER_AUTHORIZATION,
2300
+ HEADER_X_API_KEY,
2301
+ "cookie",
2302
+ "set-cookie",
2303
+ "proxy-authorization"
2304
+ ]);
2287
2305
  const CONTENT_TYPE_EVENT_STREAM = "text/event-stream";
2288
2306
  const STATUS_FORBIDDEN = 403;
2289
2307
  const STATUS_BAD_GATEWAY = 502;
2308
+ const STATUS_PAYLOAD_TOO_LARGE = 413;
2309
+ const MAX_REQUEST_BODY_BYTES = 50 * 1024 * 1024;
2290
2310
  const PRESERVE_HEADERS = /* @__PURE__ */ new Set([HEADER_CONTENT_TYPE]);
2291
2311
  class FormatRegistryImpl {
2292
2312
  handlers = /* @__PURE__ */ new Map();
@@ -2383,7 +2403,7 @@ function objectFromUnknown(value) {
2383
2403
  if (typeof value === "object" || typeof value === "function") return value;
2384
2404
  return null;
2385
2405
  }
2386
- function readProperty$4(value, name2) {
2406
+ function readProperty$3(value, name2) {
2387
2407
  let current = objectFromUnknown(value);
2388
2408
  while (current !== null) {
2389
2409
  const desc = Object.getOwnPropertyDescriptor(current, name2);
@@ -2397,7 +2417,7 @@ function readProperty$4(value, name2) {
2397
2417
  return void 0;
2398
2418
  }
2399
2419
  function readFunction(value, name2) {
2400
- const property = readProperty$4(value, name2);
2420
+ const property = readProperty$3(value, name2);
2401
2421
  return typeof property === "function" ? property : null;
2402
2422
  }
2403
2423
  function callMethod(target, methodName, args) {
@@ -2435,7 +2455,7 @@ function closeDatabase(db) {
2435
2455
  callMethod(db, "close", []);
2436
2456
  }
2437
2457
  function sqliteConstructorFromModule(moduleValue, exportName) {
2438
- const exportedValue = readProperty$4(moduleValue, exportName);
2458
+ const exportedValue = readProperty$3(moduleValue, exportName);
2439
2459
  if (typeof exportedValue === "function") {
2440
2460
  return (path2) => Reflect.construct(exportedValue, [path2]);
2441
2461
  }
@@ -2526,11 +2546,11 @@ async function archiveSessionLog(log) {
2526
2546
  }
2527
2547
  }
2528
2548
  function readString$1(row, name2) {
2529
- const value = readProperty$4(row, name2);
2549
+ const value = readProperty$3(row, name2);
2530
2550
  return typeof value === "string" ? value : null;
2531
2551
  }
2532
2552
  function readNumber(row, name2) {
2533
- const value = readProperty$4(row, name2);
2553
+ const value = readProperty$3(row, name2);
2534
2554
  return typeof value === "number" && Number.isFinite(value) ? value : null;
2535
2555
  }
2536
2556
  function logFromJson(value) {
@@ -3110,10 +3130,6 @@ function removeFromCache(id) {
3110
3130
  memoryCache.delete(id);
3111
3131
  removeLogsFromCursorPageIndexes(/* @__PURE__ */ new Set([id]));
3112
3132
  }
3113
- function textByteLength$1(value) {
3114
- if (value === null) return null;
3115
- return Buffer.byteLength(value, "utf8");
3116
- }
3117
3133
  function compactLogForList(log) {
3118
3134
  return {
3119
3135
  ...log,
@@ -3122,8 +3138,8 @@ function compactLogForList(log) {
3122
3138
  rawHeaders: void 0,
3123
3139
  headers: void 0,
3124
3140
  streamingChunks: void 0,
3125
- rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength$1(log.rawRequestBody),
3126
- responseTextBytes: log.responseTextBytes ?? textByteLength$1(log.responseText),
3141
+ rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
3142
+ responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
3127
3143
  bodyContentMode: "compact"
3128
3144
  };
3129
3145
  }
@@ -4275,7 +4291,7 @@ const AnthropicFormatHandler = {
4275
4291
  if (result.success) {
4276
4292
  return {
4277
4293
  model: result.data.model,
4278
- sessionId: result.data.metadata?.user_id ?? headers?.get("x-session-affinity") ?? null
4294
+ sessionId: result.data.metadata?.user_id ?? headers?.get("x-agent-inspector-session-id") ?? headers?.get("x-session-affinity") ?? null
4279
4295
  };
4280
4296
  }
4281
4297
  return null;
@@ -4390,24 +4406,24 @@ const anthropicProvider = {
4390
4406
  };
4391
4407
  registry.register(anthropicProvider);
4392
4408
  const MAX_CHUNKS = 1e3;
4393
- function readProperty$3(value, key) {
4409
+ function readProperty$2(value, key) {
4394
4410
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
4395
4411
  return Object.getOwnPropertyDescriptor(value, key)?.value;
4396
4412
  }
4397
4413
  function readStringProperty$2(value, key) {
4398
- const property = readProperty$3(value, key);
4414
+ const property = readProperty$2(value, key);
4399
4415
  return typeof property === "string" ? property : null;
4400
4416
  }
4401
4417
  function readNumberProperty(value, key) {
4402
- const property = readProperty$3(value, key);
4418
+ const property = readProperty$2(value, key);
4403
4419
  return typeof property === "number" && Number.isFinite(property) ? property : null;
4404
4420
  }
4405
4421
  function readObjectProperty(value, key) {
4406
- const property = readProperty$3(value, key);
4422
+ const property = readProperty$2(value, key);
4407
4423
  return property !== null && typeof property === "object" && !Array.isArray(property) ? property : null;
4408
4424
  }
4409
4425
  function readArrayProperty$1(value, key) {
4410
- const property = readProperty$3(value, key);
4426
+ const property = readProperty$2(value, key);
4411
4427
  return Array.isArray(property) ? property : [];
4412
4428
  }
4413
4429
  function parseSseEvents(raw) {
@@ -4599,7 +4615,7 @@ function extractChatCompletionsStream(events, log, fallbackModel, collectChunks)
4599
4615
  }
4600
4616
  function parseResponsesResponse(value) {
4601
4617
  const fromResponseProperty = OpenAIResponsesResponseSchema.safeParse(
4602
- readProperty$3(value, "response")
4618
+ readProperty$2(value, "response")
4603
4619
  );
4604
4620
  if (fromResponseProperty.success) return fromResponseProperty.data;
4605
4621
  const direct = OpenAIResponsesResponseSchema.safeParse(value);
@@ -5416,6 +5432,162 @@ function withBuiltinProviderModelMetadata(provider, updatedAt) {
5416
5432
  modelMetadata: result.modelMetadata
5417
5433
  };
5418
5434
  }
5435
+ function readJsonFile(path2, schema) {
5436
+ let raw;
5437
+ try {
5438
+ raw = readFileSync(path2, "utf8");
5439
+ } catch (error) {
5440
+ if (isEnoent(error)) return { ok: false, reason: "missing" };
5441
+ return { ok: false, reason: "io-error" };
5442
+ }
5443
+ let parsed;
5444
+ try {
5445
+ parsed = JSON.parse(raw);
5446
+ } catch {
5447
+ return { ok: false, reason: "parse-error" };
5448
+ }
5449
+ const result = schema.safeParse(parsed);
5450
+ if (!result.success) return { ok: false, reason: "schema-mismatch" };
5451
+ return { ok: true, data: result.data };
5452
+ }
5453
+ function isEnoent(error) {
5454
+ return readProperty$5(error, "code") === "ENOENT";
5455
+ }
5456
+ const SECRET_REFERENCE_PREFIX = "secret:provider:";
5457
+ const SECRET_FILE_VERSION = 1;
5458
+ const KEY_BYTES = 32;
5459
+ const IV_BYTES = 12;
5460
+ const AUTH_TAG_BYTES = 16;
5461
+ const SecretFileSchema = object({
5462
+ version: literal(SECRET_FILE_VERSION),
5463
+ secrets: record(string(), string())
5464
+ });
5465
+ function emptySecretFile() {
5466
+ return { version: SECRET_FILE_VERSION, secrets: {} };
5467
+ }
5468
+ function readSecretFile(path2) {
5469
+ if (!existsSync(path2)) return emptySecretFile();
5470
+ const result = readJsonFile(path2, SecretFileSchema);
5471
+ if (result.ok) return result.data;
5472
+ logger.warn(`[provider-secrets] Ignoring invalid secret file ${path2} (${result.reason})`);
5473
+ return emptySecretFile();
5474
+ }
5475
+ function writePrivateFile(path2, content) {
5476
+ const temporaryPath = `${path2}.${String(process.pid)}.tmp`;
5477
+ try {
5478
+ writeFileSync(temporaryPath, content, { encoding: "utf8", mode: 384 });
5479
+ chmodSync(temporaryPath, 384);
5480
+ renameSync(temporaryPath, path2);
5481
+ chmodSync(path2, 384);
5482
+ return true;
5483
+ } catch (error) {
5484
+ rmSync(temporaryPath, { force: true });
5485
+ logger.error(`[provider-secrets] Could not write ${path2}: ${String(error)}`);
5486
+ return false;
5487
+ }
5488
+ }
5489
+ function readKey(path2) {
5490
+ try {
5491
+ if (!existsSync(path2)) return null;
5492
+ const key = Buffer.from(readFileSync(path2, "utf8").trim(), "base64");
5493
+ if (key.byteLength === KEY_BYTES) return key;
5494
+ logger.error(`[provider-secrets] Invalid encryption key at ${path2}`);
5495
+ return null;
5496
+ } catch (error) {
5497
+ logger.error(`[provider-secrets] Could not read encryption key: ${String(error)}`);
5498
+ return null;
5499
+ }
5500
+ }
5501
+ function readOrCreateKey(keyPath, secretsPath) {
5502
+ const existingKey = readKey(keyPath);
5503
+ if (existingKey !== null) return existingKey;
5504
+ if (existsSync(keyPath)) return null;
5505
+ if (existsSync(secretsPath)) {
5506
+ logger.error(
5507
+ `[provider-secrets] Refusing to replace missing key while encrypted secrets exist at ${secretsPath}`
5508
+ );
5509
+ return null;
5510
+ }
5511
+ const key = randomBytes(KEY_BYTES);
5512
+ if (!writePrivateFile(keyPath, key.toString("base64"))) return null;
5513
+ return key;
5514
+ }
5515
+ function encrypt(secret, key) {
5516
+ try {
5517
+ const iv = randomBytes(IV_BYTES);
5518
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
5519
+ const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
5520
+ const tag = cipher.getAuthTag();
5521
+ return Buffer.concat([iv, tag, ciphertext]).toString("base64");
5522
+ } catch (error) {
5523
+ logger.error(`[provider-secrets] Encryption failed: ${String(error)}`);
5524
+ return null;
5525
+ }
5526
+ }
5527
+ function decrypt(payload, key) {
5528
+ try {
5529
+ const packed = Buffer.from(payload, "base64");
5530
+ if (packed.byteLength <= IV_BYTES + AUTH_TAG_BYTES) return null;
5531
+ const iv = packed.subarray(0, IV_BYTES);
5532
+ const tag = packed.subarray(IV_BYTES, IV_BYTES + AUTH_TAG_BYTES);
5533
+ const ciphertext = packed.subarray(IV_BYTES + AUTH_TAG_BYTES);
5534
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
5535
+ decipher.setAuthTag(tag);
5536
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
5537
+ } catch {
5538
+ return null;
5539
+ }
5540
+ }
5541
+ function providerIdFromReference(reference) {
5542
+ if (!reference.startsWith(SECRET_REFERENCE_PREFIX)) return null;
5543
+ const providerId = reference.slice(SECRET_REFERENCE_PREFIX.length);
5544
+ return providerId === "" ? null : providerId;
5545
+ }
5546
+ function createProviderSecretStore(directory) {
5547
+ mkdirSync(directory, { recursive: true });
5548
+ const keyPath = join(directory, "provider-secrets.key");
5549
+ const secretsPath = join(directory, "provider-secrets.json");
5550
+ function writeSecrets(data) {
5551
+ return writePrivateFile(secretsPath, JSON.stringify(data, null, 2));
5552
+ }
5553
+ return {
5554
+ isReference: (value) => providerIdFromReference(value) !== null,
5555
+ write: (providerId, secret) => {
5556
+ const key = readOrCreateKey(keyPath, secretsPath);
5557
+ if (key === null) return null;
5558
+ const encrypted = encrypt(secret, key);
5559
+ if (encrypted === null) return null;
5560
+ const data = readSecretFile(secretsPath);
5561
+ const next = {
5562
+ ...data,
5563
+ secrets: { ...data.secrets, [providerId]: encrypted }
5564
+ };
5565
+ if (!writeSecrets(next)) return null;
5566
+ return `${SECRET_REFERENCE_PREFIX}${providerId}`;
5567
+ },
5568
+ read: (reference) => {
5569
+ const providerId = providerIdFromReference(reference);
5570
+ if (providerId === null) return null;
5571
+ const key = readKey(keyPath);
5572
+ if (key === null) return null;
5573
+ const encrypted = readSecretFile(secretsPath).secrets[providerId];
5574
+ if (encrypted === void 0) return null;
5575
+ return decrypt(encrypted, key);
5576
+ },
5577
+ delete: (providerId) => {
5578
+ const data = readSecretFile(secretsPath);
5579
+ if (data.secrets[providerId] === void 0) return;
5580
+ const nextSecrets = { ...data.secrets };
5581
+ delete nextSecrets[providerId];
5582
+ void writeSecrets({ ...data, secrets: nextSecrets });
5583
+ }
5584
+ };
5585
+ }
5586
+ function secretValuesEqual(left, right) {
5587
+ const leftBuffer = Buffer.from(left);
5588
+ const rightBuffer = Buffer.from(right);
5589
+ return leftBuffer.byteLength === rightBuffer.byteLength && timingSafeEqual(leftBuffer, rightBuffer);
5590
+ }
5419
5591
  object({
5420
5592
  providers: array(ProviderConfigSchema)
5421
5593
  });
@@ -5562,6 +5734,60 @@ const store = new Conf({
5562
5734
  },
5563
5735
  ...configPath !== void 0 ? { path: configPath } : {}
5564
5736
  });
5737
+ const providerSecretStore = createProviderSecretStore(dirname(store.path));
5738
+ function providerWithResolvedSecret(provider) {
5739
+ if (!providerSecretStore.isReference(provider.apiKey)) return provider;
5740
+ const apiKey = providerSecretStore.read(provider.apiKey);
5741
+ if (apiKey !== null) return { ...provider, apiKey };
5742
+ logger.error(`[providers] Secret is missing or unreadable for provider "${provider.name}"`);
5743
+ return null;
5744
+ }
5745
+ function persistProviders(providers, removedProviderIds = /* @__PURE__ */ new Set()) {
5746
+ const existingReferences = /* @__PURE__ */ new Map();
5747
+ const unresolvedRecords = /* @__PURE__ */ new Map();
5748
+ for (const candidate of store.get("providers", [])) {
5749
+ const parsed = ProviderConfigSchema.safeParse(candidate);
5750
+ if (parsed.success && providerSecretStore.isReference(parsed.data.apiKey)) {
5751
+ existingReferences.set(parsed.data.id, parsed.data.apiKey);
5752
+ if (providerSecretStore.read(parsed.data.apiKey) === null) {
5753
+ unresolvedRecords.set(parsed.data.id, parsed.data);
5754
+ }
5755
+ }
5756
+ }
5757
+ const persisted = providers.map((provider) => {
5758
+ const existingReference = existingReferences.get(provider.id);
5759
+ if (existingReference !== void 0) {
5760
+ const existingSecret = providerSecretStore.read(existingReference);
5761
+ if (existingSecret !== null && secretValuesEqual(existingSecret, provider.apiKey)) {
5762
+ return { ...provider, apiKey: existingReference };
5763
+ }
5764
+ }
5765
+ const reference = providerSecretStore.write(provider.id, provider.apiKey);
5766
+ if (reference !== null) return { ...provider, apiKey: reference };
5767
+ logger.error(
5768
+ `[providers] SecretStore unavailable; provider "${provider.name}" remains in the legacy storage format`
5769
+ );
5770
+ return provider;
5771
+ });
5772
+ const persistedIds = new Set(persisted.map((provider) => provider.id));
5773
+ for (const [id, provider] of unresolvedRecords) {
5774
+ if (!persistedIds.has(id) && !removedProviderIds.has(id)) persisted.push(provider);
5775
+ }
5776
+ store.set("providers", persisted);
5777
+ }
5778
+ function migratePlaintextProviderSecrets() {
5779
+ const raw = store.get("providers", []);
5780
+ let changed = false;
5781
+ const migrated = raw.map((candidate) => {
5782
+ const parsed = ProviderConfigSchema.safeParse(candidate);
5783
+ if (!parsed.success || providerSecretStore.isReference(parsed.data.apiKey)) return candidate;
5784
+ const reference = providerSecretStore.write(parsed.data.id, parsed.data.apiKey);
5785
+ if (reference === null) return candidate;
5786
+ changed = true;
5787
+ return { ...parsed.data, apiKey: reference };
5788
+ });
5789
+ if (changed) store.set("providers", migrated);
5790
+ }
5565
5791
  migrateFromDataDirConfig(store, dataDir);
5566
5792
  migrateFromLegacyConfLocation(store);
5567
5793
  function migrateProvider(p) {
@@ -5663,13 +5889,15 @@ if (providersJson !== void 0) {
5663
5889
  } catch {
5664
5890
  }
5665
5891
  }
5892
+ migratePlaintextProviderSecrets();
5666
5893
  function getProviders() {
5667
5894
  const raw = store.get("providers", []);
5668
5895
  const valid = [];
5669
5896
  for (const candidate of raw) {
5670
5897
  const entry = ProviderConfigSchema.safeParse(candidate);
5671
5898
  if (entry.success) {
5672
- valid.push(entry.data);
5899
+ const resolved = providerWithResolvedSecret(entry.data);
5900
+ if (resolved !== null) valid.push(resolved);
5673
5901
  continue;
5674
5902
  }
5675
5903
  let candidateName = "<unknown>";
@@ -5690,7 +5918,7 @@ function getProvider(id) {
5690
5918
  function redactProvider(provider) {
5691
5919
  return {
5692
5920
  ...provider,
5693
- apiKey: maskApiKey(provider.apiKey)
5921
+ apiKey: "configured"
5694
5922
  };
5695
5923
  }
5696
5924
  function redactProviders(providers) {
@@ -5707,7 +5935,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5707
5935
  const resolvedOpenaiResponsesUrl = openaiResponsesBaseUrl ?? "";
5708
5936
  const now = (/* @__PURE__ */ new Date()).toISOString();
5709
5937
  const existing = providers.find(
5710
- (p) => p.apiKey === normalizedKey && (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") && (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") && (p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? "")
5938
+ (p) => secretValuesEqual(p.apiKey, normalizedKey) && (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") && (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") && (p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? "")
5711
5939
  );
5712
5940
  if (existing) {
5713
5941
  const existingIndex = providers.findIndex((p) => p.id === existing.id);
@@ -5724,7 +5952,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5724
5952
  if (existingIndex >= 0) {
5725
5953
  providers[existingIndex] = enriched;
5726
5954
  }
5727
- store.set("providers", providers);
5955
+ persistProviders(providers);
5728
5956
  return enriched;
5729
5957
  }
5730
5958
  const newProvider = withBuiltinProviderModelMetadata(
@@ -5749,7 +5977,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5749
5977
  now
5750
5978
  );
5751
5979
  providers.push(newProvider);
5752
- store.set("providers", providers);
5980
+ persistProviders(providers);
5753
5981
  return newProvider;
5754
5982
  }
5755
5983
  function updateProvider(id, updates) {
@@ -5781,14 +6009,15 @@ function updateProvider(id, updates) {
5781
6009
  );
5782
6010
  const index = providers.findIndex((p) => p.id === id);
5783
6011
  providers[index] = updated;
5784
- store.set("providers", providers);
6012
+ persistProviders(providers);
5785
6013
  return updated;
5786
6014
  }
5787
6015
  function deleteProvider(id) {
5788
6016
  const providers = getProviders();
5789
6017
  const filtered = providers.filter((p) => p.id !== id);
5790
6018
  if (filtered.length === providers.length) return false;
5791
- store.set("providers", filtered);
6019
+ persistProviders(filtered, /* @__PURE__ */ new Set([id]));
6020
+ providerSecretStore.delete(id);
5792
6021
  return true;
5793
6022
  }
5794
6023
  function exportProviders() {
@@ -5855,7 +6084,7 @@ function importProviders(json) {
5855
6084
  imported++;
5856
6085
  }
5857
6086
  if (newProviders.length > 0) {
5858
- store.set("providers", [...existingProviders, ...newProviders]);
6087
+ persistProviders([...existingProviders, ...newProviders]);
5859
6088
  }
5860
6089
  } catch (err) {
5861
6090
  return {
@@ -6486,6 +6715,145 @@ function toPersistedConfig(value) {
6486
6715
  timeDisplayFormat: value.timeDisplayFormat
6487
6716
  };
6488
6717
  }
6718
+ const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
6719
+ "localhost",
6720
+ "ip6-localhost",
6721
+ "ip6-loopback",
6722
+ "[::1]",
6723
+ "[::ffff:127.0.0.1]"
6724
+ ]);
6725
+ function isLoopbackHost(hostname) {
6726
+ const lower2 = hostname.toLowerCase().replace(/^\[|]$/g, "");
6727
+ if (LOOPBACK_HOSTNAMES.has(lower2)) return true;
6728
+ if (lower2 === "::1") return true;
6729
+ if (lower2.startsWith("::ffff:127.")) return true;
6730
+ if (lower2.startsWith("::ffff:7f00:")) return true;
6731
+ return /^127\./.test(lower2);
6732
+ }
6733
+ function mappedIpv4Address(hostname) {
6734
+ const lower2 = hostname.toLowerCase();
6735
+ if (!lower2.startsWith("::ffff:")) return null;
6736
+ const suffix = lower2.slice("::ffff:".length);
6737
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
6738
+ const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
6739
+ if (match === null) return null;
6740
+ const highText = match[1];
6741
+ const lowText = match[2];
6742
+ if (highText === void 0 || lowText === void 0) return null;
6743
+ const high = Number.parseInt(highText, 16);
6744
+ const low = Number.parseInt(lowText, 16);
6745
+ return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
6746
+ }
6747
+ function isInternalHost(hostname) {
6748
+ if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
6749
+ const mappedIpv4 = mappedIpv4Address(hostname);
6750
+ if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
6751
+ const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
6752
+ if (ipv4Match !== null) {
6753
+ const [, a, b] = ipv4Match;
6754
+ if (a === void 0 || b === void 0) return false;
6755
+ const first = Number.parseInt(a, 10);
6756
+ const second = Number.parseInt(b, 10);
6757
+ if (first === 127) return true;
6758
+ if (first === 10) return true;
6759
+ if (first === 172 && second >= 16 && second <= 31) return true;
6760
+ if (first === 192 && second === 168) return true;
6761
+ if (first === 169 && second === 254) return true;
6762
+ if (first === 0) return true;
6763
+ if (first === 100 && second >= 64 && second <= 127) return true;
6764
+ if (first >= 224) return true;
6765
+ if (first === 198 && (second === 18 || second === 19)) return true;
6766
+ return false;
6767
+ }
6768
+ const lower2 = hostname.toLowerCase();
6769
+ if (lower2 === "::") return true;
6770
+ if (lower2 === "::1") return true;
6771
+ if (/^fe[89ab]/.test(lower2)) return true;
6772
+ if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
6773
+ if (lower2.startsWith("ff")) return true;
6774
+ return false;
6775
+ }
6776
+ function evaluateUpstreamUrl(rawUrl, options = {}) {
6777
+ if (options.bypass === true) return { allow: true };
6778
+ let parsed;
6779
+ try {
6780
+ parsed = new URL(rawUrl);
6781
+ } catch {
6782
+ return { allow: false, reason: "Not a valid URL" };
6783
+ }
6784
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6785
+ return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
6786
+ }
6787
+ if (options.allowLoopback === true && parsed.hostname === "localhost") {
6788
+ return { allow: true };
6789
+ }
6790
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
6791
+ if (isInternalHost(hostname)) {
6792
+ if (options.allowLoopback === true) {
6793
+ if (isLoopbackHost(hostname)) return { allow: true };
6794
+ }
6795
+ return {
6796
+ allow: false,
6797
+ reason: `Refusing to proxy to internal address "${hostname}"`
6798
+ };
6799
+ }
6800
+ return { allow: true };
6801
+ }
6802
+ async function defaultResolver(hostname) {
6803
+ return lookup(hostname, { all: true, verbatim: true });
6804
+ }
6805
+ function isLoopbackAddress(hostname) {
6806
+ const lower2 = hostname.toLowerCase();
6807
+ return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
6808
+ }
6809
+ async function evaluateResolvedUpstreamUrl(rawUrl, options = {}) {
6810
+ const initial = evaluateUpstreamUrl(rawUrl, options);
6811
+ if (!initial.allow || options.bypass === true) return initial;
6812
+ const parsed = new URL(rawUrl);
6813
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
6814
+ if (isIP(hostname) !== 0 || hostname.toLowerCase() === "localhost") return initial;
6815
+ let addresses;
6816
+ try {
6817
+ addresses = await (options.resolver ?? defaultResolver)(hostname);
6818
+ } catch (error) {
6819
+ return {
6820
+ allow: false,
6821
+ reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
6822
+ };
6823
+ }
6824
+ if (addresses.length === 0) {
6825
+ return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
6826
+ }
6827
+ for (const resolved of addresses) {
6828
+ if (!isInternalHost(resolved.address)) continue;
6829
+ if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
6830
+ return {
6831
+ allow: false,
6832
+ reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
6833
+ };
6834
+ }
6835
+ return { allow: true };
6836
+ }
6837
+ function isRedirectStatus(status) {
6838
+ return status >= 300 && status < 400;
6839
+ }
6840
+ async function safeFetch(rawUrl, init = {}, options = {}) {
6841
+ const decision = await evaluateResolvedUpstreamUrl(rawUrl, options);
6842
+ if (!decision.allow) {
6843
+ return { ok: false, kind: "network-policy", reason: decision.reason };
6844
+ }
6845
+ const response = await fetch(rawUrl, { ...init, redirect: "manual" });
6846
+ if (!isRedirectStatus(response.status)) return { ok: true, response };
6847
+ if (response.body !== null) {
6848
+ await response.body.cancel().catch(() => void 0);
6849
+ }
6850
+ const location = response.headers.get("location");
6851
+ return {
6852
+ ok: false,
6853
+ kind: "redirect",
6854
+ reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
6855
+ };
6856
+ }
6489
6857
  const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
6490
6858
  function isClaudeCodeBillingHeaderBlock(text) {
6491
6859
  const trimmed = text.trimStart();
@@ -6631,30 +6999,23 @@ function parseJson$1(raw) {
6631
6999
  return { ok: false };
6632
7000
  }
6633
7001
  }
6634
- function isRecord$2(value) {
6635
- return typeof value === "object" && value !== null && !Array.isArray(value);
6636
- }
6637
- function readProperty$2(value, key) {
6638
- if (!isRecord$2(value)) return void 0;
6639
- return Object.getOwnPropertyDescriptor(value, key)?.value;
6640
- }
6641
7002
  function hasOwnProperty(value, key) {
6642
7003
  return Object.getOwnPropertyDescriptor(value, key) !== void 0;
6643
7004
  }
6644
7005
  function readString(value, key) {
6645
- const property = readProperty$2(value, key);
7006
+ const property = readProperty$5(value, key);
6646
7007
  return typeof property === "string" ? property : null;
6647
7008
  }
6648
7009
  function readArray(value, key) {
6649
- const property = readProperty$2(value, key);
7010
+ const property = readProperty$5(value, key);
6650
7011
  return Array.isArray(property) ? property : [];
6651
7012
  }
6652
7013
  function readStringArray(value, key) {
6653
7014
  return readArray(value, key).filter((item) => typeof item === "string");
6654
7015
  }
6655
7016
  function readObject(value, key) {
6656
- const property = readProperty$2(value, key);
6657
- return isRecord$2(property) ? property : null;
7017
+ const property = readProperty$5(value, key);
7018
+ return isRecord(property) ? property : null;
6658
7019
  }
6659
7020
  function normalizeExpectedType(typeName) {
6660
7021
  switch (typeName) {
@@ -6671,7 +7032,7 @@ function normalizeExpectedType(typeName) {
6671
7032
  }
6672
7033
  }
6673
7034
  function schemaTypes(schema) {
6674
- const typeValue = readProperty$2(schema, "type");
7035
+ const typeValue = readProperty$5(schema, "type");
6675
7036
  const rawTypes = Array.isArray(typeValue) ? typeValue : [typeValue];
6676
7037
  const types = [];
6677
7038
  for (const rawType of rawTypes) {
@@ -6699,7 +7060,7 @@ function actualType(value) {
6699
7060
  case "function":
6700
7061
  case "object":
6701
7062
  case "symbol":
6702
- return isRecord$2(value) ? "object" : "unknown";
7063
+ return isRecord(value) ? "object" : "unknown";
6703
7064
  }
6704
7065
  return "unknown";
6705
7066
  }
@@ -6716,7 +7077,7 @@ function matchesExpectedType(value, expected) {
6716
7077
  case "number":
6717
7078
  return typeof value === "number" && Number.isFinite(value);
6718
7079
  case "object":
6719
- return isRecord$2(value);
7080
+ return isRecord(value);
6720
7081
  case "string":
6721
7082
  return typeof value === "string";
6722
7083
  }
@@ -6761,7 +7122,7 @@ function schemaAllowsObjectValidation(schema, expectedTypes) {
6761
7122
  return expectedTypes.includes("object");
6762
7123
  }
6763
7124
  function schemaAllowsArrayValidation(schema, expectedTypes) {
6764
- if (expectedTypes.length === 0) return readProperty$2(schema, "items") !== void 0;
7125
+ if (expectedTypes.length === 0) return readProperty$5(schema, "items") !== void 0;
6765
7126
  return expectedTypes.includes("array");
6766
7127
  }
6767
7128
  function addTypeWarning(warnings, toolName, path2, expectedTypes, value) {
@@ -6794,7 +7155,7 @@ function validateObjectSchema(schema, value, toolName, path2, warnings) {
6794
7155
  }
6795
7156
  }
6796
7157
  function validateArraySchema(schema, value, toolName, path2, warnings) {
6797
- const itemsSchema = readProperty$2(schema, "items");
7158
+ const itemsSchema = readProperty$5(schema, "items");
6798
7159
  if (itemsSchema === void 0) return;
6799
7160
  let index = 0;
6800
7161
  for (const item of value) {
@@ -6803,7 +7164,7 @@ function validateArraySchema(schema, value, toolName, path2, warnings) {
6803
7164
  }
6804
7165
  }
6805
7166
  function validateEnum(schema, value, toolName, path2, warnings) {
6806
- const enumValue = readProperty$2(schema, "enum");
7167
+ const enumValue = readProperty$5(schema, "enum");
6807
7168
  if (Array.isArray(enumValue) && enumValue.length > 0 && !enumMatches(value, enumValue)) {
6808
7169
  warnings.push(
6809
7170
  `Tool call "${toolName}" ${pathLabel(path2)} expected one of ${formatJsonValue(
@@ -6821,7 +7182,7 @@ function validateEnum(schema, value, toolName, path2, warnings) {
6821
7182
  }
6822
7183
  }
6823
7184
  function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6824
- if (!isRecord$2(schema)) return;
7185
+ if (!isRecord(schema)) return;
6825
7186
  const expectedTypes = schemaTypes(schema);
6826
7187
  if (expectedTypes.length > 0 && !matchesAnyExpectedType(value, expectedTypes)) {
6827
7188
  addTypeWarning(warnings, toolName, path2, expectedTypes, value);
@@ -6829,7 +7190,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6829
7190
  }
6830
7191
  validateEnum(schema, value, toolName, path2, warnings);
6831
7192
  if (schemaAllowsObjectValidation(schema, expectedTypes)) {
6832
- if (isRecord$2(value)) {
7193
+ if (isRecord(value)) {
6833
7194
  validateObjectSchema(schema, value, toolName, path2, warnings);
6834
7195
  } else if (expectedTypes.length === 0) {
6835
7196
  warnings.push(
@@ -6850,7 +7211,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6850
7211
  function extractAnthropicToolSchemas(request) {
6851
7212
  const tools = /* @__PURE__ */ new Map();
6852
7213
  for (const tool of readArray(request, "tools")) {
6853
- if (!isRecord$2(tool)) continue;
7214
+ if (!isRecord(tool)) continue;
6854
7215
  const name2 = readString(tool, "name");
6855
7216
  if (name2 === null || name2 === "") continue;
6856
7217
  tools.set(name2, {
@@ -6920,7 +7281,7 @@ function extractOpenAIToolCalls(response) {
6920
7281
  for (const call of readArray(message, "tool_calls")) {
6921
7282
  const functionCall = readObject(call, "function");
6922
7283
  const name2 = functionCall === null ? null : readString(functionCall, "name");
6923
- const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$2(functionCall, "arguments"));
7284
+ const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$5(functionCall, "arguments"));
6924
7285
  calls.push({
6925
7286
  name: name2,
6926
7287
  arguments: parsedArguments.value,
@@ -6931,7 +7292,7 @@ function extractOpenAIToolCalls(response) {
6931
7292
  }
6932
7293
  const legacyFunctionCall = readObject(message, "function_call");
6933
7294
  if (legacyFunctionCall !== null) {
6934
- const parsedArguments = parseOpenAIArguments(readProperty$2(legacyFunctionCall, "arguments"));
7295
+ const parsedArguments = parseOpenAIArguments(readProperty$5(legacyFunctionCall, "arguments"));
6935
7296
  calls.push({
6936
7297
  name: readString(legacyFunctionCall, "name"),
6937
7298
  arguments: parsedArguments.value,
@@ -6947,7 +7308,7 @@ function extractOpenAIToolCalls(response) {
6947
7308
  outputIndex++;
6948
7309
  continue;
6949
7310
  }
6950
- const parsedArguments = parseOpenAIArguments(readProperty$2(item, "arguments"));
7311
+ const parsedArguments = parseOpenAIArguments(readProperty$5(item, "arguments"));
6951
7312
  calls.push({
6952
7313
  name: readString(item, "name"),
6953
7314
  arguments: parsedArguments.value,
@@ -6968,7 +7329,7 @@ function extractAnthropicToolCalls(response) {
6968
7329
  }
6969
7330
  calls.push({
6970
7331
  name: readString(block, "name"),
6971
- arguments: readProperty$2(block, "input") ?? {},
7332
+ arguments: readProperty$5(block, "input") ?? {},
6972
7333
  argumentIssue: null,
6973
7334
  location: `content[${contentIndex}]`
6974
7335
  });
@@ -7751,12 +8112,14 @@ function injectProviderAuth(headers, provider) {
7751
8112
  }
7752
8113
  headers.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
7753
8114
  }
8115
+ const ALLOW_LOOPBACK$2 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
7754
8116
  function buildProxyHeaders(originalHeaders, captureRawHeaders) {
7755
8117
  const rawHeaders = captureRawHeaders ? {} : void 0;
7756
8118
  const headers = new Headers();
7757
8119
  if (rawHeaders !== void 0) {
7758
8120
  originalHeaders.forEach((value, key) => {
7759
- rawHeaders[key.toLowerCase()] = value;
8121
+ const lowerKey = key.toLowerCase();
8122
+ rawHeaders[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
7760
8123
  });
7761
8124
  }
7762
8125
  headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
@@ -7784,6 +8147,37 @@ function parseRequestPath(req, url) {
7784
8147
  function errorMessage(err) {
7785
8148
  return err instanceof Error ? err.message : String(err);
7786
8149
  }
8150
+ async function readBodyWithLimit(req, limitBytes) {
8151
+ if (req.body === null || req.method === "GET" || req.method === "HEAD") {
8152
+ return { ok: true, body: null };
8153
+ }
8154
+ const contentLengthHeader = req.headers.get(HEADER_CONTENT_LENGTH);
8155
+ if (contentLengthHeader !== null) {
8156
+ const declared = Number.parseInt(contentLengthHeader, 10);
8157
+ if (Number.isFinite(declared) && declared > limitBytes) {
8158
+ return { ok: false, reason: "too-large" };
8159
+ }
8160
+ }
8161
+ const reader = req.body.getReader();
8162
+ const decoder = new TextDecoder();
8163
+ const chunks = [];
8164
+ let totalBytes = 0;
8165
+ try {
8166
+ for (; ; ) {
8167
+ const { done, value } = await reader.read();
8168
+ if (done) break;
8169
+ totalBytes += value.byteLength;
8170
+ if (totalBytes > limitBytes) {
8171
+ return { ok: false, reason: "too-large" };
8172
+ }
8173
+ chunks.push(decoder.decode(value, { stream: true }));
8174
+ }
8175
+ chunks.push(decoder.decode());
8176
+ return { ok: true, body: chunks.join("") };
8177
+ } finally {
8178
+ reader.releaseLock();
8179
+ }
8180
+ }
7787
8181
  function scheduleLogFinalization(job) {
7788
8182
  void enqueueFinalizeLogJob(job).catch((err) => {
7789
8183
  logger.error(
@@ -7891,10 +8285,12 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
7891
8285
  async function handleProxy(req) {
7892
8286
  const url = new URL(req.url);
7893
8287
  const parsed = parseRequestPath(req, url);
7894
- let requestBody = null;
7895
- if (req.body && req.method !== "GET" && req.method !== "HEAD") {
7896
- requestBody = await req.text();
8288
+ const bodyRead = await readBodyWithLimit(req, MAX_REQUEST_BODY_BYTES);
8289
+ if (!bodyRead.ok) {
8290
+ logger.warn(`[handler] Rejected oversized request body: ${req.method} ${parsed.apiPath}`);
8291
+ return new Response("Request body too large", { status: STATUS_PAYLOAD_TOO_LARGE });
7897
8292
  }
8293
+ const requestBody = bodyRead.body;
7898
8294
  const config = getConfig();
7899
8295
  let bodyToForward = requestBody;
7900
8296
  if (config.stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
@@ -7941,7 +8337,8 @@ async function handleProxy(req) {
7941
8337
  const upstreamHeadersObj = captureFullDetails ? {} : void 0;
7942
8338
  if (upstreamHeadersObj !== void 0) {
7943
8339
  upstreamHeaders.forEach((value, key) => {
7944
- upstreamHeadersObj[key.toLowerCase()] = value;
8340
+ const lowerKey = key.toLowerCase();
8341
+ upstreamHeadersObj[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
7945
8342
  });
7946
8343
  }
7947
8344
  const log = await createLog(
@@ -7959,12 +8356,30 @@ async function handleProxy(req) {
7959
8356
  );
7960
8357
  let upstreamRes;
7961
8358
  try {
7962
- upstreamRes = await fetch(upstreamUrl, {
7963
- method: req.method,
7964
- headers: upstreamHeaders,
7965
- body: bodyToForward,
7966
- signal: req.signal
7967
- });
8359
+ const fetchResult = await safeFetch(
8360
+ upstreamUrl,
8361
+ {
8362
+ method: req.method,
8363
+ headers: upstreamHeaders,
8364
+ body: bodyToForward,
8365
+ signal: req.signal
8366
+ },
8367
+ { allowLoopback: ALLOW_LOOPBACK$2 }
8368
+ );
8369
+ if (!fetchResult.ok) {
8370
+ const status = fetchResult.kind === "network-policy" ? STATUS_FORBIDDEN : STATUS_BAD_GATEWAY;
8371
+ logger.warn(`[handler] Safe fetch blocked ${upstreamUrl}: ${fetchResult.reason}`);
8372
+ log.elapsedMs = Date.now() - startTime;
8373
+ log.responseStatus = status;
8374
+ log.responseText = fetchResult.reason;
8375
+ appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
8376
+ finalizeLogUpdate(log);
8377
+ return new Response(
8378
+ fetchResult.kind === "network-policy" ? "Forbidden: upstream URL is not permitted" : "Proxy error: upstream redirects are not permitted",
8379
+ { status }
8380
+ );
8381
+ }
8382
+ upstreamRes = fetchResult.response;
7968
8383
  } catch (err) {
7969
8384
  log.elapsedMs = Date.now() - startTime;
7970
8385
  if (err instanceof Error && err.name === "AbortError") {
@@ -8290,85 +8705,99 @@ function hasStreamingMismatch(session) {
8290
8705
  }
8291
8706
  return false;
8292
8707
  }
8293
- function categorySummary(category) {
8294
- switch (category) {
8295
- case "none":
8296
- return "Session completed without captured errors.";
8297
- case "active":
8298
- return "Session still has active requests or running tasks.";
8299
- case "provider-timeout":
8300
- return "Provider call appears to have timed out.";
8301
- case "provider-http-error":
8302
- return "Provider returned an HTTP error.";
8303
- case "provider-auth-error":
8304
- return "Provider authentication or API key configuration appears invalid.";
8305
- case "provider-rate-limit":
8306
- return "Provider rejected the request because of rate limit or quota pressure.";
8307
- case "network-or-container":
8308
- return "Network connectivity looks suspicious; check container boundaries and localhost use.";
8309
- case "streaming-mismatch":
8310
- return "Streaming and non-streaming behavior differ for the same provider/model.";
8311
- case "context-pressure":
8312
- return "The failure looks related to context window or token pressure.";
8313
- case "runtime-task-error":
8314
- return "Inspector session runtime reported a task error.";
8315
- case "model-output-error":
8316
- return "The model output or upstream payload shape appears invalid.";
8317
- case "unknown-failure":
8318
- return "Inspector captured errors, but the root cause is not yet classified.";
8319
- case "no-session-data":
8320
- return "No Inspector session data was available for this run.";
8321
- }
8322
- }
8323
- function categoryHints(category) {
8324
- switch (category) {
8325
- case "none":
8326
- return ["Keep the evidence pack as the success proof for CI or release notes."];
8327
- case "active":
8328
- return ["Wait for the session to settle, then export evidence again."];
8329
- case "provider-timeout":
8330
- return [
8331
- "Increase the provider test timeout for slow models.",
8332
- "Compare streaming and non-streaming probes for the same model."
8333
- ];
8334
- case "provider-http-error":
8335
- return [
8336
- "Open the cited log ids and inspect upstream status, response body, and provider URL."
8337
- ];
8338
- case "provider-auth-error":
8339
- return ["Verify the provider API key, auth header mode, and configured base URL."];
8340
- case "provider-rate-limit":
8341
- return [
8342
- "Retry after provider quota recovers or switch to a less constrained provider/model."
8343
- ];
8344
- case "network-or-container":
8345
- return [
8346
- "If the coding agent and Inspector are in different containers, replace localhost with a reachable host.",
8347
- "Check DNS, proxy, firewall, and container network routes."
8348
- ];
8349
- case "streaming-mismatch":
8350
- return [
8351
- "Compare the streaming and non-streaming captured logs side by side.",
8352
- "Check whether the provider's OpenAI-compatible endpoint handles stream=false differently."
8353
- ];
8354
- case "context-pressure":
8355
- return [
8356
- "Reduce prompt/tool schema/history size or configure the model context window metadata."
8357
- ];
8358
- case "runtime-task-error":
8359
- return ["Inspect the session runtime error and retry after clearing stuck session tasks."];
8360
- case "model-output-error":
8361
- return ["Inspect the raw response and schema parsing details for the cited log ids."];
8362
- case "unknown-failure":
8363
- return [
8364
- "Open the cited logs and inspect error/status/latency fields to refine the diagnosis."
8365
- ];
8366
- case "no-session-data":
8367
- return [
8368
- "Confirm the run's sessionId matches captured traffic and that history scanning is enabled when needed."
8369
- ];
8708
+ const FAILURE_CATEGORY_INFO = {
8709
+ none: {
8710
+ summary: "Session completed without captured errors.",
8711
+ hints: ["Keep the evidence pack as the success proof for CI or release notes."],
8712
+ severity: "none",
8713
+ outcome: "success"
8714
+ },
8715
+ active: {
8716
+ summary: "Session still has active requests or running tasks.",
8717
+ hints: ["Wait for the session to settle, then export evidence again."],
8718
+ severity: "low",
8719
+ outcome: "active"
8720
+ },
8721
+ "provider-timeout": {
8722
+ summary: "Provider call appears to have timed out.",
8723
+ hints: [
8724
+ "Increase the provider test timeout for slow models.",
8725
+ "Compare streaming and non-streaming probes for the same model."
8726
+ ],
8727
+ severity: "high",
8728
+ outcome: "failure"
8729
+ },
8730
+ "provider-http-error": {
8731
+ summary: "Provider returned an HTTP error.",
8732
+ hints: ["Open the cited log ids and inspect upstream status, response body, and provider URL."],
8733
+ severity: "high",
8734
+ outcome: "failure"
8735
+ },
8736
+ "provider-auth-error": {
8737
+ summary: "Provider authentication or API key configuration appears invalid.",
8738
+ hints: ["Verify the provider API key, auth header mode, and configured base URL."],
8739
+ severity: "high",
8740
+ outcome: "failure"
8741
+ },
8742
+ "provider-rate-limit": {
8743
+ summary: "Provider rejected the request because of rate limit or quota pressure.",
8744
+ hints: ["Retry after provider quota recovers or switch to a less constrained provider/model."],
8745
+ severity: "high",
8746
+ outcome: "failure"
8747
+ },
8748
+ "network-or-container": {
8749
+ summary: "Network connectivity looks suspicious; check container boundaries and localhost use.",
8750
+ hints: [
8751
+ "If the coding agent and Inspector are in different containers, replace localhost with a reachable host.",
8752
+ "Check DNS, proxy, firewall, and container network routes."
8753
+ ],
8754
+ severity: "high",
8755
+ outcome: "failure"
8756
+ },
8757
+ "streaming-mismatch": {
8758
+ summary: "Streaming and non-streaming behavior differ for the same provider/model.",
8759
+ hints: [
8760
+ "Compare the streaming and non-streaming captured logs side by side.",
8761
+ "Check whether the provider's OpenAI-compatible endpoint handles stream=false differently."
8762
+ ],
8763
+ severity: "high",
8764
+ outcome: "failure"
8765
+ },
8766
+ "context-pressure": {
8767
+ summary: "The failure looks related to context window or token pressure.",
8768
+ hints: [
8769
+ "Reduce prompt/tool schema/history size or configure the model context window metadata."
8770
+ ],
8771
+ severity: "medium",
8772
+ outcome: "failure"
8773
+ },
8774
+ "runtime-task-error": {
8775
+ summary: "Inspector session runtime reported a task error.",
8776
+ hints: ["Inspect the session runtime error and retry after clearing stuck session tasks."],
8777
+ severity: "high",
8778
+ outcome: "failure"
8779
+ },
8780
+ "model-output-error": {
8781
+ summary: "The model output or upstream payload shape appears invalid.",
8782
+ hints: ["Inspect the raw response and schema parsing details for the cited log ids."],
8783
+ severity: "medium",
8784
+ outcome: "failure"
8785
+ },
8786
+ "unknown-failure": {
8787
+ summary: "Inspector captured errors, but the root cause is not yet classified.",
8788
+ hints: ["Open the cited logs and inspect error/status/latency fields to refine the diagnosis."],
8789
+ severity: "medium",
8790
+ outcome: "failure"
8791
+ },
8792
+ "no-session-data": {
8793
+ summary: "No Inspector session data was available for this run.",
8794
+ hints: [
8795
+ "Confirm the run's sessionId matches captured traffic and that history scanning is enabled when needed."
8796
+ ],
8797
+ severity: "medium",
8798
+ outcome: "unknown"
8370
8799
  }
8371
- }
8800
+ };
8372
8801
  function classifyCategory(session, candidates2) {
8373
8802
  if (session.status === "active") return "active";
8374
8803
  if (session.errorCount === 0 && session.failedRequests === 0 && session.lastTaskError === null) {
@@ -8407,46 +8836,16 @@ function classifyCategory(session, candidates2) {
8407
8836
  return "unknown-failure";
8408
8837
  }
8409
8838
  function severityFor(category) {
8410
- switch (category) {
8411
- case "none":
8412
- return "none";
8413
- case "active":
8414
- return "low";
8415
- case "provider-timeout":
8416
- case "provider-http-error":
8417
- case "provider-auth-error":
8418
- case "provider-rate-limit":
8419
- case "network-or-container":
8420
- case "streaming-mismatch":
8421
- case "runtime-task-error":
8422
- return "high";
8423
- case "context-pressure":
8424
- case "model-output-error":
8425
- case "unknown-failure":
8426
- case "no-session-data":
8427
- return "medium";
8428
- }
8839
+ return FAILURE_CATEGORY_INFO[category].severity;
8429
8840
  }
8430
8841
  function outcomeFor$1(category) {
8431
- switch (category) {
8432
- case "none":
8433
- return "success";
8434
- case "active":
8435
- return "active";
8436
- case "no-session-data":
8437
- return "unknown";
8438
- case "provider-timeout":
8439
- case "provider-http-error":
8440
- case "provider-auth-error":
8441
- case "provider-rate-limit":
8442
- case "network-or-container":
8443
- case "streaming-mismatch":
8444
- case "context-pressure":
8445
- case "runtime-task-error":
8446
- case "model-output-error":
8447
- case "unknown-failure":
8448
- return "failure";
8449
- }
8842
+ return FAILURE_CATEGORY_INFO[category].outcome;
8843
+ }
8844
+ function categorySummary(category) {
8845
+ return FAILURE_CATEGORY_INFO[category].summary;
8846
+ }
8847
+ function categoryHints(category) {
8848
+ return FAILURE_CATEGORY_INFO[category].hints;
8450
8849
  }
8451
8850
  function classifyRunFailure(run, session) {
8452
8851
  if (session === null) {
@@ -8827,15 +9226,8 @@ function nullableText(value) {
8827
9226
  return trimmed.length > 0 ? trimmed : null;
8828
9227
  }
8829
9228
  function readGroups() {
8830
- const filePath = groupsFilePath();
8831
- if (!existsSync(filePath)) return [];
8832
- try {
8833
- const parsed = JSON.parse(readFileSync(filePath, "utf8"));
8834
- const result = GroupsFileSchema.safeParse(parsed);
8835
- return result.success ? result.data.groups : [];
8836
- } catch {
8837
- return [];
8838
- }
9229
+ const result = readJsonFile(groupsFilePath(), GroupsFileSchema);
9230
+ return result.ok ? result.data.groups : [];
8839
9231
  }
8840
9232
  function writeGroups(groups) {
8841
9233
  const filePath = groupsFilePath();
@@ -9087,15 +9479,8 @@ function syncRunGroupMember(run, createIfMissing) {
9087
9479
  });
9088
9480
  }
9089
9481
  function readRuns() {
9090
- const filePath = runsFilePath();
9091
- if (!existsSync(filePath)) return [];
9092
- try {
9093
- const parsed = JSON.parse(readFileSync(filePath, "utf8"));
9094
- const result = RunsFileSchema.safeParse(parsed);
9095
- return result.success ? result.data.runs : [];
9096
- } catch {
9097
- return [];
9098
- }
9482
+ const result = readJsonFile(runsFilePath(), RunsFileSchema);
9483
+ return result.ok ? result.data.runs : [];
9099
9484
  }
9100
9485
  function writeRuns(runs) {
9101
9486
  const filePath = runsFilePath();
@@ -9363,14 +9748,9 @@ function clampRecentFailuresLimit(value) {
9363
9748
  }
9364
9749
  function readEvidenceDocument(run) {
9365
9750
  const path2 = run.evidence?.jsonPath;
9366
- if (path2 === void 0 || !existsSync(path2)) return null;
9367
- try {
9368
- const parsed = JSON.parse(readFileSync(path2, "utf8"));
9369
- const result = EvidenceExportResultSchema.safeParse(parsed);
9370
- return result.success ? result.data : null;
9371
- } catch {
9372
- return null;
9373
- }
9751
+ if (path2 === void 0) return null;
9752
+ const result = readJsonFile(path2, EvidenceExportResultSchema);
9753
+ return result.ok ? result.data : null;
9374
9754
  }
9375
9755
  function syntheticFailedClassification(run) {
9376
9756
  return {
@@ -9506,6 +9886,7 @@ const Route$H = createFileRoute("/api/runs")({
9506
9886
  }
9507
9887
  }
9508
9888
  });
9889
+ const ALLOW_LOOPBACK$1 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
9509
9890
  const ProviderInputSchema = object({
9510
9891
  name: string().min(1, "Name is required"),
9511
9892
  apiKey: string().min(1, "API key is required"),
@@ -9524,17 +9905,25 @@ const ProviderInputSchema = object({
9524
9905
  const Route$G = createFileRoute("/api/providers")({
9525
9906
  server: {
9526
9907
  handlers: {
9527
- GET: ({ request }) => {
9528
- const url = new URL(request.url);
9529
- const includeSecrets = url.searchParams.get("includeSecrets") === "1";
9530
- const providers = getProviders();
9531
- return Response.json(includeSecrets ? providers : redactProviders(providers));
9908
+ GET: () => {
9909
+ return Response.json(redactProviders(getProviders()));
9532
9910
  },
9533
9911
  POST: async ({ request }) => {
9534
9912
  const parsed = ProviderInputSchema.safeParse(await request.json());
9535
9913
  if (!parsed.success) {
9536
9914
  return Response.json({ error: parsed.error.message }, { status: 400 });
9537
9915
  }
9916
+ for (const candidate of [
9917
+ parsed.data.anthropicBaseUrl,
9918
+ parsed.data.openaiBaseUrl,
9919
+ parsed.data.openaiResponsesBaseUrl
9920
+ ]) {
9921
+ if (candidate === void 0 || candidate === "") continue;
9922
+ const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK$1 });
9923
+ if (!decision.allow) {
9924
+ return Response.json({ error: decision.reason }, { status: 400 });
9925
+ }
9926
+ }
9538
9927
  const newProvider = addProvider(
9539
9928
  parsed.data.name,
9540
9929
  parsed.data.apiKey,
@@ -9551,7 +9940,7 @@ const Route$G = createFileRoute("/api/providers")({
9551
9940
  parsed.data.modelMetadata,
9552
9941
  parsed.data.openaiResponsesBaseUrl
9553
9942
  );
9554
- return Response.json(newProvider, { status: 201 });
9943
+ return Response.json(redactProvider(newProvider), { status: 201 });
9555
9944
  }
9556
9945
  }
9557
9946
  }
@@ -9571,9 +9960,6 @@ const PiAgentResponseSchema = object({
9571
9960
  protocol: _enum(["openaiResponses", "openaiChat", "anthropic"]),
9572
9961
  logCount: number().int().nonnegative()
9573
9962
  });
9574
- function isRecord$1(value) {
9575
- return typeof value === "object" && value !== null && !Array.isArray(value);
9576
- }
9577
9963
  function normalizeAnthropicStopReason(value) {
9578
9964
  switch (value) {
9579
9965
  case "end_turn":
@@ -9612,20 +9998,20 @@ function extractStopReason(log) {
9612
9998
  if (typeof json === "string") {
9613
9999
  json = JSON.parse(json);
9614
10000
  }
9615
- if (!isRecord$1(json)) return null;
10001
+ if (!isRecord(json)) return null;
9616
10002
  if (typeof json.stop_reason === "string") {
9617
10003
  return normalizeAnthropicStopReason(json.stop_reason);
9618
10004
  }
9619
10005
  if (Array.isArray(json.choices)) {
9620
10006
  for (const choice of json.choices) {
9621
- if (!isRecord$1(choice) || typeof choice.finish_reason !== "string") continue;
10007
+ if (!isRecord(choice) || typeof choice.finish_reason !== "string") continue;
9622
10008
  const reason = normalizeOpenAIFinishReason(choice.finish_reason);
9623
10009
  if (reason !== null) return reason;
9624
10010
  }
9625
10011
  }
9626
10012
  if (Array.isArray(json.output)) {
9627
10013
  for (const item of json.output) {
9628
- if (isRecord$1(item) && item.type === "function_call") {
10014
+ if (isRecord(item) && item.type === "function_call") {
9629
10015
  return "tool_use";
9630
10016
  }
9631
10017
  }
@@ -9633,7 +10019,7 @@ function extractStopReason(log) {
9633
10019
  return "stop";
9634
10020
  }
9635
10021
  }
9636
- if (json.status === "incomplete" && isRecord$1(json.incomplete_details)) {
10022
+ if (json.status === "incomplete" && isRecord(json.incomplete_details)) {
9637
10023
  const reason = json.incomplete_details.reason;
9638
10024
  if (typeof reason === "string" && isResponsesLengthLimitReason(reason)) {
9639
10025
  return "length";
@@ -9980,7 +10366,7 @@ const Route$E = createFileRoute("/api/models")({
9980
10366
  }
9981
10367
  });
9982
10368
  const name = "@tonyclaw/agent-inspector";
9983
- const version = "3.0.26";
10369
+ const version = "3.0.28";
9984
10370
  const packageJson = {
9985
10371
  name,
9986
10372
  version
@@ -10062,8 +10448,7 @@ function isMcpWriteEnabled() {
10062
10448
  const readonlyFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_READONLY"]);
10063
10449
  if (readonlyFlag === true) return false;
10064
10450
  const writesFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_WRITES"]);
10065
- if (writesFlag === false) return false;
10066
- return true;
10451
+ return writesFlag === true;
10067
10452
  }
10068
10453
  function getMcpModeInfo() {
10069
10454
  const writesEnabled = isMcpWriteEnabled();
@@ -10074,7 +10459,7 @@ function getMcpModeInfo() {
10074
10459
  AGENT_INSPECTOR_MCP_READONLY: process.env["AGENT_INSPECTOR_MCP_READONLY"] ?? null,
10075
10460
  AGENT_INSPECTOR_MCP_WRITES: process.env["AGENT_INSPECTOR_MCP_WRITES"] ?? null
10076
10461
  },
10077
- note: writesEnabled ? "MCP write/action tools are enabled. Set AGENT_INSPECTOR_MCP_READONLY=1 to expose the same catalog while blocking mutations." : "MCP is in readonly mode. Read tools/resources still work, while write/action tools return an isError result."
10462
+ note: writesEnabled ? "MCP write/action tools are enabled explicitly by AGENT_INSPECTOR_MCP_WRITES=1." : "MCP is readonly by default. Read tools/resources still work; set AGENT_INSPECTOR_MCP_WRITES=1 to enable mutations."
10078
10463
  };
10079
10464
  }
10080
10465
  function groupEvidenceDir(groupId) {
@@ -10356,14 +10741,9 @@ function readGroupEvidenceMarkdown(group) {
10356
10741
  }
10357
10742
  function readGroupEvidenceDocument(group) {
10358
10743
  const path2 = group.evidence?.jsonPath;
10359
- if (path2 === void 0 || !existsSync(path2)) return null;
10360
- try {
10361
- const raw = JSON.parse(readFileSync(path2, "utf8"));
10362
- const parsed = GroupEvidenceExportResultSchema.safeParse(raw);
10363
- return parsed.success ? parsed.data : null;
10364
- } catch {
10365
- return null;
10366
- }
10744
+ if (path2 === void 0) return null;
10745
+ const result = readJsonFile(path2, GroupEvidenceExportResultSchema);
10746
+ return result.ok ? result.data : null;
10367
10747
  }
10368
10748
  function listInspectorGroups() {
10369
10749
  return { groups: listGroups() };
@@ -10646,10 +11026,6 @@ function toolError(message) {
10646
11026
  function inspectorBaseUrl() {
10647
11027
  return `http://127.0.0.1:${String(getCurrentPort())}`;
10648
11028
  }
10649
- function textByteLength(value) {
10650
- if (value === null) return null;
10651
- return Buffer.byteLength(value, "utf8");
10652
- }
10653
11029
  function hasBodyText(value, byteLength) {
10654
11030
  if (value !== null && value.length > 0) return true;
10655
11031
  return byteLength !== void 0 && byteLength !== null && byteLength > 0;
@@ -10780,14 +11156,29 @@ function processSummary(clientPid, logs, total, limit) {
10780
11156
  }
10781
11157
  async function readCompactLogsPage(callApi2, params) {
10782
11158
  const path2 = `/api/logs?${params.toString()}`;
10783
- const res = await callApi2(path2);
10784
- if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
10785
- const rawBody = await res.json();
10786
- const parsed = LogsListResponseSchema.safeParse(rawBody);
10787
- if (!parsed.success) return toolError("GET /api/logs returned an unparseable logs shape");
11159
+ const parsed = await fetchApiJson(callApi2, path2, LogsListResponseSchema);
11160
+ if (!parsed.ok) return parsed.error;
10788
11161
  const logs = parsed.data.logs ?? [];
10789
11162
  return { logs, total: parsed.data.total ?? logs.length };
10790
11163
  }
11164
+ async function fetchApiJson(callApi2, path2, schema, init) {
11165
+ const res = await callApi2(path2, init);
11166
+ if (!res.ok) {
11167
+ return { ok: false, error: toolError(`${methodFor(init)} ${path2} returned ${res.status}`) };
11168
+ }
11169
+ const rawBody = await res.json();
11170
+ const parsed = schema.safeParse(rawBody);
11171
+ if (!parsed.success) {
11172
+ return {
11173
+ ok: false,
11174
+ error: toolError(`${methodFor(init)} ${path2} returned an unparseable shape`)
11175
+ };
11176
+ }
11177
+ return { ok: true, data: parsed.data };
11178
+ }
11179
+ function methodFor(init) {
11180
+ return (init?.method ?? "GET").toUpperCase();
11181
+ }
10791
11182
  function isToolResult(value) {
10792
11183
  return typeof value === "object" && value !== null && Object.getOwnPropertyDescriptor(value, "content") !== void 0;
10793
11184
  }
@@ -10847,13 +11238,8 @@ async function searchLogsImpl(callApi2, args) {
10847
11238
  });
10848
11239
  }
10849
11240
  async function getLogImpl(callApi2, id) {
10850
- const res = await callApi2(`/api/logs/${id}`);
10851
- if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
10852
- const rawBody = await res.json();
10853
- const parsed = CapturedLogSchema.safeParse(rawBody);
10854
- if (!parsed.success) {
10855
- return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
10856
- }
11241
+ const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
11242
+ if (!parsed.ok) return parsed.error;
10857
11243
  return textJson(buildBoundedLogDetail(parsed.data));
10858
11244
  }
10859
11245
  async function getLogChunksImpl(callApi2, id) {
@@ -10875,11 +11261,8 @@ async function getSessionImpl(callApi2, args) {
10875
11261
  params.set("includeHistory", "1");
10876
11262
  }
10877
11263
  const path2 = `/api/sessions?${params.toString()}`;
10878
- const res = await callApi2(path2);
10879
- if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
10880
- const rawBody = await res.json();
10881
- const parsed = SessionInfoSchema.safeParse(rawBody);
10882
- if (!parsed.success) return toolError("GET /api/sessions returned an unparseable session info");
11264
+ const parsed = await fetchApiJson(callApi2, path2, SessionInfoSchema);
11265
+ if (!parsed.ok) return parsed.error;
10883
11266
  return textJson(parsed.data);
10884
11267
  }
10885
11268
  function createRunImpl(args) {
@@ -11089,13 +11472,8 @@ async function getProcessTimelineImpl(callApi2, args) {
11089
11472
  });
11090
11473
  }
11091
11474
  async function getLogHeadersImpl(callApi2, id) {
11092
- const res = await callApi2(`/api/logs/${id}`);
11093
- if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
11094
- const rawBody = await res.json();
11095
- const parsed = CapturedLogSchema.safeParse(rawBody);
11096
- if (!parsed.success) {
11097
- return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
11098
- }
11475
+ const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
11476
+ if (!parsed.ok) return parsed.error;
11099
11477
  const log = parsed.data;
11100
11478
  return textJson({
11101
11479
  id: log.id,
@@ -11346,18 +11724,13 @@ async function listModelsImpl(callApi2) {
11346
11724
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
11347
11725
  return textJson(await res.json());
11348
11726
  }
11349
- function includeSecretsQuery(includeSecrets) {
11350
- return includeSecrets === true ? "?includeSecrets=1" : "";
11351
- }
11352
- async function listProvidersImpl(callApi2, args = {}) {
11353
- const res = await callApi2(`/api/providers${includeSecretsQuery(args.includeSecrets)}`);
11727
+ async function listProvidersImpl(callApi2) {
11728
+ const res = await callApi2("/api/providers");
11354
11729
  if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
11355
11730
  return textJson(await res.json());
11356
11731
  }
11357
- async function getProviderImpl(callApi2, id, args = {}) {
11358
- const res = await callApi2(
11359
- `/api/providers/${encodeURIComponent(id)}${includeSecretsQuery(args.includeSecrets)}`
11360
- );
11732
+ async function getProviderImpl(callApi2, id) {
11733
+ const res = await callApi2(`/api/providers/${encodeURIComponent(id)}`);
11361
11734
  if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
11362
11735
  return textJson(await res.json());
11363
11736
  }
@@ -11417,9 +11790,6 @@ async function testProviderImpl(callApi2, id) {
11417
11790
  if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
11418
11791
  return textJson(await res.json());
11419
11792
  }
11420
- function isRecord(value) {
11421
- return typeof value === "object" && value !== null && !Array.isArray(value);
11422
- }
11423
11793
  function stringField(record2, key) {
11424
11794
  const value = record2[key];
11425
11795
  return typeof value === "string" && value.length > 0 ? value : null;
@@ -12442,24 +12812,21 @@ function registerTools(server) {
12442
12812
  "inspector_list_providers",
12443
12813
  {
12444
12814
  title: "List configured LLM providers",
12445
- 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.",
12446
- inputSchema: object({
12447
- includeSecrets: boolean().optional().describe("Return plaintext provider API keys. Exposes local secrets when true.")
12448
- })
12815
+ description: "Returns the configured ProviderConfig array with write-only API keys represented as configured.",
12816
+ inputSchema: object({})
12449
12817
  },
12450
- (args) => safeCall(() => listProvidersImpl(callApi, args))
12818
+ () => safeCall(() => listProvidersImpl(callApi))
12451
12819
  );
12452
12820
  server.registerTool(
12453
12821
  "inspector_get_provider",
12454
12822
  {
12455
12823
  title: "Get a single provider by id",
12456
- description: "Returns one ProviderConfig with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs the plaintext local provider key.",
12824
+ description: "Returns one ProviderConfig. API keys are write-only and represented as configured.",
12457
12825
  inputSchema: object({
12458
- id: string().describe("The provider id."),
12459
- includeSecrets: boolean().optional().describe("Return the plaintext provider API key. Exposes a local secret when true.")
12826
+ id: string().describe("The provider id.")
12460
12827
  })
12461
12828
  },
12462
- ({ id, includeSecrets }) => safeCall(() => getProviderImpl(callApi, id, { includeSecrets }))
12829
+ ({ id }) => safeCall(() => getProviderImpl(callApi, id))
12463
12830
  );
12464
12831
  server.registerTool(
12465
12832
  "inspector_replay_log",
@@ -14601,6 +14968,7 @@ const Route$u = createFileRoute("/api/providers/export")({
14601
14968
  }
14602
14969
  }
14603
14970
  });
14971
+ const ALLOW_LOOPBACK = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
14604
14972
  const ProviderUpdateSchema = object({
14605
14973
  name: string().min(1, "Name is required").optional(),
14606
14974
  apiKey: string().min(1, "API key is required").optional(),
@@ -14620,26 +14988,36 @@ const ProviderUpdateSchema = object({
14620
14988
  const Route$t = createFileRoute("/api/providers/$providerId")({
14621
14989
  server: {
14622
14990
  handlers: {
14623
- GET: ({ params, request }) => {
14624
- const url = new URL(request.url);
14625
- const includeSecrets = url.searchParams.get("includeSecrets") === "1";
14991
+ GET: ({ params }) => {
14626
14992
  const providers = getProviders();
14627
14993
  const provider = providers.find((p) => p.id === params.providerId);
14628
14994
  if (!provider) {
14629
14995
  return Response.json({ error: "Provider not found" }, { status: 404 });
14630
14996
  }
14631
- return Response.json(includeSecrets ? provider : redactProvider(provider));
14997
+ return Response.json(redactProvider(provider));
14632
14998
  },
14633
14999
  PUT: async ({ params, request }) => {
14634
15000
  const parsed = ProviderUpdateSchema.safeParse(await request.json());
14635
15001
  if (!parsed.success) {
14636
15002
  return Response.json({ error: parsed.error.message }, { status: 400 });
14637
15003
  }
15004
+ for (const candidate of [
15005
+ parsed.data.baseUrl,
15006
+ parsed.data.anthropicBaseUrl,
15007
+ parsed.data.openaiBaseUrl,
15008
+ parsed.data.openaiResponsesBaseUrl
15009
+ ]) {
15010
+ if (candidate === void 0 || candidate === "") continue;
15011
+ const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK });
15012
+ if (!decision.allow) {
15013
+ return Response.json({ error: decision.reason }, { status: 400 });
15014
+ }
15015
+ }
14638
15016
  const updated = updateProvider(params.providerId, parsed.data);
14639
15017
  if (!updated) {
14640
15018
  return Response.json({ error: "Provider not found" }, { status: 404 });
14641
15019
  }
14642
- return Response.json(updated);
15020
+ return Response.json(redactProvider(updated));
14643
15021
  },
14644
15022
  DELETE: ({ params }) => {
14645
15023
  const deleted = deleteProvider(params.providerId);
@@ -14725,6 +15103,8 @@ const Route$s = createFileRoute("/api/logs/stream")({
14725
15103
  }
14726
15104
  }
14727
15105
  });
15106
+ const MAX_ZIP_COMPRESSED_BYTES = 200 * 1024 * 1024;
15107
+ const MAX_ZIP_DECOMPRESSED_BYTES = 500 * 1024 * 1024;
14728
15108
  const ManifestSchema = object({
14729
15109
  version: string().optional(),
14730
15110
  exportedAt: string().optional(),
@@ -14925,12 +15305,15 @@ function legacyDraftFromParts(input) {
14925
15305
  error: null
14926
15306
  });
14927
15307
  }
14928
- async function readZipText(zip, path2) {
15308
+ async function readZipText(zip, path2, remaining) {
14929
15309
  const file = zip.file(path2);
14930
- return file === null ? null : await file.async("text");
15310
+ if (file === null) return null;
15311
+ const text = await file.async("text");
15312
+ remaining.bytes -= Buffer.byteLength(text, "utf8");
15313
+ return text;
14931
15314
  }
14932
- async function readManifest(zip) {
14933
- const text = await readZipText(zip, "manifest.json");
15315
+ async function readManifest(zip, remaining) {
15316
+ const text = await readZipText(zip, "manifest.json", remaining);
14934
15317
  const parsed = ManifestSchema.safeParse(parseJson(text));
14935
15318
  return parsed.success ? parsed.data : null;
14936
15319
  }
@@ -14976,10 +15359,19 @@ function validateManifestVersion(manifest) {
14976
15359
  }
14977
15360
  return { ok: true, warning: null };
14978
15361
  }
15362
+ function bombError(warnings = []) {
15363
+ return {
15364
+ ok: false,
15365
+ message: `Archive decompresses to more than ${MAX_ZIP_DECOMPRESSED_BYTES} bytes.`,
15366
+ warnings
15367
+ };
15368
+ }
14979
15369
  async function parseZipLogImport(data) {
14980
15370
  const warnings = [];
14981
15371
  const zip = await JSZip.loadAsync(data);
14982
- const manifest = await readManifest(zip);
15372
+ const remaining = { bytes: MAX_ZIP_DECOMPRESSED_BYTES };
15373
+ const manifest = await readManifest(zip, remaining);
15374
+ if (remaining.bytes < 0) return bombError();
14983
15375
  const versionCheck = validateManifestVersion(manifest);
14984
15376
  if (!versionCheck.ok) {
14985
15377
  return {
@@ -14995,15 +15387,21 @@ async function parseZipLogImport(data) {
14995
15387
  if (manifest?.redacted === true) {
14996
15388
  warnings.push("Imported redacted export. Secrets and some replay details may be unavailable.");
14997
15389
  }
14998
- const metadataText2 = await readZipText(zip, "logs.json");
15390
+ const metadataText2 = await readZipText(zip, "logs.json", remaining);
15391
+ if (remaining.bytes < 0) return bombError(warnings);
14999
15392
  const metadataLogs = metadataText2 === null ? null : parseJsonLogs(metadataText2);
15000
15393
  const logs = [];
15001
15394
  let skipped = 0;
15002
15395
  if (metadataLogs !== null) {
15003
15396
  for (const log of metadataLogs) {
15004
- const requestText = await readZipText(zip, `#${String(log.id)}.Request.json`);
15005
- const responseText = await readZipText(zip, `#${String(log.id)}.Response.json`);
15006
- const streamingText = await readZipText(zip, `#${String(log.id)}.SSE.Response.json`);
15397
+ const requestText = await readZipText(zip, `#${String(log.id)}.Request.json`, remaining);
15398
+ const responseText = await readZipText(zip, `#${String(log.id)}.Response.json`, remaining);
15399
+ const streamingText = await readZipText(
15400
+ zip,
15401
+ `#${String(log.id)}.SSE.Response.json`,
15402
+ remaining
15403
+ );
15404
+ if (remaining.bytes < 0) return bombError(warnings);
15007
15405
  logs.push(
15008
15406
  draftFromCapturedLog(
15009
15407
  log,
@@ -15016,9 +15414,14 @@ async function parseZipLogImport(data) {
15016
15414
  return { ok: true, logs, skipped, warnings };
15017
15415
  }
15018
15416
  for (const originalId of manifest?.logIds ?? []) {
15019
- const requestText = await readZipText(zip, `#${String(originalId)}.Request.json`);
15020
- const responseText = await readZipText(zip, `#${String(originalId)}.Response.json`);
15021
- const streamingText = await readZipText(zip, `#${String(originalId)}.SSE.Response.json`);
15417
+ const requestText = await readZipText(zip, `#${String(originalId)}.Request.json`, remaining);
15418
+ const responseText = await readZipText(zip, `#${String(originalId)}.Response.json`, remaining);
15419
+ const streamingText = await readZipText(
15420
+ zip,
15421
+ `#${String(originalId)}.SSE.Response.json`,
15422
+ remaining
15423
+ );
15424
+ if (remaining.bytes < 0) return bombError(warnings);
15022
15425
  if (requestText === null && responseText === null && streamingText === null) {
15023
15426
  skipped += 1;
15024
15427
  continue;
@@ -15073,8 +15476,22 @@ function parseTextLogImport(text) {
15073
15476
  async function parseImportedLogFile(input) {
15074
15477
  try {
15075
15478
  if (looksLikeZip(input.data, input.fileName)) {
15479
+ if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
15480
+ return {
15481
+ ok: false,
15482
+ message: `Archive exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
15483
+ warnings: []
15484
+ };
15485
+ }
15076
15486
  return await parseZipLogImport(input.data);
15077
15487
  }
15488
+ if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
15489
+ return {
15490
+ ok: false,
15491
+ message: `Import file exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
15492
+ warnings: []
15493
+ };
15494
+ }
15078
15495
  return parseTextLogImport(new TextDecoder("utf-8").decode(input.data));
15079
15496
  } catch (err) {
15080
15497
  return {
@@ -15352,19 +15769,25 @@ async function searchOpenClawGateway(query, project) {
15352
15769
  warning: "OpenClaw Gateway is not configured. Set OPENCLAW_GATEWAY_URL."
15353
15770
  };
15354
15771
  }
15355
- const response = await fetch(buildToolInvokeUrl(gatewayUrl), {
15356
- method: "POST",
15357
- headers: gatewayHeaders(),
15358
- body: JSON.stringify({
15359
- tool: "memory_search",
15360
- args: {
15361
- query: buildSearchQuery(query, project),
15362
- maxResults: 10,
15363
- corpus: "all"
15364
- },
15365
- sessionKey: getOpenClawSessionKey()
15366
- })
15367
- });
15772
+ const fetchResult = await safeFetch(
15773
+ buildToolInvokeUrl(gatewayUrl),
15774
+ {
15775
+ method: "POST",
15776
+ headers: gatewayHeaders(),
15777
+ body: JSON.stringify({
15778
+ tool: "memory_search",
15779
+ args: {
15780
+ query: buildSearchQuery(query, project),
15781
+ maxResults: 10,
15782
+ corpus: "all"
15783
+ },
15784
+ sessionKey: getOpenClawSessionKey()
15785
+ })
15786
+ },
15787
+ { bypass: true }
15788
+ );
15789
+ if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
15790
+ const response = fetchResult.response;
15368
15791
  const raw = await response.json().catch(() => null);
15369
15792
  if (!response.ok) {
15370
15793
  return {
@@ -15619,11 +16042,17 @@ async function promoteToOpenClaw(candidate) {
15619
16042
  error: "OpenClaw memory backend is not configured. Set OPENCLAW_WORKSPACE_DIR for file-backed memory or OPENCLAW_MEMORY_URL for a legacy HTTP backend."
15620
16043
  };
15621
16044
  }
15622
- const response = await fetch(buildMemoryUrl(endpoint), {
15623
- method: "POST",
15624
- headers: authHeaders(),
15625
- body: JSON.stringify(buildOpenClawPayload(candidate))
15626
- });
16045
+ const fetchResult = await safeFetch(
16046
+ buildMemoryUrl(endpoint),
16047
+ {
16048
+ method: "POST",
16049
+ headers: authHeaders(),
16050
+ body: JSON.stringify(buildOpenClawPayload(candidate))
16051
+ },
16052
+ { bypass: true }
16053
+ );
16054
+ if (!fetchResult.ok) return { success: false, error: fetchResult.reason };
16055
+ const response = fetchResult.response;
15627
16056
  if (!response.ok) {
15628
16057
  return { success: false, error: `OpenClaw memory write failed with ${response.status}` };
15629
16058
  }
@@ -15646,10 +16075,16 @@ async function searchOpenClaw(query, project) {
15646
16075
  warning: `OpenClaw search backend is not configured. Set OPENCLAW_GATEWAY_URL or OPENCLAW_MEMORY_URL.${workspaceHint}`
15647
16076
  };
15648
16077
  }
15649
- const response = await fetch(buildSearchUrl(endpoint, query, project), {
15650
- method: "GET",
15651
- headers: authHeaders()
15652
- });
16078
+ const fetchResult = await safeFetch(
16079
+ buildSearchUrl(endpoint, query, project),
16080
+ {
16081
+ method: "GET",
16082
+ headers: authHeaders()
16083
+ },
16084
+ { bypass: true }
16085
+ );
16086
+ if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
16087
+ const response = fetchResult.response;
15653
16088
  if (!response.ok) {
15654
16089
  return { results: [], warning: `OpenClaw search failed with ${response.status}` };
15655
16090
  }
@@ -16770,12 +17205,20 @@ async function testEndpoint(baseUrl, provider, path2, model, protocol) {
16770
17205
  const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
16771
17206
  try {
16772
17207
  requestUrl = buildUpstreamUrl(baseUrl, path2);
16773
- const response = await fetch(requestUrl, {
16774
- method: "POST",
16775
- headers: requestHeaders,
16776
- body,
16777
- signal: controller.signal
16778
- });
17208
+ const fetchResult = await safeFetch(
17209
+ requestUrl,
17210
+ {
17211
+ method: "POST",
17212
+ headers: requestHeaders,
17213
+ body,
17214
+ signal: controller.signal
17215
+ },
17216
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17217
+ );
17218
+ if (!fetchResult.ok) {
17219
+ return createErrorResult(fetchResult.reason, Date.now() - startTime, false);
17220
+ }
17221
+ const response = fetchResult.response;
16779
17222
  const latencyMs = Date.now() - startTime;
16780
17223
  if (!response.ok) {
16781
17224
  const responseText2 = await readResponseText(response);
@@ -16894,12 +17337,20 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, protocol)
16894
17337
  const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
16895
17338
  try {
16896
17339
  requestUrl = buildUpstreamUrl(baseUrl, path2);
16897
- const response = await fetch(requestUrl, {
16898
- method: "POST",
16899
- headers: requestHeaders,
16900
- body,
16901
- signal: controller.signal
16902
- });
17340
+ const fetchResult = await safeFetch(
17341
+ requestUrl,
17342
+ {
17343
+ method: "POST",
17344
+ headers: requestHeaders,
17345
+ body,
17346
+ signal: controller.signal
17347
+ },
17348
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17349
+ );
17350
+ if (!fetchResult.ok) {
17351
+ return createErrorResult(fetchResult.reason, Date.now() - startTime, true);
17352
+ }
17353
+ const response = fetchResult.response;
16903
17354
  const latencyMs = Date.now() - startTime;
16904
17355
  if (!response.ok) {
16905
17356
  const responseText = await readResponseText(response);
@@ -17281,7 +17732,16 @@ const Route$f = createFileRoute("/api/providers/$providerId/model-metadata")({
17281
17732
  }
17282
17733
  let response;
17283
17734
  try {
17284
- response = await fetch(sourceUrl, { headers: { Accept: "application/json" } });
17735
+ const fetchResult = await safeFetch(sourceUrl, {
17736
+ headers: { Accept: "application/json" }
17737
+ });
17738
+ if (!fetchResult.ok) {
17739
+ return Response.json(
17740
+ { error: `Model metadata URL blocked: ${fetchResult.reason}` },
17741
+ { status: 400 }
17742
+ );
17743
+ }
17744
+ response = fetchResult.response;
17285
17745
  } catch (err) {
17286
17746
  return Response.json(
17287
17747
  { error: `Failed to fetch model metadata: ${String(err)}` },
@@ -17393,11 +17853,22 @@ const Route$e = createFileRoute("/api/logs/$id/replay")({
17393
17853
  const startTime = Date.now();
17394
17854
  let upstreamRes;
17395
17855
  try {
17396
- upstreamRes = await fetch(upstreamUrl, {
17397
- method: "POST",
17398
- headers,
17399
- body: modifiedBody
17400
- });
17856
+ const fetchResult = await safeFetch(
17857
+ upstreamUrl,
17858
+ {
17859
+ method: "POST",
17860
+ headers,
17861
+ body: modifiedBody
17862
+ },
17863
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17864
+ );
17865
+ if (!fetchResult.ok) {
17866
+ return Response.json({
17867
+ success: false,
17868
+ error: fetchResult.reason
17869
+ });
17870
+ }
17871
+ upstreamRes = fetchResult.response;
17401
17872
  } catch (err) {
17402
17873
  return Response.json({
17403
17874
  success: false,
@@ -18778,13 +19249,13 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
18778
19249
  export {
18779
19250
  PiAgentResponseSchema as $,
18780
19251
  AnthropicResponseSchema$1 as A,
18781
- GroupEvidenceExportResultSchema as B,
19252
+ DeleteInspectorGroupsResponseSchema as B,
18782
19253
  CapturedLogSchema as C,
18783
19254
  DEFAULT_CAPTURE_MODE as D,
18784
- DeleteInspectorGroupResponseSchema as E,
18785
- providerHasContextMetadata as F,
19255
+ GroupEvidenceExportResultSchema as E,
19256
+ DeleteInspectorGroupResponseSchema as F,
18786
19257
  GroupEvidenceReadResponseSchema as G,
18787
- maskApiKey as H,
19258
+ providerHasContextMetadata as H,
18788
19259
  InspectorGroupsListResponseSchema as I,
18789
19260
  findProviderModelMetadata as J,
18790
19261
  KnowledgeCandidateSchema as K,
@@ -18817,22 +19288,22 @@ export {
18817
19288
  persistThemeMode as f,
18818
19289
  readStoredUiScalePreference as g,
18819
19290
  persistUiScalePreference as h,
18820
- applyUiScalePreference as i,
18821
- normalizeUiScalePreference as j,
18822
- AnthropicRequestSchema as k,
18823
- parseOpenAIResponse as l,
18824
- parseOpenAIResponsesResponse as m,
19291
+ isRecord as i,
19292
+ applyUiScalePreference as j,
19293
+ normalizeUiScalePreference as k,
19294
+ AnthropicRequestSchema as l,
19295
+ parseOpenAIResponse as m,
18825
19296
  normalizeThemeMode as n,
18826
- OpenAIResponsesRequestSchema as o,
19297
+ parseOpenAIResponsesResponse as o,
18827
19298
  packageJson as p,
18828
- apiFormatForPath as q,
19299
+ OpenAIResponsesRequestSchema as q,
18829
19300
  readStoredThemeMode as r,
18830
19301
  safeGetOwnProperty as s,
18831
- getSessionPath as t,
18832
- stripClaudeCodeBillingHeader as u,
18833
- isTurnBoundary as v,
18834
- extractStopReason as w,
18835
- AlertSummarySchema as x,
18836
- AlertListResponseSchema as y,
18837
- DeleteInspectorGroupsResponseSchema as z
19302
+ apiFormatForPath as t,
19303
+ getSessionPath as u,
19304
+ stripClaudeCodeBillingHeader as v,
19305
+ isTurnBoundary as w,
19306
+ extractStopReason as x,
19307
+ AlertSummarySchema as y,
19308
+ AlertListResponseSchema as z
18838
19309
  };