@tonyclaw/llm-inspector 1.17.1 → 1.18.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/{CompareDrawer-BhXCLr7m.js → CompareDrawer-BpwZCB6M.js} +1 -1
- package/.output/public/assets/{ReplayDialog-CzRPSXwa.js → ReplayDialog-Clratkzl.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-lMQonao2.js → RequestAnatomy-EtiX0r_G.js} +1 -1
- package/.output/public/assets/{ResponseView-Bt0vngo0.js → ResponseView-CJqxo-EN.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-Dq9XY2E9.js → StreamingChunkSequence-BIbRqQiV.js} +1 -1
- package/.output/public/assets/{index-B4nxi_tZ.js → index-B-0F9n1w.js} +8 -8
- package/.output/public/assets/{json-viewer-C8ttTXtv.js → json-viewer-D-z1r1Pp.js} +1 -1
- package/.output/public/assets/{main-Dgme52Fp.js → main-CZJ63sQh.js} +1 -1
- package/.output/server/_ssr/{CompareDrawer-D-Nj8wmx.mjs → CompareDrawer-BJr-913n.mjs} +4 -3
- package/.output/server/_ssr/{ReplayDialog-DcucC22E.mjs → ReplayDialog-BwmToGuR.mjs} +5 -4
- package/.output/server/_ssr/{RequestAnatomy-aL8GAcW2.mjs → RequestAnatomy-BmMiPRPB.mjs} +3 -2
- package/.output/server/_ssr/{ResponseView-BHgpoGaF.mjs → ResponseView-ZB9-8Raw.mjs} +4 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DrT7StyS.mjs → StreamingChunkSequence-DWm4CQWC.mjs} +4 -3
- package/.output/server/_ssr/{index-nUG0H1oS.mjs → index-C7I_Qgt0.mjs} +18 -20
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-DLsDT0RE.mjs → json-viewer-D9XETzwp.mjs} +3 -2
- package/.output/server/_ssr/{router-DG_jmXCF.mjs → router-711KpGkz.mjs} +647 -98
- package/.output/server/{_tanstack-start-manifest_v-D0JtrQPv.mjs → _tanstack-start-manifest_v-noQw0Vmw.mjs} +1 -1
- package/.output/server/index.mjs +53 -53
- package/package.json +1 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +10 -13
- package/src/proxy/handler.ts +52 -84
- package/src/proxy/logFinalizer.ts +301 -0
- package/src/proxy/logFinalizer.worker.ts +24 -0
- package/src/proxy/schemas.ts +8 -3
- package/src/proxy/sessionProcess.ts +133 -0
- package/src/proxy/sessionRuntime.ts +85 -0
- package/src/proxy/sessionSupervisor.ts +282 -0
- package/src/proxy/sessionWorkerEntry.ts +26 -0
- package/src/proxy/store.ts +64 -20
- package/src/routes/api/logs.stream.ts +2 -2
- package/src/routes/api/sessions.ts +9 -2
|
@@ -1,14 +1,15 @@
|
|
|
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, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync } from "node:fs";
|
|
5
5
|
import { mkdir, appendFile, readFile, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
7
7
|
import path, { join, isAbsolute, dirname } from "node:path";
|
|
8
8
|
import { C as Conf } from "../_libs/conf.mjs";
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
|
-
import { exec } from "node:child_process";
|
|
10
|
+
import { exec, fork } from "node:child_process";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
|
+
import { Worker } from "node:worker_threads";
|
|
12
13
|
import { M as McpServer, W as WebStandardStreamableHTTPServerTransport } from "../_libs/modelcontextprotocol__server.mjs";
|
|
13
14
|
import { homedir } from "node:os";
|
|
14
15
|
import { d as object, _ as _enum, b as string, a as array, u as union, n as number, c as boolean, r as record, g as discriminatedUnion, l as literal, h as _null, k as lazy, e as unknown } from "../_libs/zod.mjs";
|
|
@@ -70,7 +71,7 @@ function RootDocument({ children }) {
|
|
|
70
71
|
] })
|
|
71
72
|
] });
|
|
72
73
|
}
|
|
73
|
-
const $$splitComponentImporter = () => import("./index-
|
|
74
|
+
const $$splitComponentImporter = () => import("./index-C7I_Qgt0.mjs").then((n) => n.t);
|
|
74
75
|
const Route$j = createFileRoute("/")({
|
|
75
76
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
76
77
|
});
|
|
@@ -750,20 +751,24 @@ const LooseRequestSchema = object({
|
|
|
750
751
|
model: string().optional(),
|
|
751
752
|
metadata: object({ user_id: string().optional() }).passthrough().optional()
|
|
752
753
|
});
|
|
754
|
+
function extractSessionIdFromHeaders(headers) {
|
|
755
|
+
return headers.get("x-llm-inspector-session-id") ?? headers.get("x-session-affinity");
|
|
756
|
+
}
|
|
753
757
|
function extractRequestMetadata(body, headers) {
|
|
754
|
-
|
|
758
|
+
const headerSessionId = extractSessionIdFromHeaders(headers);
|
|
759
|
+
if (body === null) return { model: null, sessionId: headerSessionId };
|
|
755
760
|
try {
|
|
756
761
|
const json = JSON.parse(body);
|
|
757
762
|
const loose = LooseRequestSchema.safeParse(json);
|
|
758
763
|
if (loose.success) {
|
|
759
764
|
return {
|
|
760
765
|
model: loose.data.model ?? null,
|
|
761
|
-
sessionId: loose.data.metadata?.user_id ??
|
|
766
|
+
sessionId: loose.data.metadata?.user_id ?? headerSessionId
|
|
762
767
|
};
|
|
763
768
|
}
|
|
764
769
|
} catch {
|
|
765
770
|
}
|
|
766
|
-
return { model: null, sessionId:
|
|
771
|
+
return { model: null, sessionId: headerSessionId };
|
|
767
772
|
}
|
|
768
773
|
const StreamingChunkSchema = object({
|
|
769
774
|
index: number(),
|
|
@@ -833,6 +838,199 @@ function readChunks(path2) {
|
|
|
833
838
|
return null;
|
|
834
839
|
}
|
|
835
840
|
}
|
|
841
|
+
const PROVIDER_TEST_SESSION_ID = "provider-test";
|
|
842
|
+
function getRuntimeMode() {
|
|
843
|
+
if (process.env["FINALIZER_USE_WORKER"] === "0") return "in-process";
|
|
844
|
+
const mode = process.env["FINALIZER_RUNTIME"];
|
|
845
|
+
if (mode === "worker") return "worker-thread";
|
|
846
|
+
if (mode === "inline") return "in-process";
|
|
847
|
+
return "child-process";
|
|
848
|
+
}
|
|
849
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
850
|
+
const knownLogSessions = /* @__PURE__ */ new Map();
|
|
851
|
+
const completedLogIds = /* @__PURE__ */ new Set();
|
|
852
|
+
const failedLogIds = /* @__PURE__ */ new Set();
|
|
853
|
+
function normalizeExplicitSessionId(sessionId) {
|
|
854
|
+
if (sessionId === null || sessionId === void 0) return null;
|
|
855
|
+
const trimmed = sessionId.trim();
|
|
856
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
857
|
+
}
|
|
858
|
+
function buildClientSessionId(clientInfo) {
|
|
859
|
+
const projectFolder = normalizeExplicitSessionId(clientInfo.projectFolder);
|
|
860
|
+
if (clientInfo.pid !== null) {
|
|
861
|
+
return {
|
|
862
|
+
id: projectFolder !== null ? `process:${clientInfo.pid}:${projectFolder}` : `process:${clientInfo.pid}`,
|
|
863
|
+
source: "client-process"
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
if (clientInfo.port !== null) {
|
|
867
|
+
return {
|
|
868
|
+
id: `connection:${clientInfo.port}`,
|
|
869
|
+
source: "client-connection"
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
return { id: null, source: null };
|
|
873
|
+
}
|
|
874
|
+
function resolveSessionIdentity(input) {
|
|
875
|
+
if (input.isTest === true) {
|
|
876
|
+
return { id: PROVIDER_TEST_SESSION_ID, source: "provider-test" };
|
|
877
|
+
}
|
|
878
|
+
const explicit = normalizeExplicitSessionId(input.explicitSessionId);
|
|
879
|
+
if (explicit !== null) {
|
|
880
|
+
return { id: explicit, source: "explicit" };
|
|
881
|
+
}
|
|
882
|
+
if (input.clientInfo !== void 0) {
|
|
883
|
+
return buildClientSessionId(input.clientInfo);
|
|
884
|
+
}
|
|
885
|
+
return { id: null, source: null };
|
|
886
|
+
}
|
|
887
|
+
function getLogSessionIdentity(log) {
|
|
888
|
+
return resolveSessionIdentity({
|
|
889
|
+
explicitSessionId: log.sessionId,
|
|
890
|
+
clientInfo: {
|
|
891
|
+
port: log.clientPort ?? null,
|
|
892
|
+
pid: log.clientPid ?? null,
|
|
893
|
+
cwd: log.clientCwd ?? null,
|
|
894
|
+
projectFolder: log.clientProjectFolder ?? null
|
|
895
|
+
},
|
|
896
|
+
isTest: log.isTest
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function getLogSessionId(log) {
|
|
900
|
+
return getLogSessionIdentity(log).id;
|
|
901
|
+
}
|
|
902
|
+
function getTimestamp(log) {
|
|
903
|
+
return log.timestamp.length > 0 ? log.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
904
|
+
}
|
|
905
|
+
function createSessionRecord(id, source, log) {
|
|
906
|
+
const timestamp = getTimestamp(log);
|
|
907
|
+
return {
|
|
908
|
+
id,
|
|
909
|
+
source,
|
|
910
|
+
runtimeMode: getRuntimeMode(),
|
|
911
|
+
createdAt: timestamp,
|
|
912
|
+
updatedAt: timestamp,
|
|
913
|
+
requestCount: 0,
|
|
914
|
+
activeRequests: 0,
|
|
915
|
+
completedRequests: 0,
|
|
916
|
+
failedRequests: 0,
|
|
917
|
+
queuedTasks: 0,
|
|
918
|
+
runningTasks: 0,
|
|
919
|
+
lastTaskError: null,
|
|
920
|
+
lastLogId: null,
|
|
921
|
+
lastModel: null,
|
|
922
|
+
clientPid: log.clientPid ?? null,
|
|
923
|
+
clientProjectFolder: log.clientProjectFolder ?? null
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function upsertSession(log, sourceOverride) {
|
|
927
|
+
const identity = getLogSessionIdentity(log);
|
|
928
|
+
if (identity.id === null) return null;
|
|
929
|
+
const source = sourceOverride ?? identity.source;
|
|
930
|
+
if (source === null) return null;
|
|
931
|
+
const existing = sessions.get(identity.id);
|
|
932
|
+
const session = existing ?? createSessionRecord(identity.id, source, log);
|
|
933
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
934
|
+
session.lastLogId = log.id;
|
|
935
|
+
session.lastModel = log.model ?? session.lastModel;
|
|
936
|
+
session.clientPid = log.clientPid ?? session.clientPid;
|
|
937
|
+
session.clientProjectFolder = log.clientProjectFolder ?? session.clientProjectFolder;
|
|
938
|
+
sessions.set(identity.id, session);
|
|
939
|
+
return session;
|
|
940
|
+
}
|
|
941
|
+
function rememberLogSession(log) {
|
|
942
|
+
const sessionId = getLogSessionId(log);
|
|
943
|
+
if (sessionId === null) return false;
|
|
944
|
+
const knownSessionId = knownLogSessions.get(log.id);
|
|
945
|
+
if (knownSessionId === sessionId) return false;
|
|
946
|
+
knownLogSessions.set(log.id, sessionId);
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
function isFailedLog(log) {
|
|
950
|
+
const hasError = log.error !== null && log.error !== void 0 && log.error.length > 0;
|
|
951
|
+
const hasErrorStatus = log.responseStatus !== null && log.responseStatus >= 400;
|
|
952
|
+
return hasError || hasErrorStatus;
|
|
953
|
+
}
|
|
954
|
+
function markSessionStarted(log, sourceOverride) {
|
|
955
|
+
const session = upsertSession(log, sourceOverride);
|
|
956
|
+
if (session === null) return;
|
|
957
|
+
const isNewLog = rememberLogSession(log);
|
|
958
|
+
if (isNewLog) {
|
|
959
|
+
session.requestCount += 1;
|
|
960
|
+
}
|
|
961
|
+
session.activeRequests += 1;
|
|
962
|
+
}
|
|
963
|
+
function markSessionFinished(log, sourceOverride) {
|
|
964
|
+
const session = upsertSession(log, sourceOverride);
|
|
965
|
+
if (session === null) return;
|
|
966
|
+
if (session.activeRequests > 0) {
|
|
967
|
+
session.activeRequests -= 1;
|
|
968
|
+
}
|
|
969
|
+
if (completedLogIds.has(log.id) === false) {
|
|
970
|
+
completedLogIds.add(log.id);
|
|
971
|
+
session.completedRequests += 1;
|
|
972
|
+
}
|
|
973
|
+
if (isFailedLog(log) && failedLogIds.has(log.id) === false) {
|
|
974
|
+
failedLogIds.add(log.id);
|
|
975
|
+
session.failedRequests += 1;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function markSessionTaskQueued(sessionId) {
|
|
979
|
+
if (sessionId === null) return;
|
|
980
|
+
const session = sessions.get(sessionId);
|
|
981
|
+
if (session === void 0) return;
|
|
982
|
+
session.queuedTasks += 1;
|
|
983
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
984
|
+
}
|
|
985
|
+
function markSessionTaskStarted(sessionId) {
|
|
986
|
+
if (sessionId === null) return;
|
|
987
|
+
const session = sessions.get(sessionId);
|
|
988
|
+
if (session === void 0) return;
|
|
989
|
+
if (session.queuedTasks > 0) {
|
|
990
|
+
session.queuedTasks -= 1;
|
|
991
|
+
}
|
|
992
|
+
session.runningTasks += 1;
|
|
993
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
994
|
+
}
|
|
995
|
+
function markSessionTaskFinished(sessionId, error) {
|
|
996
|
+
if (sessionId === null) return;
|
|
997
|
+
const session = sessions.get(sessionId);
|
|
998
|
+
if (session === void 0) return;
|
|
999
|
+
if (session.runningTasks > 0) {
|
|
1000
|
+
session.runningTasks -= 1;
|
|
1001
|
+
}
|
|
1002
|
+
session.lastTaskError = error;
|
|
1003
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
|
+
}
|
|
1005
|
+
function observeSessionLog(log, sourceOverride) {
|
|
1006
|
+
const session = upsertSession(log, sourceOverride);
|
|
1007
|
+
if (session === null) return;
|
|
1008
|
+
const isNewLog = rememberLogSession(log);
|
|
1009
|
+
if (isNewLog) {
|
|
1010
|
+
session.requestCount += 1;
|
|
1011
|
+
}
|
|
1012
|
+
if (log.responseStatus !== null || log.responseText !== null) {
|
|
1013
|
+
markSessionFinished(log, sourceOverride);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function clearSessionRegistry() {
|
|
1017
|
+
sessions.clear();
|
|
1018
|
+
knownLogSessions.clear();
|
|
1019
|
+
completedLogIds.clear();
|
|
1020
|
+
failedLogIds.clear();
|
|
1021
|
+
}
|
|
1022
|
+
function rebuildSessionRegistry(logs) {
|
|
1023
|
+
clearSessionRegistry();
|
|
1024
|
+
for (const log of logs) {
|
|
1025
|
+
observeSessionLog(log);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function getSessionIds() {
|
|
1029
|
+
return [...sessions.values()].map((session) => session.id);
|
|
1030
|
+
}
|
|
1031
|
+
function getSessionSnapshots() {
|
|
1032
|
+
return [...sessions.values()].map((session) => ({ ...session }));
|
|
1033
|
+
}
|
|
836
1034
|
const MAX_MEMORY_CACHE = 100;
|
|
837
1035
|
const memoryCache = /* @__PURE__ */ new Map();
|
|
838
1036
|
function evictOldestIfNeeded() {
|
|
@@ -842,11 +1040,17 @@ function evictOldestIfNeeded() {
|
|
|
842
1040
|
memoryCache.delete(oldestKey);
|
|
843
1041
|
}
|
|
844
1042
|
}
|
|
1043
|
+
function normalizeLogSession(log) {
|
|
1044
|
+
const sessionId = getLogSessionId(log);
|
|
1045
|
+
if (sessionId === log.sessionId) return log;
|
|
1046
|
+
return { ...log, sessionId };
|
|
1047
|
+
}
|
|
845
1048
|
function addToCache(log) {
|
|
846
|
-
|
|
847
|
-
|
|
1049
|
+
const cachedLog = normalizeLogSession(log);
|
|
1050
|
+
if (memoryCache.has(cachedLog.id)) {
|
|
1051
|
+
memoryCache.delete(cachedLog.id);
|
|
848
1052
|
}
|
|
849
|
-
memoryCache.set(
|
|
1053
|
+
memoryCache.set(cachedLog.id, cachedLog);
|
|
850
1054
|
evictOldestIfNeeded();
|
|
851
1055
|
}
|
|
852
1056
|
function removeFromCache(id) {
|
|
@@ -867,12 +1071,18 @@ async function addTestLogEntry(entry) {
|
|
|
867
1071
|
entry.streamingChunks.truncated
|
|
868
1072
|
);
|
|
869
1073
|
}
|
|
1074
|
+
const session = resolveSessionIdentity({
|
|
1075
|
+
explicitSessionId: entry.sessionId,
|
|
1076
|
+
isTest: entry.isTest
|
|
1077
|
+
});
|
|
870
1078
|
const log = {
|
|
871
1079
|
id,
|
|
872
1080
|
...entry,
|
|
1081
|
+
sessionId: session.id,
|
|
873
1082
|
streamingChunksPath
|
|
874
1083
|
};
|
|
875
1084
|
addToCache(log);
|
|
1085
|
+
observeSessionLog(log, session.source);
|
|
876
1086
|
emitLogUpdate(log);
|
|
877
1087
|
return log;
|
|
878
1088
|
}
|
|
@@ -880,13 +1090,17 @@ async function createLog(method, path2, requestBody, headers, clientInfo, rawHea
|
|
|
880
1090
|
const userAgent = headers.get("user-agent");
|
|
881
1091
|
const origin = headers.get("origin");
|
|
882
1092
|
const id = preAcquiredId ?? await getNextLogId();
|
|
1093
|
+
const session = resolveSessionIdentity({
|
|
1094
|
+
explicitSessionId: sessionId,
|
|
1095
|
+
clientInfo
|
|
1096
|
+
});
|
|
883
1097
|
const log = {
|
|
884
1098
|
id,
|
|
885
1099
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
886
1100
|
method,
|
|
887
1101
|
path: path2,
|
|
888
1102
|
model,
|
|
889
|
-
sessionId,
|
|
1103
|
+
sessionId: session.id,
|
|
890
1104
|
rawRequestBody: requestBody,
|
|
891
1105
|
responseStatus: null,
|
|
892
1106
|
responseText: null,
|
|
@@ -913,9 +1127,15 @@ async function createLog(method, path2, requestBody, headers, clientInfo, rawHea
|
|
|
913
1127
|
appendLogEntry(log);
|
|
914
1128
|
await addToIndex(id, logFile, -1, -1);
|
|
915
1129
|
addToCache(log);
|
|
1130
|
+
markSessionStarted(log, session.source);
|
|
916
1131
|
emitLogUpdate(log);
|
|
917
1132
|
return log;
|
|
918
1133
|
}
|
|
1134
|
+
function finalizeLogUpdate(log) {
|
|
1135
|
+
addToCache(log);
|
|
1136
|
+
markSessionFinished(log);
|
|
1137
|
+
emitLogUpdate(log);
|
|
1138
|
+
}
|
|
919
1139
|
async function getLogById(id) {
|
|
920
1140
|
const cached = memoryCache.get(id);
|
|
921
1141
|
if (cached !== void 0) {
|
|
@@ -940,7 +1160,12 @@ async function getLogById(id) {
|
|
|
940
1160
|
if (line === "") return null;
|
|
941
1161
|
const parsed = JSON.parse(line);
|
|
942
1162
|
const result = CapturedLogSchema.safeParse(parsed);
|
|
943
|
-
if (result.success)
|
|
1163
|
+
if (result.success) {
|
|
1164
|
+
const log = normalizeLogSession(result.data);
|
|
1165
|
+
addToCache(log);
|
|
1166
|
+
observeSessionLog(log);
|
|
1167
|
+
return log;
|
|
1168
|
+
}
|
|
944
1169
|
} finally {
|
|
945
1170
|
await fh.close();
|
|
946
1171
|
}
|
|
@@ -958,9 +1183,11 @@ async function getLogById(id) {
|
|
|
958
1183
|
if (desc !== void 0 && typeof desc.value === "number" && desc.value === id) {
|
|
959
1184
|
const result = CapturedLogSchema.safeParse(parsed);
|
|
960
1185
|
if (result.success) {
|
|
961
|
-
lastMatch = result.data;
|
|
1186
|
+
lastMatch = normalizeLogSession(result.data);
|
|
962
1187
|
if (result.data.responseStatus !== null) {
|
|
963
|
-
|
|
1188
|
+
addToCache(lastMatch);
|
|
1189
|
+
observeSessionLog(lastMatch);
|
|
1190
|
+
return lastMatch;
|
|
964
1191
|
}
|
|
965
1192
|
}
|
|
966
1193
|
}
|
|
@@ -968,7 +1195,11 @@ async function getLogById(id) {
|
|
|
968
1195
|
} catch {
|
|
969
1196
|
}
|
|
970
1197
|
}
|
|
971
|
-
if (lastMatch !== null)
|
|
1198
|
+
if (lastMatch !== null) {
|
|
1199
|
+
addToCache(lastMatch);
|
|
1200
|
+
observeSessionLog(lastMatch);
|
|
1201
|
+
return lastMatch;
|
|
1202
|
+
}
|
|
972
1203
|
} catch (err) {
|
|
973
1204
|
logger.error("[store] Failed to read log from disk:", String(err));
|
|
974
1205
|
}
|
|
@@ -976,17 +1207,13 @@ async function getLogById(id) {
|
|
|
976
1207
|
}
|
|
977
1208
|
function getFilteredLogs(sessionId, model) {
|
|
978
1209
|
return [...memoryCache.values()].filter((l) => {
|
|
979
|
-
if (sessionId !== void 0 && l
|
|
1210
|
+
if (sessionId !== void 0 && getLogSessionId(l) !== sessionId) return false;
|
|
980
1211
|
if (model !== void 0 && l.model !== model) return false;
|
|
981
1212
|
return true;
|
|
982
1213
|
});
|
|
983
1214
|
}
|
|
984
1215
|
function getSessions() {
|
|
985
|
-
|
|
986
|
-
for (const l of memoryCache.values()) {
|
|
987
|
-
if (l.sessionId !== null && l.sessionId !== "") set.add(l.sessionId);
|
|
988
|
-
}
|
|
989
|
-
return [...set];
|
|
1216
|
+
return getSessionIds();
|
|
990
1217
|
}
|
|
991
1218
|
function getModels() {
|
|
992
1219
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -998,6 +1225,7 @@ function getModels() {
|
|
|
998
1225
|
function clearAllLogs() {
|
|
999
1226
|
const count = memoryCache.size;
|
|
1000
1227
|
memoryCache.clear();
|
|
1228
|
+
clearSessionRegistry();
|
|
1001
1229
|
return { cleared: count };
|
|
1002
1230
|
}
|
|
1003
1231
|
function clearLogsByIds(ids) {
|
|
@@ -1008,6 +1236,7 @@ function clearLogsByIds(ids) {
|
|
|
1008
1236
|
for (const id of unique) {
|
|
1009
1237
|
removeFromCache(id);
|
|
1010
1238
|
}
|
|
1239
|
+
rebuildSessionRegistry(memoryCache.values());
|
|
1011
1240
|
return { cleared: unique.size };
|
|
1012
1241
|
}
|
|
1013
1242
|
const sseHandlers = /* @__PURE__ */ new Set();
|
|
@@ -2368,6 +2597,350 @@ function stripOpenAIOrphanToolMessages(rawBody) {
|
|
|
2368
2597
|
parsed.messages = kept;
|
|
2369
2598
|
return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
|
|
2370
2599
|
}
|
|
2600
|
+
const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
|
|
2601
|
+
const MAX_RESTARTS = 3;
|
|
2602
|
+
const _processes = /* @__PURE__ */ new Map();
|
|
2603
|
+
class SessionProcess {
|
|
2604
|
+
constructor(sessionId) {
|
|
2605
|
+
this.sessionId = sessionId;
|
|
2606
|
+
}
|
|
2607
|
+
child = null;
|
|
2608
|
+
pending = /* @__PURE__ */ new Map();
|
|
2609
|
+
nextId = 0;
|
|
2610
|
+
idleTimer = null;
|
|
2611
|
+
restartCount = 0;
|
|
2612
|
+
destroyed = false;
|
|
2613
|
+
/** Number of outstanding jobs sent to this process. */
|
|
2614
|
+
get pendingCount() {
|
|
2615
|
+
return this.pending.size;
|
|
2616
|
+
}
|
|
2617
|
+
ensureRunning() {
|
|
2618
|
+
if (this.child !== null && this.child.connected) return this.child;
|
|
2619
|
+
const entryPath = new URL("./sessionWorkerEntry.ts", import.meta.url).pathname;
|
|
2620
|
+
const resolvedPath = process.platform === "win32" && entryPath.startsWith("/") ? entryPath.slice(1) : entryPath;
|
|
2621
|
+
this.child = fork(resolvedPath, [], {
|
|
2622
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
2623
|
+
});
|
|
2624
|
+
this.restartCount += 1;
|
|
2625
|
+
this.child.on("message", (msg) => {
|
|
2626
|
+
const pending = this.pending.get(msg.id);
|
|
2627
|
+
if (pending !== void 0) {
|
|
2628
|
+
this.pending.delete(msg.id);
|
|
2629
|
+
pending.resolve(msg.result);
|
|
2630
|
+
}
|
|
2631
|
+
this.resetIdleTimer();
|
|
2632
|
+
});
|
|
2633
|
+
this.child.on("error", (err) => {
|
|
2634
|
+
logger.error(`[sessionProcess] Session ${this.sessionId} process error:`, err.message);
|
|
2635
|
+
});
|
|
2636
|
+
this.child.on("exit", (code, signal) => {
|
|
2637
|
+
const wasConnected = this.child !== null;
|
|
2638
|
+
this.child = null;
|
|
2639
|
+
if (this.destroyed) return;
|
|
2640
|
+
for (const [, pending] of this.pending) {
|
|
2641
|
+
pending.reject(
|
|
2642
|
+
new Error(
|
|
2643
|
+
`Session process exited with code ${code ?? signal}, session=${this.sessionId}`
|
|
2644
|
+
)
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
this.pending.clear();
|
|
2648
|
+
if (wasConnected && this.restartCount <= MAX_RESTARTS) {
|
|
2649
|
+
logger.warn(
|
|
2650
|
+
`[sessionProcess] Session ${this.sessionId} worker exited (code=${code ?? signal}), restart ${this.restartCount}/${MAX_RESTARTS}`
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
this.resetIdleTimer();
|
|
2655
|
+
return this.child;
|
|
2656
|
+
}
|
|
2657
|
+
enqueue(job) {
|
|
2658
|
+
return new Promise((resolve, reject) => {
|
|
2659
|
+
const child = this.ensureRunning();
|
|
2660
|
+
const id = String(++this.nextId);
|
|
2661
|
+
this.pending.set(id, { resolve, reject });
|
|
2662
|
+
child.send({ id, job });
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
resetIdleTimer() {
|
|
2666
|
+
if (this.idleTimer !== null) clearTimeout(this.idleTimer);
|
|
2667
|
+
this.idleTimer = setTimeout(() => {
|
|
2668
|
+
if (this.pending.size === 0) {
|
|
2669
|
+
this.destroy();
|
|
2670
|
+
}
|
|
2671
|
+
}, IDLE_TIMEOUT_MS);
|
|
2672
|
+
}
|
|
2673
|
+
destroy() {
|
|
2674
|
+
this.destroyed = true;
|
|
2675
|
+
if (this.idleTimer !== null) {
|
|
2676
|
+
clearTimeout(this.idleTimer);
|
|
2677
|
+
this.idleTimer = null;
|
|
2678
|
+
}
|
|
2679
|
+
if (this.child !== null) {
|
|
2680
|
+
this.child.kill();
|
|
2681
|
+
this.child = null;
|
|
2682
|
+
}
|
|
2683
|
+
_processes.delete(this.sessionId);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
function getSessionProcess(sessionId) {
|
|
2687
|
+
const existing = _processes.get(sessionId);
|
|
2688
|
+
if (existing !== void 0) return existing;
|
|
2689
|
+
const sp = new SessionProcess(sessionId);
|
|
2690
|
+
_processes.set(sessionId, sp);
|
|
2691
|
+
return sp;
|
|
2692
|
+
}
|
|
2693
|
+
function buildFileLogEntry(log, upstreamUrl) {
|
|
2694
|
+
return {
|
|
2695
|
+
timestamp: log.timestamp,
|
|
2696
|
+
id: log.id,
|
|
2697
|
+
method: log.method,
|
|
2698
|
+
path: log.path,
|
|
2699
|
+
model: log.model,
|
|
2700
|
+
sessionId: log.sessionId,
|
|
2701
|
+
rawRequestBody: log.rawRequestBody,
|
|
2702
|
+
responseStatus: log.responseStatus,
|
|
2703
|
+
responseText: log.responseText,
|
|
2704
|
+
inputTokens: log.inputTokens,
|
|
2705
|
+
outputTokens: log.outputTokens,
|
|
2706
|
+
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
2707
|
+
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
2708
|
+
elapsedMs: log.elapsedMs,
|
|
2709
|
+
streaming: log.streaming,
|
|
2710
|
+
userAgent: log.userAgent,
|
|
2711
|
+
origin: log.origin,
|
|
2712
|
+
upstreamUrl,
|
|
2713
|
+
clientPort: log.clientPort,
|
|
2714
|
+
clientPid: log.clientPid,
|
|
2715
|
+
clientCwd: log.clientCwd,
|
|
2716
|
+
clientProjectFolder: log.clientProjectFolder,
|
|
2717
|
+
streamingChunks: log.streamingChunks,
|
|
2718
|
+
streamingChunksPath: log.streamingChunksPath,
|
|
2719
|
+
error: log.error
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
function cloneLog(log) {
|
|
2723
|
+
return { ...log };
|
|
2724
|
+
}
|
|
2725
|
+
function errorMessage$2(err) {
|
|
2726
|
+
return err instanceof Error ? err.message : String(err);
|
|
2727
|
+
}
|
|
2728
|
+
function persistStreamingChunks(log) {
|
|
2729
|
+
if (log.streamingChunks !== void 0 && log.streamingChunks.chunks.length > 0) {
|
|
2730
|
+
const chunkPath = writeChunks(
|
|
2731
|
+
log.id,
|
|
2732
|
+
log.streamingChunks.chunks,
|
|
2733
|
+
log.streamingChunks.truncated
|
|
2734
|
+
);
|
|
2735
|
+
log.streamingChunksPath = chunkPath;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err) {
|
|
2739
|
+
const message = errorMessage$2(err);
|
|
2740
|
+
logger.error(`[logFinalizer] Failed to finalize log #${log.id}:`, message);
|
|
2741
|
+
log.responseStatus = log.responseStatus ?? fallbackStatus;
|
|
2742
|
+
log.responseText = log.responseText ?? fallbackResponseText;
|
|
2743
|
+
log.elapsedMs = job.elapsedMs;
|
|
2744
|
+
log.error = message;
|
|
2745
|
+
return { log, upstreamUrl: job.upstreamUrl, error: message };
|
|
2746
|
+
}
|
|
2747
|
+
function finalizeNonStreaming(job, log) {
|
|
2748
|
+
const formatHandler = formatForPath(log.path);
|
|
2749
|
+
if (formatHandler === null) {
|
|
2750
|
+
return finalizeWithError(job, log, job.responseStatus, job.responseBody, "Unsupported format");
|
|
2751
|
+
}
|
|
2752
|
+
try {
|
|
2753
|
+
const tokens = formatHandler.extractTokens(job.responseBody);
|
|
2754
|
+
log.elapsedMs = job.elapsedMs;
|
|
2755
|
+
log.responseStatus = job.responseStatus;
|
|
2756
|
+
log.responseText = job.responseBody;
|
|
2757
|
+
log.inputTokens = tokens.inputTokens;
|
|
2758
|
+
log.outputTokens = tokens.outputTokens;
|
|
2759
|
+
log.cacheCreationInputTokens = tokens.cacheCreationInputTokens;
|
|
2760
|
+
log.cacheReadInputTokens = tokens.cacheReadInputTokens;
|
|
2761
|
+
return { log, upstreamUrl: job.upstreamUrl, error: null };
|
|
2762
|
+
} catch (err) {
|
|
2763
|
+
return finalizeWithError(job, log, job.responseStatus, job.responseBody, err);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
function finalizeStreaming(job, log) {
|
|
2767
|
+
const formatHandler = formatForPath(log.path);
|
|
2768
|
+
if (formatHandler === null) {
|
|
2769
|
+
return finalizeWithError(job, log, job.responseStatus, job.rawStream, "Unsupported format");
|
|
2770
|
+
}
|
|
2771
|
+
try {
|
|
2772
|
+
log.elapsedMs = job.elapsedMs;
|
|
2773
|
+
log.responseStatus = job.responseStatus;
|
|
2774
|
+
log.responseText = formatHandler.extractStream(
|
|
2775
|
+
job.rawStream,
|
|
2776
|
+
log,
|
|
2777
|
+
log.model ?? void 0,
|
|
2778
|
+
true
|
|
2779
|
+
);
|
|
2780
|
+
persistStreamingChunks(log);
|
|
2781
|
+
return { log, upstreamUrl: job.upstreamUrl, error: null };
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
return finalizeWithError(job, log, job.responseStatus, job.rawStream, err);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
function finalizeStreamAbort(job, log) {
|
|
2787
|
+
const formatHandler = formatForPath(log.path);
|
|
2788
|
+
if (formatHandler === null && job.hasChunks) {
|
|
2789
|
+
return finalizeWithError(job, log, 499, "Client aborted", "Unsupported format");
|
|
2790
|
+
}
|
|
2791
|
+
try {
|
|
2792
|
+
log.elapsedMs = job.elapsedMs;
|
|
2793
|
+
if (job.hasChunks && formatHandler !== null) {
|
|
2794
|
+
log.responseText = formatHandler.extractStream(
|
|
2795
|
+
job.rawStream,
|
|
2796
|
+
log,
|
|
2797
|
+
log.model ?? void 0,
|
|
2798
|
+
true
|
|
2799
|
+
);
|
|
2800
|
+
persistStreamingChunks(log);
|
|
2801
|
+
} else {
|
|
2802
|
+
log.responseText = "Client aborted";
|
|
2803
|
+
}
|
|
2804
|
+
return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted" };
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
return finalizeWithError(job, log, 499, "Client aborted", err);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function buildFinalizeLogResult(job) {
|
|
2810
|
+
const log = cloneLog(job.log);
|
|
2811
|
+
switch (job.type) {
|
|
2812
|
+
case "non-streaming":
|
|
2813
|
+
return finalizeNonStreaming(job, log);
|
|
2814
|
+
case "streaming":
|
|
2815
|
+
return finalizeStreaming(job, log);
|
|
2816
|
+
case "stream-abort":
|
|
2817
|
+
return finalizeStreamAbort(job, log);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
function commitFinalizeLogResult(result) {
|
|
2821
|
+
appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
|
|
2822
|
+
finalizeLogUpdate(result.log);
|
|
2823
|
+
}
|
|
2824
|
+
const RUNTIME = (() => {
|
|
2825
|
+
if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
|
|
2826
|
+
const mode = process.env["FINALIZER_RUNTIME"];
|
|
2827
|
+
if (mode === "worker") return "worker";
|
|
2828
|
+
if (mode === "inline") return "inline";
|
|
2829
|
+
return "process";
|
|
2830
|
+
})();
|
|
2831
|
+
function executeBuildInSessionProcess(job) {
|
|
2832
|
+
const sessionId = job.log.sessionId ?? "__unassigned__";
|
|
2833
|
+
return getSessionProcess(sessionId).enqueue(job).catch((err) => {
|
|
2834
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2835
|
+
logger.error(
|
|
2836
|
+
`[logFinalizer] Session process failed for log #${job.log.id}, falling back to in-process: ${message}`
|
|
2837
|
+
);
|
|
2838
|
+
return buildFinalizeLogResult(job);
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
function resolveBuildPromise(job) {
|
|
2842
|
+
switch (RUNTIME) {
|
|
2843
|
+
case "process":
|
|
2844
|
+
return executeBuildInSessionProcess(job);
|
|
2845
|
+
case "worker":
|
|
2846
|
+
return executeBuildInWorker(job);
|
|
2847
|
+
case "inline":
|
|
2848
|
+
return Promise.resolve(buildFinalizeLogResult(job));
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
const WORKER_COUNT = Math.max(1, Number(process.env["FINALIZER_WORKER_COUNT"]) || 4);
|
|
2852
|
+
let _workers = null;
|
|
2853
|
+
const _pending = /* @__PURE__ */ new Map();
|
|
2854
|
+
let _nextWorker = 0;
|
|
2855
|
+
let _jobSeq = 0;
|
|
2856
|
+
function getWorkers() {
|
|
2857
|
+
if (_workers !== null) return _workers;
|
|
2858
|
+
_workers = [];
|
|
2859
|
+
for (let i = 0; i < WORKER_COUNT; i++) {
|
|
2860
|
+
const w = new Worker(new URL("./logFinalizer.worker.ts", import.meta.url));
|
|
2861
|
+
w.on("message", (msg) => {
|
|
2862
|
+
const resolve = _pending.get(msg.id);
|
|
2863
|
+
if (resolve !== void 0) {
|
|
2864
|
+
_pending.delete(msg.id);
|
|
2865
|
+
resolve(msg.result);
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
w.on("error", (err) => {
|
|
2869
|
+
logger.error(
|
|
2870
|
+
"[logFinalizer] Worker error:",
|
|
2871
|
+
err instanceof Error ? err.message : String(err)
|
|
2872
|
+
);
|
|
2873
|
+
});
|
|
2874
|
+
_workers.push(w);
|
|
2875
|
+
}
|
|
2876
|
+
return _workers;
|
|
2877
|
+
}
|
|
2878
|
+
function executeBuildInWorker(job) {
|
|
2879
|
+
return new Promise((resolve) => {
|
|
2880
|
+
const list = getWorkers();
|
|
2881
|
+
const idx = _nextWorker % list.length;
|
|
2882
|
+
_nextWorker++;
|
|
2883
|
+
const w = list[idx];
|
|
2884
|
+
if (w === void 0) return;
|
|
2885
|
+
const id = String(++_jobSeq);
|
|
2886
|
+
_pending.set(id, resolve);
|
|
2887
|
+
w.postMessage({ id, job });
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
function executeFinalizeLogJob(job) {
|
|
2891
|
+
return resolveBuildPromise(job).then((result) => {
|
|
2892
|
+
commitFinalizeLogResult(result);
|
|
2893
|
+
if (result.error !== null && result.error !== "Client aborted") {
|
|
2894
|
+
return Promise.reject(new Error(result.error));
|
|
2895
|
+
}
|
|
2896
|
+
return void 0;
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2899
|
+
const UNASSIGNED_SESSION_QUEUE = "__unassigned__";
|
|
2900
|
+
const queues = /* @__PURE__ */ new Map();
|
|
2901
|
+
function queueKeyForSession(sessionId) {
|
|
2902
|
+
return sessionId ?? UNASSIGNED_SESSION_QUEUE;
|
|
2903
|
+
}
|
|
2904
|
+
function errorMessage$1(err) {
|
|
2905
|
+
return err instanceof Error ? err.message : String(err);
|
|
2906
|
+
}
|
|
2907
|
+
async function runSessionTask(sessionId, taskName, task) {
|
|
2908
|
+
markSessionTaskStarted(sessionId);
|
|
2909
|
+
try {
|
|
2910
|
+
const result = await task();
|
|
2911
|
+
markSessionTaskFinished(sessionId, null);
|
|
2912
|
+
return result;
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
const message = errorMessage$1(err);
|
|
2915
|
+
logger.error(`[sessionRuntime] ${taskName} failed for session ${sessionId ?? "unassigned"}`);
|
|
2916
|
+
markSessionTaskFinished(sessionId, message);
|
|
2917
|
+
return Promise.reject(err);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
function enqueueSessionTask(sessionId, taskName, task) {
|
|
2921
|
+
const queueKey = queueKeyForSession(sessionId);
|
|
2922
|
+
const previous = queues.get(queueKey) ?? Promise.resolve();
|
|
2923
|
+
markSessionTaskQueued(sessionId);
|
|
2924
|
+
const current = previous.catch(() => void 0).then(() => runSessionTask(sessionId, taskName, task));
|
|
2925
|
+
const settled = current.then(
|
|
2926
|
+
() => void 0,
|
|
2927
|
+
() => void 0
|
|
2928
|
+
);
|
|
2929
|
+
queues.set(queueKey, settled);
|
|
2930
|
+
void settled.then(() => {
|
|
2931
|
+
if (queues.get(queueKey) === settled) {
|
|
2932
|
+
queues.delete(queueKey);
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
return current;
|
|
2936
|
+
}
|
|
2937
|
+
function enqueueFinalizeLogJob(job) {
|
|
2938
|
+
return enqueueSessionTask(
|
|
2939
|
+
getLogSessionId(job.log),
|
|
2940
|
+
"finalize-log",
|
|
2941
|
+
() => executeFinalizeLogJob(job)
|
|
2942
|
+
);
|
|
2943
|
+
}
|
|
2371
2944
|
function describeApiRoute(apiPath) {
|
|
2372
2945
|
const endpointPath = apiPath.split("?")[0] ?? "";
|
|
2373
2946
|
const isChatCompletions = endpointPath === PATH_CHAT_COMPLETIONS || endpointPath === PATH_V1_CHAT_COMPLETIONS;
|
|
@@ -2430,33 +3003,6 @@ function buildProxyHeaders(originalHeaders) {
|
|
|
2430
3003
|
}
|
|
2431
3004
|
return { headers, rawHeaders };
|
|
2432
3005
|
}
|
|
2433
|
-
function buildFileLogEntry(log, upstreamUrl) {
|
|
2434
|
-
return {
|
|
2435
|
-
timestamp: log.timestamp,
|
|
2436
|
-
id: log.id,
|
|
2437
|
-
method: log.method,
|
|
2438
|
-
path: log.path,
|
|
2439
|
-
model: log.model,
|
|
2440
|
-
sessionId: log.sessionId,
|
|
2441
|
-
rawRequestBody: log.rawRequestBody,
|
|
2442
|
-
responseStatus: log.responseStatus,
|
|
2443
|
-
responseText: log.responseText,
|
|
2444
|
-
inputTokens: log.inputTokens,
|
|
2445
|
-
outputTokens: log.outputTokens,
|
|
2446
|
-
elapsedMs: log.elapsedMs,
|
|
2447
|
-
streaming: log.streaming,
|
|
2448
|
-
userAgent: log.userAgent,
|
|
2449
|
-
origin: log.origin,
|
|
2450
|
-
upstreamUrl,
|
|
2451
|
-
clientPort: log.clientPort,
|
|
2452
|
-
clientPid: log.clientPid,
|
|
2453
|
-
clientCwd: log.clientCwd,
|
|
2454
|
-
clientProjectFolder: log.clientProjectFolder,
|
|
2455
|
-
streamingChunks: log.streamingChunks,
|
|
2456
|
-
streamingChunksPath: log.streamingChunksPath,
|
|
2457
|
-
error: log.error
|
|
2458
|
-
};
|
|
2459
|
-
}
|
|
2460
3006
|
function parseRequestPath(req, url) {
|
|
2461
3007
|
const route = describeApiRoute(getProxyApiPath(url));
|
|
2462
3008
|
const isPost = req.method === "POST";
|
|
@@ -2469,17 +3015,27 @@ function parseRequestPath(req, url) {
|
|
|
2469
3015
|
normalizedPath: route.normalizedPath
|
|
2470
3016
|
};
|
|
2471
3017
|
}
|
|
2472
|
-
function
|
|
3018
|
+
function errorMessage(err) {
|
|
3019
|
+
return err instanceof Error ? err.message : String(err);
|
|
3020
|
+
}
|
|
3021
|
+
function scheduleLogFinalization(job) {
|
|
3022
|
+
void enqueueFinalizeLogJob(job).catch((err) => {
|
|
3023
|
+
logger.error(
|
|
3024
|
+
`[handler] Session finalization task failed for log #${job.log.id}:`,
|
|
3025
|
+
errorMessage(err)
|
|
3026
|
+
);
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
function handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstreamUrl, log) {
|
|
2473
3030
|
const elapsedMs = Date.now() - startTime;
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: null });
|
|
3031
|
+
scheduleLogFinalization({
|
|
3032
|
+
type: "non-streaming",
|
|
3033
|
+
log,
|
|
3034
|
+
upstreamUrl,
|
|
3035
|
+
elapsedMs,
|
|
3036
|
+
responseStatus: upstreamRes.status,
|
|
3037
|
+
responseBody
|
|
3038
|
+
});
|
|
2483
3039
|
const responseHeaders = new Headers(upstreamRes.headers);
|
|
2484
3040
|
responseHeaders.delete(HEADER_CONTENT_ENCODING);
|
|
2485
3041
|
responseHeaders.delete(HEADER_CONTENT_LENGTH);
|
|
@@ -2488,7 +3044,7 @@ function handleNonStreamingResponse(upstreamRes, responseBody, startTime, format
|
|
|
2488
3044
|
headers: responseHeaders
|
|
2489
3045
|
});
|
|
2490
3046
|
}
|
|
2491
|
-
function handleStreamingResponse(upstreamRes, req, startTime,
|
|
3047
|
+
function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log) {
|
|
2492
3048
|
log.streaming = true;
|
|
2493
3049
|
log.responseStatus = upstreamRes.status;
|
|
2494
3050
|
const chunks = [];
|
|
@@ -2500,18 +3056,15 @@ function handleStreamingResponse(upstreamRes, req, startTime, formatHandler, ups
|
|
|
2500
3056
|
},
|
|
2501
3057
|
flush() {
|
|
2502
3058
|
const full = chunks.join("");
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
}
|
|
2513
|
-
appendLogEntry(buildFileLogEntry(log, upstreamUrl));
|
|
2514
|
-
emitLogUpdate(log);
|
|
3059
|
+
const elapsedMs = Date.now() - startTime;
|
|
3060
|
+
scheduleLogFinalization({
|
|
3061
|
+
type: "streaming",
|
|
3062
|
+
log,
|
|
3063
|
+
upstreamUrl,
|
|
3064
|
+
elapsedMs,
|
|
3065
|
+
responseStatus: upstreamRes.status,
|
|
3066
|
+
rawStream: full
|
|
3067
|
+
});
|
|
2515
3068
|
}
|
|
2516
3069
|
});
|
|
2517
3070
|
if (upstreamRes.body === null) {
|
|
@@ -2521,23 +3074,17 @@ function handleStreamingResponse(upstreamRes, req, startTime, formatHandler, ups
|
|
|
2521
3074
|
req.signal?.addEventListener("abort", () => {
|
|
2522
3075
|
if (log.responseText === null) {
|
|
2523
3076
|
logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
}
|
|
2536
|
-
} else {
|
|
2537
|
-
log.responseText = "Client aborted";
|
|
2538
|
-
}
|
|
2539
|
-
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
2540
|
-
emitLogUpdate(log);
|
|
3077
|
+
const elapsedMs = Date.now() - startTime;
|
|
3078
|
+
const full = chunks.join("");
|
|
3079
|
+
const hasChunks = chunks.length > 0;
|
|
3080
|
+
scheduleLogFinalization({
|
|
3081
|
+
type: "stream-abort",
|
|
3082
|
+
log,
|
|
3083
|
+
upstreamUrl,
|
|
3084
|
+
elapsedMs,
|
|
3085
|
+
rawStream: full,
|
|
3086
|
+
hasChunks
|
|
3087
|
+
});
|
|
2541
3088
|
}
|
|
2542
3089
|
});
|
|
2543
3090
|
const responseHeaders = new Headers(upstreamRes.headers);
|
|
@@ -2625,27 +3172,22 @@ async function handleProxy(req) {
|
|
|
2625
3172
|
log.responseStatus = 499;
|
|
2626
3173
|
log.responseText = "Client aborted";
|
|
2627
3174
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
3175
|
+
finalizeLogUpdate(log);
|
|
2628
3176
|
return new Response("Client aborted", { status: 499 });
|
|
2629
3177
|
}
|
|
2630
3178
|
logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
|
|
2631
3179
|
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
2632
3180
|
log.responseText = String(err);
|
|
2633
3181
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
|
|
3182
|
+
finalizeLogUpdate(log);
|
|
2634
3183
|
return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
|
|
2635
3184
|
}
|
|
2636
3185
|
const isStream = upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ?? false;
|
|
2637
3186
|
if (!isStream) {
|
|
2638
3187
|
const responseBody = await upstreamRes.text();
|
|
2639
|
-
return handleNonStreamingResponse(
|
|
2640
|
-
upstreamRes,
|
|
2641
|
-
responseBody,
|
|
2642
|
-
startTime,
|
|
2643
|
-
formatHandler,
|
|
2644
|
-
upstreamUrl,
|
|
2645
|
-
log
|
|
2646
|
-
);
|
|
3188
|
+
return handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstreamUrl, log);
|
|
2647
3189
|
}
|
|
2648
|
-
return handleStreamingResponse(upstreamRes, req, startTime,
|
|
3190
|
+
return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log);
|
|
2649
3191
|
}
|
|
2650
3192
|
const Route$i = createFileRoute("/proxy/$")({
|
|
2651
3193
|
server: {
|
|
@@ -2662,7 +3204,14 @@ const Route$i = createFileRoute("/proxy/$")({
|
|
|
2662
3204
|
const Route$h = createFileRoute("/api/sessions")({
|
|
2663
3205
|
server: {
|
|
2664
3206
|
handlers: {
|
|
2665
|
-
GET: () =>
|
|
3207
|
+
GET: ({ request }) => {
|
|
3208
|
+
const url = new URL(request.url);
|
|
3209
|
+
const details = url.searchParams.get("details");
|
|
3210
|
+
if (details === "true" || details === "1") {
|
|
3211
|
+
return Response.json(getSessionSnapshots());
|
|
3212
|
+
}
|
|
3213
|
+
return Response.json(getSessions());
|
|
3214
|
+
}
|
|
2666
3215
|
}
|
|
2667
3216
|
}
|
|
2668
3217
|
});
|
|
@@ -3832,7 +4381,7 @@ const Route$6 = createFileRoute("/api/logs/stream")({
|
|
|
3832
4381
|
let cleanedUp = false;
|
|
3833
4382
|
const unsubscribe = onLogUpdate((log) => {
|
|
3834
4383
|
if (controllerRef === null) return;
|
|
3835
|
-
if (sessionId !== void 0 && log
|
|
4384
|
+
if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return;
|
|
3836
4385
|
if (model !== void 0 && log.model !== model) return;
|
|
3837
4386
|
try {
|
|
3838
4387
|
const data = `data: ${JSON.stringify({ type: "update", log })}
|