llm-deep-trace 0.1.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/bin/llm-deep-trace.js +24 -0
  4. package/next.config.ts +8 -0
  5. package/package.json +56 -0
  6. package/postcss.config.mjs +5 -0
  7. package/public/banner-v2.png +0 -0
  8. package/public/file.svg +1 -0
  9. package/public/globe.svg +1 -0
  10. package/public/logo.png +0 -0
  11. package/public/next.svg +1 -0
  12. package/public/vercel.svg +1 -0
  13. package/public/window.svg +1 -0
  14. package/src/app/api/agent-config/route.ts +31 -0
  15. package/src/app/api/all-sessions/route.ts +9 -0
  16. package/src/app/api/analytics/route.ts +379 -0
  17. package/src/app/api/detect-agents/route.ts +170 -0
  18. package/src/app/api/image/route.ts +73 -0
  19. package/src/app/api/search/route.ts +28 -0
  20. package/src/app/api/session-by-key/route.ts +21 -0
  21. package/src/app/api/sessions/[sessionId]/messages/route.ts +46 -0
  22. package/src/app/api/sse/route.ts +86 -0
  23. package/src/app/favicon.ico +0 -0
  24. package/src/app/globals.css +3518 -0
  25. package/src/app/icon.svg +4 -0
  26. package/src/app/layout.tsx +20 -0
  27. package/src/app/page.tsx +5 -0
  28. package/src/components/AnalyticsDashboard.tsx +393 -0
  29. package/src/components/App.tsx +243 -0
  30. package/src/components/CopyButton.tsx +42 -0
  31. package/src/components/Logo.tsx +20 -0
  32. package/src/components/MainPanel.tsx +1128 -0
  33. package/src/components/MessageRenderer.tsx +983 -0
  34. package/src/components/SessionTree.tsx +505 -0
  35. package/src/components/SettingsPanel.tsx +160 -0
  36. package/src/components/SetupView.tsx +206 -0
  37. package/src/components/Sidebar.tsx +714 -0
  38. package/src/components/ThemeToggle.tsx +54 -0
  39. package/src/lib/client-utils.ts +360 -0
  40. package/src/lib/normalizers.ts +371 -0
  41. package/src/lib/sessions.ts +1223 -0
  42. package/src/lib/store.ts +518 -0
  43. package/src/lib/types.ts +112 -0
  44. package/src/lib/useSSE.ts +81 -0
  45. package/tsconfig.json +34 -0
@@ -0,0 +1,371 @@
1
+ import { NormalizedMessage, RawEntry } from "./types";
2
+
3
+ export function normalizeClaudeEntry(
4
+ e: RawEntry
5
+ ): NormalizedMessage | NormalizedMessage[] | null {
6
+ const skip = new Set([
7
+ "progress",
8
+ "queue-operation",
9
+ "system",
10
+ "result",
11
+ "debug",
12
+ "error_json",
13
+ ]);
14
+ if (skip.has(e.type)) return null;
15
+
16
+ if (e.type === "user" || e.type === "assistant") {
17
+ const msg = (e.message || {}) as Record<string, unknown>;
18
+ const role = (msg.role as string) || e.type;
19
+ const content = msg.content;
20
+ const nc: Record<string, unknown>[] = [];
21
+ const toolResults: NormalizedMessage[] = [];
22
+
23
+ if (typeof content === "string") {
24
+ nc.push({ type: "text", text: content });
25
+ } else if (Array.isArray(content)) {
26
+ for (const b of content) {
27
+ if (!b) continue;
28
+ const block = b as Record<string, unknown>;
29
+ if (block.type === "text") nc.push(block);
30
+ else if (block.type === "thinking") nc.push(block);
31
+ else if (block.type === "tool_use") nc.push(block);
32
+ else if (block.type === "tool_result") {
33
+ const rc = Array.isArray(block.content)
34
+ ? block.content
35
+ : [{ type: "text", text: String(block.content || "") }];
36
+ toolResults.push({
37
+ type: "message",
38
+ timestamp: e.timestamp,
39
+ message: {
40
+ role: "toolResult",
41
+ toolCallId: block.tool_use_id as string,
42
+ toolName: "",
43
+ content: rc,
44
+ isError: block.is_error as boolean,
45
+ },
46
+ });
47
+ } else if (block.type === "input_text") {
48
+ nc.push({ type: "text", text: block.text || "" });
49
+ } else if (block.type === "output_text") {
50
+ nc.push({ type: "text", text: block.text || "" });
51
+ }
52
+ }
53
+ }
54
+
55
+ if (toolResults.length > 0) {
56
+ const out: NormalizedMessage[] = [];
57
+ if (nc.length)
58
+ out.push({
59
+ type: "message",
60
+ timestamp: e.timestamp,
61
+ message: { role, content: nc as unknown as string },
62
+ });
63
+ out.push(...toolResults);
64
+ return out.length === 1 ? out[0] : out;
65
+ }
66
+ return {
67
+ type: "message",
68
+ timestamp: e.timestamp,
69
+ message: {
70
+ role,
71
+ content: nc.length ? (nc as unknown as string) : (content as string),
72
+ },
73
+ };
74
+ }
75
+ return null;
76
+ }
77
+
78
+ export function normalizeCodexEntry(e: RawEntry): NormalizedMessage | null {
79
+ const skip = new Set(["event_msg", "session_meta", "turn_context"]);
80
+ if (skip.has(e.type)) return null;
81
+
82
+ if (e.type === "response_item") {
83
+ const p = (e.payload || {}) as Record<string, unknown>;
84
+ const ptype = (p.type as string) || "message";
85
+ const ts = e.timestamp;
86
+
87
+ if (ptype === "message") {
88
+ const role = p.role as string;
89
+ if (role === "developer") return null;
90
+ const rawContent = (p.content || []) as Record<string, unknown>[];
91
+ const content = rawContent.map(
92
+ (b: Record<string, unknown>) => {
93
+ if (b.type === "input_text") return { type: "text", text: b.text || "" };
94
+ if (b.type === "output_text") return { type: "text", text: b.text || "" };
95
+ if (b.type === "text") return b;
96
+ if (b.type === "refusal")
97
+ return { type: "text", text: "[refusal: " + (b.refusal || "") + "]" };
98
+ return { type: "text", text: JSON.stringify(b) };
99
+ }
100
+ );
101
+ return {
102
+ type: "message",
103
+ timestamp: ts,
104
+ message: {
105
+ role: role === "assistant" ? "assistant" : "user",
106
+ content: content as unknown as string,
107
+ },
108
+ };
109
+ }
110
+
111
+ if (ptype === "function_call") {
112
+ let input: Record<string, unknown> = {};
113
+ if (p.arguments) {
114
+ try {
115
+ input = JSON.parse(p.arguments as string);
116
+ } catch {
117
+ input = { raw: p.arguments };
118
+ }
119
+ }
120
+ return {
121
+ type: "message",
122
+ timestamp: ts,
123
+ message: {
124
+ role: "assistant",
125
+ content: [
126
+ {
127
+ type: "tool_use",
128
+ id: (p.call_id as string) || (p.id as string),
129
+ name: (p.name as string) || "function",
130
+ input,
131
+ },
132
+ ] as unknown as string,
133
+ },
134
+ };
135
+ }
136
+
137
+ if (ptype === "function_call_output") {
138
+ return {
139
+ type: "message",
140
+ timestamp: ts,
141
+ message: {
142
+ role: "toolResult",
143
+ toolCallId: p.call_id as string,
144
+ toolName: "",
145
+ content: [{ type: "text", text: (p.output as string) || "" }] as unknown as string,
146
+ },
147
+ };
148
+ }
149
+
150
+ if (ptype === "reasoning" && p.summary) {
151
+ const summaryArr = p.summary as { text?: string }[];
152
+ const text = summaryArr.map((s) => s.text || "").join("\n");
153
+ return {
154
+ type: "message",
155
+ timestamp: ts,
156
+ message: {
157
+ role: "assistant",
158
+ content: [{ type: "thinking", thinking: text }] as unknown as string,
159
+ },
160
+ };
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+
166
+ /** Normalize Kimi content: string | array of {type: "think"|"text", text} */
167
+ export function normalizeKimiEntry(e: RawEntry): NormalizedMessage | null {
168
+ const msg = (e.message || e) as Record<string, unknown>;
169
+ const role = (msg.role as string) || e.type;
170
+ if (role !== "user" && role !== "assistant") return null;
171
+
172
+ const content = msg.content;
173
+ const nc: Record<string, unknown>[] = [];
174
+
175
+ if (typeof content === "string") {
176
+ nc.push({ type: "text", text: content });
177
+ } else if (Array.isArray(content)) {
178
+ for (const block of content as Record<string, unknown>[]) {
179
+ if (!block) continue;
180
+ if (block.type === "think" && block.text) {
181
+ nc.push({ type: "thinking", thinking: block.text });
182
+ } else if (block.type === "text" && block.text) {
183
+ nc.push({ type: "text", text: block.text });
184
+ } else if (typeof block === "string") {
185
+ nc.push({ type: "text", text: block });
186
+ } else if (block.text) {
187
+ nc.push({ type: "text", text: block.text });
188
+ }
189
+ }
190
+ }
191
+
192
+ if (nc.length === 0) return null;
193
+
194
+ return {
195
+ type: "message",
196
+ timestamp: e.timestamp,
197
+ message: {
198
+ role,
199
+ content: nc as unknown as string,
200
+ },
201
+ };
202
+ }
203
+
204
+ /** Normalize Kimi context.jsonl entries — direct {role, content} with no `type` wrapper */
205
+ export function normalizeKimiDirectEntry(e: RawEntry): NormalizedMessage | null {
206
+ const raw = e as unknown as Record<string, unknown>;
207
+ const role = raw.role as string;
208
+ // Skip internal entries
209
+ if (!role || role.startsWith("_")) return null;
210
+ if (role !== "user" && role !== "assistant") return null;
211
+
212
+ const content = raw.content;
213
+ const nc: Record<string, unknown>[] = [];
214
+
215
+ if (typeof content === "string") {
216
+ if (content.trim()) nc.push({ type: "text", text: content });
217
+ } else if (Array.isArray(content)) {
218
+ for (const block of content as Record<string, unknown>[]) {
219
+ if (!block) continue;
220
+ // Kimi think block: {type:"think", think:"...", encrypted:null}
221
+ if (block.type === "think" && block.think) {
222
+ nc.push({ type: "thinking", thinking: block.think });
223
+ } else if (block.type === "text" && block.text) {
224
+ nc.push({ type: "text", text: block.text });
225
+ } else if (typeof (block as unknown) === "string" && (block as unknown as string).trim()) {
226
+ nc.push({ type: "text", text: block as unknown as string });
227
+ }
228
+ }
229
+ }
230
+
231
+ if (nc.length === 0) return null;
232
+
233
+ return {
234
+ type: "message",
235
+ timestamp: (raw.timestamp as string) || undefined,
236
+ message: {
237
+ role,
238
+ content: nc as unknown as string,
239
+ },
240
+ };
241
+ }
242
+
243
+ /** Normalize simple message entries (gemini, opencode, copilot, factory) */
244
+ export function normalizeSimpleEntry(e: RawEntry): NormalizedMessage | null {
245
+ const msg = (e.message || e) as Record<string, unknown>;
246
+ const role = (msg.role as string) || e.type;
247
+ if (role !== "user" && role !== "assistant" && role !== "model") return null;
248
+
249
+ const normalizedRole = role === "model" ? "assistant" : role;
250
+ const content = msg.content;
251
+
252
+ if (typeof content === "string") {
253
+ return {
254
+ type: "message",
255
+ timestamp: e.timestamp,
256
+ message: {
257
+ role: normalizedRole,
258
+ content: [{ type: "text", text: content }] as unknown as string,
259
+ },
260
+ };
261
+ }
262
+
263
+ if (Array.isArray(content)) {
264
+ const nc = (content as Record<string, unknown>[]).map(b => {
265
+ if (b.type === "text") return b;
266
+ if (typeof b === "string") return { type: "text", text: b };
267
+ if (b.text) return { type: "text", text: b.text };
268
+ return { type: "text", text: JSON.stringify(b) };
269
+ });
270
+ return {
271
+ type: "message",
272
+ timestamp: e.timestamp,
273
+ message: {
274
+ role: normalizedRole,
275
+ content: nc as unknown as string,
276
+ },
277
+ };
278
+ }
279
+
280
+ return null;
281
+ }
282
+
283
+ export function normalizeEntries(entries: RawEntry[]): NormalizedMessage[] {
284
+ const toolNameMap = new Map<string, string>();
285
+ const normalized: NormalizedMessage[] = [];
286
+
287
+ // Detect provider from first entry patterns
288
+ let detectedProvider = "";
289
+ for (const e of entries) {
290
+ if (e.type === "response_item" || e.type === "session_meta") { detectedProvider = "codex"; break; }
291
+ if (e.type === "user" || e.type === "assistant") {
292
+ const msg = (e.message || {}) as Record<string, unknown>;
293
+ if (msg.role && Array.isArray(msg.content)) {
294
+ const first = (msg.content as Record<string, unknown>[])[0];
295
+ if (first && (first.type === "think" || first.type === "text")) {
296
+ detectedProvider = "kimi";
297
+ }
298
+ }
299
+ if (!detectedProvider) detectedProvider = "claude";
300
+ break;
301
+ }
302
+ if (e.type === "message") {
303
+ const msg = (e.message || e) as Record<string, unknown>;
304
+ if (typeof msg.content === "string" && (msg.role === "user" || msg.role === "assistant" || msg.role === "model")) {
305
+ detectedProvider = "simple";
306
+ break;
307
+ }
308
+ }
309
+ // Kimi context.jsonl: direct {role, content} with no `type` field
310
+ if (!e.type) {
311
+ const role = (e as unknown as Record<string, unknown>).role as string;
312
+ if (role === "user" || role === "assistant") {
313
+ detectedProvider = "kimi-direct";
314
+ break;
315
+ }
316
+ }
317
+ }
318
+
319
+ for (const e of entries) {
320
+ const t = e.type;
321
+ let norm: NormalizedMessage | NormalizedMessage[] | null = null;
322
+
323
+ if (detectedProvider === "kimi-direct") {
324
+ norm = normalizeKimiDirectEntry(e);
325
+ } else if (detectedProvider === "kimi" && (t === "user" || t === "assistant" || t === "message")) {
326
+ norm = normalizeKimiEntry(e);
327
+ } else if (detectedProvider === "simple" && (t === "user" || t === "assistant" || t === "message" || t === "model")) {
328
+ norm = normalizeSimpleEntry(e);
329
+ } else if (
330
+ ["user", "assistant", "progress", "queue-operation", "system", "result", "debug"].includes(t)
331
+ ) {
332
+ norm = normalizeClaudeEntry(e);
333
+ } else if (
334
+ ["response_item", "event_msg", "session_meta", "turn_context"].includes(t)
335
+ ) {
336
+ norm = normalizeCodexEntry(e);
337
+ } else {
338
+ norm = e as unknown as NormalizedMessage;
339
+ }
340
+
341
+ if (!norm) continue;
342
+
343
+ const normList = Array.isArray(norm) ? norm : [norm];
344
+ for (const n of normList) {
345
+ normalized.push(n);
346
+ const msg = n.message;
347
+ if (msg && msg.role === "assistant" && Array.isArray(msg.content)) {
348
+ for (const b of msg.content as unknown as Record<string, unknown>[]) {
349
+ if (
350
+ b.id &&
351
+ b.name &&
352
+ (b.type === "tool_use" || b.type === "toolCall")
353
+ ) {
354
+ toolNameMap.set(b.id as string, b.name as string);
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ // Backfill tool names
362
+ for (const norm of normalized) {
363
+ const msg = norm.message;
364
+ if (msg && msg.role === "toolResult" && !msg.toolName && msg.toolCallId) {
365
+ const name = toolNameMap.get(msg.toolCallId);
366
+ if (name) msg.toolName = name;
367
+ }
368
+ }
369
+
370
+ return normalized;
371
+ }