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.
Files changed (119) hide show
  1. package/README.md +120 -25
  2. package/dist/cli-utils.d.ts +2 -1
  3. package/dist/cli-utils.d.ts.map +1 -1
  4. package/dist/cli-utils.js +40 -13
  5. package/dist/cli-utils.js.map +1 -1
  6. package/dist/cli.js +184 -68
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/conversation.d.ts +63 -0
  9. package/dist/core/conversation.d.ts.map +1 -0
  10. package/dist/core/conversation.js +305 -0
  11. package/dist/core/conversation.js.map +1 -0
  12. package/dist/core/health.d.ts +7 -0
  13. package/dist/core/health.d.ts.map +1 -0
  14. package/dist/core/health.js +311 -0
  15. package/dist/core/health.js.map +1 -0
  16. package/dist/core/models.d.ts +36 -0
  17. package/dist/core/models.d.ts.map +1 -0
  18. package/dist/core/models.js +111 -0
  19. package/dist/core/models.js.map +1 -0
  20. package/dist/core/parse.d.ts +17 -0
  21. package/dist/core/parse.d.ts.map +1 -0
  22. package/dist/core/parse.js +349 -0
  23. package/dist/core/parse.js.map +1 -0
  24. package/dist/core/routing.d.ts +47 -0
  25. package/dist/core/routing.d.ts.map +1 -0
  26. package/dist/core/routing.js +132 -0
  27. package/dist/core/routing.js.map +1 -0
  28. package/dist/core/security.d.ts +8 -0
  29. package/dist/core/security.d.ts.map +1 -0
  30. package/dist/core/security.js +222 -0
  31. package/dist/core/security.js.map +1 -0
  32. package/dist/core/source.d.ts +22 -0
  33. package/dist/core/source.d.ts.map +1 -0
  34. package/dist/core/source.js +56 -0
  35. package/dist/core/source.js.map +1 -0
  36. package/dist/core/tokens.d.ts +29 -0
  37. package/dist/core/tokens.d.ts.map +1 -0
  38. package/dist/core/tokens.js +163 -0
  39. package/dist/core/tokens.js.map +1 -0
  40. package/dist/core.d.ts +14 -22
  41. package/dist/core.d.ts.map +1 -1
  42. package/dist/core.js +14 -471
  43. package/dist/core.js.map +1 -1
  44. package/dist/http/headers.d.ts +25 -0
  45. package/dist/http/headers.d.ts.map +1 -0
  46. package/dist/http/headers.js +54 -0
  47. package/dist/http/headers.js.map +1 -0
  48. package/dist/lhar/composition.d.ts +12 -0
  49. package/dist/lhar/composition.d.ts.map +1 -0
  50. package/dist/lhar/composition.js +258 -0
  51. package/dist/lhar/composition.js.map +1 -0
  52. package/dist/lhar/export.d.ts +5 -0
  53. package/dist/lhar/export.d.ts.map +1 -0
  54. package/dist/lhar/export.js +59 -0
  55. package/dist/lhar/export.js.map +1 -0
  56. package/dist/lhar/record.d.ts +6 -0
  57. package/dist/lhar/record.d.ts.map +1 -0
  58. package/dist/lhar/record.js +216 -0
  59. package/dist/lhar/record.js.map +1 -0
  60. package/dist/lhar/response.d.ts +11 -0
  61. package/dist/lhar/response.d.ts.map +1 -0
  62. package/dist/lhar/response.js +132 -0
  63. package/dist/lhar/response.js.map +1 -0
  64. package/dist/lhar-types.generated.d.ts +24 -3
  65. package/dist/lhar-types.generated.d.ts.map +1 -1
  66. package/dist/lhar.d.ts +12 -19
  67. package/dist/lhar.d.ts.map +1 -1
  68. package/dist/lhar.js +16 -473
  69. package/dist/lhar.js.map +1 -1
  70. package/dist/server/api.d.ts +8 -0
  71. package/dist/server/api.d.ts.map +1 -0
  72. package/dist/server/api.js +292 -0
  73. package/dist/server/api.js.map +1 -0
  74. package/dist/server/config.d.ts +13 -0
  75. package/dist/server/config.d.ts.map +1 -0
  76. package/dist/server/config.js +36 -0
  77. package/dist/server/config.js.map +1 -0
  78. package/dist/server/projection.d.ts +9 -0
  79. package/dist/server/projection.d.ts.map +1 -0
  80. package/dist/server/projection.js +47 -0
  81. package/dist/server/projection.js.map +1 -0
  82. package/dist/server/proxy.d.ts +13 -0
  83. package/dist/server/proxy.d.ts.map +1 -0
  84. package/dist/server/proxy.js +218 -0
  85. package/dist/server/proxy.js.map +1 -0
  86. package/dist/server/static.d.ts +9 -0
  87. package/dist/server/static.d.ts.map +1 -0
  88. package/dist/server/static.js +78 -0
  89. package/dist/server/static.js.map +1 -0
  90. package/dist/server/store.d.ts +81 -0
  91. package/dist/server/store.d.ts.map +1 -0
  92. package/dist/server/store.js +632 -0
  93. package/dist/server/store.js.map +1 -0
  94. package/dist/server/webui.d.ts +5 -0
  95. package/dist/server/webui.d.ts.map +1 -0
  96. package/dist/server/webui.js +42 -0
  97. package/dist/server/webui.js.map +1 -0
  98. package/dist/server-utils.d.ts +2 -2
  99. package/dist/server-utils.d.ts.map +1 -1
  100. package/dist/server-utils.js +12 -21
  101. package/dist/server-utils.js.map +1 -1
  102. package/dist/server.js +31 -697
  103. package/dist/server.js.map +1 -1
  104. package/dist/types.d.ts +94 -10
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/version.generated.d.ts +2 -0
  107. package/dist/version.generated.d.ts.map +1 -0
  108. package/dist/version.generated.js +2 -0
  109. package/dist/version.generated.js.map +1 -0
  110. package/findings-screenshot.png +0 -0
  111. package/messages-screenshot.png +0 -0
  112. package/package.json +23 -10
  113. package/schema/lhar.schema.json +58 -4
  114. package/screenshot-overview.png +0 -0
  115. package/sessions-screenshot.png +0 -0
  116. package/timeline-screenshot.png +0 -0
  117. package/diff.png +0 -0
  118. package/overview-sidebar.png +0 -0
  119. package/public/index.html +0 -2804
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Shared header redaction utilities.
3
+ *
4
+ * Context Lens captures and exports some headers for debugging and provenance,
5
+ * but must never persist secrets (API keys, cookies, auth challenges, etc.).
6
+ *
7
+ * Keep this as the single source of truth to avoid drift between "capture" and "export".
8
+ */
9
+ /** Case-insensitive set of header names that must never be persisted/exported. */
10
+ export const SENSITIVE_HEADERS = new Set([
11
+ "authorization",
12
+ "x-api-key",
13
+ "cookie",
14
+ "set-cookie",
15
+ "x-target-url",
16
+ "proxy-authorization",
17
+ "x-auth-token",
18
+ "x-forwarded-authorization",
19
+ "www-authenticate",
20
+ "proxy-authenticate",
21
+ "x-goog-api-key",
22
+ ]);
23
+ /**
24
+ * Remove sensitive headers from a header map.
25
+ *
26
+ * @param headers - Header map (string -> string)
27
+ * @returns A new object with sensitive headers removed.
28
+ */
29
+ export function redactHeaders(headers) {
30
+ const result = {};
31
+ for (const [key, val] of Object.entries(headers)) {
32
+ if (SENSITIVE_HEADERS.has(key.toLowerCase()))
33
+ continue;
34
+ result[key] = val;
35
+ }
36
+ return result;
37
+ }
38
+ /**
39
+ * Select a safe subset of request/response headers for capture.
40
+ *
41
+ * - Drops sensitive headers (see `SENSITIVE_HEADERS`)
42
+ * - Keeps only string-valued headers (Node can represent multi-valued headers as arrays)
43
+ */
44
+ export function selectHeaders(headers) {
45
+ const result = {};
46
+ for (const [key, val] of Object.entries(headers)) {
47
+ if (SENSITIVE_HEADERS.has(key.toLowerCase()))
48
+ continue;
49
+ if (typeof val === "string")
50
+ result[key] = val;
51
+ }
52
+ return result;
53
+ }
54
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/http/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACvC,eAAe;IACf,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,cAAc;IACd,qBAAqB;IACrB,cAAc;IACd,2BAA2B;IAC3B,kBAAkB;IAClB,oBAAoB;IACpB,gBAAgB;CACjB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,OAA+B;IAE/B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QACvD,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,OAA4B;IAE5B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QACvD,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { CompositionEntry } from "../lhar-types.generated.js";
2
+ import type { ContextInfo } from "../types.js";
3
+ export declare function analyzeComposition(contextInfo: ContextInfo, rawBody: Record<string, any> | undefined): CompositionEntry[];
4
+ /**
5
+ * Normalize composition token counts so their sum equals an authoritative total.
6
+ *
7
+ * Scales each entry proportionally and applies a rounding residual fix on the
8
+ * largest entry so `sum(composition[].tokens) === authoritative` exactly.
9
+ * Also recomputes `pct` fields.
10
+ */
11
+ export declare function normalizeComposition(composition: CompositionEntry[], authoritative: number): void;
12
+ //# sourceMappingURL=composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../src/lhar/composition.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GACvC,gBAAgB,EAAE,CAwEpB;AA8KD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,aAAa,EAAE,MAAM,GACpB,IAAI,CAgCN"}
@@ -0,0 +1,258 @@
1
+ import { estimateTokens } from "../core.js";
2
+ export function analyzeComposition(contextInfo, rawBody) {
3
+ const counts = new Map();
4
+ function add(category, tokens) {
5
+ const existing = counts.get(category);
6
+ if (existing) {
7
+ existing.tokens += tokens;
8
+ existing.count += 1;
9
+ }
10
+ else {
11
+ counts.set(category, { tokens, count: 1 });
12
+ }
13
+ }
14
+ if (!rawBody) {
15
+ // Fallback to contextInfo aggregates
16
+ if (contextInfo.systemTokens > 0)
17
+ add("system_prompt", contextInfo.systemTokens);
18
+ if (contextInfo.toolsTokens > 0)
19
+ add("tool_definitions", contextInfo.toolsTokens);
20
+ if (contextInfo.messagesTokens > 0)
21
+ add("other", contextInfo.messagesTokens);
22
+ return buildCompositionArray(counts, contextInfo.totalTokens);
23
+ }
24
+ // System prompt(s)
25
+ if (rawBody.system) {
26
+ if (typeof rawBody.system === "string") {
27
+ add("system_prompt", estimateTokens(rawBody.system));
28
+ }
29
+ else if (Array.isArray(rawBody.system)) {
30
+ for (const block of rawBody.system) {
31
+ // cache_control metadata is negligible; count only the text content
32
+ add("system_prompt", estimateTokens(block.text || block));
33
+ }
34
+ }
35
+ }
36
+ // Instructions (OpenAI Responses API / ChatGPT)
37
+ if (rawBody.instructions) {
38
+ add("system_prompt", estimateTokens(rawBody.instructions));
39
+ }
40
+ // Tool definitions
41
+ if (rawBody.tools && Array.isArray(rawBody.tools)) {
42
+ add("tool_definitions", estimateTokens(JSON.stringify(rawBody.tools)));
43
+ }
44
+ // Gemini/Code Assist: unwrap .request wrapper if present
45
+ const geminiBody = rawBody.request || rawBody;
46
+ // Gemini systemInstruction
47
+ if (geminiBody.systemInstruction) {
48
+ const parts = geminiBody.systemInstruction.parts || [];
49
+ add("system_prompt", estimateTokens(parts.map((p) => p.text || "").join("\n")));
50
+ }
51
+ // Gemini contents[] or standard messages[]/input[]
52
+ const messages = geminiBody.contents || rawBody.messages || rawBody.input;
53
+ if (Array.isArray(messages)) {
54
+ for (const msg of messages) {
55
+ classifyMessage(msg, add);
56
+ }
57
+ }
58
+ else if (typeof messages === "string") {
59
+ add("user_text", estimateTokens(messages));
60
+ }
61
+ const total = Array.from(counts.values()).reduce((s, e) => s + e.tokens, 0);
62
+ return buildCompositionArray(counts, total);
63
+ }
64
+ function classifyMessage(msg, add) {
65
+ const type = msg.type || "";
66
+ // OpenAI Responses API typed items (no role field)
67
+ if (!msg.role && type) {
68
+ if (type === "function_call" || type === "custom_tool_call") {
69
+ add("tool_calls", estimateTokens(msg));
70
+ return;
71
+ }
72
+ if (type === "function_call_output" || type === "custom_tool_call_output") {
73
+ add("tool_results", estimateTokens(msg.output || ""));
74
+ return;
75
+ }
76
+ if (type === "reasoning") {
77
+ add("thinking", estimateTokens(msg));
78
+ return;
79
+ }
80
+ if (type === "output_text") {
81
+ add("assistant_text", estimateTokens(msg.text || ""));
82
+ return;
83
+ }
84
+ if (type === "input_text") {
85
+ add("user_text", estimateTokens(msg.text || ""));
86
+ return;
87
+ }
88
+ }
89
+ const role = msg.role || "user";
90
+ const content = msg.content;
91
+ // System / developer messages
92
+ if (role === "system" || role === "developer") {
93
+ add("system_prompt", estimateTokens(content));
94
+ return;
95
+ }
96
+ // String content
97
+ if (typeof content === "string") {
98
+ if (content.includes("<system-reminder>")) {
99
+ add("system_injections", estimateTokens(content));
100
+ }
101
+ else if (role === "assistant") {
102
+ add("assistant_text", estimateTokens(content));
103
+ }
104
+ else {
105
+ add("user_text", estimateTokens(content));
106
+ }
107
+ return;
108
+ }
109
+ // Gemini parts array (role + parts instead of role + content)
110
+ if (msg.parts && Array.isArray(msg.parts)) {
111
+ for (const part of msg.parts) {
112
+ classifyGeminiPart(part, role, add);
113
+ }
114
+ return;
115
+ }
116
+ // Array of content blocks
117
+ if (Array.isArray(content)) {
118
+ for (const block of content) {
119
+ classifyBlock(block, role, add);
120
+ }
121
+ return;
122
+ }
123
+ // Fallback
124
+ if (content) {
125
+ add("other", estimateTokens(content));
126
+ }
127
+ }
128
+ function classifyBlock(block, role, add) {
129
+ const type = block.type || "";
130
+ if (type === "tool_use") {
131
+ add("tool_calls", estimateTokens(block));
132
+ return;
133
+ }
134
+ if (type === "tool_result") {
135
+ add("tool_results", estimateTokens(block.content || ""));
136
+ return;
137
+ }
138
+ if (type === "thinking") {
139
+ add("thinking", estimateTokens(block.thinking || block.text || ""));
140
+ return;
141
+ }
142
+ if (type === "image" || type === "image_url") {
143
+ add("images", estimateTokens(block)); // estimateTokens handles image blocks with fixed estimate
144
+ return;
145
+ }
146
+ // Text blocks
147
+ const text = block.text || "";
148
+ if (type === "text" || type === "input_text" || !type) {
149
+ if (text.includes("<system-reminder>")) {
150
+ add("system_injections", estimateTokens(text));
151
+ }
152
+ else if (block.cache_control) {
153
+ // Count text in its natural category only; the cache_control metadata
154
+ // itself is negligible overhead and should not inflate the total.
155
+ if (role === "assistant") {
156
+ add("assistant_text", estimateTokens(text));
157
+ }
158
+ else {
159
+ add("user_text", estimateTokens(text));
160
+ }
161
+ }
162
+ else if (role === "assistant") {
163
+ add("assistant_text", estimateTokens(text));
164
+ }
165
+ else {
166
+ add("user_text", estimateTokens(text));
167
+ }
168
+ return;
169
+ }
170
+ add("other", estimateTokens(block));
171
+ }
172
+ function classifyGeminiPart(part, role, add) {
173
+ if (part.text) {
174
+ if (role === "model") {
175
+ add("assistant_text", estimateTokens(part.text));
176
+ }
177
+ else {
178
+ add("user_text", estimateTokens(part.text));
179
+ }
180
+ return;
181
+ }
182
+ if (part.functionCall) {
183
+ add("tool_calls", estimateTokens(part.functionCall));
184
+ return;
185
+ }
186
+ if (part.functionResponse) {
187
+ add("tool_results", estimateTokens(part.functionResponse));
188
+ return;
189
+ }
190
+ if (part.inlineData || part.fileData) {
191
+ add("images", estimateTokens(part));
192
+ return;
193
+ }
194
+ if (part.executableCode || part.codeExecutionResult) {
195
+ add("assistant_text", estimateTokens(part));
196
+ return;
197
+ }
198
+ add("other", estimateTokens(part));
199
+ }
200
+ function buildCompositionArray(counts, total) {
201
+ const result = [];
202
+ for (const [category, { tokens, count }] of counts) {
203
+ if (tokens === 0)
204
+ continue;
205
+ result.push({
206
+ category,
207
+ tokens,
208
+ pct: total > 0 ? Math.round((tokens / total) * 1000) / 10 : 0,
209
+ count,
210
+ });
211
+ }
212
+ // Sort by tokens descending
213
+ result.sort((a, b) => b.tokens - a.tokens);
214
+ return result;
215
+ }
216
+ /**
217
+ * Normalize composition token counts so their sum equals an authoritative total.
218
+ *
219
+ * Scales each entry proportionally and applies a rounding residual fix on the
220
+ * largest entry so `sum(composition[].tokens) === authoritative` exactly.
221
+ * Also recomputes `pct` fields.
222
+ */
223
+ export function normalizeComposition(composition, authoritative) {
224
+ if (composition.length === 0)
225
+ return;
226
+ const rawSum = composition.reduce((s, c) => s + c.tokens, 0);
227
+ if (rawSum === 0 || authoritative === 0)
228
+ return;
229
+ if (rawSum === authoritative) {
230
+ // Already matches; just recompute pct for consistency
231
+ for (const c of composition) {
232
+ c.pct =
233
+ authoritative > 0
234
+ ? Math.round((c.tokens / authoritative) * 1000) / 10
235
+ : 0;
236
+ }
237
+ return;
238
+ }
239
+ const scale = authoritative / rawSum;
240
+ let running = 0;
241
+ for (const c of composition) {
242
+ c.tokens = Math.round(c.tokens * scale);
243
+ running += c.tokens;
244
+ }
245
+ // Fix rounding residual on the first (largest) entry
246
+ const residual = authoritative - running;
247
+ if (residual !== 0) {
248
+ composition[0].tokens += residual;
249
+ }
250
+ // Recompute pct
251
+ for (const c of composition) {
252
+ c.pct =
253
+ authoritative > 0
254
+ ? Math.round((c.tokens / authoritative) * 1000) / 10
255
+ : 0;
256
+ }
257
+ }
258
+ //# sourceMappingURL=composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.js","sourceRoot":"","sources":["../../src/lhar/composition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { LharJsonWrapper } from "../lhar-types.generated.js";
2
+ import type { CapturedEntry, Conversation, PrivacyLevel } from "../types.js";
3
+ export declare function toLharJsonl(entries: CapturedEntry[], conversations: Map<string, Conversation>, privacy?: PrivacyLevel): string;
4
+ export declare function toLharJson(entries: CapturedEntry[], conversations: Map<string, Conversation>, privacy?: PrivacyLevel): LharJsonWrapper;
5
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/lhar/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY7E,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,EAAE,EACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACxC,OAAO,GAAE,YAAyB,GACjC,MAAM,CAmCR;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,aAAa,EAAE,EACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACxC,OAAO,GAAE,YAAyB,GACjC,eAAe,CAwCjB"}
@@ -0,0 +1,59 @@
1
+ import { VERSION } from "../version.generated.js";
2
+ import { buildLharRecord, buildSessionLine, traceIdFromConversation, } from "./record.js";
3
+ const COLLECTOR_NAME = "context-lens";
4
+ const COLLECTOR_VERSION = VERSION;
5
+ const LHAR_VERSION = "0.1.0";
6
+ export function toLharJsonl(entries, conversations, privacy = "standard") {
7
+ // Sort oldest-first for JSONL
8
+ const sorted = [...entries].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
9
+ const lines = [];
10
+ const emittedSessions = new Set();
11
+ for (const entry of sorted) {
12
+ const record = buildLharRecord(entry, entries, privacy);
13
+ // Emit session preamble on first occurrence of each trace_id
14
+ if (!emittedSessions.has(record.trace_id)) {
15
+ emittedSessions.add(record.trace_id);
16
+ const convo = entry.conversationId
17
+ ? conversations.get(entry.conversationId)
18
+ : undefined;
19
+ if (convo) {
20
+ lines.push(JSON.stringify(buildSessionLine(entry.conversationId, convo, record.gen_ai.request.model)));
21
+ }
22
+ }
23
+ lines.push(JSON.stringify(record));
24
+ }
25
+ return `${lines.join("\n")}\n`;
26
+ }
27
+ export function toLharJson(entries, conversations, privacy = "standard") {
28
+ const sorted = [...entries].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
29
+ const records = sorted.map((entry) => buildLharRecord(entry, entries, privacy));
30
+ // Build sessions from conversations map
31
+ const sessions = [];
32
+ const seenTraces = new Set();
33
+ for (const record of records) {
34
+ if (!seenTraces.has(record.trace_id)) {
35
+ seenTraces.add(record.trace_id);
36
+ const convo = record.trace_id
37
+ ? Array.from(conversations.values()).find((c) => traceIdFromConversation(c.id) === record.trace_id)
38
+ : undefined;
39
+ sessions.push({
40
+ trace_id: record.trace_id,
41
+ started_at: convo?.firstSeen || record.timestamp,
42
+ tool: record.source.tool,
43
+ model: record.gen_ai.request.model,
44
+ });
45
+ }
46
+ }
47
+ return {
48
+ lhar: {
49
+ version: LHAR_VERSION,
50
+ creator: {
51
+ name: COLLECTOR_NAME,
52
+ version: COLLECTOR_VERSION,
53
+ },
54
+ sessions,
55
+ entries: records,
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/lhar/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { LharRecord, LharSessionLine } from "../lhar-types.generated.js";
2
+ import type { CapturedEntry, Conversation, PrivacyLevel } from "../types.js";
3
+ export declare function traceIdFromConversation(conversationId: string | null): string;
4
+ export declare function buildLharRecord(entry: CapturedEntry, prevEntries: CapturedEntry[], privacy?: PrivacyLevel): LharRecord;
5
+ export declare function buildSessionLine(conversationId: string, conversation: Conversation, model: string): LharSessionLine;
6
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/lhar/record.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY7E,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAI7E;AA2BD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,aAAa,EAAE,EAC5B,OAAO,GAAE,YAAyB,GACjC,UAAU,CAqMZ;AAED,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,MAAM,GACZ,eAAe,CAQjB"}
@@ -0,0 +1,216 @@
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import { redactHeaders } from "../http/headers.js";
3
+ import { VERSION } from "../version.generated.js";
4
+ import { analyzeComposition } from "./composition.js";
5
+ import { parseResponseUsage } from "./response.js";
6
+ const COLLECTOR_NAME = "context-lens";
7
+ const COLLECTOR_VERSION = VERSION;
8
+ function hexId(bytes) {
9
+ return randomBytes(bytes).toString("hex");
10
+ }
11
+ export function traceIdFromConversation(conversationId) {
12
+ if (!conversationId)
13
+ return hexId(16);
14
+ // Deterministic: hash the conversationId to a 32-hex-char trace ID
15
+ return createHash("sha256").update(conversationId).digest("hex").slice(0, 32);
16
+ }
17
+ /**
18
+ * Extract the response body for raw capture. Returns null if the response
19
+ * data doesn't contain a capturable body.
20
+ */
21
+ function responseBodyForCapture(response) {
22
+ if (!response)
23
+ return null;
24
+ const resp = response;
25
+ // Streaming: raw SSE chunks
26
+ if (resp.streaming && typeof resp.chunks === "string") {
27
+ return resp.chunks;
28
+ }
29
+ // Raw string response
30
+ if (resp.raw && typeof resp.raw === "string") {
31
+ return resp.raw;
32
+ }
33
+ // Marker-only raw response (body wasn't captured)
34
+ if (resp.raw === true) {
35
+ return null;
36
+ }
37
+ // Parsed JSON response object
38
+ return resp;
39
+ }
40
+ export function buildLharRecord(entry, prevEntries, privacy = "standard") {
41
+ const ci = entry.contextInfo;
42
+ // Use pre-computed composition from storeRequest; fall back to recomputing
43
+ const composition = entry.composition.length > 0
44
+ ? entry.composition
45
+ : analyzeComposition(ci, entry.rawBody);
46
+ const usage = parseResponseUsage(entry.response);
47
+ // Sequence + growth must be derived from a stable ordering.
48
+ // Use oldest-first timestamp ordering within the conversation; tie-break by id.
49
+ let convoEntries = entry.conversationId
50
+ ? prevEntries.filter((e) => e.conversationId === entry.conversationId)
51
+ : [entry];
52
+ // Make buildLharRecord work even if the caller doesn't include `entry` in `prevEntries`.
53
+ if (entry.conversationId) {
54
+ const found = convoEntries.some((e) => e.id === entry.id && e.timestamp === entry.timestamp);
55
+ if (!found)
56
+ convoEntries = [...convoEntries, entry];
57
+ }
58
+ convoEntries.sort((a, b) => {
59
+ const dt = new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
60
+ if (dt !== 0)
61
+ return dt;
62
+ return a.id - b.id;
63
+ });
64
+ let convoIndex = convoEntries.findIndex((e) => e.id === entry.id && e.timestamp === entry.timestamp);
65
+ if (convoIndex < 0)
66
+ convoIndex = convoEntries.findIndex((e) => e.id === entry.id);
67
+ if (convoIndex < 0)
68
+ convoIndex = 0;
69
+ const sequence = convoIndex + 1;
70
+ // Agent role: the most common agentKey in the conversation is the "main" agent.
71
+ // Entries with a different key (or null when main is non-null) are subagents.
72
+ let agentRole = "main";
73
+ let mainKey = null;
74
+ if (entry.conversationId && convoEntries.length > 1) {
75
+ const keyCounts = new Map();
76
+ for (const e of convoEntries) {
77
+ keyCounts.set(e.agentKey, (keyCounts.get(e.agentKey) || 0) + 1);
78
+ }
79
+ let maxCount = 0;
80
+ for (const [key, count] of keyCounts) {
81
+ if (count > maxCount) {
82
+ maxCount = count;
83
+ mainKey = key;
84
+ }
85
+ }
86
+ if (entry.agentKey !== mainKey) {
87
+ agentRole = "subagent";
88
+ }
89
+ }
90
+ // Growth tracking: compare to the previous same-role entry in the conversation
91
+ // to avoid false compaction/growth spikes from main↔subagent transitions.
92
+ let prevEntry = null;
93
+ for (let i = convoIndex - 1; i >= 0; i--) {
94
+ if (convoEntries[i].agentKey === entry.agentKey) {
95
+ prevEntry = convoEntries[i];
96
+ break;
97
+ }
98
+ }
99
+ const prevTokens = prevEntry ? prevEntry.contextInfo.totalTokens : 0;
100
+ const tokensAdded = prevEntry ? ci.totalTokens - prevTokens : null;
101
+ const compactionDetected = tokensAdded !== null && tokensAdded < 0;
102
+ // Tokens per second
103
+ let tokensPerSecond = null;
104
+ if (entry.timings && entry.timings.receive_ms > 0 && usage.outputTokens > 0) {
105
+ tokensPerSecond =
106
+ Math.round((usage.outputTokens / entry.timings.receive_ms) * 1000 * 10) /
107
+ 10;
108
+ }
109
+ const timings = entry.timings
110
+ ? {
111
+ ...entry.timings,
112
+ tokens_per_second: tokensPerSecond,
113
+ }
114
+ : null;
115
+ return {
116
+ type: "entry",
117
+ id: randomUUID(),
118
+ trace_id: traceIdFromConversation(entry.conversationId),
119
+ span_id: hexId(8),
120
+ parent_span_id: null,
121
+ timestamp: entry.timestamp,
122
+ sequence,
123
+ source: {
124
+ tool: entry.source || "unknown",
125
+ tool_version: null,
126
+ agent_role: agentRole,
127
+ collector: COLLECTOR_NAME,
128
+ collector_version: COLLECTOR_VERSION,
129
+ },
130
+ gen_ai: {
131
+ system: ci.provider,
132
+ request: {
133
+ model: ci.model,
134
+ max_tokens: entry.rawBody?.max_tokens ?? null,
135
+ temperature: entry.rawBody?.temperature ?? null,
136
+ top_p: entry.rawBody?.top_p ?? null,
137
+ stop_sequences: entry.rawBody?.stop_sequences || [],
138
+ },
139
+ response: {
140
+ model: usage.model,
141
+ finish_reasons: usage.finishReasons,
142
+ },
143
+ usage: {
144
+ input_tokens: usage.inputTokens || ci.totalTokens,
145
+ output_tokens: usage.outputTokens,
146
+ total_tokens: (usage.inputTokens || ci.totalTokens) + usage.outputTokens,
147
+ },
148
+ },
149
+ usage_ext: {
150
+ cache_read_tokens: usage.cacheReadTokens,
151
+ cache_write_tokens: usage.cacheWriteTokens,
152
+ cost_usd: entry.costUsd,
153
+ },
154
+ http: {
155
+ method: "POST",
156
+ url: entry.targetUrl,
157
+ status_code: entry.httpStatus,
158
+ api_format: ci.apiFormat,
159
+ stream: usage.stream,
160
+ request_headers: privacy === "minimal" ? {} : redactHeaders(entry.requestHeaders),
161
+ response_headers: privacy === "minimal" ? {} : redactHeaders(entry.responseHeaders),
162
+ },
163
+ timings,
164
+ transfer: {
165
+ request_bytes: entry.requestBytes,
166
+ response_bytes: entry.responseBytes,
167
+ compressed: false,
168
+ },
169
+ context_lens: {
170
+ window_size: entry.contextLimit,
171
+ utilization: entry.contextLimit > 0
172
+ ? Math.round((ci.totalTokens / entry.contextLimit) * 1000) / 1000
173
+ : 0,
174
+ system_tokens: ci.systemTokens,
175
+ tools_tokens: ci.toolsTokens,
176
+ messages_tokens: ci.messagesTokens,
177
+ composition,
178
+ growth: {
179
+ tokens_added_this_turn: tokensAdded,
180
+ cumulative_tokens: ci.totalTokens,
181
+ compaction_detected: compactionDetected,
182
+ },
183
+ security: {
184
+ alerts: (entry.securityAlerts || []).map((a) => ({
185
+ message_index: a.messageIndex,
186
+ role: a.role,
187
+ tool_name: a.toolName,
188
+ severity: a.severity,
189
+ pattern: a.pattern,
190
+ match: a.match,
191
+ offset: a.offset,
192
+ length: a.length,
193
+ })),
194
+ summary: {
195
+ high: (entry.securityAlerts || []).filter((a) => a.severity === "high").length,
196
+ medium: (entry.securityAlerts || []).filter((a) => a.severity === "medium").length,
197
+ info: (entry.securityAlerts || []).filter((a) => a.severity === "info").length,
198
+ },
199
+ },
200
+ },
201
+ raw: {
202
+ request_body: privacy === "full" && entry.rawBody ? entry.rawBody : null,
203
+ response_body: privacy === "full" ? responseBodyForCapture(entry.response) : null,
204
+ },
205
+ };
206
+ }
207
+ export function buildSessionLine(conversationId, conversation, model) {
208
+ return {
209
+ type: "session",
210
+ trace_id: traceIdFromConversation(conversationId),
211
+ started_at: conversation.firstSeen,
212
+ tool: conversation.source,
213
+ model,
214
+ };
215
+ }
216
+ //# sourceMappingURL=record.js.map