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