@uvl000/claude-trace 3.0.13-workflowexp.1

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 (258) hide show
  1. package/README.md +477 -0
  2. package/README.zh-CN.md +477 -0
  3. package/dist/adapt/api-format.d.ts +41 -0
  4. package/dist/adapt/api-format.d.ts.map +1 -0
  5. package/dist/adapt/api-format.js +132 -0
  6. package/dist/adapt/api-format.js.map +1 -0
  7. package/dist/adapt/openai-adapter.d.ts +123 -0
  8. package/dist/adapt/openai-adapter.d.ts.map +1 -0
  9. package/dist/adapt/openai-adapter.js +628 -0
  10. package/dist/adapt/openai-adapter.js.map +1 -0
  11. package/dist/cli/cli-common.d.ts +73 -0
  12. package/dist/cli/cli-common.d.ts.map +1 -0
  13. package/dist/cli/cli-common.js +189 -0
  14. package/dist/cli/cli-common.js.map +1 -0
  15. package/dist/cli/cli.d.ts +23 -0
  16. package/dist/cli/cli.d.ts.map +1 -0
  17. package/dist/cli/cli.js +175 -0
  18. package/dist/cli/cli.js.map +1 -0
  19. package/dist/cli/codex-cli.d.ts +24 -0
  20. package/dist/cli/codex-cli.d.ts.map +1 -0
  21. package/dist/cli/codex-cli.js +146 -0
  22. package/dist/cli/codex-cli.js.map +1 -0
  23. package/dist/cli/opencode-cli.d.ts +23 -0
  24. package/dist/cli/opencode-cli.d.ts.map +1 -0
  25. package/dist/cli/opencode-cli.js +156 -0
  26. package/dist/cli/opencode-cli.js.map +1 -0
  27. package/dist/cli/trace-runner.d.ts +21 -0
  28. package/dist/cli/trace-runner.d.ts.map +1 -0
  29. package/dist/cli/trace-runner.js +282 -0
  30. package/dist/cli/trace-runner.js.map +1 -0
  31. package/dist/cli/validate-trace.d.ts +30 -0
  32. package/dist/cli/validate-trace.d.ts.map +1 -0
  33. package/dist/cli/validate-trace.js +230 -0
  34. package/dist/cli/validate-trace.js.map +1 -0
  35. package/dist/cli/verify-offline-bundle.d.ts +13 -0
  36. package/dist/cli/verify-offline-bundle.d.ts.map +1 -0
  37. package/dist/cli/verify-offline-bundle.js +89 -0
  38. package/dist/cli/verify-offline-bundle.js.map +1 -0
  39. package/dist/cli/vibe-coding-proxy-cli.d.ts +12 -0
  40. package/dist/cli/vibe-coding-proxy-cli.d.ts.map +1 -0
  41. package/dist/cli/vibe-coding-proxy-cli.js +218 -0
  42. package/dist/cli/vibe-coding-proxy-cli.js.map +1 -0
  43. package/dist/config/claude-config-overlay.d.ts +69 -0
  44. package/dist/config/claude-config-overlay.d.ts.map +1 -0
  45. package/dist/config/claude-config-overlay.js +229 -0
  46. package/dist/config/claude-config-overlay.js.map +1 -0
  47. package/dist/config/codex-config-overlay.d.ts +122 -0
  48. package/dist/config/codex-config-overlay.d.ts.map +1 -0
  49. package/dist/config/codex-config-overlay.js +352 -0
  50. package/dist/config/codex-config-overlay.js.map +1 -0
  51. package/dist/index.d.ts +15 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +37 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/intercept/forward-proxy.d.ts +77 -0
  56. package/dist/intercept/forward-proxy.d.ts.map +1 -0
  57. package/dist/intercept/forward-proxy.js +368 -0
  58. package/dist/intercept/forward-proxy.js.map +1 -0
  59. package/dist/intercept/header-redaction.d.ts +7 -0
  60. package/dist/intercept/header-redaction.d.ts.map +1 -0
  61. package/dist/intercept/header-redaction.js +28 -0
  62. package/dist/intercept/header-redaction.js.map +1 -0
  63. package/dist/intercept/interceptor-loader.js +26 -0
  64. package/dist/intercept/interceptor.d.ts +164 -0
  65. package/dist/intercept/interceptor.d.ts.map +1 -0
  66. package/dist/intercept/interceptor.js +496 -0
  67. package/dist/intercept/interceptor.js.map +1 -0
  68. package/dist/intercept/mitm-cert.d.ts +53 -0
  69. package/dist/intercept/mitm-cert.d.ts.map +1 -0
  70. package/dist/intercept/mitm-cert.js +259 -0
  71. package/dist/intercept/mitm-cert.js.map +1 -0
  72. package/dist/intercept/proxy-log-writer.d.ts +77 -0
  73. package/dist/intercept/proxy-log-writer.d.ts.map +1 -0
  74. package/dist/intercept/proxy-log-writer.js +323 -0
  75. package/dist/intercept/proxy-log-writer.js.map +1 -0
  76. package/dist/intercept/proxy-targets.d.ts +69 -0
  77. package/dist/intercept/proxy-targets.d.ts.map +1 -0
  78. package/dist/intercept/proxy-targets.js +129 -0
  79. package/dist/intercept/proxy-targets.js.map +1 -0
  80. package/dist/intercept/reverse-proxy.d.ts +191 -0
  81. package/dist/intercept/reverse-proxy.d.ts.map +1 -0
  82. package/dist/intercept/reverse-proxy.js +807 -0
  83. package/dist/intercept/reverse-proxy.js.map +1 -0
  84. package/dist/intercept/token-extractor.js +28 -0
  85. package/dist/report/html-generator.d.ts +96 -0
  86. package/dist/report/html-generator.d.ts.map +1 -0
  87. package/dist/report/html-generator.js +225 -0
  88. package/dist/report/html-generator.js.map +1 -0
  89. package/dist/report/index-generator.d.ts +89 -0
  90. package/dist/report/index-generator.d.ts.map +1 -0
  91. package/dist/report/index-generator.js +319 -0
  92. package/dist/report/index-generator.js.map +1 -0
  93. package/dist/report/shared-conversation-processor.d.ts +200 -0
  94. package/dist/report/shared-conversation-processor.d.ts.map +1 -0
  95. package/dist/report/shared-conversation-processor.js +720 -0
  96. package/dist/report/shared-conversation-processor.js.map +1 -0
  97. package/dist/routing/codex-routing.d.ts +40 -0
  98. package/dist/routing/codex-routing.d.ts.map +1 -0
  99. package/dist/routing/codex-routing.js +96 -0
  100. package/dist/routing/codex-routing.js.map +1 -0
  101. package/dist/routing/proxy-routing.d.ts +48 -0
  102. package/dist/routing/proxy-routing.d.ts.map +1 -0
  103. package/dist/routing/proxy-routing.js +113 -0
  104. package/dist/routing/proxy-routing.js.map +1 -0
  105. package/dist/tools/binary-utils.d.ts +29 -0
  106. package/dist/tools/binary-utils.d.ts.map +1 -0
  107. package/dist/tools/binary-utils.js +134 -0
  108. package/dist/tools/binary-utils.js.map +1 -0
  109. package/dist/tools/claude.d.ts +61 -0
  110. package/dist/tools/claude.d.ts.map +1 -0
  111. package/dist/tools/claude.js +449 -0
  112. package/dist/tools/claude.js.map +1 -0
  113. package/dist/tools/codex.d.ts +70 -0
  114. package/dist/tools/codex.d.ts.map +1 -0
  115. package/dist/tools/codex.js +492 -0
  116. package/dist/tools/codex.js.map +1 -0
  117. package/dist/tools/opencode.d.ts +85 -0
  118. package/dist/tools/opencode.d.ts.map +1 -0
  119. package/dist/tools/opencode.js +401 -0
  120. package/dist/tools/opencode.js.map +1 -0
  121. package/dist/tools/types.d.ts +91 -0
  122. package/dist/tools/types.d.ts.map +1 -0
  123. package/dist/tools/types.js +11 -0
  124. package/dist/tools/types.js.map +1 -0
  125. package/dist/types.d.ts +158 -0
  126. package/dist/types.d.ts.map +1 -0
  127. package/dist/types.js +12 -0
  128. package/dist/types.js.map +1 -0
  129. package/frontend/dist/index.global.js +1551 -0
  130. package/frontend/dist/styles.css +1027 -0
  131. package/frontend/template.html +19 -0
  132. package/node_modules/agent-base/LICENSE +22 -0
  133. package/node_modules/agent-base/README.md +69 -0
  134. package/node_modules/agent-base/dist/helpers.d.ts +15 -0
  135. package/node_modules/agent-base/dist/helpers.d.ts.map +1 -0
  136. package/node_modules/agent-base/dist/helpers.js +66 -0
  137. package/node_modules/agent-base/dist/helpers.js.map +1 -0
  138. package/node_modules/agent-base/dist/index.d.ts +41 -0
  139. package/node_modules/agent-base/dist/index.d.ts.map +1 -0
  140. package/node_modules/agent-base/dist/index.js +178 -0
  141. package/node_modules/agent-base/dist/index.js.map +1 -0
  142. package/node_modules/agent-base/package.json +46 -0
  143. package/node_modules/debug/LICENSE +20 -0
  144. package/node_modules/debug/README.md +481 -0
  145. package/node_modules/debug/package.json +64 -0
  146. package/node_modules/debug/src/browser.js +272 -0
  147. package/node_modules/debug/src/common.js +292 -0
  148. package/node_modules/debug/src/index.js +10 -0
  149. package/node_modules/debug/src/node.js +263 -0
  150. package/node_modules/http-proxy-agent/LICENSE +22 -0
  151. package/node_modules/http-proxy-agent/README.md +44 -0
  152. package/node_modules/http-proxy-agent/dist/index.d.ts +44 -0
  153. package/node_modules/http-proxy-agent/dist/index.d.ts.map +1 -0
  154. package/node_modules/http-proxy-agent/dist/index.js +148 -0
  155. package/node_modules/http-proxy-agent/dist/index.js.map +1 -0
  156. package/node_modules/http-proxy-agent/package.json +47 -0
  157. package/node_modules/https-proxy-agent/LICENSE +22 -0
  158. package/node_modules/https-proxy-agent/README.md +70 -0
  159. package/node_modules/https-proxy-agent/dist/index.d.ts +47 -0
  160. package/node_modules/https-proxy-agent/dist/index.d.ts.map +1 -0
  161. package/node_modules/https-proxy-agent/dist/index.js +180 -0
  162. package/node_modules/https-proxy-agent/dist/index.js.map +1 -0
  163. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +15 -0
  164. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts.map +1 -0
  165. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +101 -0
  166. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +1 -0
  167. package/node_modules/https-proxy-agent/package.json +50 -0
  168. package/node_modules/ms/index.js +162 -0
  169. package/node_modules/ms/license.md +21 -0
  170. package/node_modules/ms/package.json +38 -0
  171. package/node_modules/ms/readme.md +59 -0
  172. package/node_modules/node-forge/LICENSE +331 -0
  173. package/node_modules/node-forge/README.md +2071 -0
  174. package/node_modules/node-forge/dist/forge.all.min.js +2 -0
  175. package/node_modules/node-forge/dist/forge.all.min.js.map +1 -0
  176. package/node_modules/node-forge/dist/forge.min.js +2 -0
  177. package/node_modules/node-forge/dist/forge.min.js.map +1 -0
  178. package/node_modules/node-forge/dist/prime.worker.min.js +2 -0
  179. package/node_modules/node-forge/dist/prime.worker.min.js.map +1 -0
  180. package/node_modules/node-forge/flash/swf/SocketPool.swf +0 -0
  181. package/node_modules/node-forge/lib/aes.js +1091 -0
  182. package/node_modules/node-forge/lib/aesCipherSuites.js +282 -0
  183. package/node_modules/node-forge/lib/asn1-validator.js +91 -0
  184. package/node_modules/node-forge/lib/asn1.js +1503 -0
  185. package/node_modules/node-forge/lib/baseN.js +186 -0
  186. package/node_modules/node-forge/lib/cipher.js +230 -0
  187. package/node_modules/node-forge/lib/cipherModes.js +999 -0
  188. package/node_modules/node-forge/lib/des.js +496 -0
  189. package/node_modules/node-forge/lib/ed25519.js +1091 -0
  190. package/node_modules/node-forge/lib/forge.js +13 -0
  191. package/node_modules/node-forge/lib/form.js +149 -0
  192. package/node_modules/node-forge/lib/hmac.js +146 -0
  193. package/node_modules/node-forge/lib/http.js +1346 -0
  194. package/node_modules/node-forge/lib/index.all.js +16 -0
  195. package/node_modules/node-forge/lib/index.js +33 -0
  196. package/node_modules/node-forge/lib/jsbn.js +1294 -0
  197. package/node_modules/node-forge/lib/kem.js +168 -0
  198. package/node_modules/node-forge/lib/log.js +319 -0
  199. package/node_modules/node-forge/lib/md.all.js +13 -0
  200. package/node_modules/node-forge/lib/md.js +11 -0
  201. package/node_modules/node-forge/lib/md5.js +289 -0
  202. package/node_modules/node-forge/lib/mgf.js +12 -0
  203. package/node_modules/node-forge/lib/mgf1.js +57 -0
  204. package/node_modules/node-forge/lib/oids.js +180 -0
  205. package/node_modules/node-forge/lib/pbe.js +1023 -0
  206. package/node_modules/node-forge/lib/pbkdf2.js +211 -0
  207. package/node_modules/node-forge/lib/pem.js +237 -0
  208. package/node_modules/node-forge/lib/pkcs1.js +276 -0
  209. package/node_modules/node-forge/lib/pkcs12.js +1078 -0
  210. package/node_modules/node-forge/lib/pkcs7.js +1260 -0
  211. package/node_modules/node-forge/lib/pkcs7asn1.js +410 -0
  212. package/node_modules/node-forge/lib/pki.js +102 -0
  213. package/node_modules/node-forge/lib/prime.js +297 -0
  214. package/node_modules/node-forge/lib/prime.worker.js +168 -0
  215. package/node_modules/node-forge/lib/prng.js +419 -0
  216. package/node_modules/node-forge/lib/pss.js +241 -0
  217. package/node_modules/node-forge/lib/random.js +191 -0
  218. package/node_modules/node-forge/lib/rc2.js +410 -0
  219. package/node_modules/node-forge/lib/rsa.js +1968 -0
  220. package/node_modules/node-forge/lib/sha1.js +319 -0
  221. package/node_modules/node-forge/lib/sha256.js +327 -0
  222. package/node_modules/node-forge/lib/sha512.js +561 -0
  223. package/node_modules/node-forge/lib/socket.js +287 -0
  224. package/node_modules/node-forge/lib/ssh.js +236 -0
  225. package/node_modules/node-forge/lib/tls.js +4282 -0
  226. package/node_modules/node-forge/lib/tlssocket.js +249 -0
  227. package/node_modules/node-forge/lib/util.js +2652 -0
  228. package/node_modules/node-forge/lib/x509.js +3251 -0
  229. package/node_modules/node-forge/lib/xhr.js +738 -0
  230. package/node_modules/node-forge/package.json +123 -0
  231. package/node_modules/smol-toml/LICENSE +24 -0
  232. package/node_modules/smol-toml/README.md +239 -0
  233. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  234. package/node_modules/smol-toml/dist/date.js +127 -0
  235. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  236. package/node_modules/smol-toml/dist/error.js +63 -0
  237. package/node_modules/smol-toml/dist/extract.d.ts +30 -0
  238. package/node_modules/smol-toml/dist/extract.js +100 -0
  239. package/node_modules/smol-toml/dist/index.cjs +897 -0
  240. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  241. package/node_modules/smol-toml/dist/index.js +33 -0
  242. package/node_modules/smol-toml/dist/parse.d.ts +37 -0
  243. package/node_modules/smol-toml/dist/parse.js +148 -0
  244. package/node_modules/smol-toml/dist/primitive.d.ts +31 -0
  245. package/node_modules/smol-toml/dist/primitive.js +179 -0
  246. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  247. package/node_modules/smol-toml/dist/stringify.js +167 -0
  248. package/node_modules/smol-toml/dist/struct.d.ts +32 -0
  249. package/node_modules/smol-toml/dist/struct.js +184 -0
  250. package/node_modules/smol-toml/dist/util.d.ts +42 -0
  251. package/node_modules/smol-toml/dist/util.js +111 -0
  252. package/node_modules/smol-toml/package.json +54 -0
  253. package/offline/bin/claude-trace +6 -0
  254. package/offline/bin/claude-wrapper +13 -0
  255. package/offline/bin/install-agent +14 -0
  256. package/offline/bin/validate-trace +6 -0
  257. package/offline/bin/verify-bundle +7 -0
  258. package/package.json +103 -0
@@ -0,0 +1,720 @@
1
+ "use strict";
2
+ /**
3
+ * @file Shared conversation parsing for frontend and backend.
4
+ *
5
+ * Converts raw JSONL request/response pairs into normalized Anthropic-shaped
6
+ * messages, groups them into conversation threads, pairs tool_use with
7
+ * tool_result blocks, and detects compacted (summarized) conversations.
8
+ *
9
+ * Used by both the Lit HTML viewer and the CLI index generator so parsing
10
+ * behavior stays identical across surfaces.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.SharedConversationProcessor = void 0;
14
+ exports.aggregateUsageFromPairs = aggregateUsageFromPairs;
15
+ const api_format_1 = require("../adapt/api-format");
16
+ const openai_adapter_1 = require("../adapt/openai-adapter");
17
+ /**
18
+ * Aggregates token usage from chronologically ordered API pairs.
19
+ *
20
+ * Input tokens reflect the last turn's context snapshot (not summed across turns).
21
+ * Output tokens are summed across all pairs as the session generation cost.
22
+ *
23
+ * @param pairs - Processed pairs belonging to one conversation thread.
24
+ */
25
+ function aggregateUsageFromPairs(pairs) {
26
+ if (!pairs || pairs.length === 0) {
27
+ return {
28
+ inputTokens: 0,
29
+ outputTokens: 0,
30
+ totalTokens: 0,
31
+ lastTurnInputTokens: 0,
32
+ lastTurnOutputTokens: 0,
33
+ sessionOutputTokens: 0,
34
+ cacheReadTokens: null,
35
+ cacheCreationTokens: null,
36
+ };
37
+ }
38
+ const sortedPairs = [...pairs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
39
+ const lastPair = sortedPairs[sortedPairs.length - 1];
40
+ const lastUsage = lastPair.response?.usage;
41
+ const lastTurnInputTokens = lastUsage?.input_tokens ?? 0;
42
+ const lastTurnOutputTokens = lastUsage?.output_tokens ?? 0;
43
+ const sessionOutputTokens = sortedPairs.reduce((sum, pair) => sum + (pair.response?.usage?.output_tokens ?? 0), 0);
44
+ return {
45
+ inputTokens: lastTurnInputTokens,
46
+ outputTokens: sessionOutputTokens,
47
+ totalTokens: sessionOutputTokens,
48
+ lastTurnInputTokens,
49
+ lastTurnOutputTokens,
50
+ sessionOutputTokens,
51
+ cacheReadTokens: lastUsage?.cache_read_input_tokens ?? null,
52
+ cacheCreationTokens: lastUsage?.cache_creation_input_tokens ?? null,
53
+ };
54
+ }
55
+ /**
56
+ * Parses raw trace pairs and merges them into viewer-ready conversations.
57
+ * Used by both the Lit frontend and the CLI index generator.
58
+ */
59
+ class SharedConversationProcessor {
60
+ /**
61
+ * Converts each {@link RawPair} into a {@link ProcessedPair}.
62
+ *
63
+ * Handles Anthropic JSON, Anthropic/Bedrock SSE, and OpenAI Chat/Responses
64
+ * formats. Pairs missing request/response or failing parse are skipped with
65
+ * a console warning.
66
+ *
67
+ * @param rawPairs - Lines parsed from a session JSONL log.
68
+ * @returns Normalized pairs ready for merging or JSON debug display.
69
+ */
70
+ processRawPairs(rawPairs) {
71
+ if (!rawPairs || rawPairs.length === 0) {
72
+ return [];
73
+ }
74
+ const processedPairs = [];
75
+ for (let i = 0; i < rawPairs.length; i++) {
76
+ const pair = rawPairs[i];
77
+ if (!pair?.request || !pair?.response) {
78
+ continue;
79
+ }
80
+ try {
81
+ const apiFormat = (0, openai_adapter_1.resolvePairApiFormat)(pair);
82
+ const isOpenAIFormat = apiFormat === "openai" || apiFormat === "openai-responses";
83
+ const isStreaming = !!pair.response.body_raw;
84
+ let response;
85
+ let streamFormat = null;
86
+ let request;
87
+ if (isOpenAIFormat) {
88
+ request = (0, openai_adapter_1.normalizeOpenAIRequest)(pair.request.body, apiFormat);
89
+ response = (0, openai_adapter_1.parseOpenAIResponse)(pair.response, apiFormat, (0, openai_adapter_1.extractModelFromPair)(pair));
90
+ }
91
+ else if (pair.response.body_raw) {
92
+ streamFormat = this.isBedrockResponse(pair.response.body_raw) ? "bedrock" : "standard";
93
+ response = this.parseStreamingResponse(pair.response.body_raw);
94
+ request = pair.request.body;
95
+ }
96
+ else if (pair.response.body) {
97
+ response = pair.response.body;
98
+ request = pair.request.body;
99
+ }
100
+ else {
101
+ continue;
102
+ }
103
+ const model = this.extractModel(pair);
104
+ processedPairs.push({
105
+ id: `${pair.request.timestamp || Date.now()}_${Math.random()}`,
106
+ timestamp: new Date((pair.request.timestamp || Date.now()) * 1000).toISOString(),
107
+ request,
108
+ response,
109
+ model,
110
+ isStreaming,
111
+ rawStreamData: pair.response.body_raw,
112
+ streamFormat,
113
+ apiFormat: apiFormat !== "unknown" ? apiFormat : undefined,
114
+ });
115
+ }
116
+ catch (error) {
117
+ console.warn(`Failed to process raw pair at index ${i}:`, error);
118
+ // Continue processing other pairs
119
+ }
120
+ }
121
+ return processedPairs;
122
+ }
123
+ /**
124
+ * Detects AWS Bedrock binary event-stream responses by their leading NUL bytes.
125
+ *
126
+ * @param bodyRaw - Raw response body string from the proxy log.
127
+ */
128
+ isBedrockResponse(bodyRaw) {
129
+ // Bedrock EventStream frames start with a 4-byte total-length prefix of zeroes
130
+ return bodyRaw.startsWith("\u0000\u0000");
131
+ }
132
+ /**
133
+ * Parses Bedrock binary event stream into a synthetic Anthropic {@link Message}.
134
+ *
135
+ * Extracts base64-wrapped JSON events from `event{"bytes":...}` frames and
136
+ * merges Bedrock invocation metrics into usage when present.
137
+ */
138
+ parseBedrockStreamingResponse(bodyRaw) {
139
+ if (!bodyRaw || bodyRaw.length === 0) {
140
+ throw new Error("Empty bodyRaw provided to parseBedrockStreamingResponse");
141
+ }
142
+ const events = [];
143
+ let bedrockMetrics = null;
144
+ try {
145
+ // Extract JSON payloads from AWS EventStream format
146
+ // The format contains binary headers followed by JSON payloads
147
+ const jsonChunks = this.extractJsonChunksFromEventStream(bodyRaw);
148
+ for (const jsonChunk of jsonChunks) {
149
+ try {
150
+ const eventPayload = JSON.parse(jsonChunk);
151
+ // Each frame wraps the Anthropic SSE event JSON in a base64 `bytes` field
152
+ if (eventPayload.bytes) {
153
+ const base64Data = eventPayload.bytes;
154
+ const decodedJson = this.decodeBase64ToUtf8(base64Data);
155
+ const event = JSON.parse(decodedJson);
156
+ events.push(event);
157
+ }
158
+ }
159
+ catch (chunkError) {
160
+ console.warn("Failed to parse JSON chunk:", jsonChunk, chunkError);
161
+ // Continue with other chunks
162
+ }
163
+ }
164
+ // Extract Bedrock metrics from the last event if present
165
+ bedrockMetrics = this.extractBedrockMetrics(bodyRaw);
166
+ }
167
+ catch (error) {
168
+ console.error("Failed to parse Bedrock streaming response:", error);
169
+ throw new Error(`Bedrock streaming response parsing failed: ${error}`);
170
+ }
171
+ return this.buildMessageFromEvents(events, bedrockMetrics);
172
+ }
173
+ /**
174
+ * Decodes base64 to UTF-8 in both browser (atob) and Node.js (Buffer).
175
+ *
176
+ * @throws When neither API is available (unlikely in this project's targets).
177
+ */
178
+ decodeBase64ToUtf8(base64Data) {
179
+ if (typeof window !== "undefined" && typeof atob !== "undefined") {
180
+ return atob(base64Data);
181
+ }
182
+ else if (typeof Buffer !== "undefined") {
183
+ return Buffer.from(base64Data, "base64").toString("utf-8");
184
+ }
185
+ else {
186
+ throw new Error("Base64 decoding not supported in this environment");
187
+ }
188
+ }
189
+ /**
190
+ * Scans raw Bedrock event-stream text for `event{"bytes":{...}}` JSON wrappers.
191
+ *
192
+ * Uses brace counting rather than regex to handle nested JSON objects safely.
193
+ */
194
+ extractJsonChunksFromEventStream(bodyRaw) {
195
+ if (!bodyRaw || bodyRaw.length === 0) {
196
+ return [];
197
+ }
198
+ const jsonChunks = [];
199
+ const pattern = 'event{"bytes":';
200
+ let searchIndex = 0;
201
+ while (searchIndex < bodyRaw.length) {
202
+ const patternIndex = bodyRaw.indexOf(pattern, searchIndex);
203
+ if (patternIndex === -1) {
204
+ break;
205
+ }
206
+ // Start extracting JSON from the '{' after 'event'
207
+ const jsonStartIndex = patternIndex + 5;
208
+ let braceCount = 0;
209
+ let jsonEndIndex = -1;
210
+ for (let i = jsonStartIndex; i < bodyRaw.length; i++) {
211
+ const char = bodyRaw[i];
212
+ if (char === "{") {
213
+ braceCount++;
214
+ }
215
+ else if (char === "}") {
216
+ braceCount--;
217
+ if (braceCount === 0) {
218
+ jsonEndIndex = i;
219
+ break;
220
+ }
221
+ }
222
+ }
223
+ if (jsonEndIndex !== -1) {
224
+ const jsonChunk = bodyRaw.substring(jsonStartIndex, jsonEndIndex + 1);
225
+ jsonChunks.push(jsonChunk);
226
+ searchIndex = jsonEndIndex + 1;
227
+ }
228
+ else {
229
+ searchIndex = patternIndex + pattern.length;
230
+ }
231
+ }
232
+ return jsonChunks;
233
+ }
234
+ /**
235
+ * Best-effort extraction of Bedrock token metrics embedded in the raw stream.
236
+ */
237
+ extractBedrockMetrics(bodyRaw) {
238
+ try {
239
+ const metricsMatch = bodyRaw.match(/"amazon-bedrock-invocationMetrics":\s*(\{[^}]+\})/);
240
+ if (metricsMatch && metricsMatch[1]) {
241
+ return JSON.parse(metricsMatch[1]);
242
+ }
243
+ }
244
+ catch (e) {
245
+ // Skip invalid metrics
246
+ }
247
+ return null;
248
+ }
249
+ /**
250
+ * Dispatches streaming body parsing to Bedrock or standard Anthropic SSE.
251
+ *
252
+ * @param bodyRaw - Raw `body_raw` field from a logged response.
253
+ * @returns Reconstructed assistant {@link Message}.
254
+ */
255
+ parseStreamingResponse(bodyRaw) {
256
+ if (this.isBedrockResponse(bodyRaw)) {
257
+ return this.parseBedrockStreamingResponse(bodyRaw);
258
+ }
259
+ else {
260
+ return this.parseStandardStreamingResponse(bodyRaw);
261
+ }
262
+ }
263
+ /**
264
+ * Parses Anthropic-style `data: {...}` SSE lines into a {@link Message}.
265
+ */
266
+ parseStandardStreamingResponse(bodyRaw) {
267
+ if (!bodyRaw || bodyRaw.length === 0) {
268
+ throw new Error("Empty bodyRaw provided to parseStandardStreamingResponse");
269
+ }
270
+ const lines = bodyRaw.split("\n");
271
+ const events = [];
272
+ for (const line of lines) {
273
+ if (!line.startsWith("data: "))
274
+ continue;
275
+ const data = line.substring(6).trim();
276
+ if (data === "[DONE]")
277
+ break;
278
+ try {
279
+ const event = JSON.parse(data);
280
+ events.push(event);
281
+ }
282
+ catch (e) {
283
+ console.warn("Failed to parse SSE event:", data, e);
284
+ }
285
+ }
286
+ return this.buildMessageFromEvents(events);
287
+ }
288
+ /**
289
+ * Replays streaming events into a complete {@link Message}, accumulating
290
+ * text, thinking, and tool_use deltas and parsing tool JSON at block stop.
291
+ *
292
+ * @param events - Ordered stream events from SSE or Bedrock decode.
293
+ * @param bedrockMetrics - Optional Bedrock token counts overriding usage.
294
+ */
295
+ buildMessageFromEvents(events, bedrockMetrics) {
296
+ let message = {
297
+ id: "",
298
+ type: "message",
299
+ role: "assistant",
300
+ content: [],
301
+ model: "",
302
+ stop_reason: null,
303
+ stop_sequence: null,
304
+ usage: {
305
+ input_tokens: 0,
306
+ output_tokens: 0,
307
+ cache_creation_input_tokens: null,
308
+ cache_read_input_tokens: null,
309
+ server_tool_use: null,
310
+ service_tier: null,
311
+ },
312
+ };
313
+ const contentBlocks = [];
314
+ let currentBlockIndex = -1;
315
+ for (const event of events) {
316
+ switch (event.type) {
317
+ case "message_start":
318
+ message = { ...message, ...event.message };
319
+ break;
320
+ case "content_block_start":
321
+ currentBlockIndex = event.index;
322
+ contentBlocks[currentBlockIndex] = { ...event.content_block };
323
+ break;
324
+ case "content_block_delta":
325
+ if (currentBlockIndex >= 0 && contentBlocks[currentBlockIndex]) {
326
+ const block = contentBlocks[currentBlockIndex];
327
+ const delta = event.delta;
328
+ switch (delta.type) {
329
+ case "text_delta":
330
+ if (block.type === "text") {
331
+ block.text = (block.text || "") + delta.text;
332
+ }
333
+ break;
334
+ case "input_json_delta":
335
+ if (block.type === "tool_use") {
336
+ const toolBlock = block;
337
+ if (typeof toolBlock.input === "string") {
338
+ toolBlock.input = toolBlock.input + delta.partial_json;
339
+ }
340
+ else {
341
+ toolBlock.input = delta.partial_json;
342
+ }
343
+ }
344
+ break;
345
+ case "thinking_delta":
346
+ if (block.type === "thinking") {
347
+ block.thinking =
348
+ (block.thinking || "") + delta.thinking;
349
+ }
350
+ break;
351
+ case "signature_delta":
352
+ if (block.type === "thinking") {
353
+ block.signature =
354
+ (block.signature || "") + delta.signature;
355
+ }
356
+ break;
357
+ case "citations_delta":
358
+ break;
359
+ }
360
+ }
361
+ break;
362
+ case "content_block_stop":
363
+ if (currentBlockIndex >= 0 && contentBlocks[currentBlockIndex]) {
364
+ const block = contentBlocks[currentBlockIndex];
365
+ if (block.type === "tool_use") {
366
+ const toolBlock = block;
367
+ if (typeof toolBlock.input === "string") {
368
+ try {
369
+ toolBlock.input = JSON.parse(toolBlock.input);
370
+ }
371
+ catch (e) {
372
+ console.warn("Failed to parse tool input JSON:", toolBlock.input);
373
+ }
374
+ }
375
+ }
376
+ }
377
+ break;
378
+ case "message_delta":
379
+ if (event.delta.stop_reason) {
380
+ message.stop_reason = event.delta.stop_reason;
381
+ }
382
+ if (event.delta.stop_sequence) {
383
+ message.stop_sequence = event.delta.stop_sequence;
384
+ }
385
+ if (event.usage) {
386
+ // Input tokens are usually sent once; preserve prior value on later deltas
387
+ const currentInputTokens = message.usage?.input_tokens ?? 0;
388
+ message.usage = {
389
+ input_tokens: event.usage.input_tokens ?? currentInputTokens,
390
+ output_tokens: event.usage.output_tokens ?? message.usage?.output_tokens ?? 0,
391
+ cache_creation_input_tokens: event.usage.cache_creation_input_tokens ?? message.usage?.cache_creation_input_tokens ?? null,
392
+ cache_read_input_tokens: event.usage.cache_read_input_tokens ?? message.usage?.cache_read_input_tokens ?? null,
393
+ server_tool_use: event.usage.server_tool_use ?? message.usage?.server_tool_use ?? null,
394
+ service_tier: null,
395
+ };
396
+ }
397
+ break;
398
+ case "message_stop":
399
+ break;
400
+ }
401
+ }
402
+ message.content = contentBlocks.filter((block) => block != null);
403
+ if (bedrockMetrics && message.usage) {
404
+ message.usage.input_tokens = bedrockMetrics.inputTokenCount;
405
+ message.usage.output_tokens = bedrockMetrics.outputTokenCount;
406
+ }
407
+ return message;
408
+ }
409
+ /**
410
+ * Resolves a display model id from Bedrock URL, request body, or response body.
411
+ */
412
+ extractModel(pair) {
413
+ if (pair.request?.url && pair.request.url.includes("bedrock-runtime")) {
414
+ const urlMatch = pair.request.url.match(/\/model\/([^\/]+)/);
415
+ if (urlMatch && urlMatch[1]) {
416
+ return this.normalizeModelName(urlMatch[1]);
417
+ }
418
+ }
419
+ if (pair.request?.body && typeof pair.request.body === "object" && "model" in pair.request.body) {
420
+ return this.normalizeModelName(pair.request.body.model);
421
+ }
422
+ if (pair.response?.body && typeof pair.response.body === "object" && "model" in pair.response.body) {
423
+ return this.normalizeModelName(pair.response.body.model);
424
+ }
425
+ return "unknown";
426
+ }
427
+ /**
428
+ * Strips Bedrock inference-profile prefixes for shorter display names.
429
+ */
430
+ normalizeModelName(modelName) {
431
+ if (!modelName)
432
+ return "unknown";
433
+ if (modelName.startsWith("us.anthropic.")) {
434
+ const match = modelName.match(/us\.anthropic\.([^:]+)/);
435
+ if (match && match[1]) {
436
+ return match[1];
437
+ }
438
+ }
439
+ return modelName;
440
+ }
441
+ /**
442
+ * Groups {@link ProcessedPair} rows into {@link SimpleConversation} threads.
443
+ *
444
+ * Threading heuristic: same system prompt + model, then bucket by normalized
445
+ * first user message. Within each bucket the pair with the most messages
446
+ * becomes the thread snapshot. Compacted conversations are merged when detected.
447
+ *
448
+ * @param pairs - Normalized pairs from {@link processRawPairs}.
449
+ * @param options.includeShortConversations - Include threads with ≤2 messages.
450
+ * @returns Conversations sorted by start time.
451
+ */
452
+ mergeConversations(pairs, options = {}) {
453
+ if (!pairs || pairs.length === 0)
454
+ return [];
455
+ // First split by system prompt + model so unrelated sessions don't merge
456
+ const pairsBySystem = new Map();
457
+ for (const pair of pairs) {
458
+ const system = pair.request.system;
459
+ const model = pair.model;
460
+ const systemKey = JSON.stringify({ system, model });
461
+ if (!pairsBySystem.has(systemKey)) {
462
+ pairsBySystem.set(systemKey, []);
463
+ }
464
+ pairsBySystem.get(systemKey).push(pair);
465
+ }
466
+ const allConversations = [];
467
+ for (const [, systemPairs] of pairsBySystem) {
468
+ const sortedPairs = [...systemPairs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
469
+ const conversationThreads = new Map();
470
+ for (const pair of sortedPairs) {
471
+ const messages = pair.request.messages || [];
472
+ if (messages.length === 0)
473
+ continue;
474
+ const firstUserMessage = messages[0];
475
+ const normalizedFirstMessage = this.normalizeMessageForGrouping(firstUserMessage);
476
+ const conversationKey = JSON.stringify({ firstMessage: normalizedFirstMessage });
477
+ const keyHash = this.hashString(conversationKey);
478
+ if (!conversationThreads.has(keyHash)) {
479
+ conversationThreads.set(keyHash, []);
480
+ }
481
+ conversationThreads.get(keyHash).push(pair);
482
+ }
483
+ for (const [conversationKey, threadPairs] of conversationThreads) {
484
+ const sortedThreadPairs = [...threadPairs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
485
+ // Longest message list ≈ latest turn in a multi-request thread
486
+ const finalPair = sortedThreadPairs.reduce((longest, current) => {
487
+ const currentMessages = current.request.messages || [];
488
+ const longestMessages = longest.request.messages || [];
489
+ return currentMessages.length > longestMessages.length ? current : longest;
490
+ });
491
+ const modelsUsed = new Set(sortedThreadPairs.map((pair) => pair.model));
492
+ const enhancedMessages = this.processToolResults(finalPair.request.messages || []);
493
+ const apiFormatDisplay = (0, api_format_1.formatApiFormatsDisplay)(sortedThreadPairs.map((pair) => pair.apiFormat || "unknown"));
494
+ const usage = aggregateUsageFromPairs(sortedThreadPairs);
495
+ const conversation = {
496
+ id: this.hashString(conversationKey),
497
+ models: modelsUsed,
498
+ system: finalPair.request.system,
499
+ messages: enhancedMessages,
500
+ response: finalPair.response,
501
+ allPairs: sortedThreadPairs,
502
+ finalPair: finalPair,
503
+ apiFormatDisplay,
504
+ metadata: {
505
+ startTime: sortedThreadPairs[0].timestamp,
506
+ endTime: finalPair.timestamp,
507
+ totalPairs: sortedThreadPairs.length,
508
+ ...usage,
509
+ },
510
+ };
511
+ allConversations.push(conversation);
512
+ }
513
+ }
514
+ const mergedConversations = this.detectAndMergeCompactConversations(allConversations);
515
+ const filteredConversations = options.includeShortConversations
516
+ ? mergedConversations
517
+ : mergedConversations.filter((conv) => conv.messages.length > 2);
518
+ return filteredConversations.sort((a, b) => new Date(a.metadata.startTime).getTime() - new Date(b.metadata.startTime).getTime());
519
+ }
520
+ /**
521
+ * Attaches tool_result blocks to their tool_use messages and hides
522
+ * user turns that contain only orphaned tool results.
523
+ */
524
+ processToolResults(messages) {
525
+ const enhancedMessages = [];
526
+ const pendingToolUses = {};
527
+ for (let i = 0; i < messages.length; i++) {
528
+ const message = messages[i];
529
+ const enhancedMessage = { ...message, toolResults: {}, hide: false };
530
+ if (Array.isArray(message.content)) {
531
+ let hasOnlyToolResults = true;
532
+ let hasTextContent = false;
533
+ for (let j = 0; j < message.content.length; j++) {
534
+ const block = message.content[j];
535
+ if (block.type === "tool_use" && "id" in block) {
536
+ const toolUse = block;
537
+ pendingToolUses[toolUse.id] = { messageIndex: i, toolIndex: j };
538
+ hasOnlyToolResults = false;
539
+ }
540
+ else if (block.type === "tool_result" && "tool_use_id" in block) {
541
+ const toolResult = block;
542
+ const toolUseId = toolResult.tool_use_id;
543
+ if (pendingToolUses[toolUseId]) {
544
+ const { messageIndex } = pendingToolUses[toolUseId];
545
+ if (!enhancedMessages[messageIndex]) {
546
+ enhancedMessages[messageIndex] = { ...messages[messageIndex], toolResults: {}, hide: false };
547
+ }
548
+ enhancedMessages[messageIndex].toolResults[toolUseId] = toolResult;
549
+ delete pendingToolUses[toolUseId];
550
+ }
551
+ }
552
+ else if (block.type === "text") {
553
+ hasTextContent = true;
554
+ hasOnlyToolResults = false;
555
+ }
556
+ else {
557
+ hasOnlyToolResults = false;
558
+ }
559
+ }
560
+ if (hasOnlyToolResults && !hasTextContent) {
561
+ enhancedMessage.hide = true;
562
+ }
563
+ }
564
+ enhancedMessages[i] = enhancedMessage;
565
+ }
566
+ return enhancedMessages;
567
+ }
568
+ /**
569
+ * Detects Claude Code compaction: a single-pair conversation whose message
570
+ * list is two longer than an earlier thread with matching tail messages.
571
+ */
572
+ detectAndMergeCompactConversations(conversations) {
573
+ if (conversations.length <= 1)
574
+ return conversations;
575
+ const sortedConversations = [...conversations].sort((a, b) => new Date(a.metadata.startTime).getTime() - new Date(b.metadata.startTime).getTime());
576
+ const usedConversations = new Set();
577
+ const mergedConversations = [];
578
+ for (let i = 0; i < sortedConversations.length; i++) {
579
+ const currentConv = sortedConversations[i];
580
+ if (usedConversations.has(i))
581
+ continue;
582
+ if (currentConv.allPairs.length === 1 && currentConv.messages.length > 2) {
583
+ let originalConv = null;
584
+ let originalIndex = -1;
585
+ for (let j = 0; j < sortedConversations.length; j++) {
586
+ if (j === i || usedConversations.has(j))
587
+ continue;
588
+ const otherConv = sortedConversations[j];
589
+ if (otherConv.messages.length === currentConv.messages.length - 2) {
590
+ let messagesMatch = true;
591
+ for (let k = 1; k < otherConv.messages.length; k++) {
592
+ if (!this.messagesRoughlyEqual(otherConv.messages[k], currentConv.messages[k])) {
593
+ messagesMatch = false;
594
+ break;
595
+ }
596
+ }
597
+ if (messagesMatch) {
598
+ originalConv = otherConv;
599
+ originalIndex = j;
600
+ break;
601
+ }
602
+ }
603
+ }
604
+ if (originalConv) {
605
+ const mergedConv = this.mergeCompactConversation(originalConv, currentConv);
606
+ mergedConversations.push(mergedConv);
607
+ usedConversations.add(i);
608
+ usedConversations.add(originalIndex);
609
+ }
610
+ else {
611
+ currentConv.compacted = true;
612
+ mergedConversations.push(currentConv);
613
+ usedConversations.add(i);
614
+ }
615
+ }
616
+ else {
617
+ mergedConversations.push(currentConv);
618
+ usedConversations.add(i);
619
+ }
620
+ }
621
+ for (let i = 0; i < sortedConversations.length; i++) {
622
+ if (!usedConversations.has(i)) {
623
+ mergedConversations.push(sortedConversations[i]);
624
+ }
625
+ }
626
+ return mergedConversations.sort((a, b) => new Date(a.metadata.startTime).getTime() - new Date(b.metadata.startTime).getTime());
627
+ }
628
+ /**
629
+ * Combines pre- and post-compaction threads, restoring the original first
630
+ * user message while keeping the compacted response and pair list.
631
+ */
632
+ mergeCompactConversation(originalConv, compactConv) {
633
+ const originalMessages = originalConv.messages || [];
634
+ const compactMessages = compactConv.messages || [];
635
+ const mergedMessages = [...compactMessages];
636
+ if (originalMessages.length > 0 && mergedMessages.length > 0) {
637
+ mergedMessages[0] = originalMessages[0];
638
+ }
639
+ const allPairs = [...originalConv.allPairs, ...compactConv.allPairs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
640
+ const allModels = new Set([...originalConv.models, ...compactConv.models]);
641
+ const startTime = allPairs[0].timestamp;
642
+ const endTime = allPairs[allPairs.length - 1].timestamp;
643
+ const apiFormatDisplay = (0, api_format_1.formatApiFormatsDisplay)(allPairs.map((pair) => pair.apiFormat || "unknown"));
644
+ const usage = aggregateUsageFromPairs(allPairs);
645
+ return {
646
+ id: compactConv.id,
647
+ models: allModels,
648
+ system: originalConv.system,
649
+ messages: mergedMessages,
650
+ response: compactConv.response,
651
+ allPairs: allPairs,
652
+ finalPair: compactConv.finalPair,
653
+ compacted: true,
654
+ apiFormatDisplay,
655
+ metadata: {
656
+ startTime: startTime,
657
+ endTime: endTime,
658
+ totalPairs: allPairs.length,
659
+ ...usage,
660
+ },
661
+ };
662
+ }
663
+ /**
664
+ * Loose equality check for compaction pairing — compares role and content shape only.
665
+ */
666
+ messagesRoughlyEqual(msg1, msg2) {
667
+ if (msg1.role !== msg2.role)
668
+ return false;
669
+ const content1 = msg1.content;
670
+ const content2 = msg2.content;
671
+ if (typeof content1 !== typeof content2)
672
+ return false;
673
+ if (Array.isArray(content1) !== Array.isArray(content2))
674
+ return false;
675
+ return true;
676
+ }
677
+ /**
678
+ * Strips volatile substrings from the first message so threading keys stay
679
+ * stable across retries (timestamps, IDE open events, system reminders).
680
+ */
681
+ normalizeMessageForGrouping(message) {
682
+ if (!message || !message.content)
683
+ return message;
684
+ let normalizedContent;
685
+ if (Array.isArray(message.content)) {
686
+ normalizedContent = message.content.map((block) => {
687
+ if (block.type === "text" && "text" in block) {
688
+ let text = block.text;
689
+ text = text.replace(/Generated \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/g, "Generated [TIMESTAMP]");
690
+ text = text.replace(/The user opened the file [^\s]+ in the IDE\./g, "The user opened file in IDE.");
691
+ text = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "[SYSTEM-REMINDER]");
692
+ text = text.replace(/<EXTREMELY_IMPORTANT>.*?<\/EXTREMELY_IMPORTANT>/gs, "[EXTREMELY-IMPORTANT]");
693
+ return { type: "text", text: text };
694
+ }
695
+ return block;
696
+ });
697
+ }
698
+ else {
699
+ normalizedContent = message.content;
700
+ }
701
+ return {
702
+ role: message.role,
703
+ content: normalizedContent,
704
+ };
705
+ }
706
+ /**
707
+ * Simple string hash for conversation grouping keys (not cryptographic).
708
+ */
709
+ hashString(str) {
710
+ let hash = 0;
711
+ for (let i = 0; i < str.length; i++) {
712
+ const char = str.charCodeAt(i);
713
+ hash = (hash << 5) - hash + char;
714
+ hash = hash & hash;
715
+ }
716
+ return Math.abs(hash).toString();
717
+ }
718
+ }
719
+ exports.SharedConversationProcessor = SharedConversationProcessor;
720
+ //# sourceMappingURL=shared-conversation-processor.js.map