@tonyclaw/llm-inspector 1.8.0 → 1.9.0
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-BLVa7n9b.css → index-DdJSLfxK.css} +1 -1
- package/.output/public/assets/{index-DH3FOgcK.js → index-DyKLPMPn.js} +18 -18
- package/.output/public/assets/{main-Beo3LJDa.js → main-Cu0oTDfX.js} +1 -1
- package/.output/server/_ssr/{index-HkueJ4Un.mjs → index-COIATcfa.mjs} +74 -28
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DTswxb7l.mjs → router-CwmgKXBJ.mjs} +236 -65
- package/.output/server/{_tanstack-start-manifest_v-DhUuivt-.mjs → _tanstack-start-manifest_v-C7hQOzvX.mjs} +1 -1
- package/.output/server/index.mjs +23 -23
- 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/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 +34 -27
- package/src/proxy/logIndex.ts +52 -7
- package/src/proxy/logger.ts +60 -10
- 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 +24 -14
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +2 -0
|
@@ -2,13 +2,13 @@ import { c as createRouter, a as createRootRoute, b as createFileRoute, l as laz
|
|
|
2
2
|
import { j as jsxRuntimeExports } from "../_libs/react.mjs";
|
|
3
3
|
import { S as SWRConfig } from "../_libs/swr.mjs";
|
|
4
4
|
import { mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import path, { join,
|
|
6
|
-
import {
|
|
5
|
+
import path, { join, isAbsolute, dirname } from "node:path";
|
|
6
|
+
import { mkdir, appendFile, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import { C as Conf } from "../_libs/conf.mjs";
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
9
9
|
import { exec } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
|
-
import { o as object,
|
|
11
|
+
import { o as object, s as string, _ as _enum, u as union, a as array, n as number, d as discriminatedUnion, l as literal, b as boolean, r as record, c as lazy, e as _null, f as unknown } from "../_libs/zod.mjs";
|
|
12
12
|
import "../_libs/tiny-warning.mjs";
|
|
13
13
|
import "../_libs/tanstack__router-core.mjs";
|
|
14
14
|
import "../_libs/cookie-es.mjs";
|
|
@@ -44,7 +44,7 @@ import "../_libs/debounce-fn.mjs";
|
|
|
44
44
|
import "../_libs/mimic-function.mjs";
|
|
45
45
|
import "../_libs/semver.mjs";
|
|
46
46
|
import "../_libs/uint8array-extras.mjs";
|
|
47
|
-
const appCss = "/assets/index-
|
|
47
|
+
const appCss = "/assets/index-DdJSLfxK.css";
|
|
48
48
|
const Route$g = createRootRoute({
|
|
49
49
|
head: () => ({
|
|
50
50
|
meta: [
|
|
@@ -68,12 +68,13 @@ function RootDocument({ children }) {
|
|
|
68
68
|
] })
|
|
69
69
|
] });
|
|
70
70
|
}
|
|
71
|
-
const $$splitComponentImporter = () => import("./index-
|
|
71
|
+
const $$splitComponentImporter = () => import("./index-COIATcfa.mjs");
|
|
72
72
|
const Route$f = createFileRoute("/")({
|
|
73
73
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
74
74
|
});
|
|
75
75
|
const LOG_DIR_ENV = process.env["LOG_DIR"];
|
|
76
76
|
Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
|
|
77
|
+
const LOG_FILE_ENV = process.env["LLM_INSPECTOR_LOG_FILE"];
|
|
77
78
|
function getUserDataDir() {
|
|
78
79
|
if (process.platform === "win32") {
|
|
79
80
|
return process.env["APPDATA"] ?? path.join(process.env["USERPROFILE"] ?? "C:\\", ".llm-inspector");
|
|
@@ -98,18 +99,59 @@ function getLogFilePath() {
|
|
|
98
99
|
const dd = String(date.getUTCDate()).padStart(2, "0");
|
|
99
100
|
return path.join(resolveLogDir(), `${yyyy}-${mm}-${dd}.jsonl`);
|
|
100
101
|
}
|
|
102
|
+
function getInspectorLogPath() {
|
|
103
|
+
if (LOG_FILE_ENV !== void 0) {
|
|
104
|
+
return LOG_FILE_ENV;
|
|
105
|
+
}
|
|
106
|
+
const base = getUserDataDir();
|
|
107
|
+
return path.join(base, ".llm-inspector", "logs", "inspector.log");
|
|
108
|
+
}
|
|
109
|
+
async function writeAppLog(message) {
|
|
110
|
+
try {
|
|
111
|
+
const logPath = getInspectorLogPath();
|
|
112
|
+
const logDirPath = path.dirname(logPath);
|
|
113
|
+
await mkdir(logDirPath, { recursive: true });
|
|
114
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
115
|
+
await appendFile(logPath, `[${timestamp}] ${message}
|
|
116
|
+
`, "utf-8");
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const logger = {
|
|
121
|
+
debug(message, ...args) {
|
|
122
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
123
|
+
void writeAppLog(`[DEBUG] ${msg}`);
|
|
124
|
+
},
|
|
125
|
+
info(message, ...args) {
|
|
126
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
127
|
+
void writeAppLog(`[INFO] ${msg}`);
|
|
128
|
+
},
|
|
129
|
+
warn(message, ...args) {
|
|
130
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
131
|
+
void writeAppLog(`[WARN] ${msg}`);
|
|
132
|
+
},
|
|
133
|
+
error(message, ...args) {
|
|
134
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
135
|
+
void writeAppLog(`[ERROR] ${msg}`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const MAX_BUFFER_SIZE = 1e3;
|
|
101
139
|
let writeBuffer = [];
|
|
102
140
|
let writeScheduled = false;
|
|
141
|
+
let isFlushing = false;
|
|
103
142
|
async function flushWriteBuffer() {
|
|
104
143
|
if (writeBuffer.length === 0) return;
|
|
144
|
+
if (isFlushing) return;
|
|
145
|
+
isFlushing = true;
|
|
105
146
|
const toWrite = writeBuffer.join("");
|
|
106
147
|
writeBuffer = [];
|
|
107
148
|
try {
|
|
108
149
|
const filePath = getLogFilePath();
|
|
109
150
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
110
151
|
await appendFile(filePath, toWrite, "utf-8");
|
|
111
|
-
} catch
|
|
112
|
-
|
|
152
|
+
} catch {
|
|
153
|
+
} finally {
|
|
154
|
+
isFlushing = false;
|
|
113
155
|
}
|
|
114
156
|
}
|
|
115
157
|
function scheduleFlush() {
|
|
@@ -123,7 +165,11 @@ function scheduleFlush() {
|
|
|
123
165
|
function appendLogEntry(entry) {
|
|
124
166
|
const line = JSON.stringify(entry) + "\n";
|
|
125
167
|
writeBuffer.push(line);
|
|
126
|
-
|
|
168
|
+
if (writeBuffer.length >= MAX_BUFFER_SIZE) {
|
|
169
|
+
void flushWriteBuffer();
|
|
170
|
+
} else {
|
|
171
|
+
scheduleFlush();
|
|
172
|
+
}
|
|
127
173
|
}
|
|
128
174
|
const INDEX_VERSION = 1;
|
|
129
175
|
const INDEX_FILE = "logs.idx";
|
|
@@ -162,7 +208,7 @@ async function loadIndex() {
|
|
|
162
208
|
}
|
|
163
209
|
return cachedIndex;
|
|
164
210
|
} catch (err) {
|
|
165
|
-
|
|
211
|
+
logger.error("[logIndex] Failed to load index:", String(err));
|
|
166
212
|
cachedIndex = createEmptyIndex();
|
|
167
213
|
return cachedIndex;
|
|
168
214
|
}
|
|
@@ -177,7 +223,7 @@ async function saveIndex(index) {
|
|
|
177
223
|
try {
|
|
178
224
|
await writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
179
225
|
} catch (err) {
|
|
180
|
-
|
|
226
|
+
logger.error("[logIndex] Failed to save index:", String(err));
|
|
181
227
|
}
|
|
182
228
|
}
|
|
183
229
|
async function addToIndex(id, file, lineStart, lineEnd) {
|
|
@@ -186,15 +232,40 @@ async function addToIndex(id, file, lineStart, lineEnd) {
|
|
|
186
232
|
if (id > index.maxId) {
|
|
187
233
|
index.maxId = id;
|
|
188
234
|
}
|
|
235
|
+
scheduleIndexFlush();
|
|
236
|
+
}
|
|
237
|
+
let indexFlushScheduled = false;
|
|
238
|
+
async function flushIndexAsync() {
|
|
239
|
+
indexFlushScheduled = false;
|
|
240
|
+
const index = await loadIndex();
|
|
189
241
|
await saveIndex(index);
|
|
190
242
|
}
|
|
243
|
+
function scheduleIndexFlush() {
|
|
244
|
+
if (indexFlushScheduled) return;
|
|
245
|
+
indexFlushScheduled = true;
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
void flushIndexAsync();
|
|
248
|
+
}, 1e3);
|
|
249
|
+
}
|
|
191
250
|
async function findInIndex(id) {
|
|
192
251
|
const index = await loadIndex();
|
|
193
252
|
return index.entries[id] ?? null;
|
|
194
253
|
}
|
|
254
|
+
let idGenerationLock = false;
|
|
195
255
|
async function getNextLogId() {
|
|
196
|
-
|
|
197
|
-
|
|
256
|
+
while (idGenerationLock) {
|
|
257
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
258
|
+
}
|
|
259
|
+
idGenerationLock = true;
|
|
260
|
+
try {
|
|
261
|
+
const index = await loadIndex();
|
|
262
|
+
const nextId = index.maxId + 1;
|
|
263
|
+
index.maxId = nextId;
|
|
264
|
+
cachedIndex = index;
|
|
265
|
+
return nextId;
|
|
266
|
+
} finally {
|
|
267
|
+
idGenerationLock = false;
|
|
268
|
+
}
|
|
198
269
|
}
|
|
199
270
|
function getCurrentLogFile() {
|
|
200
271
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -375,6 +446,13 @@ const SseMessageStopEvent = object({
|
|
|
375
446
|
const SsePingEvent = object({
|
|
376
447
|
type: literal("ping")
|
|
377
448
|
});
|
|
449
|
+
const SseErrorEvent = object({
|
|
450
|
+
type: literal("error"),
|
|
451
|
+
error: object({
|
|
452
|
+
type: string(),
|
|
453
|
+
message: string()
|
|
454
|
+
})
|
|
455
|
+
});
|
|
378
456
|
const SseEventSchema = discriminatedUnion("type", [
|
|
379
457
|
SseMessageStartEvent,
|
|
380
458
|
SseContentBlockStartEvent,
|
|
@@ -382,7 +460,8 @@ const SseEventSchema = discriminatedUnion("type", [
|
|
|
382
460
|
SseContentBlockStopEvent,
|
|
383
461
|
SseMessageDeltaEvent,
|
|
384
462
|
SseMessageStopEvent,
|
|
385
|
-
SsePingEvent
|
|
463
|
+
SsePingEvent,
|
|
464
|
+
SseErrorEvent
|
|
386
465
|
]);
|
|
387
466
|
const InspectorRequestSchema = AnthropicRequestSchema;
|
|
388
467
|
const InspectorResponseSchema = AnthropicResponseSchema$1;
|
|
@@ -541,7 +620,9 @@ const CapturedLogSchema = object({
|
|
|
541
620
|
clientCwd: string().nullable().optional(),
|
|
542
621
|
clientProjectFolder: string().nullable().optional(),
|
|
543
622
|
streamingChunks: StreamingChunksArraySchema.optional(),
|
|
544
|
-
streamingChunksPath: string().nullable().optional()
|
|
623
|
+
streamingChunksPath: string().nullable().optional(),
|
|
624
|
+
/** Error message from streaming response (e.g., SSE error event) */
|
|
625
|
+
error: string().nullable().optional()
|
|
545
626
|
});
|
|
546
627
|
const RequestModelSchema = object({
|
|
547
628
|
model: string()
|
|
@@ -601,13 +682,13 @@ function writeChunks(logId, chunks, truncated) {
|
|
|
601
682
|
try {
|
|
602
683
|
mkdirSync(dir, { recursive: true });
|
|
603
684
|
} catch (err) {
|
|
604
|
-
|
|
685
|
+
logger.error("[chunkStorage] Failed to create chunks directory:", String(err));
|
|
605
686
|
}
|
|
606
687
|
const data = { chunks, truncated };
|
|
607
688
|
try {
|
|
608
689
|
writeFileSync(tempPath, JSON.stringify(data), "utf-8");
|
|
609
690
|
} catch (err) {
|
|
610
|
-
|
|
691
|
+
logger.error("[chunkStorage] Failed to write chunks temp file:", String(err));
|
|
611
692
|
return targetPath;
|
|
612
693
|
}
|
|
613
694
|
try {
|
|
@@ -618,7 +699,7 @@ function writeChunks(logId, chunks, truncated) {
|
|
|
618
699
|
copyFileSync(tempPath, targetPath);
|
|
619
700
|
unlinkSync(tempPath);
|
|
620
701
|
} catch (copyErr) {
|
|
621
|
-
|
|
702
|
+
logger.error("[chunkStorage] Failed to copy chunks file:", String(copyErr));
|
|
622
703
|
}
|
|
623
704
|
}
|
|
624
705
|
return targetPath;
|
|
@@ -791,12 +872,12 @@ async function getLogById(id) {
|
|
|
791
872
|
}
|
|
792
873
|
if (lastMatch !== null) return lastMatch;
|
|
793
874
|
} catch (err) {
|
|
794
|
-
|
|
875
|
+
logger.error("[store] Failed to read log from disk:", String(err));
|
|
795
876
|
}
|
|
796
877
|
return null;
|
|
797
878
|
}
|
|
798
879
|
function getFilteredLogs(sessionId, model) {
|
|
799
|
-
const cachedLogs = Array.from(memoryCache.values())
|
|
880
|
+
const cachedLogs = Array.from(memoryCache.values());
|
|
800
881
|
return cachedLogs.filter((l) => {
|
|
801
882
|
if (sessionId !== void 0 && l.sessionId !== sessionId) return false;
|
|
802
883
|
if (model !== void 0 && l.model !== model) return false;
|
|
@@ -1009,6 +1090,15 @@ function extractAnthropicStream(raw, log, fallbackModel, collectChunks) {
|
|
|
1009
1090
|
stopSequence = data.delta.stop_sequence ?? null;
|
|
1010
1091
|
outputTokens = data.usage.output_tokens;
|
|
1011
1092
|
log.outputTokens = outputTokens;
|
|
1093
|
+
if (data.usage.cache_creation_input_tokens !== void 0) {
|
|
1094
|
+
log.cacheCreationInputTokens = data.usage.cache_creation_input_tokens;
|
|
1095
|
+
}
|
|
1096
|
+
if (data.usage.cache_read_input_tokens !== void 0) {
|
|
1097
|
+
log.cacheReadInputTokens = data.usage.cache_read_input_tokens;
|
|
1098
|
+
}
|
|
1099
|
+
break;
|
|
1100
|
+
case "error":
|
|
1101
|
+
log.error = data.error.message;
|
|
1012
1102
|
break;
|
|
1013
1103
|
case "content_block_stop":
|
|
1014
1104
|
case "message_stop":
|
|
@@ -1185,6 +1275,16 @@ function extractOpenAIStream(raw, log, fallbackModel, collectChunks = true) {
|
|
|
1185
1275
|
if (dataStr === "[DONE]") break;
|
|
1186
1276
|
try {
|
|
1187
1277
|
const parsed = JSON.parse(dataStr);
|
|
1278
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1279
|
+
const errorDesc = Object.getOwnPropertyDescriptor(parsed, "error");
|
|
1280
|
+
if (errorDesc !== void 0 && typeof errorDesc.value === "object" && errorDesc.value !== null) {
|
|
1281
|
+
const msgDesc = Object.getOwnPropertyDescriptor(errorDesc.value, "message");
|
|
1282
|
+
if (msgDesc !== void 0 && typeof msgDesc.value === "string") {
|
|
1283
|
+
log.error = msgDesc.value;
|
|
1284
|
+
}
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1188
1288
|
const chunkResult = OpenAISSERawChunkSchema.safeParse(parsed);
|
|
1189
1289
|
if (!chunkResult.success) continue;
|
|
1190
1290
|
const chunk = chunkResult.data;
|
|
@@ -1395,6 +1495,8 @@ const ProviderConfigSchema = object({
|
|
|
1395
1495
|
openaiBaseUrl: string().optional(),
|
|
1396
1496
|
/** Auth header to use: "bearer" (default) or "x-api-key" */
|
|
1397
1497
|
authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
1498
|
+
/** API documentation URL */
|
|
1499
|
+
apiDocsUrl: string().optional(),
|
|
1398
1500
|
createdAt: string(),
|
|
1399
1501
|
updatedAt: string()
|
|
1400
1502
|
});
|
|
@@ -1473,7 +1575,7 @@ function getProvider(id) {
|
|
|
1473
1575
|
function normalizeApiKey(apiKey) {
|
|
1474
1576
|
return apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
1475
1577
|
}
|
|
1476
|
-
function addProvider(name, apiKey, format, baseUrl, model, authHeader) {
|
|
1578
|
+
function addProvider(name, apiKey, format, baseUrl, model, authHeader, apiDocsUrl) {
|
|
1477
1579
|
const providers = getProviders();
|
|
1478
1580
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1479
1581
|
const newProvider = {
|
|
@@ -1484,6 +1586,7 @@ function addProvider(name, apiKey, format, baseUrl, model, authHeader) {
|
|
|
1484
1586
|
baseUrl,
|
|
1485
1587
|
model,
|
|
1486
1588
|
authHeader: authHeader ?? "bearer",
|
|
1589
|
+
apiDocsUrl,
|
|
1487
1590
|
createdAt: now,
|
|
1488
1591
|
updatedAt: now,
|
|
1489
1592
|
anthropicBaseUrl: format === "anthropic" && baseUrl !== void 0 ? baseUrl : "",
|
|
@@ -1505,6 +1608,7 @@ function updateProvider(id, updates) {
|
|
|
1505
1608
|
format: updates.format ?? existing.format,
|
|
1506
1609
|
baseUrl: updates.baseUrl !== void 0 ? updates.baseUrl : existing.baseUrl,
|
|
1507
1610
|
authHeader: updates.authHeader ?? existing.authHeader,
|
|
1611
|
+
apiDocsUrl: updates.apiDocsUrl ?? existing.apiDocsUrl,
|
|
1508
1612
|
createdAt: existing.createdAt,
|
|
1509
1613
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1510
1614
|
// Handle format-specific URLs
|
|
@@ -1636,45 +1740,99 @@ function extractRemotePort(request) {
|
|
|
1636
1740
|
return null;
|
|
1637
1741
|
}
|
|
1638
1742
|
async function lookupPidByPort(port) {
|
|
1743
|
+
const platform = process.platform;
|
|
1639
1744
|
try {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1745
|
+
if (platform === "win32") {
|
|
1746
|
+
const { stdout } = await execAsync(
|
|
1747
|
+
`powershell -NoProfile -Command "Get-NetTCPConnection -LocalPort ${port} -State Established | Select-Object -ExpandProperty OwningProcess"`,
|
|
1748
|
+
{ windowsHide: true }
|
|
1749
|
+
);
|
|
1750
|
+
const pid = parseInt(stdout.trim(), 10);
|
|
1751
|
+
return !isNaN(pid) && pid > 0 ? pid : null;
|
|
1752
|
+
} else if (platform === "darwin") {
|
|
1753
|
+
const { stdout } = await execAsync(`lsof -i :${port} -sTCP:ESTABLISHED -t 2>/dev/null`, {
|
|
1754
|
+
shell: "/bin/sh"
|
|
1755
|
+
});
|
|
1756
|
+
const pid = parseInt(stdout.trim(), 10);
|
|
1757
|
+
return !isNaN(pid) && pid > 0 ? pid : null;
|
|
1758
|
+
} else {
|
|
1759
|
+
try {
|
|
1760
|
+
const { stdout: stdout2 } = await execAsync(
|
|
1761
|
+
`ss -tlnp 'sport = :${port}' 2>/dev/null | grep ESTAB | awk '{print $6}' | grep -o 'pid=[0-9]*' | cut -d= -f2`,
|
|
1762
|
+
{ shell: "/bin/sh" }
|
|
1763
|
+
);
|
|
1764
|
+
const pid2 = parseInt(stdout2.trim(), 10);
|
|
1765
|
+
if (!isNaN(pid2) && pid2 > 0) return pid2;
|
|
1766
|
+
} catch {
|
|
1650
1767
|
}
|
|
1768
|
+
const { stdout } = await execAsync(
|
|
1769
|
+
`netstat -tan 2>/dev/null | grep ':${port}' | grep ESTABLISHED | awk '{print $7}' | cut -d/ -f1`,
|
|
1770
|
+
{ shell: "/bin/sh" }
|
|
1771
|
+
);
|
|
1772
|
+
const pid = parseInt(stdout.trim(), 10);
|
|
1773
|
+
return !isNaN(pid) && pid > 0 ? pid : null;
|
|
1651
1774
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
logger.debug(`[socketTracker] Failed to lookup PID for port ${port}:`, String(err));
|
|
1654
1777
|
return null;
|
|
1655
1778
|
}
|
|
1656
1779
|
}
|
|
1657
1780
|
async function lookupProcessInfo(pid) {
|
|
1781
|
+
const platform = process.platform;
|
|
1658
1782
|
try {
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1783
|
+
if (platform === "win32") {
|
|
1784
|
+
const { stdout } = await execAsync(
|
|
1785
|
+
`wmic process where processid=${pid} get commandline /value`,
|
|
1786
|
+
{ windowsHide: true }
|
|
1787
|
+
);
|
|
1788
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
1789
|
+
for (const line of lines) {
|
|
1790
|
+
if (line.includes("=")) {
|
|
1791
|
+
const commandLine = (line.split("=")[1] ?? "").trim();
|
|
1792
|
+
if (commandLine) {
|
|
1793
|
+
const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
|
|
1794
|
+
if (exeMatch && exeMatch[1] !== void 0) {
|
|
1795
|
+
const exePath = exeMatch[1];
|
|
1796
|
+
const exeDir = exePath.substring(0, exePath.lastIndexOf("\\"));
|
|
1797
|
+
return {
|
|
1798
|
+
cwd: exeDir,
|
|
1799
|
+
projectFolder: exeDir.split("\\").pop() ?? null
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
} else {
|
|
1806
|
+
try {
|
|
1807
|
+
const { stdout } = await execAsync(`cat /proc/${pid}/cmdline 2>/dev/null | tr '\\0' ' '`, {
|
|
1808
|
+
shell: "/bin/sh"
|
|
1809
|
+
});
|
|
1810
|
+
const commandLine = stdout.trim();
|
|
1667
1811
|
if (commandLine) {
|
|
1668
1812
|
const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
|
|
1669
1813
|
if (exeMatch && exeMatch[1] !== void 0) {
|
|
1670
1814
|
const exePath = exeMatch[1];
|
|
1671
|
-
const exeDir = exePath.substring(0, exePath.lastIndexOf("
|
|
1815
|
+
const exeDir = exePath.substring(0, exePath.lastIndexOf("/"));
|
|
1672
1816
|
return {
|
|
1673
1817
|
cwd: exeDir,
|
|
1674
|
-
projectFolder: exeDir.split("
|
|
1818
|
+
projectFolder: exeDir.split("/").pop() ?? null
|
|
1675
1819
|
};
|
|
1676
1820
|
}
|
|
1677
1821
|
}
|
|
1822
|
+
} catch {
|
|
1823
|
+
}
|
|
1824
|
+
if (platform === "darwin") {
|
|
1825
|
+
const { stdout } = await execAsync(`ps -p ${pid} -o comm= 2>/dev/null`, {
|
|
1826
|
+
shell: "/bin/sh"
|
|
1827
|
+
});
|
|
1828
|
+
const exePath = stdout.trim();
|
|
1829
|
+
if (exePath) {
|
|
1830
|
+
const exeDir = exePath.substring(0, exePath.lastIndexOf("/"));
|
|
1831
|
+
return {
|
|
1832
|
+
cwd: exeDir,
|
|
1833
|
+
projectFolder: exePath.split("/").pop() ?? null
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1678
1836
|
}
|
|
1679
1837
|
}
|
|
1680
1838
|
return { cwd: null, projectFolder: null };
|
|
@@ -1747,7 +1905,8 @@ function buildFileLogEntry(log, upstreamUrl) {
|
|
|
1747
1905
|
clientCwd: log.clientCwd,
|
|
1748
1906
|
clientProjectFolder: log.clientProjectFolder,
|
|
1749
1907
|
streamingChunks: log.streamingChunks,
|
|
1750
|
-
streamingChunksPath: log.streamingChunksPath
|
|
1908
|
+
streamingChunksPath: log.streamingChunksPath,
|
|
1909
|
+
error: log.error
|
|
1751
1910
|
};
|
|
1752
1911
|
}
|
|
1753
1912
|
function parseRequestPath(req, url) {
|
|
@@ -1840,7 +1999,7 @@ function handleStreamingResponse(upstreamRes, req, startTime, formatHandler, ups
|
|
|
1840
1999
|
);
|
|
1841
2000
|
log.streamingChunksPath = chunkPath;
|
|
1842
2001
|
}
|
|
1843
|
-
appendLogEntry(
|
|
2002
|
+
appendLogEntry(buildFileLogEntry(log, upstreamUrl));
|
|
1844
2003
|
emitLogUpdate(log);
|
|
1845
2004
|
}
|
|
1846
2005
|
});
|
|
@@ -1849,17 +2008,22 @@ function handleStreamingResponse(upstreamRes, req, startTime, formatHandler, ups
|
|
|
1849
2008
|
}
|
|
1850
2009
|
const loggedStream = upstreamRes.body.pipeThrough(transform);
|
|
1851
2010
|
req.signal?.addEventListener("abort", () => {
|
|
1852
|
-
if (log.responseText === null
|
|
1853
|
-
|
|
2011
|
+
if (log.responseText === null) {
|
|
2012
|
+
logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
|
|
1854
2013
|
log.elapsedMs = Date.now() - startTime;
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2014
|
+
if (chunks.length > 0) {
|
|
2015
|
+
const full = chunks.join("");
|
|
2016
|
+
log.responseText = formatHandler.extractStream(full, log, log.model ?? void 0, true);
|
|
2017
|
+
if (log.streamingChunks && log.streamingChunks.chunks.length > 0) {
|
|
2018
|
+
const chunkPath = writeChunks(
|
|
2019
|
+
log.id,
|
|
2020
|
+
log.streamingChunks.chunks,
|
|
2021
|
+
log.streamingChunks.truncated
|
|
2022
|
+
);
|
|
2023
|
+
log.streamingChunksPath = chunkPath;
|
|
2024
|
+
}
|
|
2025
|
+
} else {
|
|
2026
|
+
log.responseText = "Client aborted";
|
|
1863
2027
|
}
|
|
1864
2028
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
1865
2029
|
emitLogUpdate(log);
|
|
@@ -1881,7 +2045,7 @@ async function handleProxy(req) {
|
|
|
1881
2045
|
requestBody = await req.text();
|
|
1882
2046
|
}
|
|
1883
2047
|
const model = requestBody !== null ? extractModelFromBody(requestBody) : null;
|
|
1884
|
-
const matchedProviderConfig =
|
|
2048
|
+
const matchedProviderConfig = model !== null ? findProviderByModel(model) : null;
|
|
1885
2049
|
const upstreamBase = selectUpstreamBase$1(parsed.isChatCompletions, matchedProviderConfig);
|
|
1886
2050
|
const upstreamUrl = buildUpstreamUrl$1(upstreamBase, parsed.normalizedPath);
|
|
1887
2051
|
const upstreamHost = getHostFromUrl$1(upstreamBase);
|
|
@@ -1891,6 +2055,7 @@ async function handleProxy(req) {
|
|
|
1891
2055
|
injectAuthHeaders$1(upstreamHeaders, matchedProviderConfig);
|
|
1892
2056
|
const provider = model !== null ? registry.findProvider(model) : null;
|
|
1893
2057
|
if (model === null || provider === null) {
|
|
2058
|
+
logger.warn(`[handler] Unsupported provider: model=${model}`);
|
|
1894
2059
|
return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
|
|
1895
2060
|
}
|
|
1896
2061
|
let formatHandler;
|
|
@@ -1922,10 +2087,19 @@ async function handleProxy(req) {
|
|
|
1922
2087
|
upstreamRes = await fetch(upstreamUrl, {
|
|
1923
2088
|
method: req.method,
|
|
1924
2089
|
headers: upstreamHeaders,
|
|
1925
|
-
body: requestBody
|
|
2090
|
+
body: requestBody,
|
|
2091
|
+
signal: req.signal
|
|
1926
2092
|
});
|
|
1927
2093
|
} catch (err) {
|
|
1928
2094
|
log.elapsedMs = Date.now() - startTime;
|
|
2095
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2096
|
+
logger.info(`[handler] Client aborted: ${req.method} ${parsed.apiPath}`);
|
|
2097
|
+
log.responseStatus = 499;
|
|
2098
|
+
log.responseText = "Client aborted";
|
|
2099
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
2100
|
+
return new Response("Client aborted", { status: 499 });
|
|
2101
|
+
}
|
|
2102
|
+
logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
|
|
1929
2103
|
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
1930
2104
|
log.responseText = String(err);
|
|
1931
2105
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
|
|
@@ -1945,12 +2119,6 @@ async function handleProxy(req) {
|
|
|
1945
2119
|
}
|
|
1946
2120
|
return handleStreamingResponse(upstreamRes, req, startTime, formatHandler, upstreamUrl, log);
|
|
1947
2121
|
}
|
|
1948
|
-
function findProviderByModelFromConfig(requestBody) {
|
|
1949
|
-
if (requestBody === null) return null;
|
|
1950
|
-
const model = extractModelFromBody(requestBody);
|
|
1951
|
-
if (model === null) return null;
|
|
1952
|
-
return findProviderByModel(model);
|
|
1953
|
-
}
|
|
1954
2122
|
const Route$e = createFileRoute("/proxy/$")({
|
|
1955
2123
|
server: {
|
|
1956
2124
|
handlers: {
|
|
@@ -1976,7 +2144,8 @@ const ProviderInputSchema = object({
|
|
|
1976
2144
|
format: _enum(["anthropic", "openai"]),
|
|
1977
2145
|
baseUrl: string().min(1, "Base URL is required"),
|
|
1978
2146
|
model: string().min(1, "Model is required"),
|
|
1979
|
-
authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer")
|
|
2147
|
+
authHeader: _enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
2148
|
+
apiDocsUrl: string().optional()
|
|
1980
2149
|
});
|
|
1981
2150
|
const Route$c = createFileRoute("/api/providers")({
|
|
1982
2151
|
server: {
|
|
@@ -1995,7 +2164,8 @@ const Route$c = createFileRoute("/api/providers")({
|
|
|
1995
2164
|
parsed.data.format,
|
|
1996
2165
|
parsed.data.baseUrl,
|
|
1997
2166
|
parsed.data.model,
|
|
1998
|
-
parsed.data.authHeader
|
|
2167
|
+
parsed.data.authHeader,
|
|
2168
|
+
parsed.data.apiDocsUrl
|
|
1999
2169
|
);
|
|
2000
2170
|
return Response.json(newProvider, { status: 201 });
|
|
2001
2171
|
}
|
|
@@ -2103,7 +2273,8 @@ const ProviderUpdateSchema = object({
|
|
|
2103
2273
|
model: string().min(1, "Model is required").optional(),
|
|
2104
2274
|
authHeader: _enum(["bearer", "x-api-key"]).optional(),
|
|
2105
2275
|
anthropicBaseUrl: string().optional(),
|
|
2106
|
-
openaiBaseUrl: string().optional()
|
|
2276
|
+
openaiBaseUrl: string().optional(),
|
|
2277
|
+
apiDocsUrl: string().optional()
|
|
2107
2278
|
});
|
|
2108
2279
|
const Route$6 = createFileRoute("/api/providers/$providerId")({
|
|
2109
2280
|
server: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/api/config/paths"], "preloads": ["/assets/main-
|
|
1
|
+
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/health", "/api/logs", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/api/config/paths"], "preloads": ["/assets/main-Cu0oTDfX.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DyKLPMPn.js"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts" } }, "clientEntry": "/assets/main-Cu0oTDfX.js" });
|
|
2
2
|
export {
|
|
3
3
|
tsrStartManifest
|
|
4
4
|
};
|
package/.output/server/index.mjs
CHANGED
|
@@ -100,51 +100,51 @@ const assets = {
|
|
|
100
100
|
"/assets/alibaba-TTwafVwX.svg": {
|
|
101
101
|
"type": "image/svg+xml",
|
|
102
102
|
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
103
|
-
"mtime": "2026-06-
|
|
103
|
+
"mtime": "2026-06-04T03:52:13.283Z",
|
|
104
104
|
"size": 5915,
|
|
105
105
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
106
106
|
},
|
|
107
|
+
"/assets/index-DdJSLfxK.css": {
|
|
108
|
+
"type": "text/css; charset=utf-8",
|
|
109
|
+
"etag": '"10da0-LYeZ5d/vwqh4bAnuP/9hr6Wka6g"',
|
|
110
|
+
"mtime": "2026-06-04T03:52:13.283Z",
|
|
111
|
+
"size": 69024,
|
|
112
|
+
"path": "../public/assets/index-DdJSLfxK.css"
|
|
113
|
+
},
|
|
107
114
|
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
108
115
|
"type": "image/jpeg",
|
|
109
116
|
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
110
|
-
"mtime": "2026-06-
|
|
117
|
+
"mtime": "2026-06-04T03:52:13.278Z",
|
|
111
118
|
"size": 6918,
|
|
112
119
|
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
113
120
|
},
|
|
114
|
-
"/assets/main-Beo3LJDa.js": {
|
|
115
|
-
"type": "text/javascript; charset=utf-8",
|
|
116
|
-
"etag": '"50591-/XG/Oh/RlDy7LRgwa0Uqfltq6fM"',
|
|
117
|
-
"mtime": "2026-06-04T01:19:08.282Z",
|
|
118
|
-
"size": 329105,
|
|
119
|
-
"path": "../public/assets/main-Beo3LJDa.js"
|
|
120
|
-
},
|
|
121
121
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
122
122
|
"type": "image/svg+xml",
|
|
123
123
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
124
|
-
"mtime": "2026-06-
|
|
124
|
+
"mtime": "2026-06-04T03:52:13.283Z",
|
|
125
125
|
"size": 11256,
|
|
126
126
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
127
127
|
},
|
|
128
|
+
"/assets/main-Cu0oTDfX.js": {
|
|
129
|
+
"type": "text/javascript; charset=utf-8",
|
|
130
|
+
"etag": '"50591-/K5L0AnXTcumA7dHm7kmA5HXlCE"',
|
|
131
|
+
"mtime": "2026-06-04T03:52:13.283Z",
|
|
132
|
+
"size": 329105,
|
|
133
|
+
"path": "../public/assets/main-Cu0oTDfX.js"
|
|
134
|
+
},
|
|
128
135
|
"/assets/qwen-CONDcHqt.png": {
|
|
129
136
|
"type": "image/png",
|
|
130
137
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
131
|
-
"mtime": "2026-06-
|
|
138
|
+
"mtime": "2026-06-04T03:52:13.283Z",
|
|
132
139
|
"size": 357059,
|
|
133
140
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
134
141
|
},
|
|
135
|
-
"/assets/index-
|
|
136
|
-
"type": "text/css; charset=utf-8",
|
|
137
|
-
"etag": '"10ce0-rnZGppItQl8rOmyih342MZyZcI0"',
|
|
138
|
-
"mtime": "2026-06-04T01:19:08.282Z",
|
|
139
|
-
"size": 68832,
|
|
140
|
-
"path": "../public/assets/index-BLVa7n9b.css"
|
|
141
|
-
},
|
|
142
|
-
"/assets/index-DH3FOgcK.js": {
|
|
142
|
+
"/assets/index-DyKLPMPn.js": {
|
|
143
143
|
"type": "text/javascript; charset=utf-8",
|
|
144
|
-
"etag": '"
|
|
145
|
-
"mtime": "2026-06-
|
|
146
|
-
"size":
|
|
147
|
-
"path": "../public/assets/index-
|
|
144
|
+
"etag": '"84a31-iF3/oGIdY77bqBWoGeM9Ctiyids"',
|
|
145
|
+
"mtime": "2026-06-04T03:52:13.284Z",
|
|
146
|
+
"size": 543281,
|
|
147
|
+
"path": "../public/assets/index-DyKLPMPn.js"
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
function readAsset(id) {
|