@tonyclaw/llm-inspector 1.8.0 → 1.9.1
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/nitro.json +1 -1
- package/.output/public/assets/{index-DH3FOgcK.js → index-BmEH5jeO.js} +18 -18
- package/.output/public/assets/{index-BLVa7n9b.css → index-DdJSLfxK.css} +1 -1
- package/.output/public/assets/{main-Beo3LJDa.js → main-GVpFMVGE.js} +1 -1
- package/.output/server/_ssr/{index-HkueJ4Un.mjs → index-D0d6QxPt.mjs} +85 -31
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DTswxb7l.mjs → router-D9uLXa9A.mjs} +287 -67
- package/.output/server/{_tanstack-start-manifest_v-DhUuivt-.mjs → _tanstack-start-manifest_v-ByfnNZV_.mjs} +1 -1
- package/.output/server/index.mjs +26 -26
- package/README.md +8 -209
- package/package.json +1 -1
- package/src/components/ProxyViewerContainer.tsx +10 -1
- package/src/components/providers/ProviderCard.tsx +19 -15
- package/src/components/providers/ProviderForm.tsx +21 -0
- package/src/components/proxy-viewer/LogEntry.tsx +7 -0
- package/src/components/proxy-viewer/ResponseView.tsx +32 -6
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +12 -3
- package/src/proxy/chunkStorage.ts +4 -6
- package/src/proxy/formats/anthropic/schemas.ts +9 -0
- package/src/proxy/formats/anthropic/stream.ts +11 -0
- package/src/proxy/formats/openai/stream.ts +15 -0
- package/src/proxy/handler.ts +44 -27
- package/src/proxy/logIndex.ts +73 -7
- package/src/proxy/logger.ts +62 -6
- package/src/proxy/providers.ts +5 -0
- package/src/proxy/schemas.ts +2 -0
- package/src/proxy/socketTracker.ts +90 -36
- package/src/proxy/store.ts +32 -18
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +2 -0
package/src/proxy/store.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync, existsSync, createReadStream } from "node:fs";
|
|
3
|
+
import { readdir as readdirCallback } from "node:fs/promises";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { appendLogEntry, resolveLogDir } from "./logger";
|
|
6
|
+
import { appendLogEntry, resolveLogDir, logger } from "./logger";
|
|
7
7
|
import {
|
|
8
8
|
addToIndex,
|
|
9
9
|
findInIndex,
|
|
@@ -258,18 +258,16 @@ export async function getLogById(id: number): Promise<CapturedLog | null> {
|
|
|
258
258
|
// Fallback: return the last matching entry (incomplete is better than nothing)
|
|
259
259
|
if (lastMatch !== null) return lastMatch;
|
|
260
260
|
} catch (err) {
|
|
261
|
-
|
|
262
|
-
console.error("[store] Failed to read log from disk:", err);
|
|
261
|
+
logger.error("[store] Failed to read log from disk:", String(err));
|
|
263
262
|
}
|
|
264
263
|
|
|
265
264
|
return null;
|
|
266
265
|
}
|
|
267
266
|
|
|
268
267
|
export function getFilteredLogs(sessionId?: string, model?: string): CapturedLog[] {
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return cachedLogs.filter((l) => {
|
|
268
|
+
// Cache maintains insertion order (sorted by ID since logs are added in ID order)
|
|
269
|
+
// Use spread instead of Array.from for slightly better performance
|
|
270
|
+
return [...memoryCache.values()].filter((l) => {
|
|
273
271
|
if (sessionId !== undefined && l.sessionId !== sessionId) return false;
|
|
274
272
|
if (model !== undefined && l.model !== model) return false;
|
|
275
273
|
return true;
|
|
@@ -293,20 +291,32 @@ export function getModels(): string[] {
|
|
|
293
291
|
}
|
|
294
292
|
|
|
295
293
|
/**
|
|
296
|
-
* Load recent completed log entries from
|
|
294
|
+
* Load recent completed log entries from all log files into the memory cache.
|
|
297
295
|
* Called on server startup so the frontend shows existing logs.
|
|
298
296
|
*/
|
|
299
297
|
export async function loadLogsIntoMemory(): Promise<void> {
|
|
300
298
|
const logDir = resolveLogDir();
|
|
301
299
|
if (!existsSync(logDir)) return;
|
|
302
300
|
|
|
303
|
-
const today = getCurrentLogFile();
|
|
304
|
-
const filePath = join(logDir, today);
|
|
305
|
-
if (!existsSync(filePath)) return;
|
|
306
|
-
|
|
307
301
|
// Keep track of IDs seen so we only load the completed entry for each
|
|
308
302
|
const seenComplete = new Set<number>();
|
|
309
303
|
|
|
304
|
+
// Get all .jsonl files sorted by name (date)
|
|
305
|
+
try {
|
|
306
|
+
const entries = await readdirCallback(logDir);
|
|
307
|
+
const filesWithExt = entries.filter((f: string) => f.endsWith(".jsonl")).sort();
|
|
308
|
+
for (const file of filesWithExt) {
|
|
309
|
+
const filePath = join(logDir, file);
|
|
310
|
+
await loadLogFile(filePath, seenComplete);
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
logger.error("[store] Failed to read log directory:", String(err));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function loadLogFile(filePath: string, seenComplete: Set<number>): Promise<void> {
|
|
318
|
+
if (!existsSync(filePath)) return;
|
|
319
|
+
|
|
310
320
|
try {
|
|
311
321
|
const fileStream = createInterface({
|
|
312
322
|
input: createReadStream(filePath),
|
|
@@ -348,8 +358,7 @@ export async function loadLogsIntoMemory(): Promise<void> {
|
|
|
348
358
|
}
|
|
349
359
|
}
|
|
350
360
|
} catch (err) {
|
|
351
|
-
|
|
352
|
-
console.error("[store] Failed to load logs into memory:", err);
|
|
361
|
+
logger.error("[store] Failed to load logs into memory:", String(err));
|
|
353
362
|
}
|
|
354
363
|
}
|
|
355
364
|
|
|
@@ -375,12 +384,17 @@ export function onLogUpdate(handler: LogUpdateHandler): () => void {
|
|
|
375
384
|
}
|
|
376
385
|
|
|
377
386
|
export function emitLogUpdate(log: CapturedLog): void {
|
|
387
|
+
const failedHandlers: LogUpdateHandler[] = [];
|
|
378
388
|
for (const handler of sseHandlers) {
|
|
379
389
|
try {
|
|
380
390
|
handler(log);
|
|
381
391
|
} catch {
|
|
382
|
-
//
|
|
383
|
-
|
|
392
|
+
// Collect failed handlers to remove after iteration
|
|
393
|
+
failedHandlers.push(handler);
|
|
384
394
|
}
|
|
385
395
|
}
|
|
396
|
+
// Remove failed handlers after iteration to avoid Set mutation during for...of
|
|
397
|
+
for (const handler of failedHandlers) {
|
|
398
|
+
sseHandlers.delete(handler);
|
|
399
|
+
}
|
|
386
400
|
}
|
|
@@ -11,6 +11,7 @@ const ProviderUpdateSchema = z.object({
|
|
|
11
11
|
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
12
12
|
anthropicBaseUrl: z.string().optional(),
|
|
13
13
|
openaiBaseUrl: z.string().optional(),
|
|
14
|
+
apiDocsUrl: z.string().optional(),
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
export const Route = createFileRoute("/api/providers/$providerId")({
|
|
@@ -9,6 +9,7 @@ const ProviderInputSchema = z.object({
|
|
|
9
9
|
baseUrl: z.string().min(1, "Base URL is required"),
|
|
10
10
|
model: z.string().min(1, "Model is required"),
|
|
11
11
|
authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
12
|
+
apiDocsUrl: z.string().optional(),
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
export const Route = createFileRoute("/api/providers")({
|
|
@@ -29,6 +30,7 @@ export const Route = createFileRoute("/api/providers")({
|
|
|
29
30
|
parsed.data.baseUrl,
|
|
30
31
|
parsed.data.model,
|
|
31
32
|
parsed.data.authHeader,
|
|
33
|
+
parsed.data.apiDocsUrl,
|
|
32
34
|
);
|
|
33
35
|
return Response.json(newProvider, { status: 201 });
|
|
34
36
|
},
|