context-lens 0.2.0 → 0.3.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/README.md +120 -25
- package/dist/cli-utils.d.ts +2 -1
- package/dist/cli-utils.d.ts.map +1 -1
- package/dist/cli-utils.js +40 -13
- package/dist/cli-utils.js.map +1 -1
- package/dist/cli.js +184 -68
- package/dist/cli.js.map +1 -1
- package/dist/core/conversation.d.ts +63 -0
- package/dist/core/conversation.d.ts.map +1 -0
- package/dist/core/conversation.js +305 -0
- package/dist/core/conversation.js.map +1 -0
- package/dist/core/health.d.ts +7 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +311 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/models.d.ts +36 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +111 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/parse.d.ts +17 -0
- package/dist/core/parse.d.ts.map +1 -0
- package/dist/core/parse.js +349 -0
- package/dist/core/parse.js.map +1 -0
- package/dist/core/routing.d.ts +47 -0
- package/dist/core/routing.d.ts.map +1 -0
- package/dist/core/routing.js +132 -0
- package/dist/core/routing.js.map +1 -0
- package/dist/core/security.d.ts +8 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +222 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/source.d.ts +22 -0
- package/dist/core/source.d.ts.map +1 -0
- package/dist/core/source.js +56 -0
- package/dist/core/source.js.map +1 -0
- package/dist/core/tokens.d.ts +29 -0
- package/dist/core/tokens.d.ts.map +1 -0
- package/dist/core/tokens.js +163 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/core.d.ts +14 -22
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +14 -471
- package/dist/core.js.map +1 -1
- package/dist/http/headers.d.ts +25 -0
- package/dist/http/headers.d.ts.map +1 -0
- package/dist/http/headers.js +54 -0
- package/dist/http/headers.js.map +1 -0
- package/dist/lhar/composition.d.ts +12 -0
- package/dist/lhar/composition.d.ts.map +1 -0
- package/dist/lhar/composition.js +258 -0
- package/dist/lhar/composition.js.map +1 -0
- package/dist/lhar/export.d.ts +5 -0
- package/dist/lhar/export.d.ts.map +1 -0
- package/dist/lhar/export.js +59 -0
- package/dist/lhar/export.js.map +1 -0
- package/dist/lhar/record.d.ts +6 -0
- package/dist/lhar/record.d.ts.map +1 -0
- package/dist/lhar/record.js +216 -0
- package/dist/lhar/record.js.map +1 -0
- package/dist/lhar/response.d.ts +11 -0
- package/dist/lhar/response.d.ts.map +1 -0
- package/dist/lhar/response.js +132 -0
- package/dist/lhar/response.js.map +1 -0
- package/dist/lhar-types.generated.d.ts +24 -3
- package/dist/lhar-types.generated.d.ts.map +1 -1
- package/dist/lhar.d.ts +12 -19
- package/dist/lhar.d.ts.map +1 -1
- package/dist/lhar.js +16 -473
- package/dist/lhar.js.map +1 -1
- package/dist/server/api.d.ts +8 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +292 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/config.d.ts +13 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +36 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/projection.d.ts +9 -0
- package/dist/server/projection.d.ts.map +1 -0
- package/dist/server/projection.js +47 -0
- package/dist/server/projection.js.map +1 -0
- package/dist/server/proxy.d.ts +13 -0
- package/dist/server/proxy.d.ts.map +1 -0
- package/dist/server/proxy.js +218 -0
- package/dist/server/proxy.js.map +1 -0
- package/dist/server/static.d.ts +9 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +78 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/store.d.ts +81 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/store.js +632 -0
- package/dist/server/store.js.map +1 -0
- package/dist/server/webui.d.ts +5 -0
- package/dist/server/webui.d.ts.map +1 -0
- package/dist/server/webui.js +42 -0
- package/dist/server/webui.js.map +1 -0
- package/dist/server-utils.d.ts +2 -2
- package/dist/server-utils.d.ts.map +1 -1
- package/dist/server-utils.js +12 -21
- package/dist/server-utils.js.map +1 -1
- package/dist/server.js +31 -697
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +94 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/version.generated.d.ts +2 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +2 -0
- package/dist/version.generated.js.map +1 -0
- package/findings-screenshot.png +0 -0
- package/messages-screenshot.png +0 -0
- package/package.json +23 -10
- package/schema/lhar.schema.json +58 -4
- package/screenshot-overview.png +0 -0
- package/sessions-screenshot.png +0 -0
- package/timeline-screenshot.png +0 -0
- package/diff.png +0 -0
- package/overview-sidebar.png +0 -0
- package/public/index.html +0 -2804
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import url from "node:url";
|
|
4
|
+
import { detectApiFormat, estimateTokens, extractSource, parseContextInfo, resolveTargetUrl, } from "../core.js";
|
|
5
|
+
import { headersForResolution, selectHeaders } from "../server-utils.js";
|
|
6
|
+
function buildForwardHeaders(reqHeaders, targetHost, bodyLength) {
|
|
7
|
+
const forwardHeaders = { ...reqHeaders };
|
|
8
|
+
delete forwardHeaders["x-target-url"];
|
|
9
|
+
delete forwardHeaders.host;
|
|
10
|
+
if (targetHost) {
|
|
11
|
+
forwardHeaders.host = targetHost;
|
|
12
|
+
}
|
|
13
|
+
if (bodyLength != null) {
|
|
14
|
+
delete forwardHeaders["transfer-encoding"];
|
|
15
|
+
forwardHeaders["content-length"] = bodyLength;
|
|
16
|
+
}
|
|
17
|
+
return forwardHeaders;
|
|
18
|
+
}
|
|
19
|
+
function attachProxyLifecycleHandlers(res, proxyReq) {
|
|
20
|
+
// Abort upstream request if client disconnects
|
|
21
|
+
res.on("close", () => {
|
|
22
|
+
if (!proxyReq.destroyed)
|
|
23
|
+
proxyReq.destroy();
|
|
24
|
+
});
|
|
25
|
+
proxyReq.on("error", (err) => {
|
|
26
|
+
// Suppress errors from client-initiated disconnects
|
|
27
|
+
if (res.destroyed)
|
|
28
|
+
return;
|
|
29
|
+
const detail = err.message || ("code" in err ? err.code : "unknown");
|
|
30
|
+
console.error("Proxy error:", detail);
|
|
31
|
+
if (!res.headersSent) {
|
|
32
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
33
|
+
}
|
|
34
|
+
if (!res.destroyed) {
|
|
35
|
+
res.end(JSON.stringify({ error: "Proxy error", details: err.message }));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function forwardRequest(req, res, parsedUrl, body, opts) {
|
|
40
|
+
const { targetUrl } = resolveTargetUrl({ pathname: parsedUrl.pathname, search: parsedUrl.search }, headersForResolution(req.headers, req.socket.remoteAddress, opts.allowTargetOverride), opts.upstreams);
|
|
41
|
+
const targetParsed = url.parse(targetUrl);
|
|
42
|
+
// When we buffer the body, replace chunked encoding with exact content-length
|
|
43
|
+
const forwardHeaders = buildForwardHeaders(req.headers, targetParsed.host, body ? body.length : undefined);
|
|
44
|
+
const protocol = targetParsed.protocol === "https:" ? https : http;
|
|
45
|
+
const proxyReq = protocol.request({
|
|
46
|
+
hostname: targetParsed.hostname,
|
|
47
|
+
port: targetParsed.port,
|
|
48
|
+
path: targetParsed.path,
|
|
49
|
+
method: req.method,
|
|
50
|
+
headers: forwardHeaders,
|
|
51
|
+
}, (proxyRes) => {
|
|
52
|
+
if (!res.headersSent)
|
|
53
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
54
|
+
proxyRes.pipe(res);
|
|
55
|
+
proxyRes.on("error", (err) => {
|
|
56
|
+
console.error("Upstream response error (forward):", err.message);
|
|
57
|
+
if (!res.destroyed)
|
|
58
|
+
res.end();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
attachProxyLifecycleHandlers(res, proxyReq);
|
|
62
|
+
if (body)
|
|
63
|
+
proxyReq.write(body);
|
|
64
|
+
proxyReq.end();
|
|
65
|
+
}
|
|
66
|
+
export function createProxyHandler(store, opts) {
|
|
67
|
+
return function handleProxy(req, res) {
|
|
68
|
+
const parsedUrl = url.parse(req.url);
|
|
69
|
+
const { source, cleanPath } = extractSource(parsedUrl.pathname);
|
|
70
|
+
// Use clean path (without source prefix) for routing
|
|
71
|
+
const cleanUrl = { ...parsedUrl, pathname: cleanPath };
|
|
72
|
+
const { targetUrl, provider } = resolveTargetUrl({ pathname: cleanPath, search: parsedUrl.search }, headersForResolution(req.headers, req.socket.remoteAddress, opts.allowTargetOverride), opts.upstreams);
|
|
73
|
+
const hasAuth = !!req.headers.authorization;
|
|
74
|
+
const sourceTag = source ? `[${source}]` : "";
|
|
75
|
+
console.log(`${req.method} ${req.url} → ${targetUrl} [${provider}] ${sourceTag} auth=${hasAuth}`);
|
|
76
|
+
// For non-POST requests (GET /v1/models, OPTIONS, etc.), pass through directly
|
|
77
|
+
if (req.method !== "POST") {
|
|
78
|
+
forwardRequest(req, res, cleanUrl, null, opts);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Collect body as raw Buffers to avoid corrupting multi-byte UTF-8 at chunk boundaries
|
|
82
|
+
const chunks = [];
|
|
83
|
+
let clientAborted = false;
|
|
84
|
+
req.on("data", (chunk) => {
|
|
85
|
+
chunks.push(chunk);
|
|
86
|
+
});
|
|
87
|
+
req.on("error", () => {
|
|
88
|
+
clientAborted = true;
|
|
89
|
+
});
|
|
90
|
+
req.on("end", () => {
|
|
91
|
+
if (clientAborted)
|
|
92
|
+
return;
|
|
93
|
+
const bodyBuffer = Buffer.concat(chunks);
|
|
94
|
+
const body = bodyBuffer.toString("utf8");
|
|
95
|
+
let bodyData;
|
|
96
|
+
try {
|
|
97
|
+
bodyData = JSON.parse(body);
|
|
98
|
+
}
|
|
99
|
+
catch (_e) {
|
|
100
|
+
console.log(` ⚠ Body is not JSON (${bodyBuffer.length} bytes), capturing raw`);
|
|
101
|
+
// Still capture the raw request even if body isn't JSON
|
|
102
|
+
const rawInfo = {
|
|
103
|
+
provider,
|
|
104
|
+
apiFormat: "raw",
|
|
105
|
+
model: "unknown",
|
|
106
|
+
systemTokens: 0,
|
|
107
|
+
toolsTokens: 0,
|
|
108
|
+
messagesTokens: estimateTokens(body),
|
|
109
|
+
totalTokens: estimateTokens(body),
|
|
110
|
+
systemPrompts: [],
|
|
111
|
+
tools: [],
|
|
112
|
+
messages: [
|
|
113
|
+
{
|
|
114
|
+
role: "raw",
|
|
115
|
+
content: body.substring(0, 2000),
|
|
116
|
+
tokens: estimateTokens(body),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
store.storeRequest(rawInfo, { raw: true }, source, undefined, undefined, selectHeaders(req.headers));
|
|
121
|
+
forwardRequest(req, res, cleanUrl, bodyBuffer, opts);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(` ✓ Parsed JSON body (${Object.keys(bodyData).join(", ")})`);
|
|
125
|
+
const apiFormat = detectApiFormat(cleanPath);
|
|
126
|
+
// Gemini: model is in the URL path, not in the body
|
|
127
|
+
if (apiFormat === "gemini" && !bodyData.model) {
|
|
128
|
+
const modelMatch = cleanPath.match(/\/models\/([^/:]+)/);
|
|
129
|
+
if (modelMatch)
|
|
130
|
+
bodyData.model = modelMatch[1];
|
|
131
|
+
}
|
|
132
|
+
const contextInfo = parseContextInfo(provider, bodyData, apiFormat);
|
|
133
|
+
// Skip capturing utility endpoints (count_tokens, etc.). Not real conversation turns
|
|
134
|
+
const isUtilityEndpoint = /\/count_tokens\b|:countTokens\b|:loadCodeAssist\b|:retrieveUserQuota\b|:listExperiments\b|:onboardUser\b|:fetchAdminControls\b|:recordCodeAssistMetrics\b/.test(cleanPath);
|
|
135
|
+
const targetParsed = url.parse(targetUrl);
|
|
136
|
+
// Ensure content-length matches the exact bytes we forward
|
|
137
|
+
const forwardHeaders = buildForwardHeaders(req.headers, targetParsed.host, bodyBuffer.length);
|
|
138
|
+
// Make request to actual API
|
|
139
|
+
const protocol = targetParsed.protocol === "https:" ? https : http;
|
|
140
|
+
const startTime = performance.now();
|
|
141
|
+
let firstByteTime = 0;
|
|
142
|
+
const reqBytes = bodyBuffer.length;
|
|
143
|
+
const proxyReq = protocol.request({
|
|
144
|
+
hostname: targetParsed.hostname,
|
|
145
|
+
port: targetParsed.port,
|
|
146
|
+
path: targetParsed.path,
|
|
147
|
+
method: req.method,
|
|
148
|
+
headers: forwardHeaders,
|
|
149
|
+
}, (proxyRes) => {
|
|
150
|
+
console.log(` ← ${proxyRes.statusCode} ${proxyRes.statusMessage}`);
|
|
151
|
+
// Forward response headers
|
|
152
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
153
|
+
const httpStatus = proxyRes.statusCode || 0;
|
|
154
|
+
// Handle streaming vs non-streaming
|
|
155
|
+
const isStreaming = proxyRes.headers["content-type"]?.includes("text/event-stream");
|
|
156
|
+
let respBytes = 0;
|
|
157
|
+
const capturedReqHeaders = selectHeaders(req.headers);
|
|
158
|
+
const capturedResHeaders = selectHeaders(proxyRes.headers);
|
|
159
|
+
// Collect response as Buffer[] to avoid corrupting multi-byte UTF-8 at chunk boundaries
|
|
160
|
+
const respChunks = [];
|
|
161
|
+
proxyRes.on("data", (chunk) => {
|
|
162
|
+
if (!firstByteTime)
|
|
163
|
+
firstByteTime = performance.now();
|
|
164
|
+
respBytes += chunk.length;
|
|
165
|
+
respChunks.push(chunk);
|
|
166
|
+
if (!res.destroyed)
|
|
167
|
+
res.write(chunk);
|
|
168
|
+
});
|
|
169
|
+
proxyRes.on("end", () => {
|
|
170
|
+
const endTime = performance.now();
|
|
171
|
+
if (!firstByteTime)
|
|
172
|
+
firstByteTime = endTime;
|
|
173
|
+
const meta = {
|
|
174
|
+
httpStatus,
|
|
175
|
+
timings: {
|
|
176
|
+
send_ms: Math.round(firstByteTime - startTime),
|
|
177
|
+
wait_ms: Math.round(firstByteTime - startTime),
|
|
178
|
+
receive_ms: Math.round(endTime - firstByteTime),
|
|
179
|
+
total_ms: Math.round(endTime - startTime),
|
|
180
|
+
tokens_per_second: null,
|
|
181
|
+
},
|
|
182
|
+
requestBytes: reqBytes,
|
|
183
|
+
responseBytes: respBytes,
|
|
184
|
+
targetUrl,
|
|
185
|
+
requestHeaders: capturedReqHeaders,
|
|
186
|
+
responseHeaders: capturedResHeaders,
|
|
187
|
+
};
|
|
188
|
+
const respBody = Buffer.concat(respChunks).toString("utf8");
|
|
189
|
+
if (!isUtilityEndpoint) {
|
|
190
|
+
if (isStreaming) {
|
|
191
|
+
store.storeRequest(contextInfo, { streaming: true, chunks: respBody }, source, bodyData, meta, capturedReqHeaders);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
try {
|
|
195
|
+
const responseData = JSON.parse(respBody);
|
|
196
|
+
store.storeRequest(contextInfo, responseData, source, bodyData, meta, capturedReqHeaders);
|
|
197
|
+
}
|
|
198
|
+
catch (_e) {
|
|
199
|
+
store.storeRequest(contextInfo, { raw: respBody }, source, bodyData, meta, capturedReqHeaders);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!res.destroyed)
|
|
204
|
+
res.end();
|
|
205
|
+
});
|
|
206
|
+
proxyRes.on("error", (err) => {
|
|
207
|
+
console.error("Upstream response error:", err.message);
|
|
208
|
+
if (!res.destroyed)
|
|
209
|
+
res.end();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
attachProxyLifecycleHandlers(res, proxyReq);
|
|
213
|
+
proxyReq.write(bodyBuffer);
|
|
214
|
+
proxyReq.end();
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/server/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* Create a static file handler that serves from `uiDir` if it exists,
|
|
4
|
+
* otherwise falls back to serving `fallbackHtml` at `/`.
|
|
5
|
+
*
|
|
6
|
+
* Returns a function that handles the request, always responds.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createStaticHandler(uiDir: string | null, fallbackHtml: string): (req: http.IncomingMessage, res: http.ServerResponse) => void;
|
|
9
|
+
//# sourceMappingURL=static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/server/static.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAgBlC;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,YAAY,EAAE,MAAM,GACnB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,KAAK,IAAI,CA+D/D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const MIME_TYPES = {
|
|
4
|
+
".html": "text/html",
|
|
5
|
+
".js": "application/javascript",
|
|
6
|
+
".css": "text/css",
|
|
7
|
+
".json": "application/json",
|
|
8
|
+
".png": "image/png",
|
|
9
|
+
".svg": "image/svg+xml",
|
|
10
|
+
".ico": "image/x-icon",
|
|
11
|
+
".woff": "font/woff",
|
|
12
|
+
".woff2": "font/woff2",
|
|
13
|
+
".map": "application/json",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a static file handler that serves from `uiDir` if it exists,
|
|
17
|
+
* otherwise falls back to serving `fallbackHtml` at `/`.
|
|
18
|
+
*
|
|
19
|
+
* Returns a function that handles the request, always responds.
|
|
20
|
+
*/
|
|
21
|
+
export function createStaticHandler(uiDir, fallbackHtml) {
|
|
22
|
+
// Check if ui/dist exists at startup
|
|
23
|
+
const uiDirExists = uiDir !== null && fs.existsSync(uiDir);
|
|
24
|
+
return function handleStatic(req, res) {
|
|
25
|
+
if (uiDirExists && uiDir) {
|
|
26
|
+
// Serve from ui/dist
|
|
27
|
+
let reqPath = req.url?.split("?")[0] || "/";
|
|
28
|
+
// Prevent directory traversal
|
|
29
|
+
reqPath = path.normalize(reqPath).replace(/^(\.\.[/\\])+/, "");
|
|
30
|
+
// Map / to /index.html
|
|
31
|
+
if (reqPath === "/")
|
|
32
|
+
reqPath = "/index.html";
|
|
33
|
+
const filePath = path.join(uiDir, reqPath);
|
|
34
|
+
// Ensure we don't serve files outside uiDir
|
|
35
|
+
if (!filePath.startsWith(uiDir)) {
|
|
36
|
+
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
37
|
+
res.end("Forbidden");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const stat = fs.statSync(filePath);
|
|
42
|
+
if (stat.isFile()) {
|
|
43
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
44
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
45
|
+
const content = fs.readFileSync(filePath);
|
|
46
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
47
|
+
res.end(content);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// File not found; fall through to SPA fallback
|
|
53
|
+
}
|
|
54
|
+
// SPA fallback: serve index.html for unmatched routes
|
|
55
|
+
const indexPath = path.join(uiDir, "index.html");
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(indexPath);
|
|
58
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
59
|
+
res.end(content);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
63
|
+
res.end("Not Found");
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Fallback: serve legacy HTML UI
|
|
68
|
+
if (req.url === "/" || req.url === "/index.html") {
|
|
69
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
70
|
+
res.end(fallbackHtml);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
74
|
+
res.end("Not Found");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/server/static.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { CapturedEntry, ContextInfo, Conversation, PrivacyLevel, RequestMeta, ResponseData } from "../types.js";
|
|
2
|
+
export interface StoreChangeEvent {
|
|
3
|
+
type: string;
|
|
4
|
+
revision: number;
|
|
5
|
+
conversationId?: string | null;
|
|
6
|
+
}
|
|
7
|
+
type StoreChangeListener = (event: StoreChangeEvent) => void;
|
|
8
|
+
export declare class Store {
|
|
9
|
+
private readonly dataDir;
|
|
10
|
+
private readonly stateFile;
|
|
11
|
+
private readonly detailDir;
|
|
12
|
+
private readonly maxSessions;
|
|
13
|
+
private readonly maxCompactMessages;
|
|
14
|
+
private readonly privacy;
|
|
15
|
+
private capturedRequests;
|
|
16
|
+
private conversations;
|
|
17
|
+
private responseIdToConvo;
|
|
18
|
+
private diskSessionsWritten;
|
|
19
|
+
private dataRevision;
|
|
20
|
+
private nextEntryId;
|
|
21
|
+
private changeListeners;
|
|
22
|
+
constructor(opts: {
|
|
23
|
+
dataDir: string;
|
|
24
|
+
stateFile: string;
|
|
25
|
+
maxSessions: number;
|
|
26
|
+
maxCompactMessages: number;
|
|
27
|
+
privacy?: PrivacyLevel;
|
|
28
|
+
});
|
|
29
|
+
getRevision(): number;
|
|
30
|
+
getPrivacy(): PrivacyLevel;
|
|
31
|
+
on(_event: "change", listener: StoreChangeListener): void;
|
|
32
|
+
off(_event: "change", listener: StoreChangeListener): void;
|
|
33
|
+
private emitChange;
|
|
34
|
+
getCapturedRequests(): CapturedEntry[];
|
|
35
|
+
getConversations(): Map<string, Conversation>;
|
|
36
|
+
loadState(): void;
|
|
37
|
+
storeRequest(contextInfo: ContextInfo, responseData: ResponseData, source: string | null, rawBody?: Record<string, any>, meta?: RequestMeta, requestHeaders?: Record<string, string>): CapturedEntry;
|
|
38
|
+
deleteConversation(convoId: string): void;
|
|
39
|
+
resetAll(): void;
|
|
40
|
+
/** Backfill health scores for entries loaded from state that don't have one. */
|
|
41
|
+
private backfillHealthScores;
|
|
42
|
+
/**
|
|
43
|
+
* Recalculate token counts for messages that contain image blocks.
|
|
44
|
+
*
|
|
45
|
+
* Before the image token fix, estimateTokens() would stringify base64 image
|
|
46
|
+
* data, producing millions of phantom tokens. Persisted entries still have
|
|
47
|
+
* those inflated counts. This migration recalculates from the compacted
|
|
48
|
+
/**
|
|
49
|
+
* Backfill totalTokens from API usage data for entries that only have estimates.
|
|
50
|
+
* The estimate (chars/4) can be wildly inaccurate; the API's real token count
|
|
51
|
+
* (input + cacheRead + cacheWrite) is authoritative.
|
|
52
|
+
*/
|
|
53
|
+
private backfillTotalTokensFromUsage;
|
|
54
|
+
/**
|
|
55
|
+
* Migrate image token counts: re-estimate token counts for messages containing
|
|
56
|
+
* contentBlocks (which have no base64 data) using the fixed estimateTokens().
|
|
57
|
+
*/
|
|
58
|
+
private migrateImageTokenCounts;
|
|
59
|
+
/**
|
|
60
|
+
* Save full contextInfo for an entry to disk so the UI can retrieve
|
|
61
|
+
* uncompacted message content on demand.
|
|
62
|
+
*/
|
|
63
|
+
private saveEntryDetail;
|
|
64
|
+
/**
|
|
65
|
+
* Load full contextInfo for an entry from disk.
|
|
66
|
+
* Returns null if the detail file doesn't exist (old entries before this feature).
|
|
67
|
+
*/
|
|
68
|
+
getEntryDetail(entryId: number): ContextInfo | null;
|
|
69
|
+
/**
|
|
70
|
+
* Remove detail files for entries belonging to a conversation.
|
|
71
|
+
*/
|
|
72
|
+
private removeConversationDetails;
|
|
73
|
+
private logToDisk;
|
|
74
|
+
private compactBlock;
|
|
75
|
+
private compactMessages;
|
|
76
|
+
private compactContextInfo;
|
|
77
|
+
private compactEntry;
|
|
78
|
+
private saveState;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/server/store.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,aAAa,EAEb,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,KAAK,mBAAmB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAE7D,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,mBAAmB,CAAqB;IAEhD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAK;IAGxB,OAAO,CAAC,eAAe,CAAkC;gBAE7C,IAAI,EAAE;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,YAAY,CAAC;KACxB;IAoBD,WAAW,IAAI,MAAM;IAIrB,UAAU,IAAI,YAAY;IAM1B,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAIzD,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAI1D,OAAO,CAAC,UAAU;IAelB,mBAAmB,IAAI,aAAa,EAAE;IAItC,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAI7C,SAAS,IAAI,IAAI;IAgEjB,YAAY,CACV,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,IAAI,CAAC,EAAE,WAAW,EAClB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,aAAa;IAiNhB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAgBzC,QAAQ,IAAI,IAAI;IA2BhB,gFAAgF;IAChF,OAAO,CAAC,oBAAoB;IA4C5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,4BAA4B;IAgBpC;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAwD/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAiBvB;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAUnD;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,SAAS;IA4CjB,OAAO,CAAC,YAAY;IAmCpB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,SAAS;CAoBlB"}
|