@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,4 +1,4 @@
|
|
|
1
|
-
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-
|
|
1
|
+
const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$"], "preloads": ["/assets/main-CZJ63sQh.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-B-0F9n1w.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/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/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/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/providers/scan"] }, "/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/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.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", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-CZJ63sQh.js" });
|
|
2
2
|
export {
|
|
3
3
|
tsrStartManifest
|
|
4
4
|
};
|
package/.output/server/index.mjs
CHANGED
|
@@ -38,93 +38,93 @@ const assets = {
|
|
|
38
38
|
"/assets/alibaba-TTwafVwX.svg": {
|
|
39
39
|
"type": "image/svg+xml",
|
|
40
40
|
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
41
|
-
"mtime": "2026-06-
|
|
41
|
+
"mtime": "2026-06-17T03:16:09.841Z",
|
|
42
42
|
"size": 5915,
|
|
43
43
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
44
|
},
|
|
45
|
-
"/assets/CompareDrawer-BhXCLr7m.js": {
|
|
46
|
-
"type": "text/javascript; charset=utf-8",
|
|
47
|
-
"etag": '"4a10-YLpRKqytDE/DVrz+mZssMHopfG4"',
|
|
48
|
-
"mtime": "2026-06-16T13:55:29.091Z",
|
|
49
|
-
"size": 18960,
|
|
50
|
-
"path": "../public/assets/CompareDrawer-BhXCLr7m.js"
|
|
51
|
-
},
|
|
52
45
|
"/assets/index-DoGvsnbA.css": {
|
|
53
46
|
"type": "text/css; charset=utf-8",
|
|
54
47
|
"etag": '"16d26-qw65JIM4oxztXa/jhWYD9PPuvfA"',
|
|
55
|
-
"mtime": "2026-06-
|
|
48
|
+
"mtime": "2026-06-17T03:16:09.841Z",
|
|
56
49
|
"size": 93478,
|
|
57
50
|
"path": "../public/assets/index-DoGvsnbA.css"
|
|
58
51
|
},
|
|
59
|
-
"/assets/json-viewer-
|
|
52
|
+
"/assets/json-viewer-D-z1r1Pp.js": {
|
|
60
53
|
"type": "text/javascript; charset=utf-8",
|
|
61
|
-
"etag": '"1e60c-
|
|
62
|
-
"mtime": "2026-06-
|
|
54
|
+
"etag": '"1e60c-b+Su3KxY/1YyCIOXy/kCh7EVJWA"',
|
|
55
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
63
56
|
"size": 124428,
|
|
64
|
-
"path": "../public/assets/json-viewer-
|
|
57
|
+
"path": "../public/assets/json-viewer-D-z1r1Pp.js"
|
|
65
58
|
},
|
|
66
|
-
"/assets/
|
|
67
|
-
"type": "
|
|
68
|
-
"etag": '"
|
|
69
|
-
"mtime": "2026-06-
|
|
70
|
-
"size":
|
|
71
|
-
"path": "../public/assets/
|
|
59
|
+
"/assets/CompareDrawer-BpwZCB6M.js": {
|
|
60
|
+
"type": "text/javascript; charset=utf-8",
|
|
61
|
+
"etag": '"4a10-Mv9zkKfYXObP08//mt6T/CvprxM"',
|
|
62
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
63
|
+
"size": 18960,
|
|
64
|
+
"path": "../public/assets/CompareDrawer-BpwZCB6M.js"
|
|
72
65
|
},
|
|
73
|
-
"/assets/ResponseView-
|
|
66
|
+
"/assets/ResponseView-CJqxo-EN.js": {
|
|
74
67
|
"type": "text/javascript; charset=utf-8",
|
|
75
|
-
"etag": '"6e82-
|
|
76
|
-
"mtime": "2026-06-
|
|
68
|
+
"etag": '"6e82-6eo0MjJytLP7tyoCDUCMVX+Imqs"',
|
|
69
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
77
70
|
"size": 28290,
|
|
78
|
-
"path": "../public/assets/ResponseView-
|
|
71
|
+
"path": "../public/assets/ResponseView-CJqxo-EN.js"
|
|
79
72
|
},
|
|
80
|
-
"/assets/
|
|
73
|
+
"/assets/RequestAnatomy-EtiX0r_G.js": {
|
|
81
74
|
"type": "text/javascript; charset=utf-8",
|
|
82
|
-
"etag": '"
|
|
83
|
-
"mtime": "2026-06-
|
|
84
|
-
"size":
|
|
85
|
-
"path": "../public/assets/
|
|
75
|
+
"etag": '"141b-XyuVMPaOmzNpTd/Yosdaset1y/Q"',
|
|
76
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
77
|
+
"size": 5147,
|
|
78
|
+
"path": "../public/assets/RequestAnatomy-EtiX0r_G.js"
|
|
86
79
|
},
|
|
87
|
-
"/assets/
|
|
80
|
+
"/assets/ReplayDialog-Clratkzl.js": {
|
|
88
81
|
"type": "text/javascript; charset=utf-8",
|
|
89
|
-
"etag": '"
|
|
90
|
-
"mtime": "2026-06-
|
|
91
|
-
"size":
|
|
92
|
-
"path": "../public/assets/
|
|
82
|
+
"etag": '"11b1-d+cNxLktG4hY5CiGmJvG2zg1hgE"',
|
|
83
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
84
|
+
"size": 4529,
|
|
85
|
+
"path": "../public/assets/ReplayDialog-Clratkzl.js"
|
|
93
86
|
},
|
|
94
|
-
"/assets/StreamingChunkSequence-
|
|
87
|
+
"/assets/StreamingChunkSequence-BIbRqQiV.js": {
|
|
95
88
|
"type": "text/javascript; charset=utf-8",
|
|
96
|
-
"etag": '"d72-
|
|
97
|
-
"mtime": "2026-06-
|
|
89
|
+
"etag": '"d72-t3qm896NP0azRIGukgZdUpqJo4U"',
|
|
90
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
98
91
|
"size": 3442,
|
|
99
|
-
"path": "../public/assets/StreamingChunkSequence-
|
|
92
|
+
"path": "../public/assets/StreamingChunkSequence-BIbRqQiV.js"
|
|
93
|
+
},
|
|
94
|
+
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
95
|
+
"type": "image/jpeg",
|
|
96
|
+
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
97
|
+
"mtime": "2026-06-17T03:16:09.841Z",
|
|
98
|
+
"size": 6918,
|
|
99
|
+
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
100
|
+
},
|
|
101
|
+
"/assets/index-B-0F9n1w.js": {
|
|
102
|
+
"type": "text/javascript; charset=utf-8",
|
|
103
|
+
"etag": '"743e9-3qHa5OB4CA7Rxh5qKe3GN7HIf8w"',
|
|
104
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
105
|
+
"size": 476137,
|
|
106
|
+
"path": "../public/assets/index-B-0F9n1w.js"
|
|
100
107
|
},
|
|
101
108
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
102
109
|
"type": "image/svg+xml",
|
|
103
110
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
104
|
-
"mtime": "2026-06-
|
|
111
|
+
"mtime": "2026-06-17T03:16:09.841Z",
|
|
105
112
|
"size": 11256,
|
|
106
113
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
107
114
|
},
|
|
108
|
-
"/assets/index-B4nxi_tZ.js": {
|
|
109
|
-
"type": "text/javascript; charset=utf-8",
|
|
110
|
-
"etag": '"743d2-L+tnpvRJci6Om1zGffL+DMn8ebc"',
|
|
111
|
-
"mtime": "2026-06-16T13:55:29.091Z",
|
|
112
|
-
"size": 476114,
|
|
113
|
-
"path": "../public/assets/index-B4nxi_tZ.js"
|
|
114
|
-
},
|
|
115
|
-
"/assets/main-Dgme52Fp.js": {
|
|
116
|
-
"type": "text/javascript; charset=utf-8",
|
|
117
|
-
"etag": '"505ae-rOzjw777KA3OeVo1WNNZIYQ7M2g"',
|
|
118
|
-
"mtime": "2026-06-16T13:55:29.091Z",
|
|
119
|
-
"size": 329134,
|
|
120
|
-
"path": "../public/assets/main-Dgme52Fp.js"
|
|
121
|
-
},
|
|
122
115
|
"/assets/qwen-CONDcHqt.png": {
|
|
123
116
|
"type": "image/png",
|
|
124
117
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
125
|
-
"mtime": "2026-06-
|
|
118
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
126
119
|
"size": 357059,
|
|
127
120
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
121
|
+
},
|
|
122
|
+
"/assets/main-CZJ63sQh.js": {
|
|
123
|
+
"type": "text/javascript; charset=utf-8",
|
|
124
|
+
"etag": '"505ae-5KYVtgQMJj49Xmb8K+9UosNpz6U"',
|
|
125
|
+
"mtime": "2026-06-17T03:16:09.843Z",
|
|
126
|
+
"size": 329134,
|
|
127
|
+
"path": "../public/assets/main-CZJ63sQh.js"
|
|
128
128
|
}
|
|
129
129
|
};
|
|
130
130
|
function readAsset(id) {
|
package/package.json
CHANGED
|
@@ -64,9 +64,8 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
64
64
|
const aggregate = useMemo(() => {
|
|
65
65
|
let totalInput = 0;
|
|
66
66
|
let totalOutput = 0;
|
|
67
|
-
let
|
|
67
|
+
let maxElapsed: number | null = null;
|
|
68
68
|
let hasTokens = false;
|
|
69
|
-
let hasElapsed = false;
|
|
70
69
|
for (const e of entries) {
|
|
71
70
|
if (e.log.inputTokens !== null) {
|
|
72
71
|
totalInput += e.log.inputTokens;
|
|
@@ -77,16 +76,14 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
77
76
|
hasTokens = true;
|
|
78
77
|
}
|
|
79
78
|
if (e.log.elapsedMs !== null) {
|
|
80
|
-
|
|
81
|
-
hasElapsed = true;
|
|
79
|
+
maxElapsed = maxElapsed === null ? e.log.elapsedMs : Math.max(maxElapsed, e.log.elapsedMs);
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
return {
|
|
85
83
|
totalInput,
|
|
86
84
|
totalOutput,
|
|
87
85
|
hasTokens,
|
|
88
|
-
|
|
89
|
-
hasElapsed,
|
|
86
|
+
maxElapsed,
|
|
90
87
|
};
|
|
91
88
|
}, [entries, lastIdx]);
|
|
92
89
|
|
|
@@ -106,9 +103,9 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
106
103
|
|
|
107
104
|
const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
|
|
108
105
|
const aggregateIsSlow =
|
|
109
|
-
aggregate.
|
|
106
|
+
aggregate.maxElapsed !== null &&
|
|
110
107
|
slowResponseThresholdSeconds > 0 &&
|
|
111
|
-
aggregate.
|
|
108
|
+
aggregate.maxElapsed > slowResponseThresholdSeconds * 1000;
|
|
112
109
|
|
|
113
110
|
// ResizeObserver → re-render connectors when any LogEntry height changes
|
|
114
111
|
const [layoutVersion, setLayoutVersion] = useState(0);
|
|
@@ -239,8 +236,8 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
239
236
|
</span>
|
|
240
237
|
)}
|
|
241
238
|
|
|
242
|
-
{/* Elapsed */}
|
|
243
|
-
{aggregate.
|
|
239
|
+
{/* Elapsed — slowest single request in the turn (not the sum) */}
|
|
240
|
+
{aggregate.maxElapsed !== null && (
|
|
244
241
|
<TooltipProvider>
|
|
245
242
|
<Tooltip>
|
|
246
243
|
<TooltipTrigger asChild>
|
|
@@ -252,7 +249,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
252
249
|
>
|
|
253
250
|
<Clock className="size-3" />
|
|
254
251
|
<span className="font-mono tabular-nums">
|
|
255
|
-
{formatElapsed(aggregate.
|
|
252
|
+
{formatElapsed(aggregate.maxElapsed)}
|
|
256
253
|
</span>
|
|
257
254
|
{aggregateIsSlow && (
|
|
258
255
|
<AlertTriangle className="size-3" aria-label="Slow response" />
|
|
@@ -262,9 +259,9 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
262
259
|
<TooltipContent>
|
|
263
260
|
{aggregateIsSlow
|
|
264
261
|
? `Slow response: ${formatElapsed(
|
|
265
|
-
aggregate.
|
|
262
|
+
aggregate.maxElapsed,
|
|
266
263
|
)} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1000)}`
|
|
267
|
-
: "
|
|
264
|
+
: "Slowest request in this turn"}
|
|
268
265
|
</TooltipContent>
|
|
269
266
|
</Tooltip>
|
|
270
267
|
</TooltipProvider>
|
package/src/proxy/handler.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createLog,
|
|
1
|
+
import { createLog, finalizeLogUpdate, getNextLogId, type CapturedLog } from "./store";
|
|
2
2
|
import { appendLogEntry, logger } from "./logger";
|
|
3
|
-
import { writeChunks } from "./chunkStorage";
|
|
4
3
|
import { extractRequestMetadata } from "./schemas";
|
|
5
4
|
import { registry } from "./formats";
|
|
6
5
|
import { findProviderByModel } from "./providers";
|
|
7
6
|
import { getClientInfo } from "./socketTracker";
|
|
8
|
-
import { formatForPath
|
|
7
|
+
import { formatForPath } from "./formats";
|
|
9
8
|
import {
|
|
10
9
|
PROXY_IDENTITY,
|
|
11
10
|
PRESERVE_HEADERS,
|
|
@@ -22,6 +21,8 @@ import {
|
|
|
22
21
|
import { getConfig } from "./config";
|
|
23
22
|
import { stripClaudeCodeBillingHeader } from "./claudeCodeStrip";
|
|
24
23
|
import { stripOpenAIOrphanToolMessages } from "./openaiOrphanToolStrip";
|
|
24
|
+
import { buildFileLogEntry, type FinalizeLogJob } from "./logFinalizer";
|
|
25
|
+
import { enqueueFinalizeLogJob } from "./sessionRuntime";
|
|
25
26
|
import {
|
|
26
27
|
buildUpstreamUrl,
|
|
27
28
|
describeApiRoute,
|
|
@@ -59,34 +60,6 @@ function buildProxyHeaders(originalHeaders: Headers): {
|
|
|
59
60
|
return { headers, rawHeaders };
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
function buildFileLogEntry(log: CapturedLog, upstreamUrl: string): Record<string, unknown> {
|
|
63
|
-
return {
|
|
64
|
-
timestamp: log.timestamp,
|
|
65
|
-
id: log.id,
|
|
66
|
-
method: log.method,
|
|
67
|
-
path: log.path,
|
|
68
|
-
model: log.model,
|
|
69
|
-
sessionId: log.sessionId,
|
|
70
|
-
rawRequestBody: log.rawRequestBody,
|
|
71
|
-
responseStatus: log.responseStatus,
|
|
72
|
-
responseText: log.responseText,
|
|
73
|
-
inputTokens: log.inputTokens,
|
|
74
|
-
outputTokens: log.outputTokens,
|
|
75
|
-
elapsedMs: log.elapsedMs,
|
|
76
|
-
streaming: log.streaming,
|
|
77
|
-
userAgent: log.userAgent,
|
|
78
|
-
origin: log.origin,
|
|
79
|
-
upstreamUrl,
|
|
80
|
-
clientPort: log.clientPort,
|
|
81
|
-
clientPid: log.clientPid,
|
|
82
|
-
clientCwd: log.clientCwd,
|
|
83
|
-
clientProjectFolder: log.clientProjectFolder,
|
|
84
|
-
streamingChunks: log.streamingChunks,
|
|
85
|
-
streamingChunksPath: log.streamingChunksPath,
|
|
86
|
-
error: log.error,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
63
|
type ParsedRequestPath = {
|
|
91
64
|
apiPath: string;
|
|
92
65
|
isMessages: boolean;
|
|
@@ -108,26 +81,36 @@ function parseRequestPath(req: Request, url: URL): ParsedRequestPath {
|
|
|
108
81
|
};
|
|
109
82
|
}
|
|
110
83
|
|
|
84
|
+
function errorMessage(err: unknown): string {
|
|
85
|
+
return err instanceof Error ? err.message : String(err);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function scheduleLogFinalization(job: FinalizeLogJob): void {
|
|
89
|
+
void enqueueFinalizeLogJob(job).catch((err) => {
|
|
90
|
+
logger.error(
|
|
91
|
+
`[handler] Session finalization task failed for log #${job.log.id}:`,
|
|
92
|
+
errorMessage(err),
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
111
97
|
function handleNonStreamingResponse(
|
|
112
98
|
upstreamRes: Response,
|
|
113
99
|
responseBody: string,
|
|
114
100
|
startTime: number,
|
|
115
|
-
formatHandler: FormatHandler,
|
|
116
101
|
upstreamUrl: string,
|
|
117
102
|
log: CapturedLog,
|
|
118
103
|
): Response {
|
|
119
104
|
const elapsedMs = Date.now() - startTime;
|
|
120
|
-
const tokens = formatHandler.extractTokens(responseBody);
|
|
121
105
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: null });
|
|
106
|
+
scheduleLogFinalization({
|
|
107
|
+
type: "non-streaming",
|
|
108
|
+
log,
|
|
109
|
+
upstreamUrl,
|
|
110
|
+
elapsedMs,
|
|
111
|
+
responseStatus: upstreamRes.status,
|
|
112
|
+
responseBody,
|
|
113
|
+
});
|
|
131
114
|
|
|
132
115
|
const responseHeaders = new Headers(upstreamRes.headers);
|
|
133
116
|
responseHeaders.delete(HEADER_CONTENT_ENCODING);
|
|
@@ -143,7 +126,6 @@ function handleStreamingResponse(
|
|
|
143
126
|
upstreamRes: Response,
|
|
144
127
|
req: Request,
|
|
145
128
|
startTime: number,
|
|
146
|
-
formatHandler: FormatHandler,
|
|
147
129
|
upstreamUrl: string,
|
|
148
130
|
log: CapturedLog,
|
|
149
131
|
): Response {
|
|
@@ -160,19 +142,16 @@ function handleStreamingResponse(
|
|
|
160
142
|
},
|
|
161
143
|
flush() {
|
|
162
144
|
const full = chunks.join("");
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
appendLogEntry(buildFileLogEntry(log, upstreamUrl));
|
|
175
|
-
emitLogUpdate(log);
|
|
145
|
+
const elapsedMs = Date.now() - startTime;
|
|
146
|
+
|
|
147
|
+
scheduleLogFinalization({
|
|
148
|
+
type: "streaming",
|
|
149
|
+
log,
|
|
150
|
+
upstreamUrl,
|
|
151
|
+
elapsedMs,
|
|
152
|
+
responseStatus: upstreamRes.status,
|
|
153
|
+
rawStream: full,
|
|
154
|
+
});
|
|
176
155
|
},
|
|
177
156
|
});
|
|
178
157
|
|
|
@@ -185,24 +164,18 @@ function handleStreamingResponse(
|
|
|
185
164
|
req.signal?.addEventListener("abort", () => {
|
|
186
165
|
if (log.responseText === null) {
|
|
187
166
|
logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
log.responseText = "Client aborted";
|
|
203
|
-
}
|
|
204
|
-
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
205
|
-
emitLogUpdate(log);
|
|
167
|
+
const elapsedMs = Date.now() - startTime;
|
|
168
|
+
const full = chunks.join("");
|
|
169
|
+
const hasChunks = chunks.length > 0;
|
|
170
|
+
|
|
171
|
+
scheduleLogFinalization({
|
|
172
|
+
type: "stream-abort",
|
|
173
|
+
log,
|
|
174
|
+
upstreamUrl,
|
|
175
|
+
elapsedMs,
|
|
176
|
+
rawStream: full,
|
|
177
|
+
hasChunks,
|
|
178
|
+
});
|
|
206
179
|
}
|
|
207
180
|
});
|
|
208
181
|
|
|
@@ -293,7 +266,7 @@ export async function handleProxy(req: Request): Promise<Response> {
|
|
|
293
266
|
// with format="openai" may still expose an Anthropic-compatible endpoint
|
|
294
267
|
// (e.g. MiniMax's /anthropic path), and the response shape always follows
|
|
295
268
|
// the URL path the client hit, not the provider.format label.
|
|
296
|
-
const formatHandler
|
|
269
|
+
const formatHandler = formatForPath(parsed.apiPath);
|
|
297
270
|
if (formatHandler === null) {
|
|
298
271
|
return new Response("Forbidden: unsupported format", { status: STATUS_FORBIDDEN });
|
|
299
272
|
}
|
|
@@ -338,12 +311,14 @@ export async function handleProxy(req: Request): Promise<Response> {
|
|
|
338
311
|
log.responseStatus = 499; // Client Closed Request (non-standard but descriptive)
|
|
339
312
|
log.responseText = "Client aborted";
|
|
340
313
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
314
|
+
finalizeLogUpdate(log);
|
|
341
315
|
return new Response("Client aborted", { status: 499 });
|
|
342
316
|
}
|
|
343
317
|
logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
|
|
344
318
|
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
345
319
|
log.responseText = String(err);
|
|
346
320
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
|
|
321
|
+
finalizeLogUpdate(log);
|
|
347
322
|
return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
|
|
348
323
|
}
|
|
349
324
|
|
|
@@ -352,15 +327,8 @@ export async function handleProxy(req: Request): Promise<Response> {
|
|
|
352
327
|
|
|
353
328
|
if (!isStream) {
|
|
354
329
|
const responseBody = await upstreamRes.text();
|
|
355
|
-
return handleNonStreamingResponse(
|
|
356
|
-
upstreamRes,
|
|
357
|
-
responseBody,
|
|
358
|
-
startTime,
|
|
359
|
-
formatHandler,
|
|
360
|
-
upstreamUrl,
|
|
361
|
-
log,
|
|
362
|
-
);
|
|
330
|
+
return handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstreamUrl, log);
|
|
363
331
|
}
|
|
364
332
|
|
|
365
|
-
return handleStreamingResponse(upstreamRes, req, startTime,
|
|
333
|
+
return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log);
|
|
366
334
|
}
|