@tonyclaw/agent-inspector 3.0.25 → 3.0.27
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.
- package/.output/cli.js +80 -11
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BnvOPwpe.js → CompareDrawer-BVCAAbbu.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-CYOXB81n.js +4234 -0
- package/.output/public/assets/{ReplayDialog-BJgoQWdW.js → ReplayDialog-DMX23O5G.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CpBZEHnM.js → RequestAnatomy-S17pYDRc.js} +1 -1
- package/.output/public/assets/{ResponseView-CWmyRb2M.js → ResponseView-Cf1XrawD.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BWhC8bwx.js → StreamingChunkSequence-BdOrVlCi.js} +1 -1
- package/.output/public/assets/{_sessionId-Cq-2OqUU.js → _sessionId-BnKGmJrR.js} +1 -1
- package/.output/public/assets/{index-Cf8O0Oos.js → index-9fMLCU4d.js} +1 -1
- package/.output/public/assets/{index-BKi9IRtV.js → index-B1atk19i.js} +3 -3
- package/.output/public/assets/index-CmeF5XXS.css +1 -0
- package/.output/public/assets/{index-DdgTJ4QJ.js → index-gY3I-7-D.js} +1 -1
- package/.output/public/assets/{json-viewer-Bqu_4hmm.js → json-viewer-CpyYpPjx.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +6 -6
- package/.output/server/_libs/three.mjs +26016 -0
- package/.output/server/{_sessionId-Ba7H3Gjw.mjs → _sessionId-eA_h2_s6.mjs} +9 -6
- package/.output/server/_ssr/{CompareDrawer-aH59Ab6x.mjs → CompareDrawer-K2k9G7r1.mjs} +10 -7
- package/.output/server/_ssr/{ProxyViewerContainer-By-I0tJv.mjs → ProxyViewerContainer-CbyBpCAj.mjs} +804 -363
- package/.output/server/_ssr/{ReplayDialog-BO4Vy0et.mjs → ReplayDialog-DacI2Iv0.mjs} +10 -7
- package/.output/server/_ssr/{RequestAnatomy-FtOr7vSS.mjs → RequestAnatomy-gh-pAdkO.mjs} +9 -6
- package/.output/server/_ssr/{ResponseView-cvw6ajue.mjs → ResponseView-BRJKN50-.mjs} +10 -7
- package/.output/server/_ssr/{StreamingChunkSequence-Oc8S2kD6.mjs → StreamingChunkSequence-BQLVEuWY.mjs} +9 -6
- package/.output/server/_ssr/{index-BDRlQheR.mjs → index-DuSWrjbH.mjs} +9 -6
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Cd6trhQr.mjs → json-viewer-DNs1ZjKI.mjs} +9 -6
- package/.output/server/_ssr/{router-bofP-Ekl.mjs → router-Dgx6-ot3.mjs} +713 -218
- package/.output/server/{_tanstack-start-manifest_v-BPuf86qB.mjs → _tanstack-start-manifest_v-CoGjnkQD.mjs} +1 -1
- package/.output/server/index.mjs +65 -65
- package/README.md +8 -0
- package/package.json +6 -5
- package/src/cli.ts +43 -9
- package/src/components/inspector-pet/InspectorPet.tsx +66 -51
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +455 -0
- package/src/components/inspector-pet/inspectorPetLogic.ts +28 -43
- package/src/components/providers/ProviderCard.tsx +3 -36
- package/src/components/providers/ProviderForm.tsx +251 -175
- package/src/components/providers/ProvidersPanel.tsx +18 -8
- package/src/components/providers/SettingsDialog.tsx +3 -3
- package/src/components/providers/providerFormCapabilities.ts +36 -0
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
- package/src/contracts/anthropic.ts +0 -5
- package/src/contracts/index.ts +0 -4
- package/src/contracts/log.ts +0 -1
- package/src/contracts/openai.ts +0 -3
- package/src/knowledge/openclawClient.ts +22 -9
- package/src/knowledge/openclawGatewayClient.ts +20 -13
- package/src/lib/alertContract.ts +0 -3
- package/src/lib/ecosystemContract.ts +0 -3
- package/src/lib/export-logs.ts +1 -4
- package/src/lib/safeFetch.ts +100 -0
- package/src/lib/ssrfGuard.ts +133 -0
- package/src/lib/stopReason.ts +1 -4
- package/src/lib/unknown.ts +27 -0
- package/src/lib/useProviders.ts +1 -1
- package/src/mcp/mode.ts +3 -9
- package/src/mcp/server.ts +5 -14
- package/src/mcp/toolHandlers.ts +45 -54
- package/src/proxy/constants.ts +17 -0
- package/src/proxy/formats/anthropic/handler.ts +5 -1
- package/src/proxy/formats/anthropic/schemas.ts +1 -7
- package/src/proxy/formats/index.ts +1 -1
- package/src/proxy/formats/openai/schemas.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -3
- package/src/proxy/handler.ts +91 -12
- package/src/proxy/identityProxy.ts +74 -1
- package/src/proxy/logImporter.ts +82 -12
- package/src/proxy/logIndex.ts +1 -4
- package/src/proxy/providerSecretStore.ts +181 -0
- package/src/proxy/providers.ts +84 -11
- package/src/proxy/schemas.ts +1 -8
- package/src/proxy/sqliteLogIndex.ts +0 -16
- package/src/proxy/store.ts +1 -5
- package/src/proxy/toolSchemaWarnings.ts +1 -9
- package/src/routes/api/logs.$id.replay.ts +17 -5
- package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
- package/src/routes/api/providers.$providerId.ts +18 -5
- package/src/routes/api/providers.ts +18 -7
- package/styles/globals.css +0 -61
- package/.output/public/assets/ProxyViewerContainer-DcqFqo1m.js +0 -127
- package/.output/public/assets/index-BMYuDL5X.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-
|
|
69
|
+
const appCss = "/assets/index-CmeF5XXS.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-
|
|
375
|
+
const $$splitComponentImporter$1 = () => import("./index-DuSWrjbH.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-
|
|
419
|
+
const $$splitComponentImporter = () => import("../_sessionId-eA_h2_s6.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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
1755
|
-
responseTextBytes: log.responseTextBytes ?? textByteLength
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
3126
|
-
responseTextBytes: log.responseTextBytes ?? textByteLength
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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,146 @@ function withBuiltinProviderModelMetadata(provider, updatedAt) {
|
|
|
5416
5432
|
modelMetadata: result.modelMetadata
|
|
5417
5433
|
};
|
|
5418
5434
|
}
|
|
5435
|
+
const SECRET_REFERENCE_PREFIX = "secret:provider:";
|
|
5436
|
+
const SECRET_FILE_VERSION = 1;
|
|
5437
|
+
const KEY_BYTES = 32;
|
|
5438
|
+
const IV_BYTES = 12;
|
|
5439
|
+
const AUTH_TAG_BYTES = 16;
|
|
5440
|
+
const SecretFileSchema = object({
|
|
5441
|
+
version: literal(SECRET_FILE_VERSION),
|
|
5442
|
+
secrets: record(string(), string())
|
|
5443
|
+
});
|
|
5444
|
+
function emptySecretFile() {
|
|
5445
|
+
return { version: SECRET_FILE_VERSION, secrets: {} };
|
|
5446
|
+
}
|
|
5447
|
+
function readSecretFile(path2) {
|
|
5448
|
+
if (!existsSync(path2)) return emptySecretFile();
|
|
5449
|
+
try {
|
|
5450
|
+
const parsed = JSON.parse(readFileSync(path2, "utf8"));
|
|
5451
|
+
const result = SecretFileSchema.safeParse(parsed);
|
|
5452
|
+
if (result.success) return result.data;
|
|
5453
|
+
logger.warn(`[provider-secrets] Ignoring invalid secret file ${path2}`);
|
|
5454
|
+
} catch (error) {
|
|
5455
|
+
logger.warn(`[provider-secrets] Could not read ${path2}: ${String(error)}`);
|
|
5456
|
+
}
|
|
5457
|
+
return emptySecretFile();
|
|
5458
|
+
}
|
|
5459
|
+
function writePrivateFile(path2, content) {
|
|
5460
|
+
const temporaryPath = `${path2}.${String(process.pid)}.tmp`;
|
|
5461
|
+
try {
|
|
5462
|
+
writeFileSync(temporaryPath, content, { encoding: "utf8", mode: 384 });
|
|
5463
|
+
chmodSync(temporaryPath, 384);
|
|
5464
|
+
renameSync(temporaryPath, path2);
|
|
5465
|
+
chmodSync(path2, 384);
|
|
5466
|
+
return true;
|
|
5467
|
+
} catch (error) {
|
|
5468
|
+
rmSync(temporaryPath, { force: true });
|
|
5469
|
+
logger.error(`[provider-secrets] Could not write ${path2}: ${String(error)}`);
|
|
5470
|
+
return false;
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
function readKey(path2) {
|
|
5474
|
+
try {
|
|
5475
|
+
if (!existsSync(path2)) return null;
|
|
5476
|
+
const key = Buffer.from(readFileSync(path2, "utf8").trim(), "base64");
|
|
5477
|
+
if (key.byteLength === KEY_BYTES) return key;
|
|
5478
|
+
logger.error(`[provider-secrets] Invalid encryption key at ${path2}`);
|
|
5479
|
+
return null;
|
|
5480
|
+
} catch (error) {
|
|
5481
|
+
logger.error(`[provider-secrets] Could not read encryption key: ${String(error)}`);
|
|
5482
|
+
return null;
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
function readOrCreateKey(keyPath, secretsPath) {
|
|
5486
|
+
const existingKey = readKey(keyPath);
|
|
5487
|
+
if (existingKey !== null) return existingKey;
|
|
5488
|
+
if (existsSync(keyPath)) return null;
|
|
5489
|
+
if (existsSync(secretsPath)) {
|
|
5490
|
+
logger.error(
|
|
5491
|
+
`[provider-secrets] Refusing to replace missing key while encrypted secrets exist at ${secretsPath}`
|
|
5492
|
+
);
|
|
5493
|
+
return null;
|
|
5494
|
+
}
|
|
5495
|
+
const key = randomBytes(KEY_BYTES);
|
|
5496
|
+
if (!writePrivateFile(keyPath, key.toString("base64"))) return null;
|
|
5497
|
+
return key;
|
|
5498
|
+
}
|
|
5499
|
+
function encrypt(secret, key) {
|
|
5500
|
+
try {
|
|
5501
|
+
const iv = randomBytes(IV_BYTES);
|
|
5502
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
5503
|
+
const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
5504
|
+
const tag = cipher.getAuthTag();
|
|
5505
|
+
return Buffer.concat([iv, tag, ciphertext]).toString("base64");
|
|
5506
|
+
} catch (error) {
|
|
5507
|
+
logger.error(`[provider-secrets] Encryption failed: ${String(error)}`);
|
|
5508
|
+
return null;
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
function decrypt(payload, key) {
|
|
5512
|
+
try {
|
|
5513
|
+
const packed = Buffer.from(payload, "base64");
|
|
5514
|
+
if (packed.byteLength <= IV_BYTES + AUTH_TAG_BYTES) return null;
|
|
5515
|
+
const iv = packed.subarray(0, IV_BYTES);
|
|
5516
|
+
const tag = packed.subarray(IV_BYTES, IV_BYTES + AUTH_TAG_BYTES);
|
|
5517
|
+
const ciphertext = packed.subarray(IV_BYTES + AUTH_TAG_BYTES);
|
|
5518
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
5519
|
+
decipher.setAuthTag(tag);
|
|
5520
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
5521
|
+
} catch {
|
|
5522
|
+
return null;
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
function providerIdFromReference(reference) {
|
|
5526
|
+
if (!reference.startsWith(SECRET_REFERENCE_PREFIX)) return null;
|
|
5527
|
+
const providerId = reference.slice(SECRET_REFERENCE_PREFIX.length);
|
|
5528
|
+
return providerId === "" ? null : providerId;
|
|
5529
|
+
}
|
|
5530
|
+
function createProviderSecretStore(directory) {
|
|
5531
|
+
mkdirSync(directory, { recursive: true });
|
|
5532
|
+
const keyPath = join(directory, "provider-secrets.key");
|
|
5533
|
+
const secretsPath = join(directory, "provider-secrets.json");
|
|
5534
|
+
function writeSecrets(data) {
|
|
5535
|
+
return writePrivateFile(secretsPath, JSON.stringify(data, null, 2));
|
|
5536
|
+
}
|
|
5537
|
+
return {
|
|
5538
|
+
isReference: (value) => providerIdFromReference(value) !== null,
|
|
5539
|
+
write: (providerId, secret) => {
|
|
5540
|
+
const key = readOrCreateKey(keyPath, secretsPath);
|
|
5541
|
+
if (key === null) return null;
|
|
5542
|
+
const encrypted = encrypt(secret, key);
|
|
5543
|
+
if (encrypted === null) return null;
|
|
5544
|
+
const data = readSecretFile(secretsPath);
|
|
5545
|
+
const next = {
|
|
5546
|
+
...data,
|
|
5547
|
+
secrets: { ...data.secrets, [providerId]: encrypted }
|
|
5548
|
+
};
|
|
5549
|
+
if (!writeSecrets(next)) return null;
|
|
5550
|
+
return `${SECRET_REFERENCE_PREFIX}${providerId}`;
|
|
5551
|
+
},
|
|
5552
|
+
read: (reference) => {
|
|
5553
|
+
const providerId = providerIdFromReference(reference);
|
|
5554
|
+
if (providerId === null) return null;
|
|
5555
|
+
const key = readKey(keyPath);
|
|
5556
|
+
if (key === null) return null;
|
|
5557
|
+
const encrypted = readSecretFile(secretsPath).secrets[providerId];
|
|
5558
|
+
if (encrypted === void 0) return null;
|
|
5559
|
+
return decrypt(encrypted, key);
|
|
5560
|
+
},
|
|
5561
|
+
delete: (providerId) => {
|
|
5562
|
+
const data = readSecretFile(secretsPath);
|
|
5563
|
+
if (data.secrets[providerId] === void 0) return;
|
|
5564
|
+
const nextSecrets = { ...data.secrets };
|
|
5565
|
+
delete nextSecrets[providerId];
|
|
5566
|
+
void writeSecrets({ ...data, secrets: nextSecrets });
|
|
5567
|
+
}
|
|
5568
|
+
};
|
|
5569
|
+
}
|
|
5570
|
+
function secretValuesEqual(left, right) {
|
|
5571
|
+
const leftBuffer = Buffer.from(left);
|
|
5572
|
+
const rightBuffer = Buffer.from(right);
|
|
5573
|
+
return leftBuffer.byteLength === rightBuffer.byteLength && timingSafeEqual(leftBuffer, rightBuffer);
|
|
5574
|
+
}
|
|
5419
5575
|
object({
|
|
5420
5576
|
providers: array(ProviderConfigSchema)
|
|
5421
5577
|
});
|
|
@@ -5562,6 +5718,60 @@ const store = new Conf({
|
|
|
5562
5718
|
},
|
|
5563
5719
|
...configPath !== void 0 ? { path: configPath } : {}
|
|
5564
5720
|
});
|
|
5721
|
+
const providerSecretStore = createProviderSecretStore(dirname(store.path));
|
|
5722
|
+
function providerWithResolvedSecret(provider) {
|
|
5723
|
+
if (!providerSecretStore.isReference(provider.apiKey)) return provider;
|
|
5724
|
+
const apiKey = providerSecretStore.read(provider.apiKey);
|
|
5725
|
+
if (apiKey !== null) return { ...provider, apiKey };
|
|
5726
|
+
logger.error(`[providers] Secret is missing or unreadable for provider "${provider.name}"`);
|
|
5727
|
+
return null;
|
|
5728
|
+
}
|
|
5729
|
+
function persistProviders(providers, removedProviderIds = /* @__PURE__ */ new Set()) {
|
|
5730
|
+
const existingReferences = /* @__PURE__ */ new Map();
|
|
5731
|
+
const unresolvedRecords = /* @__PURE__ */ new Map();
|
|
5732
|
+
for (const candidate of store.get("providers", [])) {
|
|
5733
|
+
const parsed = ProviderConfigSchema.safeParse(candidate);
|
|
5734
|
+
if (parsed.success && providerSecretStore.isReference(parsed.data.apiKey)) {
|
|
5735
|
+
existingReferences.set(parsed.data.id, parsed.data.apiKey);
|
|
5736
|
+
if (providerSecretStore.read(parsed.data.apiKey) === null) {
|
|
5737
|
+
unresolvedRecords.set(parsed.data.id, parsed.data);
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
const persisted = providers.map((provider) => {
|
|
5742
|
+
const existingReference = existingReferences.get(provider.id);
|
|
5743
|
+
if (existingReference !== void 0) {
|
|
5744
|
+
const existingSecret = providerSecretStore.read(existingReference);
|
|
5745
|
+
if (existingSecret !== null && secretValuesEqual(existingSecret, provider.apiKey)) {
|
|
5746
|
+
return { ...provider, apiKey: existingReference };
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
const reference = providerSecretStore.write(provider.id, provider.apiKey);
|
|
5750
|
+
if (reference !== null) return { ...provider, apiKey: reference };
|
|
5751
|
+
logger.error(
|
|
5752
|
+
`[providers] SecretStore unavailable; provider "${provider.name}" remains in the legacy storage format`
|
|
5753
|
+
);
|
|
5754
|
+
return provider;
|
|
5755
|
+
});
|
|
5756
|
+
const persistedIds = new Set(persisted.map((provider) => provider.id));
|
|
5757
|
+
for (const [id, provider] of unresolvedRecords) {
|
|
5758
|
+
if (!persistedIds.has(id) && !removedProviderIds.has(id)) persisted.push(provider);
|
|
5759
|
+
}
|
|
5760
|
+
store.set("providers", persisted);
|
|
5761
|
+
}
|
|
5762
|
+
function migratePlaintextProviderSecrets() {
|
|
5763
|
+
const raw = store.get("providers", []);
|
|
5764
|
+
let changed = false;
|
|
5765
|
+
const migrated = raw.map((candidate) => {
|
|
5766
|
+
const parsed = ProviderConfigSchema.safeParse(candidate);
|
|
5767
|
+
if (!parsed.success || providerSecretStore.isReference(parsed.data.apiKey)) return candidate;
|
|
5768
|
+
const reference = providerSecretStore.write(parsed.data.id, parsed.data.apiKey);
|
|
5769
|
+
if (reference === null) return candidate;
|
|
5770
|
+
changed = true;
|
|
5771
|
+
return { ...parsed.data, apiKey: reference };
|
|
5772
|
+
});
|
|
5773
|
+
if (changed) store.set("providers", migrated);
|
|
5774
|
+
}
|
|
5565
5775
|
migrateFromDataDirConfig(store, dataDir);
|
|
5566
5776
|
migrateFromLegacyConfLocation(store);
|
|
5567
5777
|
function migrateProvider(p) {
|
|
@@ -5663,13 +5873,15 @@ if (providersJson !== void 0) {
|
|
|
5663
5873
|
} catch {
|
|
5664
5874
|
}
|
|
5665
5875
|
}
|
|
5876
|
+
migratePlaintextProviderSecrets();
|
|
5666
5877
|
function getProviders() {
|
|
5667
5878
|
const raw = store.get("providers", []);
|
|
5668
5879
|
const valid = [];
|
|
5669
5880
|
for (const candidate of raw) {
|
|
5670
5881
|
const entry = ProviderConfigSchema.safeParse(candidate);
|
|
5671
5882
|
if (entry.success) {
|
|
5672
|
-
|
|
5883
|
+
const resolved = providerWithResolvedSecret(entry.data);
|
|
5884
|
+
if (resolved !== null) valid.push(resolved);
|
|
5673
5885
|
continue;
|
|
5674
5886
|
}
|
|
5675
5887
|
let candidateName = "<unknown>";
|
|
@@ -5690,7 +5902,7 @@ function getProvider(id) {
|
|
|
5690
5902
|
function redactProvider(provider) {
|
|
5691
5903
|
return {
|
|
5692
5904
|
...provider,
|
|
5693
|
-
apiKey:
|
|
5905
|
+
apiKey: "configured"
|
|
5694
5906
|
};
|
|
5695
5907
|
}
|
|
5696
5908
|
function redactProviders(providers) {
|
|
@@ -5707,7 +5919,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
|
|
|
5707
5919
|
const resolvedOpenaiResponsesUrl = openaiResponsesBaseUrl ?? "";
|
|
5708
5920
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5709
5921
|
const existing = providers.find(
|
|
5710
|
-
(p) => p.apiKey
|
|
5922
|
+
(p) => secretValuesEqual(p.apiKey, normalizedKey) && (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") && (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") && (p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? "")
|
|
5711
5923
|
);
|
|
5712
5924
|
if (existing) {
|
|
5713
5925
|
const existingIndex = providers.findIndex((p) => p.id === existing.id);
|
|
@@ -5724,7 +5936,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
|
|
|
5724
5936
|
if (existingIndex >= 0) {
|
|
5725
5937
|
providers[existingIndex] = enriched;
|
|
5726
5938
|
}
|
|
5727
|
-
|
|
5939
|
+
persistProviders(providers);
|
|
5728
5940
|
return enriched;
|
|
5729
5941
|
}
|
|
5730
5942
|
const newProvider = withBuiltinProviderModelMetadata(
|
|
@@ -5749,7 +5961,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
|
|
|
5749
5961
|
now
|
|
5750
5962
|
);
|
|
5751
5963
|
providers.push(newProvider);
|
|
5752
|
-
|
|
5964
|
+
persistProviders(providers);
|
|
5753
5965
|
return newProvider;
|
|
5754
5966
|
}
|
|
5755
5967
|
function updateProvider(id, updates) {
|
|
@@ -5781,14 +5993,15 @@ function updateProvider(id, updates) {
|
|
|
5781
5993
|
);
|
|
5782
5994
|
const index = providers.findIndex((p) => p.id === id);
|
|
5783
5995
|
providers[index] = updated;
|
|
5784
|
-
|
|
5996
|
+
persistProviders(providers);
|
|
5785
5997
|
return updated;
|
|
5786
5998
|
}
|
|
5787
5999
|
function deleteProvider(id) {
|
|
5788
6000
|
const providers = getProviders();
|
|
5789
6001
|
const filtered = providers.filter((p) => p.id !== id);
|
|
5790
6002
|
if (filtered.length === providers.length) return false;
|
|
5791
|
-
|
|
6003
|
+
persistProviders(filtered, /* @__PURE__ */ new Set([id]));
|
|
6004
|
+
providerSecretStore.delete(id);
|
|
5792
6005
|
return true;
|
|
5793
6006
|
}
|
|
5794
6007
|
function exportProviders() {
|
|
@@ -5855,7 +6068,7 @@ function importProviders(json) {
|
|
|
5855
6068
|
imported++;
|
|
5856
6069
|
}
|
|
5857
6070
|
if (newProviders.length > 0) {
|
|
5858
|
-
|
|
6071
|
+
persistProviders([...existingProviders, ...newProviders]);
|
|
5859
6072
|
}
|
|
5860
6073
|
} catch (err) {
|
|
5861
6074
|
return {
|
|
@@ -6486,6 +6699,145 @@ function toPersistedConfig(value) {
|
|
|
6486
6699
|
timeDisplayFormat: value.timeDisplayFormat
|
|
6487
6700
|
};
|
|
6488
6701
|
}
|
|
6702
|
+
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
6703
|
+
"localhost",
|
|
6704
|
+
"ip6-localhost",
|
|
6705
|
+
"ip6-loopback",
|
|
6706
|
+
"[::1]",
|
|
6707
|
+
"[::ffff:127.0.0.1]"
|
|
6708
|
+
]);
|
|
6709
|
+
function isLoopbackHost(hostname) {
|
|
6710
|
+
const lower2 = hostname.toLowerCase().replace(/^\[|]$/g, "");
|
|
6711
|
+
if (LOOPBACK_HOSTNAMES.has(lower2)) return true;
|
|
6712
|
+
if (lower2 === "::1") return true;
|
|
6713
|
+
if (lower2.startsWith("::ffff:127.")) return true;
|
|
6714
|
+
if (lower2.startsWith("::ffff:7f00:")) return true;
|
|
6715
|
+
return /^127\./.test(lower2);
|
|
6716
|
+
}
|
|
6717
|
+
function mappedIpv4Address(hostname) {
|
|
6718
|
+
const lower2 = hostname.toLowerCase();
|
|
6719
|
+
if (!lower2.startsWith("::ffff:")) return null;
|
|
6720
|
+
const suffix = lower2.slice("::ffff:".length);
|
|
6721
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
|
|
6722
|
+
const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
|
|
6723
|
+
if (match === null) return null;
|
|
6724
|
+
const highText = match[1];
|
|
6725
|
+
const lowText = match[2];
|
|
6726
|
+
if (highText === void 0 || lowText === void 0) return null;
|
|
6727
|
+
const high = Number.parseInt(highText, 16);
|
|
6728
|
+
const low = Number.parseInt(lowText, 16);
|
|
6729
|
+
return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
|
|
6730
|
+
}
|
|
6731
|
+
function isInternalHost(hostname) {
|
|
6732
|
+
if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
|
|
6733
|
+
const mappedIpv4 = mappedIpv4Address(hostname);
|
|
6734
|
+
if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
|
|
6735
|
+
const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
6736
|
+
if (ipv4Match !== null) {
|
|
6737
|
+
const [, a, b] = ipv4Match;
|
|
6738
|
+
if (a === void 0 || b === void 0) return false;
|
|
6739
|
+
const first = Number.parseInt(a, 10);
|
|
6740
|
+
const second = Number.parseInt(b, 10);
|
|
6741
|
+
if (first === 127) return true;
|
|
6742
|
+
if (first === 10) return true;
|
|
6743
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
6744
|
+
if (first === 192 && second === 168) return true;
|
|
6745
|
+
if (first === 169 && second === 254) return true;
|
|
6746
|
+
if (first === 0) return true;
|
|
6747
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
6748
|
+
if (first >= 224) return true;
|
|
6749
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
6750
|
+
return false;
|
|
6751
|
+
}
|
|
6752
|
+
const lower2 = hostname.toLowerCase();
|
|
6753
|
+
if (lower2 === "::") return true;
|
|
6754
|
+
if (lower2 === "::1") return true;
|
|
6755
|
+
if (/^fe[89ab]/.test(lower2)) return true;
|
|
6756
|
+
if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
|
|
6757
|
+
if (lower2.startsWith("ff")) return true;
|
|
6758
|
+
return false;
|
|
6759
|
+
}
|
|
6760
|
+
function evaluateUpstreamUrl(rawUrl, options = {}) {
|
|
6761
|
+
if (options.bypass === true) return { allow: true };
|
|
6762
|
+
let parsed;
|
|
6763
|
+
try {
|
|
6764
|
+
parsed = new URL(rawUrl);
|
|
6765
|
+
} catch {
|
|
6766
|
+
return { allow: false, reason: "Not a valid URL" };
|
|
6767
|
+
}
|
|
6768
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
6769
|
+
return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
|
|
6770
|
+
}
|
|
6771
|
+
if (options.allowLoopback === true && parsed.hostname === "localhost") {
|
|
6772
|
+
return { allow: true };
|
|
6773
|
+
}
|
|
6774
|
+
const hostname = parsed.hostname.replace(/^\[|]$/g, "");
|
|
6775
|
+
if (isInternalHost(hostname)) {
|
|
6776
|
+
if (options.allowLoopback === true) {
|
|
6777
|
+
if (isLoopbackHost(hostname)) return { allow: true };
|
|
6778
|
+
}
|
|
6779
|
+
return {
|
|
6780
|
+
allow: false,
|
|
6781
|
+
reason: `Refusing to proxy to internal address "${hostname}"`
|
|
6782
|
+
};
|
|
6783
|
+
}
|
|
6784
|
+
return { allow: true };
|
|
6785
|
+
}
|
|
6786
|
+
async function defaultResolver(hostname) {
|
|
6787
|
+
return lookup(hostname, { all: true, verbatim: true });
|
|
6788
|
+
}
|
|
6789
|
+
function isLoopbackAddress(hostname) {
|
|
6790
|
+
const lower2 = hostname.toLowerCase();
|
|
6791
|
+
return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
|
|
6792
|
+
}
|
|
6793
|
+
async function evaluateResolvedUpstreamUrl(rawUrl, options = {}) {
|
|
6794
|
+
const initial = evaluateUpstreamUrl(rawUrl, options);
|
|
6795
|
+
if (!initial.allow || options.bypass === true) return initial;
|
|
6796
|
+
const parsed = new URL(rawUrl);
|
|
6797
|
+
const hostname = parsed.hostname.replace(/^\[|]$/g, "");
|
|
6798
|
+
if (isIP(hostname) !== 0 || hostname.toLowerCase() === "localhost") return initial;
|
|
6799
|
+
let addresses;
|
|
6800
|
+
try {
|
|
6801
|
+
addresses = await (options.resolver ?? defaultResolver)(hostname);
|
|
6802
|
+
} catch (error) {
|
|
6803
|
+
return {
|
|
6804
|
+
allow: false,
|
|
6805
|
+
reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
|
|
6806
|
+
};
|
|
6807
|
+
}
|
|
6808
|
+
if (addresses.length === 0) {
|
|
6809
|
+
return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
|
|
6810
|
+
}
|
|
6811
|
+
for (const resolved of addresses) {
|
|
6812
|
+
if (!isInternalHost(resolved.address)) continue;
|
|
6813
|
+
if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
|
|
6814
|
+
return {
|
|
6815
|
+
allow: false,
|
|
6816
|
+
reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
return { allow: true };
|
|
6820
|
+
}
|
|
6821
|
+
function isRedirectStatus(status) {
|
|
6822
|
+
return status >= 300 && status < 400;
|
|
6823
|
+
}
|
|
6824
|
+
async function safeFetch(rawUrl, init = {}, options = {}) {
|
|
6825
|
+
const decision = await evaluateResolvedUpstreamUrl(rawUrl, options);
|
|
6826
|
+
if (!decision.allow) {
|
|
6827
|
+
return { ok: false, kind: "network-policy", reason: decision.reason };
|
|
6828
|
+
}
|
|
6829
|
+
const response = await fetch(rawUrl, { ...init, redirect: "manual" });
|
|
6830
|
+
if (!isRedirectStatus(response.status)) return { ok: true, response };
|
|
6831
|
+
if (response.body !== null) {
|
|
6832
|
+
await response.body.cancel().catch(() => void 0);
|
|
6833
|
+
}
|
|
6834
|
+
const location = response.headers.get("location");
|
|
6835
|
+
return {
|
|
6836
|
+
ok: false,
|
|
6837
|
+
kind: "redirect",
|
|
6838
|
+
reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
|
|
6839
|
+
};
|
|
6840
|
+
}
|
|
6489
6841
|
const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
6490
6842
|
function isClaudeCodeBillingHeaderBlock(text) {
|
|
6491
6843
|
const trimmed = text.trimStart();
|
|
@@ -6631,30 +6983,23 @@ function parseJson$1(raw) {
|
|
|
6631
6983
|
return { ok: false };
|
|
6632
6984
|
}
|
|
6633
6985
|
}
|
|
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
6986
|
function hasOwnProperty(value, key) {
|
|
6642
6987
|
return Object.getOwnPropertyDescriptor(value, key) !== void 0;
|
|
6643
6988
|
}
|
|
6644
6989
|
function readString(value, key) {
|
|
6645
|
-
const property = readProperty$
|
|
6990
|
+
const property = readProperty$5(value, key);
|
|
6646
6991
|
return typeof property === "string" ? property : null;
|
|
6647
6992
|
}
|
|
6648
6993
|
function readArray(value, key) {
|
|
6649
|
-
const property = readProperty$
|
|
6994
|
+
const property = readProperty$5(value, key);
|
|
6650
6995
|
return Array.isArray(property) ? property : [];
|
|
6651
6996
|
}
|
|
6652
6997
|
function readStringArray(value, key) {
|
|
6653
6998
|
return readArray(value, key).filter((item) => typeof item === "string");
|
|
6654
6999
|
}
|
|
6655
7000
|
function readObject(value, key) {
|
|
6656
|
-
const property = readProperty$
|
|
6657
|
-
return isRecord
|
|
7001
|
+
const property = readProperty$5(value, key);
|
|
7002
|
+
return isRecord(property) ? property : null;
|
|
6658
7003
|
}
|
|
6659
7004
|
function normalizeExpectedType(typeName) {
|
|
6660
7005
|
switch (typeName) {
|
|
@@ -6671,7 +7016,7 @@ function normalizeExpectedType(typeName) {
|
|
|
6671
7016
|
}
|
|
6672
7017
|
}
|
|
6673
7018
|
function schemaTypes(schema) {
|
|
6674
|
-
const typeValue = readProperty$
|
|
7019
|
+
const typeValue = readProperty$5(schema, "type");
|
|
6675
7020
|
const rawTypes = Array.isArray(typeValue) ? typeValue : [typeValue];
|
|
6676
7021
|
const types = [];
|
|
6677
7022
|
for (const rawType of rawTypes) {
|
|
@@ -6699,7 +7044,7 @@ function actualType(value) {
|
|
|
6699
7044
|
case "function":
|
|
6700
7045
|
case "object":
|
|
6701
7046
|
case "symbol":
|
|
6702
|
-
return isRecord
|
|
7047
|
+
return isRecord(value) ? "object" : "unknown";
|
|
6703
7048
|
}
|
|
6704
7049
|
return "unknown";
|
|
6705
7050
|
}
|
|
@@ -6716,7 +7061,7 @@ function matchesExpectedType(value, expected) {
|
|
|
6716
7061
|
case "number":
|
|
6717
7062
|
return typeof value === "number" && Number.isFinite(value);
|
|
6718
7063
|
case "object":
|
|
6719
|
-
return isRecord
|
|
7064
|
+
return isRecord(value);
|
|
6720
7065
|
case "string":
|
|
6721
7066
|
return typeof value === "string";
|
|
6722
7067
|
}
|
|
@@ -6761,7 +7106,7 @@ function schemaAllowsObjectValidation(schema, expectedTypes) {
|
|
|
6761
7106
|
return expectedTypes.includes("object");
|
|
6762
7107
|
}
|
|
6763
7108
|
function schemaAllowsArrayValidation(schema, expectedTypes) {
|
|
6764
|
-
if (expectedTypes.length === 0) return readProperty$
|
|
7109
|
+
if (expectedTypes.length === 0) return readProperty$5(schema, "items") !== void 0;
|
|
6765
7110
|
return expectedTypes.includes("array");
|
|
6766
7111
|
}
|
|
6767
7112
|
function addTypeWarning(warnings, toolName, path2, expectedTypes, value) {
|
|
@@ -6794,7 +7139,7 @@ function validateObjectSchema(schema, value, toolName, path2, warnings) {
|
|
|
6794
7139
|
}
|
|
6795
7140
|
}
|
|
6796
7141
|
function validateArraySchema(schema, value, toolName, path2, warnings) {
|
|
6797
|
-
const itemsSchema = readProperty$
|
|
7142
|
+
const itemsSchema = readProperty$5(schema, "items");
|
|
6798
7143
|
if (itemsSchema === void 0) return;
|
|
6799
7144
|
let index = 0;
|
|
6800
7145
|
for (const item of value) {
|
|
@@ -6803,7 +7148,7 @@ function validateArraySchema(schema, value, toolName, path2, warnings) {
|
|
|
6803
7148
|
}
|
|
6804
7149
|
}
|
|
6805
7150
|
function validateEnum(schema, value, toolName, path2, warnings) {
|
|
6806
|
-
const enumValue = readProperty$
|
|
7151
|
+
const enumValue = readProperty$5(schema, "enum");
|
|
6807
7152
|
if (Array.isArray(enumValue) && enumValue.length > 0 && !enumMatches(value, enumValue)) {
|
|
6808
7153
|
warnings.push(
|
|
6809
7154
|
`Tool call "${toolName}" ${pathLabel(path2)} expected one of ${formatJsonValue(
|
|
@@ -6821,7 +7166,7 @@ function validateEnum(schema, value, toolName, path2, warnings) {
|
|
|
6821
7166
|
}
|
|
6822
7167
|
}
|
|
6823
7168
|
function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
|
|
6824
|
-
if (!isRecord
|
|
7169
|
+
if (!isRecord(schema)) return;
|
|
6825
7170
|
const expectedTypes = schemaTypes(schema);
|
|
6826
7171
|
if (expectedTypes.length > 0 && !matchesAnyExpectedType(value, expectedTypes)) {
|
|
6827
7172
|
addTypeWarning(warnings, toolName, path2, expectedTypes, value);
|
|
@@ -6829,7 +7174,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
|
|
|
6829
7174
|
}
|
|
6830
7175
|
validateEnum(schema, value, toolName, path2, warnings);
|
|
6831
7176
|
if (schemaAllowsObjectValidation(schema, expectedTypes)) {
|
|
6832
|
-
if (isRecord
|
|
7177
|
+
if (isRecord(value)) {
|
|
6833
7178
|
validateObjectSchema(schema, value, toolName, path2, warnings);
|
|
6834
7179
|
} else if (expectedTypes.length === 0) {
|
|
6835
7180
|
warnings.push(
|
|
@@ -6850,7 +7195,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
|
|
|
6850
7195
|
function extractAnthropicToolSchemas(request) {
|
|
6851
7196
|
const tools = /* @__PURE__ */ new Map();
|
|
6852
7197
|
for (const tool of readArray(request, "tools")) {
|
|
6853
|
-
if (!isRecord
|
|
7198
|
+
if (!isRecord(tool)) continue;
|
|
6854
7199
|
const name2 = readString(tool, "name");
|
|
6855
7200
|
if (name2 === null || name2 === "") continue;
|
|
6856
7201
|
tools.set(name2, {
|
|
@@ -6920,7 +7265,7 @@ function extractOpenAIToolCalls(response) {
|
|
|
6920
7265
|
for (const call of readArray(message, "tool_calls")) {
|
|
6921
7266
|
const functionCall = readObject(call, "function");
|
|
6922
7267
|
const name2 = functionCall === null ? null : readString(functionCall, "name");
|
|
6923
|
-
const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$
|
|
7268
|
+
const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$5(functionCall, "arguments"));
|
|
6924
7269
|
calls.push({
|
|
6925
7270
|
name: name2,
|
|
6926
7271
|
arguments: parsedArguments.value,
|
|
@@ -6931,7 +7276,7 @@ function extractOpenAIToolCalls(response) {
|
|
|
6931
7276
|
}
|
|
6932
7277
|
const legacyFunctionCall = readObject(message, "function_call");
|
|
6933
7278
|
if (legacyFunctionCall !== null) {
|
|
6934
|
-
const parsedArguments = parseOpenAIArguments(readProperty$
|
|
7279
|
+
const parsedArguments = parseOpenAIArguments(readProperty$5(legacyFunctionCall, "arguments"));
|
|
6935
7280
|
calls.push({
|
|
6936
7281
|
name: readString(legacyFunctionCall, "name"),
|
|
6937
7282
|
arguments: parsedArguments.value,
|
|
@@ -6947,7 +7292,7 @@ function extractOpenAIToolCalls(response) {
|
|
|
6947
7292
|
outputIndex++;
|
|
6948
7293
|
continue;
|
|
6949
7294
|
}
|
|
6950
|
-
const parsedArguments = parseOpenAIArguments(readProperty$
|
|
7295
|
+
const parsedArguments = parseOpenAIArguments(readProperty$5(item, "arguments"));
|
|
6951
7296
|
calls.push({
|
|
6952
7297
|
name: readString(item, "name"),
|
|
6953
7298
|
arguments: parsedArguments.value,
|
|
@@ -6968,7 +7313,7 @@ function extractAnthropicToolCalls(response) {
|
|
|
6968
7313
|
}
|
|
6969
7314
|
calls.push({
|
|
6970
7315
|
name: readString(block, "name"),
|
|
6971
|
-
arguments: readProperty$
|
|
7316
|
+
arguments: readProperty$5(block, "input") ?? {},
|
|
6972
7317
|
argumentIssue: null,
|
|
6973
7318
|
location: `content[${contentIndex}]`
|
|
6974
7319
|
});
|
|
@@ -7751,12 +8096,14 @@ function injectProviderAuth(headers, provider) {
|
|
|
7751
8096
|
}
|
|
7752
8097
|
headers.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
|
|
7753
8098
|
}
|
|
8099
|
+
const ALLOW_LOOPBACK$2 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
|
|
7754
8100
|
function buildProxyHeaders(originalHeaders, captureRawHeaders) {
|
|
7755
8101
|
const rawHeaders = captureRawHeaders ? {} : void 0;
|
|
7756
8102
|
const headers = new Headers();
|
|
7757
8103
|
if (rawHeaders !== void 0) {
|
|
7758
8104
|
originalHeaders.forEach((value, key) => {
|
|
7759
|
-
|
|
8105
|
+
const lowerKey = key.toLowerCase();
|
|
8106
|
+
rawHeaders[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
|
|
7760
8107
|
});
|
|
7761
8108
|
}
|
|
7762
8109
|
headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
|
|
@@ -7784,6 +8131,37 @@ function parseRequestPath(req, url) {
|
|
|
7784
8131
|
function errorMessage(err) {
|
|
7785
8132
|
return err instanceof Error ? err.message : String(err);
|
|
7786
8133
|
}
|
|
8134
|
+
async function readBodyWithLimit(req, limitBytes) {
|
|
8135
|
+
if (req.body === null || req.method === "GET" || req.method === "HEAD") {
|
|
8136
|
+
return { ok: true, body: null };
|
|
8137
|
+
}
|
|
8138
|
+
const contentLengthHeader = req.headers.get(HEADER_CONTENT_LENGTH);
|
|
8139
|
+
if (contentLengthHeader !== null) {
|
|
8140
|
+
const declared = Number.parseInt(contentLengthHeader, 10);
|
|
8141
|
+
if (Number.isFinite(declared) && declared > limitBytes) {
|
|
8142
|
+
return { ok: false, reason: "too-large" };
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
const reader = req.body.getReader();
|
|
8146
|
+
const decoder = new TextDecoder();
|
|
8147
|
+
const chunks = [];
|
|
8148
|
+
let totalBytes = 0;
|
|
8149
|
+
try {
|
|
8150
|
+
for (; ; ) {
|
|
8151
|
+
const { done, value } = await reader.read();
|
|
8152
|
+
if (done) break;
|
|
8153
|
+
totalBytes += value.byteLength;
|
|
8154
|
+
if (totalBytes > limitBytes) {
|
|
8155
|
+
return { ok: false, reason: "too-large" };
|
|
8156
|
+
}
|
|
8157
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
8158
|
+
}
|
|
8159
|
+
chunks.push(decoder.decode());
|
|
8160
|
+
return { ok: true, body: chunks.join("") };
|
|
8161
|
+
} finally {
|
|
8162
|
+
reader.releaseLock();
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
7787
8165
|
function scheduleLogFinalization(job) {
|
|
7788
8166
|
void enqueueFinalizeLogJob(job).catch((err) => {
|
|
7789
8167
|
logger.error(
|
|
@@ -7891,10 +8269,12 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
|
|
|
7891
8269
|
async function handleProxy(req) {
|
|
7892
8270
|
const url = new URL(req.url);
|
|
7893
8271
|
const parsed = parseRequestPath(req, url);
|
|
7894
|
-
|
|
7895
|
-
if (
|
|
7896
|
-
|
|
8272
|
+
const bodyRead = await readBodyWithLimit(req, MAX_REQUEST_BODY_BYTES);
|
|
8273
|
+
if (!bodyRead.ok) {
|
|
8274
|
+
logger.warn(`[handler] Rejected oversized request body: ${req.method} ${parsed.apiPath}`);
|
|
8275
|
+
return new Response("Request body too large", { status: STATUS_PAYLOAD_TOO_LARGE });
|
|
7897
8276
|
}
|
|
8277
|
+
const requestBody = bodyRead.body;
|
|
7898
8278
|
const config = getConfig();
|
|
7899
8279
|
let bodyToForward = requestBody;
|
|
7900
8280
|
if (config.stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
|
|
@@ -7941,7 +8321,8 @@ async function handleProxy(req) {
|
|
|
7941
8321
|
const upstreamHeadersObj = captureFullDetails ? {} : void 0;
|
|
7942
8322
|
if (upstreamHeadersObj !== void 0) {
|
|
7943
8323
|
upstreamHeaders.forEach((value, key) => {
|
|
7944
|
-
|
|
8324
|
+
const lowerKey = key.toLowerCase();
|
|
8325
|
+
upstreamHeadersObj[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
|
|
7945
8326
|
});
|
|
7946
8327
|
}
|
|
7947
8328
|
const log = await createLog(
|
|
@@ -7959,12 +8340,30 @@ async function handleProxy(req) {
|
|
|
7959
8340
|
);
|
|
7960
8341
|
let upstreamRes;
|
|
7961
8342
|
try {
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
8343
|
+
const fetchResult = await safeFetch(
|
|
8344
|
+
upstreamUrl,
|
|
8345
|
+
{
|
|
8346
|
+
method: req.method,
|
|
8347
|
+
headers: upstreamHeaders,
|
|
8348
|
+
body: bodyToForward,
|
|
8349
|
+
signal: req.signal
|
|
8350
|
+
},
|
|
8351
|
+
{ allowLoopback: ALLOW_LOOPBACK$2 }
|
|
8352
|
+
);
|
|
8353
|
+
if (!fetchResult.ok) {
|
|
8354
|
+
const status = fetchResult.kind === "network-policy" ? STATUS_FORBIDDEN : STATUS_BAD_GATEWAY;
|
|
8355
|
+
logger.warn(`[handler] Safe fetch blocked ${upstreamUrl}: ${fetchResult.reason}`);
|
|
8356
|
+
log.elapsedMs = Date.now() - startTime;
|
|
8357
|
+
log.responseStatus = status;
|
|
8358
|
+
log.responseText = fetchResult.reason;
|
|
8359
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
|
|
8360
|
+
finalizeLogUpdate(log);
|
|
8361
|
+
return new Response(
|
|
8362
|
+
fetchResult.kind === "network-policy" ? "Forbidden: upstream URL is not permitted" : "Proxy error: upstream redirects are not permitted",
|
|
8363
|
+
{ status }
|
|
8364
|
+
);
|
|
8365
|
+
}
|
|
8366
|
+
upstreamRes = fetchResult.response;
|
|
7968
8367
|
} catch (err) {
|
|
7969
8368
|
log.elapsedMs = Date.now() - startTime;
|
|
7970
8369
|
if (err instanceof Error && err.name === "AbortError") {
|
|
@@ -9506,6 +9905,7 @@ const Route$H = createFileRoute("/api/runs")({
|
|
|
9506
9905
|
}
|
|
9507
9906
|
}
|
|
9508
9907
|
});
|
|
9908
|
+
const ALLOW_LOOPBACK$1 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
|
|
9509
9909
|
const ProviderInputSchema = object({
|
|
9510
9910
|
name: string().min(1, "Name is required"),
|
|
9511
9911
|
apiKey: string().min(1, "API key is required"),
|
|
@@ -9524,17 +9924,25 @@ const ProviderInputSchema = object({
|
|
|
9524
9924
|
const Route$G = createFileRoute("/api/providers")({
|
|
9525
9925
|
server: {
|
|
9526
9926
|
handlers: {
|
|
9527
|
-
GET: (
|
|
9528
|
-
|
|
9529
|
-
const includeSecrets = url.searchParams.get("includeSecrets") === "1";
|
|
9530
|
-
const providers = getProviders();
|
|
9531
|
-
return Response.json(includeSecrets ? providers : redactProviders(providers));
|
|
9927
|
+
GET: () => {
|
|
9928
|
+
return Response.json(redactProviders(getProviders()));
|
|
9532
9929
|
},
|
|
9533
9930
|
POST: async ({ request }) => {
|
|
9534
9931
|
const parsed = ProviderInputSchema.safeParse(await request.json());
|
|
9535
9932
|
if (!parsed.success) {
|
|
9536
9933
|
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
9537
9934
|
}
|
|
9935
|
+
for (const candidate of [
|
|
9936
|
+
parsed.data.anthropicBaseUrl,
|
|
9937
|
+
parsed.data.openaiBaseUrl,
|
|
9938
|
+
parsed.data.openaiResponsesBaseUrl
|
|
9939
|
+
]) {
|
|
9940
|
+
if (candidate === void 0 || candidate === "") continue;
|
|
9941
|
+
const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK$1 });
|
|
9942
|
+
if (!decision.allow) {
|
|
9943
|
+
return Response.json({ error: decision.reason }, { status: 400 });
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9538
9946
|
const newProvider = addProvider(
|
|
9539
9947
|
parsed.data.name,
|
|
9540
9948
|
parsed.data.apiKey,
|
|
@@ -9551,7 +9959,7 @@ const Route$G = createFileRoute("/api/providers")({
|
|
|
9551
9959
|
parsed.data.modelMetadata,
|
|
9552
9960
|
parsed.data.openaiResponsesBaseUrl
|
|
9553
9961
|
);
|
|
9554
|
-
return Response.json(newProvider, { status: 201 });
|
|
9962
|
+
return Response.json(redactProvider(newProvider), { status: 201 });
|
|
9555
9963
|
}
|
|
9556
9964
|
}
|
|
9557
9965
|
}
|
|
@@ -9571,9 +9979,6 @@ const PiAgentResponseSchema = object({
|
|
|
9571
9979
|
protocol: _enum(["openaiResponses", "openaiChat", "anthropic"]),
|
|
9572
9980
|
logCount: number().int().nonnegative()
|
|
9573
9981
|
});
|
|
9574
|
-
function isRecord$1(value) {
|
|
9575
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9576
|
-
}
|
|
9577
9982
|
function normalizeAnthropicStopReason(value) {
|
|
9578
9983
|
switch (value) {
|
|
9579
9984
|
case "end_turn":
|
|
@@ -9612,20 +10017,20 @@ function extractStopReason(log) {
|
|
|
9612
10017
|
if (typeof json === "string") {
|
|
9613
10018
|
json = JSON.parse(json);
|
|
9614
10019
|
}
|
|
9615
|
-
if (!isRecord
|
|
10020
|
+
if (!isRecord(json)) return null;
|
|
9616
10021
|
if (typeof json.stop_reason === "string") {
|
|
9617
10022
|
return normalizeAnthropicStopReason(json.stop_reason);
|
|
9618
10023
|
}
|
|
9619
10024
|
if (Array.isArray(json.choices)) {
|
|
9620
10025
|
for (const choice of json.choices) {
|
|
9621
|
-
if (!isRecord
|
|
10026
|
+
if (!isRecord(choice) || typeof choice.finish_reason !== "string") continue;
|
|
9622
10027
|
const reason = normalizeOpenAIFinishReason(choice.finish_reason);
|
|
9623
10028
|
if (reason !== null) return reason;
|
|
9624
10029
|
}
|
|
9625
10030
|
}
|
|
9626
10031
|
if (Array.isArray(json.output)) {
|
|
9627
10032
|
for (const item of json.output) {
|
|
9628
|
-
if (isRecord
|
|
10033
|
+
if (isRecord(item) && item.type === "function_call") {
|
|
9629
10034
|
return "tool_use";
|
|
9630
10035
|
}
|
|
9631
10036
|
}
|
|
@@ -9633,7 +10038,7 @@ function extractStopReason(log) {
|
|
|
9633
10038
|
return "stop";
|
|
9634
10039
|
}
|
|
9635
10040
|
}
|
|
9636
|
-
if (json.status === "incomplete" && isRecord
|
|
10041
|
+
if (json.status === "incomplete" && isRecord(json.incomplete_details)) {
|
|
9637
10042
|
const reason = json.incomplete_details.reason;
|
|
9638
10043
|
if (typeof reason === "string" && isResponsesLengthLimitReason(reason)) {
|
|
9639
10044
|
return "length";
|
|
@@ -9980,7 +10385,7 @@ const Route$E = createFileRoute("/api/models")({
|
|
|
9980
10385
|
}
|
|
9981
10386
|
});
|
|
9982
10387
|
const name = "@tonyclaw/agent-inspector";
|
|
9983
|
-
const version = "3.0.
|
|
10388
|
+
const version = "3.0.27";
|
|
9984
10389
|
const packageJson = {
|
|
9985
10390
|
name,
|
|
9986
10391
|
version
|
|
@@ -10062,8 +10467,7 @@ function isMcpWriteEnabled() {
|
|
|
10062
10467
|
const readonlyFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_READONLY"]);
|
|
10063
10468
|
if (readonlyFlag === true) return false;
|
|
10064
10469
|
const writesFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_WRITES"]);
|
|
10065
|
-
|
|
10066
|
-
return true;
|
|
10470
|
+
return writesFlag === true;
|
|
10067
10471
|
}
|
|
10068
10472
|
function getMcpModeInfo() {
|
|
10069
10473
|
const writesEnabled = isMcpWriteEnabled();
|
|
@@ -10074,7 +10478,7 @@ function getMcpModeInfo() {
|
|
|
10074
10478
|
AGENT_INSPECTOR_MCP_READONLY: process.env["AGENT_INSPECTOR_MCP_READONLY"] ?? null,
|
|
10075
10479
|
AGENT_INSPECTOR_MCP_WRITES: process.env["AGENT_INSPECTOR_MCP_WRITES"] ?? null
|
|
10076
10480
|
},
|
|
10077
|
-
note: writesEnabled ? "MCP write/action tools are enabled
|
|
10481
|
+
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
10482
|
};
|
|
10079
10483
|
}
|
|
10080
10484
|
function groupEvidenceDir(groupId) {
|
|
@@ -10646,10 +11050,6 @@ function toolError(message) {
|
|
|
10646
11050
|
function inspectorBaseUrl() {
|
|
10647
11051
|
return `http://127.0.0.1:${String(getCurrentPort())}`;
|
|
10648
11052
|
}
|
|
10649
|
-
function textByteLength(value) {
|
|
10650
|
-
if (value === null) return null;
|
|
10651
|
-
return Buffer.byteLength(value, "utf8");
|
|
10652
|
-
}
|
|
10653
11053
|
function hasBodyText(value, byteLength) {
|
|
10654
11054
|
if (value !== null && value.length > 0) return true;
|
|
10655
11055
|
return byteLength !== void 0 && byteLength !== null && byteLength > 0;
|
|
@@ -10780,14 +11180,29 @@ function processSummary(clientPid, logs, total, limit) {
|
|
|
10780
11180
|
}
|
|
10781
11181
|
async function readCompactLogsPage(callApi2, params) {
|
|
10782
11182
|
const path2 = `/api/logs?${params.toString()}`;
|
|
10783
|
-
const
|
|
10784
|
-
if (!
|
|
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");
|
|
11183
|
+
const parsed = await fetchApiJson(callApi2, path2, LogsListResponseSchema);
|
|
11184
|
+
if (!parsed.ok) return parsed.error;
|
|
10788
11185
|
const logs = parsed.data.logs ?? [];
|
|
10789
11186
|
return { logs, total: parsed.data.total ?? logs.length };
|
|
10790
11187
|
}
|
|
11188
|
+
async function fetchApiJson(callApi2, path2, schema, init) {
|
|
11189
|
+
const res = await callApi2(path2, init);
|
|
11190
|
+
if (!res.ok) {
|
|
11191
|
+
return { ok: false, error: toolError(`${methodFor(init)} ${path2} returned ${res.status}`) };
|
|
11192
|
+
}
|
|
11193
|
+
const rawBody = await res.json();
|
|
11194
|
+
const parsed = schema.safeParse(rawBody);
|
|
11195
|
+
if (!parsed.success) {
|
|
11196
|
+
return {
|
|
11197
|
+
ok: false,
|
|
11198
|
+
error: toolError(`${methodFor(init)} ${path2} returned an unparseable shape`)
|
|
11199
|
+
};
|
|
11200
|
+
}
|
|
11201
|
+
return { ok: true, data: parsed.data };
|
|
11202
|
+
}
|
|
11203
|
+
function methodFor(init) {
|
|
11204
|
+
return (init?.method ?? "GET").toUpperCase();
|
|
11205
|
+
}
|
|
10791
11206
|
function isToolResult(value) {
|
|
10792
11207
|
return typeof value === "object" && value !== null && Object.getOwnPropertyDescriptor(value, "content") !== void 0;
|
|
10793
11208
|
}
|
|
@@ -10847,13 +11262,8 @@ async function searchLogsImpl(callApi2, args) {
|
|
|
10847
11262
|
});
|
|
10848
11263
|
}
|
|
10849
11264
|
async function getLogImpl(callApi2, id) {
|
|
10850
|
-
const
|
|
10851
|
-
if (!
|
|
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
|
-
}
|
|
11265
|
+
const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
|
|
11266
|
+
if (!parsed.ok) return parsed.error;
|
|
10857
11267
|
return textJson(buildBoundedLogDetail(parsed.data));
|
|
10858
11268
|
}
|
|
10859
11269
|
async function getLogChunksImpl(callApi2, id) {
|
|
@@ -10875,11 +11285,8 @@ async function getSessionImpl(callApi2, args) {
|
|
|
10875
11285
|
params.set("includeHistory", "1");
|
|
10876
11286
|
}
|
|
10877
11287
|
const path2 = `/api/sessions?${params.toString()}`;
|
|
10878
|
-
const
|
|
10879
|
-
if (!
|
|
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");
|
|
11288
|
+
const parsed = await fetchApiJson(callApi2, path2, SessionInfoSchema);
|
|
11289
|
+
if (!parsed.ok) return parsed.error;
|
|
10883
11290
|
return textJson(parsed.data);
|
|
10884
11291
|
}
|
|
10885
11292
|
function createRunImpl(args) {
|
|
@@ -11089,13 +11496,8 @@ async function getProcessTimelineImpl(callApi2, args) {
|
|
|
11089
11496
|
});
|
|
11090
11497
|
}
|
|
11091
11498
|
async function getLogHeadersImpl(callApi2, id) {
|
|
11092
|
-
const
|
|
11093
|
-
if (!
|
|
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
|
-
}
|
|
11499
|
+
const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
|
|
11500
|
+
if (!parsed.ok) return parsed.error;
|
|
11099
11501
|
const log = parsed.data;
|
|
11100
11502
|
return textJson({
|
|
11101
11503
|
id: log.id,
|
|
@@ -11346,18 +11748,13 @@ async function listModelsImpl(callApi2) {
|
|
|
11346
11748
|
if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
|
|
11347
11749
|
return textJson(await res.json());
|
|
11348
11750
|
}
|
|
11349
|
-
function
|
|
11350
|
-
|
|
11351
|
-
}
|
|
11352
|
-
async function listProvidersImpl(callApi2, args = {}) {
|
|
11353
|
-
const res = await callApi2(`/api/providers${includeSecretsQuery(args.includeSecrets)}`);
|
|
11751
|
+
async function listProvidersImpl(callApi2) {
|
|
11752
|
+
const res = await callApi2("/api/providers");
|
|
11354
11753
|
if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
|
|
11355
11754
|
return textJson(await res.json());
|
|
11356
11755
|
}
|
|
11357
|
-
async function getProviderImpl(callApi2, id
|
|
11358
|
-
const res = await callApi2(
|
|
11359
|
-
`/api/providers/${encodeURIComponent(id)}${includeSecretsQuery(args.includeSecrets)}`
|
|
11360
|
-
);
|
|
11756
|
+
async function getProviderImpl(callApi2, id) {
|
|
11757
|
+
const res = await callApi2(`/api/providers/${encodeURIComponent(id)}`);
|
|
11361
11758
|
if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
|
|
11362
11759
|
return textJson(await res.json());
|
|
11363
11760
|
}
|
|
@@ -11417,9 +11814,6 @@ async function testProviderImpl(callApi2, id) {
|
|
|
11417
11814
|
if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
|
|
11418
11815
|
return textJson(await res.json());
|
|
11419
11816
|
}
|
|
11420
|
-
function isRecord(value) {
|
|
11421
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11422
|
-
}
|
|
11423
11817
|
function stringField(record2, key) {
|
|
11424
11818
|
const value = record2[key];
|
|
11425
11819
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
@@ -12442,24 +12836,21 @@ function registerTools(server) {
|
|
|
12442
12836
|
"inspector_list_providers",
|
|
12443
12837
|
{
|
|
12444
12838
|
title: "List configured LLM providers",
|
|
12445
|
-
description: "Returns the configured ProviderConfig array with
|
|
12446
|
-
inputSchema: object({
|
|
12447
|
-
includeSecrets: boolean().optional().describe("Return plaintext provider API keys. Exposes local secrets when true.")
|
|
12448
|
-
})
|
|
12839
|
+
description: "Returns the configured ProviderConfig array with write-only API keys represented as configured.",
|
|
12840
|
+
inputSchema: object({})
|
|
12449
12841
|
},
|
|
12450
|
-
(
|
|
12842
|
+
() => safeCall(() => listProvidersImpl(callApi))
|
|
12451
12843
|
);
|
|
12452
12844
|
server.registerTool(
|
|
12453
12845
|
"inspector_get_provider",
|
|
12454
12846
|
{
|
|
12455
12847
|
title: "Get a single provider by id",
|
|
12456
|
-
description: "Returns one ProviderConfig
|
|
12848
|
+
description: "Returns one ProviderConfig. API keys are write-only and represented as configured.",
|
|
12457
12849
|
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.")
|
|
12850
|
+
id: string().describe("The provider id.")
|
|
12460
12851
|
})
|
|
12461
12852
|
},
|
|
12462
|
-
({ id
|
|
12853
|
+
({ id }) => safeCall(() => getProviderImpl(callApi, id))
|
|
12463
12854
|
);
|
|
12464
12855
|
server.registerTool(
|
|
12465
12856
|
"inspector_replay_log",
|
|
@@ -14601,6 +14992,7 @@ const Route$u = createFileRoute("/api/providers/export")({
|
|
|
14601
14992
|
}
|
|
14602
14993
|
}
|
|
14603
14994
|
});
|
|
14995
|
+
const ALLOW_LOOPBACK = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
|
|
14604
14996
|
const ProviderUpdateSchema = object({
|
|
14605
14997
|
name: string().min(1, "Name is required").optional(),
|
|
14606
14998
|
apiKey: string().min(1, "API key is required").optional(),
|
|
@@ -14620,26 +15012,36 @@ const ProviderUpdateSchema = object({
|
|
|
14620
15012
|
const Route$t = createFileRoute("/api/providers/$providerId")({
|
|
14621
15013
|
server: {
|
|
14622
15014
|
handlers: {
|
|
14623
|
-
GET: ({ params
|
|
14624
|
-
const url = new URL(request.url);
|
|
14625
|
-
const includeSecrets = url.searchParams.get("includeSecrets") === "1";
|
|
15015
|
+
GET: ({ params }) => {
|
|
14626
15016
|
const providers = getProviders();
|
|
14627
15017
|
const provider = providers.find((p) => p.id === params.providerId);
|
|
14628
15018
|
if (!provider) {
|
|
14629
15019
|
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
14630
15020
|
}
|
|
14631
|
-
return Response.json(
|
|
15021
|
+
return Response.json(redactProvider(provider));
|
|
14632
15022
|
},
|
|
14633
15023
|
PUT: async ({ params, request }) => {
|
|
14634
15024
|
const parsed = ProviderUpdateSchema.safeParse(await request.json());
|
|
14635
15025
|
if (!parsed.success) {
|
|
14636
15026
|
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
14637
15027
|
}
|
|
15028
|
+
for (const candidate of [
|
|
15029
|
+
parsed.data.baseUrl,
|
|
15030
|
+
parsed.data.anthropicBaseUrl,
|
|
15031
|
+
parsed.data.openaiBaseUrl,
|
|
15032
|
+
parsed.data.openaiResponsesBaseUrl
|
|
15033
|
+
]) {
|
|
15034
|
+
if (candidate === void 0 || candidate === "") continue;
|
|
15035
|
+
const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK });
|
|
15036
|
+
if (!decision.allow) {
|
|
15037
|
+
return Response.json({ error: decision.reason }, { status: 400 });
|
|
15038
|
+
}
|
|
15039
|
+
}
|
|
14638
15040
|
const updated = updateProvider(params.providerId, parsed.data);
|
|
14639
15041
|
if (!updated) {
|
|
14640
15042
|
return Response.json({ error: "Provider not found" }, { status: 404 });
|
|
14641
15043
|
}
|
|
14642
|
-
return Response.json(updated);
|
|
15044
|
+
return Response.json(redactProvider(updated));
|
|
14643
15045
|
},
|
|
14644
15046
|
DELETE: ({ params }) => {
|
|
14645
15047
|
const deleted = deleteProvider(params.providerId);
|
|
@@ -14725,6 +15127,8 @@ const Route$s = createFileRoute("/api/logs/stream")({
|
|
|
14725
15127
|
}
|
|
14726
15128
|
}
|
|
14727
15129
|
});
|
|
15130
|
+
const MAX_ZIP_COMPRESSED_BYTES = 200 * 1024 * 1024;
|
|
15131
|
+
const MAX_ZIP_DECOMPRESSED_BYTES = 500 * 1024 * 1024;
|
|
14728
15132
|
const ManifestSchema = object({
|
|
14729
15133
|
version: string().optional(),
|
|
14730
15134
|
exportedAt: string().optional(),
|
|
@@ -14925,12 +15329,15 @@ function legacyDraftFromParts(input) {
|
|
|
14925
15329
|
error: null
|
|
14926
15330
|
});
|
|
14927
15331
|
}
|
|
14928
|
-
async function readZipText(zip, path2) {
|
|
15332
|
+
async function readZipText(zip, path2, remaining) {
|
|
14929
15333
|
const file = zip.file(path2);
|
|
14930
|
-
|
|
15334
|
+
if (file === null) return null;
|
|
15335
|
+
const text = await file.async("text");
|
|
15336
|
+
remaining.bytes -= Buffer.byteLength(text, "utf8");
|
|
15337
|
+
return text;
|
|
14931
15338
|
}
|
|
14932
|
-
async function readManifest(zip) {
|
|
14933
|
-
const text = await readZipText(zip, "manifest.json");
|
|
15339
|
+
async function readManifest(zip, remaining) {
|
|
15340
|
+
const text = await readZipText(zip, "manifest.json", remaining);
|
|
14934
15341
|
const parsed = ManifestSchema.safeParse(parseJson(text));
|
|
14935
15342
|
return parsed.success ? parsed.data : null;
|
|
14936
15343
|
}
|
|
@@ -14976,10 +15383,19 @@ function validateManifestVersion(manifest) {
|
|
|
14976
15383
|
}
|
|
14977
15384
|
return { ok: true, warning: null };
|
|
14978
15385
|
}
|
|
15386
|
+
function bombError(warnings = []) {
|
|
15387
|
+
return {
|
|
15388
|
+
ok: false,
|
|
15389
|
+
message: `Archive decompresses to more than ${MAX_ZIP_DECOMPRESSED_BYTES} bytes.`,
|
|
15390
|
+
warnings
|
|
15391
|
+
};
|
|
15392
|
+
}
|
|
14979
15393
|
async function parseZipLogImport(data) {
|
|
14980
15394
|
const warnings = [];
|
|
14981
15395
|
const zip = await JSZip.loadAsync(data);
|
|
14982
|
-
const
|
|
15396
|
+
const remaining = { bytes: MAX_ZIP_DECOMPRESSED_BYTES };
|
|
15397
|
+
const manifest = await readManifest(zip, remaining);
|
|
15398
|
+
if (remaining.bytes < 0) return bombError();
|
|
14983
15399
|
const versionCheck = validateManifestVersion(manifest);
|
|
14984
15400
|
if (!versionCheck.ok) {
|
|
14985
15401
|
return {
|
|
@@ -14995,15 +15411,21 @@ async function parseZipLogImport(data) {
|
|
|
14995
15411
|
if (manifest?.redacted === true) {
|
|
14996
15412
|
warnings.push("Imported redacted export. Secrets and some replay details may be unavailable.");
|
|
14997
15413
|
}
|
|
14998
|
-
const metadataText2 = await readZipText(zip, "logs.json");
|
|
15414
|
+
const metadataText2 = await readZipText(zip, "logs.json", remaining);
|
|
15415
|
+
if (remaining.bytes < 0) return bombError(warnings);
|
|
14999
15416
|
const metadataLogs = metadataText2 === null ? null : parseJsonLogs(metadataText2);
|
|
15000
15417
|
const logs = [];
|
|
15001
15418
|
let skipped = 0;
|
|
15002
15419
|
if (metadataLogs !== null) {
|
|
15003
15420
|
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(
|
|
15421
|
+
const requestText = await readZipText(zip, `#${String(log.id)}.Request.json`, remaining);
|
|
15422
|
+
const responseText = await readZipText(zip, `#${String(log.id)}.Response.json`, remaining);
|
|
15423
|
+
const streamingText = await readZipText(
|
|
15424
|
+
zip,
|
|
15425
|
+
`#${String(log.id)}.SSE.Response.json`,
|
|
15426
|
+
remaining
|
|
15427
|
+
);
|
|
15428
|
+
if (remaining.bytes < 0) return bombError(warnings);
|
|
15007
15429
|
logs.push(
|
|
15008
15430
|
draftFromCapturedLog(
|
|
15009
15431
|
log,
|
|
@@ -15016,9 +15438,14 @@ async function parseZipLogImport(data) {
|
|
|
15016
15438
|
return { ok: true, logs, skipped, warnings };
|
|
15017
15439
|
}
|
|
15018
15440
|
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(
|
|
15441
|
+
const requestText = await readZipText(zip, `#${String(originalId)}.Request.json`, remaining);
|
|
15442
|
+
const responseText = await readZipText(zip, `#${String(originalId)}.Response.json`, remaining);
|
|
15443
|
+
const streamingText = await readZipText(
|
|
15444
|
+
zip,
|
|
15445
|
+
`#${String(originalId)}.SSE.Response.json`,
|
|
15446
|
+
remaining
|
|
15447
|
+
);
|
|
15448
|
+
if (remaining.bytes < 0) return bombError(warnings);
|
|
15022
15449
|
if (requestText === null && responseText === null && streamingText === null) {
|
|
15023
15450
|
skipped += 1;
|
|
15024
15451
|
continue;
|
|
@@ -15073,8 +15500,22 @@ function parseTextLogImport(text) {
|
|
|
15073
15500
|
async function parseImportedLogFile(input) {
|
|
15074
15501
|
try {
|
|
15075
15502
|
if (looksLikeZip(input.data, input.fileName)) {
|
|
15503
|
+
if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
|
|
15504
|
+
return {
|
|
15505
|
+
ok: false,
|
|
15506
|
+
message: `Archive exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
|
|
15507
|
+
warnings: []
|
|
15508
|
+
};
|
|
15509
|
+
}
|
|
15076
15510
|
return await parseZipLogImport(input.data);
|
|
15077
15511
|
}
|
|
15512
|
+
if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
|
|
15513
|
+
return {
|
|
15514
|
+
ok: false,
|
|
15515
|
+
message: `Import file exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
|
|
15516
|
+
warnings: []
|
|
15517
|
+
};
|
|
15518
|
+
}
|
|
15078
15519
|
return parseTextLogImport(new TextDecoder("utf-8").decode(input.data));
|
|
15079
15520
|
} catch (err) {
|
|
15080
15521
|
return {
|
|
@@ -15352,19 +15793,25 @@ async function searchOpenClawGateway(query, project) {
|
|
|
15352
15793
|
warning: "OpenClaw Gateway is not configured. Set OPENCLAW_GATEWAY_URL."
|
|
15353
15794
|
};
|
|
15354
15795
|
}
|
|
15355
|
-
const
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15796
|
+
const fetchResult = await safeFetch(
|
|
15797
|
+
buildToolInvokeUrl(gatewayUrl),
|
|
15798
|
+
{
|
|
15799
|
+
method: "POST",
|
|
15800
|
+
headers: gatewayHeaders(),
|
|
15801
|
+
body: JSON.stringify({
|
|
15802
|
+
tool: "memory_search",
|
|
15803
|
+
args: {
|
|
15804
|
+
query: buildSearchQuery(query, project),
|
|
15805
|
+
maxResults: 10,
|
|
15806
|
+
corpus: "all"
|
|
15807
|
+
},
|
|
15808
|
+
sessionKey: getOpenClawSessionKey()
|
|
15809
|
+
})
|
|
15810
|
+
},
|
|
15811
|
+
{ bypass: true }
|
|
15812
|
+
);
|
|
15813
|
+
if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
|
|
15814
|
+
const response = fetchResult.response;
|
|
15368
15815
|
const raw = await response.json().catch(() => null);
|
|
15369
15816
|
if (!response.ok) {
|
|
15370
15817
|
return {
|
|
@@ -15619,11 +16066,17 @@ async function promoteToOpenClaw(candidate) {
|
|
|
15619
16066
|
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
16067
|
};
|
|
15621
16068
|
}
|
|
15622
|
-
const
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
16069
|
+
const fetchResult = await safeFetch(
|
|
16070
|
+
buildMemoryUrl(endpoint),
|
|
16071
|
+
{
|
|
16072
|
+
method: "POST",
|
|
16073
|
+
headers: authHeaders(),
|
|
16074
|
+
body: JSON.stringify(buildOpenClawPayload(candidate))
|
|
16075
|
+
},
|
|
16076
|
+
{ bypass: true }
|
|
16077
|
+
);
|
|
16078
|
+
if (!fetchResult.ok) return { success: false, error: fetchResult.reason };
|
|
16079
|
+
const response = fetchResult.response;
|
|
15627
16080
|
if (!response.ok) {
|
|
15628
16081
|
return { success: false, error: `OpenClaw memory write failed with ${response.status}` };
|
|
15629
16082
|
}
|
|
@@ -15646,10 +16099,16 @@ async function searchOpenClaw(query, project) {
|
|
|
15646
16099
|
warning: `OpenClaw search backend is not configured. Set OPENCLAW_GATEWAY_URL or OPENCLAW_MEMORY_URL.${workspaceHint}`
|
|
15647
16100
|
};
|
|
15648
16101
|
}
|
|
15649
|
-
const
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
16102
|
+
const fetchResult = await safeFetch(
|
|
16103
|
+
buildSearchUrl(endpoint, query, project),
|
|
16104
|
+
{
|
|
16105
|
+
method: "GET",
|
|
16106
|
+
headers: authHeaders()
|
|
16107
|
+
},
|
|
16108
|
+
{ bypass: true }
|
|
16109
|
+
);
|
|
16110
|
+
if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
|
|
16111
|
+
const response = fetchResult.response;
|
|
15653
16112
|
if (!response.ok) {
|
|
15654
16113
|
return { results: [], warning: `OpenClaw search failed with ${response.status}` };
|
|
15655
16114
|
}
|
|
@@ -16770,12 +17229,20 @@ async function testEndpoint(baseUrl, provider, path2, model, protocol) {
|
|
|
16770
17229
|
const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
|
|
16771
17230
|
try {
|
|
16772
17231
|
requestUrl = buildUpstreamUrl(baseUrl, path2);
|
|
16773
|
-
const
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
17232
|
+
const fetchResult = await safeFetch(
|
|
17233
|
+
requestUrl,
|
|
17234
|
+
{
|
|
17235
|
+
method: "POST",
|
|
17236
|
+
headers: requestHeaders,
|
|
17237
|
+
body,
|
|
17238
|
+
signal: controller.signal
|
|
17239
|
+
},
|
|
17240
|
+
{ allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
|
|
17241
|
+
);
|
|
17242
|
+
if (!fetchResult.ok) {
|
|
17243
|
+
return createErrorResult(fetchResult.reason, Date.now() - startTime, false);
|
|
17244
|
+
}
|
|
17245
|
+
const response = fetchResult.response;
|
|
16779
17246
|
const latencyMs = Date.now() - startTime;
|
|
16780
17247
|
if (!response.ok) {
|
|
16781
17248
|
const responseText2 = await readResponseText(response);
|
|
@@ -16894,12 +17361,20 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, protocol)
|
|
|
16894
17361
|
const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
|
|
16895
17362
|
try {
|
|
16896
17363
|
requestUrl = buildUpstreamUrl(baseUrl, path2);
|
|
16897
|
-
const
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
17364
|
+
const fetchResult = await safeFetch(
|
|
17365
|
+
requestUrl,
|
|
17366
|
+
{
|
|
17367
|
+
method: "POST",
|
|
17368
|
+
headers: requestHeaders,
|
|
17369
|
+
body,
|
|
17370
|
+
signal: controller.signal
|
|
17371
|
+
},
|
|
17372
|
+
{ allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
|
|
17373
|
+
);
|
|
17374
|
+
if (!fetchResult.ok) {
|
|
17375
|
+
return createErrorResult(fetchResult.reason, Date.now() - startTime, true);
|
|
17376
|
+
}
|
|
17377
|
+
const response = fetchResult.response;
|
|
16903
17378
|
const latencyMs = Date.now() - startTime;
|
|
16904
17379
|
if (!response.ok) {
|
|
16905
17380
|
const responseText = await readResponseText(response);
|
|
@@ -17281,7 +17756,16 @@ const Route$f = createFileRoute("/api/providers/$providerId/model-metadata")({
|
|
|
17281
17756
|
}
|
|
17282
17757
|
let response;
|
|
17283
17758
|
try {
|
|
17284
|
-
|
|
17759
|
+
const fetchResult = await safeFetch(sourceUrl, {
|
|
17760
|
+
headers: { Accept: "application/json" }
|
|
17761
|
+
});
|
|
17762
|
+
if (!fetchResult.ok) {
|
|
17763
|
+
return Response.json(
|
|
17764
|
+
{ error: `Model metadata URL blocked: ${fetchResult.reason}` },
|
|
17765
|
+
{ status: 400 }
|
|
17766
|
+
);
|
|
17767
|
+
}
|
|
17768
|
+
response = fetchResult.response;
|
|
17285
17769
|
} catch (err) {
|
|
17286
17770
|
return Response.json(
|
|
17287
17771
|
{ error: `Failed to fetch model metadata: ${String(err)}` },
|
|
@@ -17393,11 +17877,22 @@ const Route$e = createFileRoute("/api/logs/$id/replay")({
|
|
|
17393
17877
|
const startTime = Date.now();
|
|
17394
17878
|
let upstreamRes;
|
|
17395
17879
|
try {
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17880
|
+
const fetchResult = await safeFetch(
|
|
17881
|
+
upstreamUrl,
|
|
17882
|
+
{
|
|
17883
|
+
method: "POST",
|
|
17884
|
+
headers,
|
|
17885
|
+
body: modifiedBody
|
|
17886
|
+
},
|
|
17887
|
+
{ allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
|
|
17888
|
+
);
|
|
17889
|
+
if (!fetchResult.ok) {
|
|
17890
|
+
return Response.json({
|
|
17891
|
+
success: false,
|
|
17892
|
+
error: fetchResult.reason
|
|
17893
|
+
});
|
|
17894
|
+
}
|
|
17895
|
+
upstreamRes = fetchResult.response;
|
|
17401
17896
|
} catch (err) {
|
|
17402
17897
|
return Response.json({
|
|
17403
17898
|
success: false,
|
|
@@ -18778,13 +19273,13 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
18778
19273
|
export {
|
|
18779
19274
|
PiAgentResponseSchema as $,
|
|
18780
19275
|
AnthropicResponseSchema$1 as A,
|
|
18781
|
-
|
|
19276
|
+
DeleteInspectorGroupsResponseSchema as B,
|
|
18782
19277
|
CapturedLogSchema as C,
|
|
18783
19278
|
DEFAULT_CAPTURE_MODE as D,
|
|
18784
|
-
|
|
18785
|
-
|
|
19279
|
+
GroupEvidenceExportResultSchema as E,
|
|
19280
|
+
DeleteInspectorGroupResponseSchema as F,
|
|
18786
19281
|
GroupEvidenceReadResponseSchema as G,
|
|
18787
|
-
|
|
19282
|
+
providerHasContextMetadata as H,
|
|
18788
19283
|
InspectorGroupsListResponseSchema as I,
|
|
18789
19284
|
findProviderModelMetadata as J,
|
|
18790
19285
|
KnowledgeCandidateSchema as K,
|
|
@@ -18817,22 +19312,22 @@ export {
|
|
|
18817
19312
|
persistThemeMode as f,
|
|
18818
19313
|
readStoredUiScalePreference as g,
|
|
18819
19314
|
persistUiScalePreference as h,
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
19315
|
+
isRecord as i,
|
|
19316
|
+
applyUiScalePreference as j,
|
|
19317
|
+
normalizeUiScalePreference as k,
|
|
19318
|
+
AnthropicRequestSchema as l,
|
|
19319
|
+
parseOpenAIResponse as m,
|
|
18825
19320
|
normalizeThemeMode as n,
|
|
18826
|
-
|
|
19321
|
+
parseOpenAIResponsesResponse as o,
|
|
18827
19322
|
packageJson as p,
|
|
18828
|
-
|
|
19323
|
+
OpenAIResponsesRequestSchema as q,
|
|
18829
19324
|
readStoredThemeMode as r,
|
|
18830
19325
|
safeGetOwnProperty as s,
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
19326
|
+
apiFormatForPath as t,
|
|
19327
|
+
getSessionPath as u,
|
|
19328
|
+
stripClaudeCodeBillingHeader as v,
|
|
19329
|
+
isTurnBoundary as w,
|
|
19330
|
+
extractStopReason as x,
|
|
19331
|
+
AlertSummarySchema as y,
|
|
19332
|
+
AlertListResponseSchema as z
|
|
18838
19333
|
};
|