@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.
Files changed (33) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BhXCLr7m.js → CompareDrawer-BpwZCB6M.js} +1 -1
  3. package/.output/public/assets/{ReplayDialog-CzRPSXwa.js → ReplayDialog-Clratkzl.js} +1 -1
  4. package/.output/public/assets/{RequestAnatomy-lMQonao2.js → RequestAnatomy-EtiX0r_G.js} +1 -1
  5. package/.output/public/assets/{ResponseView-Bt0vngo0.js → ResponseView-CJqxo-EN.js} +1 -1
  6. package/.output/public/assets/{StreamingChunkSequence-Dq9XY2E9.js → StreamingChunkSequence-BIbRqQiV.js} +1 -1
  7. package/.output/public/assets/{index-B4nxi_tZ.js → index-B-0F9n1w.js} +8 -8
  8. package/.output/public/assets/{json-viewer-C8ttTXtv.js → json-viewer-D-z1r1Pp.js} +1 -1
  9. package/.output/public/assets/{main-Dgme52Fp.js → main-CZJ63sQh.js} +1 -1
  10. package/.output/server/_ssr/{CompareDrawer-D-Nj8wmx.mjs → CompareDrawer-BJr-913n.mjs} +4 -3
  11. package/.output/server/_ssr/{ReplayDialog-DcucC22E.mjs → ReplayDialog-BwmToGuR.mjs} +5 -4
  12. package/.output/server/_ssr/{RequestAnatomy-aL8GAcW2.mjs → RequestAnatomy-BmMiPRPB.mjs} +3 -2
  13. package/.output/server/_ssr/{ResponseView-BHgpoGaF.mjs → ResponseView-ZB9-8Raw.mjs} +4 -3
  14. package/.output/server/_ssr/{StreamingChunkSequence-DrT7StyS.mjs → StreamingChunkSequence-DWm4CQWC.mjs} +4 -3
  15. package/.output/server/_ssr/{index-nUG0H1oS.mjs → index-C7I_Qgt0.mjs} +18 -20
  16. package/.output/server/_ssr/index.mjs +2 -2
  17. package/.output/server/_ssr/{json-viewer-DLsDT0RE.mjs → json-viewer-D9XETzwp.mjs} +3 -2
  18. package/.output/server/_ssr/{router-DG_jmXCF.mjs → router-711KpGkz.mjs} +647 -98
  19. package/.output/server/{_tanstack-start-manifest_v-D0JtrQPv.mjs → _tanstack-start-manifest_v-noQw0Vmw.mjs} +1 -1
  20. package/.output/server/index.mjs +53 -53
  21. package/package.json +1 -1
  22. package/src/components/proxy-viewer/TurnGroup.tsx +10 -13
  23. package/src/proxy/handler.ts +52 -84
  24. package/src/proxy/logFinalizer.ts +301 -0
  25. package/src/proxy/logFinalizer.worker.ts +24 -0
  26. package/src/proxy/schemas.ts +8 -3
  27. package/src/proxy/sessionProcess.ts +133 -0
  28. package/src/proxy/sessionRuntime.ts +85 -0
  29. package/src/proxy/sessionSupervisor.ts +282 -0
  30. package/src/proxy/sessionWorkerEntry.ts +26 -0
  31. package/src/proxy/store.ts +64 -20
  32. package/src/routes/api/logs.stream.ts +2 -2
  33. 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-Dgme52Fp.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-B4nxi_tZ.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-Dgme52Fp.js" });
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
  };
@@ -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-16T13:55:29.091Z",
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-16T13:55:29.091Z",
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-C8ttTXtv.js": {
52
+ "/assets/json-viewer-D-z1r1Pp.js": {
60
53
  "type": "text/javascript; charset=utf-8",
61
- "etag": '"1e60c-SeTWoRDshC9JNLsXxB+oZqqYa9Q"',
62
- "mtime": "2026-06-16T13:55:29.091Z",
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-C8ttTXtv.js"
57
+ "path": "../public/assets/json-viewer-D-z1r1Pp.js"
65
58
  },
66
- "/assets/minimax-BPMzvuL-.jpeg": {
67
- "type": "image/jpeg",
68
- "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
69
- "mtime": "2026-06-16T13:55:29.091Z",
70
- "size": 6918,
71
- "path": "../public/assets/minimax-BPMzvuL-.jpeg"
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-Bt0vngo0.js": {
66
+ "/assets/ResponseView-CJqxo-EN.js": {
74
67
  "type": "text/javascript; charset=utf-8",
75
- "etag": '"6e82-qyYOt0PCIhyWDXDEAIXQwFBrhZ4"',
76
- "mtime": "2026-06-16T13:55:29.091Z",
68
+ "etag": '"6e82-6eo0MjJytLP7tyoCDUCMVX+Imqs"',
69
+ "mtime": "2026-06-17T03:16:09.843Z",
77
70
  "size": 28290,
78
- "path": "../public/assets/ResponseView-Bt0vngo0.js"
71
+ "path": "../public/assets/ResponseView-CJqxo-EN.js"
79
72
  },
80
- "/assets/ReplayDialog-CzRPSXwa.js": {
73
+ "/assets/RequestAnatomy-EtiX0r_G.js": {
81
74
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"11b1-B/HuE6bAKHy7tYViDlzZLxkMxYU"',
83
- "mtime": "2026-06-16T13:55:29.091Z",
84
- "size": 4529,
85
- "path": "../public/assets/ReplayDialog-CzRPSXwa.js"
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/RequestAnatomy-lMQonao2.js": {
80
+ "/assets/ReplayDialog-Clratkzl.js": {
88
81
  "type": "text/javascript; charset=utf-8",
89
- "etag": '"141b-MRuvyBmDPVx58t88HF6lUTpd46k"',
90
- "mtime": "2026-06-16T13:55:29.091Z",
91
- "size": 5147,
92
- "path": "../public/assets/RequestAnatomy-lMQonao2.js"
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-Dq9XY2E9.js": {
87
+ "/assets/StreamingChunkSequence-BIbRqQiV.js": {
95
88
  "type": "text/javascript; charset=utf-8",
96
- "etag": '"d72-2j21Nk/nFpIRvqZLJEvSPNG4/bA"',
97
- "mtime": "2026-06-16T13:55:29.091Z",
89
+ "etag": '"d72-t3qm896NP0azRIGukgZdUpqJo4U"',
90
+ "mtime": "2026-06-17T03:16:09.843Z",
98
91
  "size": 3442,
99
- "path": "../public/assets/StreamingChunkSequence-Dq9XY2E9.js"
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-16T13:55:29.091Z",
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-16T13:55:29.091Z",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "type": "module",
5
5
  "description": "LLM API proxy inspector — captures and displays requests/responses from AI coding tools in a web UI",
6
6
  "license": "MIT",
@@ -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 totalElapsed = 0;
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
- totalElapsed += e.log.elapsedMs;
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
- totalElapsed,
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.hasElapsed &&
106
+ aggregate.maxElapsed !== null &&
110
107
  slowResponseThresholdSeconds > 0 &&
111
- aggregate.totalElapsed > slowResponseThresholdSeconds * 1000;
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.hasElapsed && (
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.totalElapsed)}
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.totalElapsed,
262
+ aggregate.maxElapsed,
266
263
  )} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1000)}`
267
- : "Total elapsed response time"}
264
+ : "Slowest request in this turn"}
268
265
  </TooltipContent>
269
266
  </Tooltip>
270
267
  </TooltipProvider>
@@ -1,11 +1,10 @@
1
- import { createLog, emitLogUpdate, getNextLogId, type CapturedLog } from "./store";
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, type FormatHandler } from "./formats";
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
- log.elapsedMs = elapsedMs;
123
- log.responseStatus = upstreamRes.status;
124
- log.responseText = responseBody;
125
- log.inputTokens = tokens.inputTokens;
126
- log.outputTokens = tokens.outputTokens;
127
- log.cacheCreationInputTokens = tokens.cacheCreationInputTokens;
128
- log.cacheReadInputTokens = tokens.cacheReadInputTokens;
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
- log.elapsedMs = Date.now() - startTime;
164
- log.responseText = formatHandler.extractStream(full, log, log.model ?? undefined, true);
165
- // Persist chunks to disk
166
- if (log.streamingChunks && log.streamingChunks.chunks.length > 0) {
167
- const chunkPath = writeChunks(
168
- log.id,
169
- log.streamingChunks.chunks,
170
- log.streamingChunks.truncated,
171
- );
172
- log.streamingChunksPath = chunkPath;
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
- log.elapsedMs = Date.now() - startTime;
189
- if (chunks.length > 0) {
190
- const full = chunks.join("");
191
- log.responseText = formatHandler.extractStream(full, log, log.model ?? undefined, true);
192
- // Persist chunks to disk on abort
193
- if (log.streamingChunks && log.streamingChunks.chunks.length > 0) {
194
- const chunkPath = writeChunks(
195
- log.id,
196
- log.streamingChunks.chunks,
197
- log.streamingChunks.truncated,
198
- );
199
- log.streamingChunksPath = chunkPath;
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: FormatHandler | null = formatForPath(parsed.apiPath);
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, formatHandler, upstreamUrl, log);
333
+ return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log);
366
334
  }