sidekick-shared 0.27.6 → 0.28.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 (269) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +58 -18
  3. package/dist/browser.d.ts +13 -2
  4. package/dist/browser.js +30 -2
  5. package/dist/claudePaths.d.ts +11 -0
  6. package/dist/claudePaths.js +61 -0
  7. package/dist/claudeProfiles.d.ts +2 -9
  8. package/dist/claudeProfiles.js +10 -25
  9. package/dist/codexPaths.d.ts +22 -0
  10. package/dist/codexPaths.js +67 -0
  11. package/dist/codexProfiles.d.ts +1 -4
  12. package/dist/codexProfiles.js +5 -43
  13. package/dist/doctor.js +2 -2
  14. package/dist/esm/accountChangeSignal.js +15 -0
  15. package/dist/esm/accountChanges.js +152 -0
  16. package/dist/esm/accountHealth.js +288 -0
  17. package/dist/esm/accountLaunch.js +145 -0
  18. package/dist/esm/accountLease.js +60 -0
  19. package/dist/esm/accountManager.js +606 -0
  20. package/dist/esm/accountRegistry.js +214 -0
  21. package/dist/esm/accountStatus.js +51 -0
  22. package/dist/esm/accountSwitch.js +74 -0
  23. package/dist/esm/accountSync.js +156 -0
  24. package/dist/esm/accountSyncTypes.js +4 -0
  25. package/dist/esm/accounts.js +1291 -0
  26. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  27. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  28. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  29. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  30. package/dist/esm/aggregation/costProvenance.js +31 -0
  31. package/dist/esm/aggregation/snapshot.js +161 -0
  32. package/dist/esm/aggregation/types.js +5 -0
  33. package/dist/esm/analytics/codeImpact.js +18 -0
  34. package/dist/esm/analytics/compactionLedger.js +37 -0
  35. package/dist/esm/analytics/qualityScore.js +112 -0
  36. package/dist/esm/autoSwitch.js +185 -0
  37. package/dist/esm/browser.js +34 -0
  38. package/dist/esm/claudeCredentialDoctor.js +239 -0
  39. package/dist/esm/claudeCredentialLedger.js +91 -0
  40. package/dist/esm/claudeCredentials.js +111 -0
  41. package/dist/esm/claudeLiveGuard.js +43 -0
  42. package/dist/esm/claudePaths.js +22 -0
  43. package/dist/esm/claudeProfiles.js +142 -0
  44. package/dist/esm/claudeRefreshLock.js +106 -0
  45. package/dist/esm/codexAuth.js +101 -0
  46. package/dist/esm/codexCredentialDoctor.js +265 -0
  47. package/dist/esm/codexCredentialStore.js +164 -0
  48. package/dist/esm/codexKeepAlive.js +112 -0
  49. package/dist/esm/codexPaths.js +172 -0
  50. package/dist/esm/codexProfiles.js +1251 -0
  51. package/dist/esm/codexQuota.js +564 -0
  52. package/dist/esm/codexQuotaWatcher.js +363 -0
  53. package/dist/esm/context/composer.js +71 -0
  54. package/dist/esm/context/sessionContext.js +546 -0
  55. package/dist/esm/credentialIO.js +283 -0
  56. package/dist/esm/credentials.js +49 -0
  57. package/dist/esm/doctor.js +305 -0
  58. package/dist/esm/ensureDefaultAccounts.js +62 -0
  59. package/dist/esm/errorHistory.js +56 -0
  60. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  61. package/dist/esm/extractors/gatherAssets.js +32 -0
  62. package/dist/esm/extractors/sessionAssets.js +176 -0
  63. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  64. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  65. package/dist/esm/extractors/tokenUsage.js +41 -0
  66. package/dist/esm/extractors/toolCall.js +142 -0
  67. package/dist/esm/failingTools.js +65 -0
  68. package/dist/esm/fileHead.js +27 -0
  69. package/dist/esm/formatters/eventHighlighter.js +332 -0
  70. package/dist/esm/formatters/noiseClassifier.js +209 -0
  71. package/dist/esm/formatters/sessionDump.js +328 -0
  72. package/dist/esm/formatters/toolSummary.js +226 -0
  73. package/dist/esm/formatting.js +82 -0
  74. package/dist/esm/handoffUrl.js +23 -0
  75. package/dist/esm/historicalStore.js +305 -0
  76. package/dist/esm/humanPrompt.js +88 -0
  77. package/dist/esm/index.js +213 -0
  78. package/dist/esm/keepAlive.js +189 -0
  79. package/dist/esm/liveCredentialGuard.js +33 -0
  80. package/dist/esm/modelAliases.js +51 -0
  81. package/dist/esm/modelCatalog.js +139 -0
  82. package/dist/esm/modelContext.js +280 -0
  83. package/dist/esm/modelInfo.js +832 -0
  84. package/dist/esm/multiProviderQuotaService.js +450 -0
  85. package/dist/esm/node.js +46 -0
  86. package/dist/esm/observedContextWindows.js +165 -0
  87. package/dist/esm/observedSessionCollector.js +939 -0
  88. package/dist/esm/parsers/changelogParser.js +71 -0
  89. package/dist/esm/parsers/codexHistory.js +89 -0
  90. package/dist/esm/parsers/codexParser.js +906 -0
  91. package/dist/esm/parsers/debugLogParser.js +126 -0
  92. package/dist/esm/parsers/jsonl.js +66 -0
  93. package/dist/esm/parsers/mcpToolName.js +28 -0
  94. package/dist/esm/parsers/openCodeParser.js +625 -0
  95. package/dist/esm/parsers/planExtractor.js +335 -0
  96. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  97. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  98. package/dist/esm/parsers/subagentScanner.js +360 -0
  99. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  100. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  101. package/dist/esm/paths.js +134 -0
  102. package/dist/esm/peakHours.js +194 -0
  103. package/dist/esm/phrases.js +1330 -0
  104. package/dist/esm/pricingCatalog.js +247 -0
  105. package/dist/esm/processDetection.js +200 -0
  106. package/dist/esm/projectMigration.js +59 -0
  107. package/dist/esm/promptHistory.js +1221 -0
  108. package/dist/esm/providerFailure.js +362 -0
  109. package/dist/esm/providerIds.js +6 -0
  110. package/dist/esm/providerQuota.js +1 -0
  111. package/dist/esm/providerServiceStatus.js +145 -0
  112. package/dist/esm/providerServiceStatusTypes.js +1 -0
  113. package/dist/esm/providerStatus.js +117 -0
  114. package/dist/esm/providers/claudeCode.js +575 -0
  115. package/dist/esm/providers/claudeListing.js +121 -0
  116. package/dist/esm/providers/codex.js +1034 -0
  117. package/dist/esm/providers/codexDatabase.js +297 -0
  118. package/dist/esm/providers/codexRollouts.js +215 -0
  119. package/dist/esm/providers/detect.js +185 -0
  120. package/dist/esm/providers/diagnostics.js +83 -0
  121. package/dist/esm/providers/directoryListingCache.js +206 -0
  122. package/dist/esm/providers/factory.js +54 -0
  123. package/dist/esm/providers/openCode.js +2008 -0
  124. package/dist/esm/providers/openCodeDatabase.js +419 -0
  125. package/dist/esm/providers/rolloutWalker.js +252 -0
  126. package/dist/esm/providers/types.js +12 -0
  127. package/dist/esm/quota.js +182 -0
  128. package/dist/esm/quotaHistory.js +364 -0
  129. package/dist/esm/quotaPoller.js +230 -0
  130. package/dist/esm/quotaPresentation.js +97 -0
  131. package/dist/esm/quotaResolver.js +231 -0
  132. package/dist/esm/quotaSnapshots.js +130 -0
  133. package/dist/esm/quotaThresholds.js +85 -0
  134. package/dist/esm/readers/decisions.js +24 -0
  135. package/dist/esm/readers/handoff.js +20 -0
  136. package/dist/esm/readers/helpers.js +27 -0
  137. package/dist/esm/readers/history.js +9 -0
  138. package/dist/esm/readers/notes.js +48 -0
  139. package/dist/esm/readers/plans.js +225 -0
  140. package/dist/esm/readers/tasks.js +19 -0
  141. package/dist/esm/report/htmlHelpers.js +280 -0
  142. package/dist/esm/report/htmlReportGenerator.js +704 -0
  143. package/dist/esm/report/index.js +7 -0
  144. package/dist/esm/report/logo.js +2 -0
  145. package/dist/esm/report/openBrowser.js +19 -0
  146. package/dist/esm/report/sessionReportInputs.js +33 -0
  147. package/dist/esm/report/transcriptParser.js +83 -0
  148. package/dist/esm/report/types.js +4 -0
  149. package/dist/esm/schemas/accountManager.js +204 -0
  150. package/dist/esm/schemas/accountStatus.js +25 -0
  151. package/dist/esm/schemas/assistantTurn.js +74 -0
  152. package/dist/esm/schemas/index.js +25 -0
  153. package/dist/esm/schemas/observedSessionV1.js +77 -0
  154. package/dist/esm/schemas/providerFailure.js +37 -0
  155. package/dist/esm/schemas/quota.js +132 -0
  156. package/dist/esm/schemas/quotaHistory.js +40 -0
  157. package/dist/esm/schemas/sessionEvent.js +216 -0
  158. package/dist/esm/schemas/stateFile.js +75 -0
  159. package/dist/esm/schemas/tokenEstimation.js +7 -0
  160. package/dist/esm/schemas/transcript.js +86 -0
  161. package/dist/esm/schemas/usageNormalization.js +38 -0
  162. package/dist/esm/search/advancedFilter.js +197 -0
  163. package/dist/esm/search/sessionSearch.js +66 -0
  164. package/dist/esm/sessionFingerprint.js +40 -0
  165. package/dist/esm/sessionMonitor.js +198 -0
  166. package/dist/esm/sessionPreviews.js +522 -0
  167. package/dist/esm/sessionPromptHistory.js +254 -0
  168. package/dist/esm/sessionPromptSignals.js +364 -0
  169. package/dist/esm/sessionStats.js +160 -0
  170. package/dist/esm/sessionTokenTotals.js +98 -0
  171. package/dist/esm/sessionTranscripts.js +49 -0
  172. package/dist/esm/stateFile.js +119 -0
  173. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  174. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  175. package/dist/esm/statusline/formatter.js +122 -0
  176. package/dist/esm/statusline/index.js +8 -0
  177. package/dist/esm/terminalSync.js +95 -0
  178. package/dist/esm/testing/credentialGuard.js +197 -0
  179. package/dist/esm/testing/index.js +12 -0
  180. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  181. package/dist/esm/tokenEstimation.js +67 -0
  182. package/dist/esm/tokenSummary.js +113 -0
  183. package/dist/esm/toolCategory.js +82 -0
  184. package/dist/esm/transcript.js +257 -0
  185. package/dist/esm/turns/assistantTurn.js +373 -0
  186. package/dist/esm/types/codex.js +23 -0
  187. package/dist/esm/types/decisionLog.js +5 -0
  188. package/dist/esm/types/historicalData.js +47 -0
  189. package/dist/esm/types/knowledgeNote.js +15 -0
  190. package/dist/esm/types/observedSessionV1.js +257 -0
  191. package/dist/esm/types/opencode.js +12 -0
  192. package/dist/esm/types/plan.js +7 -0
  193. package/dist/esm/types/sessionEvent.js +10 -0
  194. package/dist/esm/types/taskPersistence.js +12 -0
  195. package/dist/esm/usage/billingBlocks.js +148 -0
  196. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  197. package/dist/esm/usage/importSessionHistory.js +84 -0
  198. package/dist/esm/usage/subagentUsage.js +18 -0
  199. package/dist/esm/usage/usageEvents.js +402 -0
  200. package/dist/esm/usage/usageReports.js +202 -0
  201. package/dist/esm/usageNormalization.js +95 -0
  202. package/dist/esm/watchers/eventBridge.js +219 -0
  203. package/dist/esm/watchers/factory.js +63 -0
  204. package/dist/esm/watchers/jsonlTail.js +289 -0
  205. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  206. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  207. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  208. package/dist/esm/watchers/types.js +4 -0
  209. package/dist/esm/workspaceRoots.js +56 -0
  210. package/dist/esm/writers/atomic.js +375 -0
  211. package/dist/esm/writers/decisions.js +51 -0
  212. package/dist/esm/writers/notes.js +46 -0
  213. package/dist/esm/writers/tasks.js +63 -0
  214. package/dist/esm/zaiQuota.js +323 -0
  215. package/dist/esm/zaiQuotaApi.js +271 -0
  216. package/dist/esm/zaiQuotaWatcher.js +265 -0
  217. package/dist/fileHead.d.ts +22 -0
  218. package/dist/fileHead.js +33 -0
  219. package/dist/index.d.ts +16 -7
  220. package/dist/index.js +49 -15
  221. package/dist/node.d.ts +13 -1
  222. package/dist/node.js +43 -2
  223. package/dist/parsers/codexHistory.js +2 -2
  224. package/dist/parsers/codexParser.d.ts +12 -0
  225. package/dist/parsers/codexParser.js +34 -12
  226. package/dist/parsers/debugLogParser.js +2 -2
  227. package/dist/parsers/sessionPathResolver.js +5 -4
  228. package/dist/promptHistory.d.ts +32 -0
  229. package/dist/promptHistory.js +115 -59
  230. package/dist/providerFailure.d.ts +12 -6
  231. package/dist/providerFailure.js +45 -0
  232. package/dist/providers/claudeCode.js +7 -99
  233. package/dist/providers/claudeListing.d.ts +22 -0
  234. package/dist/providers/claudeListing.js +158 -0
  235. package/dist/providers/codex.d.ts +3 -1
  236. package/dist/providers/codex.js +26 -227
  237. package/dist/providers/codexRollouts.d.ts +44 -0
  238. package/dist/providers/codexRollouts.js +261 -0
  239. package/dist/providers/detect.js +4 -4
  240. package/dist/readers/plans.js +3 -3
  241. package/dist/schemas/index.d.ts +2 -0
  242. package/dist/schemas/index.js +7 -2
  243. package/dist/schemas/providerFailure.d.ts +154 -0
  244. package/dist/schemas/providerFailure.js +40 -0
  245. package/dist/sessionPromptHistory.d.ts +12 -7
  246. package/dist/sessionPromptHistory.js +15 -3
  247. package/dist/sessionPromptSignals.d.ts +9 -0
  248. package/dist/sessionPromptSignals.js +13 -0
  249. package/dist/testing/credentialGuard.d.ts +23 -0
  250. package/dist/testing/credentialGuard.js +233 -0
  251. package/dist/testing/index.d.ts +14 -0
  252. package/dist/testing/index.js +22 -0
  253. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  254. package/dist/testing/sessionLogFixtures.js +169 -0
  255. package/dist/tokenEstimation.d.ts +2 -0
  256. package/dist/tokenEstimation.js +21 -11
  257. package/dist/toolCategory.d.ts +18 -0
  258. package/dist/toolCategory.js +86 -0
  259. package/dist/types/codex.d.ts +4 -0
  260. package/dist/usage/usageEvents.d.ts +16 -0
  261. package/dist/usage/usageEvents.js +53 -9
  262. package/dist/watchers/jsonlTail.d.ts +26 -1
  263. package/dist/watchers/jsonlTail.js +136 -28
  264. package/dist/watchers/jsonlWatcher.js +7 -0
  265. package/dist/watchers/providerReaderWatcher.js +1 -0
  266. package/dist/watchers/types.d.ts +6 -0
  267. package/dist/workspaceRoots.d.ts +23 -0
  268. package/dist/workspaceRoots.js +93 -0
  269. package/package.json +22 -2
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One vocabulary for token totals across every Sidekick surface.
3
+ *
4
+ * Every report, dashboard, status bar, and export derives its headline token
5
+ * numbers from here so "Total" means the same thing everywhere:
6
+ *
7
+ * - `total` — every token the provider billed: uncached input, cache writes,
8
+ * cache reads, and output. Matches `NormalizedUsage.totalTokens`
9
+ * and the per-model `tokens` the `EventAggregator` keeps.
10
+ * - `context` — tokens that occupied the context window on the last request:
11
+ * uncached input, cache writes, and cache reads. Output is
12
+ * excluded, matching the input-only formula Claude Code uses for
13
+ * its status-line `used_percentage`.
14
+ * - `output` — output tokens as billed.
15
+ *
16
+ * Cache reads are usually the majority of a coding-agent session's tokens, so
17
+ * an "input + output" total silently drops most of the work. Sidekick used to
18
+ * do that on some surfaces and not others; this helper is the single place the
19
+ * arithmetic lives.
20
+ *
21
+ * Browser-safe: no Node imports.
22
+ *
23
+ * @module tokenSummary
24
+ */
25
+ /** Column label for `TokenSummary.total` on any surface. */
26
+ export const TOKEN_TOTAL_LABEL = 'Total (incl. cache)';
27
+ /** Column label for `TokenSummary.context` on any surface. */
28
+ export const TOKEN_CONTEXT_LABEL = 'Context';
29
+ /** Label for a session's own (main-thread) tokens when subagents are shown beside it. */
30
+ export const TOKEN_MAIN_THREAD_LABEL = 'Main thread';
31
+ /** Label for the tokens a session's subagents used. */
32
+ export const TOKEN_SUBAGENTS_LABEL = 'Subagents';
33
+ /** Label for main thread plus subagents. */
34
+ export const TOKEN_SESSION_TOTAL_LABEL = 'Session total (incl. subagents)';
35
+ function count(value) {
36
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
37
+ }
38
+ /** Derive the shared token vocabulary from any four-bucket token record. */
39
+ export function summarizeTokens(totals) {
40
+ const input = count(totals.inputTokens);
41
+ const output = count(totals.outputTokens);
42
+ const cacheRead = count(totals.cacheReadTokens);
43
+ const cacheWrite = count(totals.cacheWriteTokens);
44
+ const context = input + cacheWrite + cacheRead;
45
+ const fourBucket = context + output;
46
+ const provided = totals.totalTokens;
47
+ const total = typeof provided === 'number' && Number.isFinite(provided) && provided >= 0
48
+ ? Math.floor(provided)
49
+ : fourBucket;
50
+ return {
51
+ total,
52
+ context,
53
+ input,
54
+ output,
55
+ cacheRead,
56
+ cacheWrite,
57
+ cacheHitRatio: context > 0 ? cacheRead / context : null,
58
+ reasoning: count(totals.reasoningTokens),
59
+ billedOutsideBuckets: Math.max(0, total - fourBucket),
60
+ };
61
+ }
62
+ /**
63
+ * One-line breakdown whose parts add up to the headline:
64
+ * `1.2M total incl. cache (12k in · 1.1M cache read · 40k cache write · 30k out)`.
65
+ * Pass the surface's own number formatter; the default prints plain integers.
66
+ */
67
+ export function formatTokenBreakdown(summary, fmt = (n) => String(n)) {
68
+ const parts = [
69
+ `${fmt(summary.input)} in`,
70
+ `${fmt(summary.cacheRead)} cache read`,
71
+ `${fmt(summary.cacheWrite)} cache write`,
72
+ `${fmt(summary.output)} out`,
73
+ ];
74
+ if (summary.billedOutsideBuckets > 0) {
75
+ parts.push(`${fmt(summary.billedOutsideBuckets)} reasoning`);
76
+ }
77
+ return `${fmt(summary.total)} total incl. cache (${parts.join(' · ')})`;
78
+ }
79
+ /**
80
+ * Sum several token records bucket-by-bucket before summarizing. `totalTokens`
81
+ * is summed only when every record carries one, so provider-aware totals
82
+ * survive the sum instead of falling back to the four-bucket arithmetic.
83
+ */
84
+ export function sumTokenTotals(records) {
85
+ let inputTokens = 0;
86
+ let outputTokens = 0;
87
+ let cacheWriteTokens = 0;
88
+ let cacheReadTokens = 0;
89
+ let reasoningTokens = 0;
90
+ let totalTokens = 0;
91
+ let allHaveTotal = records.length > 0;
92
+ for (const record of records) {
93
+ inputTokens += count(record.inputTokens);
94
+ outputTokens += count(record.outputTokens);
95
+ cacheWriteTokens += count(record.cacheWriteTokens);
96
+ cacheReadTokens += count(record.cacheReadTokens);
97
+ reasoningTokens += count(record.reasoningTokens);
98
+ if (typeof record.totalTokens === 'number' && Number.isFinite(record.totalTokens)) {
99
+ totalTokens += count(record.totalTokens);
100
+ }
101
+ else {
102
+ allHaveTotal = false;
103
+ }
104
+ }
105
+ return {
106
+ inputTokens,
107
+ outputTokens,
108
+ cacheWriteTokens,
109
+ cacheReadTokens,
110
+ reasoningTokens,
111
+ ...(allHaveTotal ? { totalTokens } : {}),
112
+ };
113
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * One vocabulary for what a tool call does, across Claude Code, Codex, and
3
+ * OpenCode tool names. Browser-safe: no Node imports.
4
+ *
5
+ * @module toolCategory
6
+ */
7
+ import { parseMcpToolName } from './parsers/mcpToolName';
8
+ export const TOOL_CATEGORIES = [
9
+ 'shell',
10
+ 'file_read',
11
+ 'file_edit',
12
+ 'search',
13
+ 'web',
14
+ 'subagent',
15
+ 'planning',
16
+ 'mcp',
17
+ 'other',
18
+ ];
19
+ /** Lower-cased tool names of every provider, by category. */
20
+ const CATEGORY_BY_NAME = new Map([
21
+ [
22
+ 'shell',
23
+ [
24
+ 'bash',
25
+ 'bashoutput',
26
+ 'killbash',
27
+ 'killshell',
28
+ 'exec_command',
29
+ 'shell',
30
+ 'local_shell',
31
+ 'local_shell_call',
32
+ 'container.exec',
33
+ 'write_stdin',
34
+ ],
35
+ ],
36
+ ['file_read', ['read', 'notebookread', 'ls', 'list', 'view_image']],
37
+ [
38
+ 'file_edit',
39
+ ['edit', 'multiedit', 'write', 'notebookedit', 'apply_patch', 'patch', 'patch_applied'],
40
+ ],
41
+ ['search', ['grep', 'glob', 'codesearch', 'toolsearch']],
42
+ ['web', ['webfetch', 'websearch', 'web_search', 'web_search_call', 'fetch']],
43
+ [
44
+ 'subagent',
45
+ [
46
+ 'task',
47
+ 'agent',
48
+ 'spawn_agent',
49
+ 'wait_agent',
50
+ 'send_message',
51
+ 'followup_task',
52
+ 'list_agents',
53
+ 'wait',
54
+ ],
55
+ ],
56
+ [
57
+ 'planning',
58
+ [
59
+ 'todowrite',
60
+ 'todoread',
61
+ 'update_plan',
62
+ 'updateplan',
63
+ 'enterplanmode',
64
+ 'exitplanmode',
65
+ 'taskcreate',
66
+ 'taskupdate',
67
+ 'tasklist',
68
+ 'askuserquestion',
69
+ 'request_user_input',
70
+ ],
71
+ ],
72
+ ].flatMap(([category, names]) => names.map((name) => [name, category])));
73
+ /**
74
+ * Classifies a tool name from any supported provider. `mcp__<server>__<tool>`
75
+ * names and calls with a known `mcpServer` are `mcp`; unknown names are
76
+ * `other`.
77
+ */
78
+ export function classifyTool(name, options = {}) {
79
+ if (options.mcpServer || parseMcpToolName(name))
80
+ return 'mcp';
81
+ return CATEGORY_BY_NAME.get(name.trim().toLowerCase()) ?? 'other';
82
+ }
@@ -0,0 +1,257 @@
1
+ import { calculateNormalizedUsageCost, extractNormalizedUsage, } from './usageNormalization';
2
+ const EMPTY_TOTALS = {
3
+ uncachedInputTokens: 0,
4
+ cacheReadTokens: 0,
5
+ cacheWriteTokens: 0,
6
+ outputTokens: 0,
7
+ reasoningTokens: 0,
8
+ cacheInclusiveInputTokens: 0,
9
+ billableOutputTokens: 0,
10
+ totalTokens: 0,
11
+ };
12
+ /** Project canonical provider events into a bounded or full-fidelity transcript. */
13
+ export function projectSessionTranscript(events, options = {}) {
14
+ const fidelity = options.fidelity ?? 'snippet';
15
+ const requestedMax = options.maxContentChars ?? 2_000;
16
+ const maxContentChars = Number.isFinite(requestedMax) ? Math.max(0, requestedMax) : 2_000;
17
+ const messages = [];
18
+ const tools = [];
19
+ const commands = [];
20
+ const models = new Set();
21
+ const usageEvents = [];
22
+ const usageCosts = [];
23
+ const totals = { ...EMPTY_TOTALS };
24
+ const toolsById = new Map();
25
+ let cwd;
26
+ let gitBranch;
27
+ events.forEach((event, eventIndex) => {
28
+ if (nonEmpty(event.cwd))
29
+ cwd = event.cwd;
30
+ if (nonEmpty(event.gitBranch))
31
+ gitBranch = event.gitBranch;
32
+ const message = event.message;
33
+ const source = {
34
+ provider: options.provider ?? event.providerMetadata?.providerId,
35
+ sessionId: options.sessionId,
36
+ sourcePath: options.sourcePath,
37
+ source: event.providerMetadata?.source,
38
+ entrypoint: event.entrypoint,
39
+ isMeta: event.isMeta,
40
+ isSidechain: event.isSidechain,
41
+ ...(event.isCompactSummary !== undefined ? { isCompactSummary: event.isCompactSummary } : {}),
42
+ ...(event.origin?.kind !== undefined ? { originKind: event.origin.kind } : {}),
43
+ ...(event.promptSource !== undefined ? { promptSource: event.promptSource } : {}),
44
+ originalRole: message?.role,
45
+ eventIndex,
46
+ eventType: event.type,
47
+ eventId: message?.id,
48
+ };
49
+ const usage = extractNormalizedUsage(event) ?? undefined;
50
+ if (usage) {
51
+ usageEvents.push(usage);
52
+ addUsage(totals, usage);
53
+ }
54
+ const cost = usage
55
+ ? calculateNormalizedUsageCost({ usage, modelId: message?.model })
56
+ : undefined;
57
+ if (cost)
58
+ usageCosts.push(cost);
59
+ if (message?.model)
60
+ models.add(message.model);
61
+ const blocks = contentBlocks(message?.content, fidelity, maxContentChars, options.includeUnknownBlocks);
62
+ if (event.type === 'summary' && event.summary && blocks.length === 0) {
63
+ blocks.push({ type: 'text', ...boundedText(event.summary, fidelity, maxContentChars) });
64
+ }
65
+ if (event.type === 'tool_use') {
66
+ blocks.push({
67
+ type: 'tool_use',
68
+ toolName: event.tool.name,
69
+ input: boundedValue(event.tool.input, fidelity, maxContentChars),
70
+ });
71
+ }
72
+ if (event.type === 'tool_result') {
73
+ blocks.push({
74
+ type: 'tool_result',
75
+ toolUseId: event.result.tool_use_id,
76
+ output: boundedValue(event.result.output, fidelity, maxContentChars),
77
+ isError: event.result.is_error,
78
+ });
79
+ }
80
+ const messageTools = [];
81
+ for (const block of blocks) {
82
+ if (block.type === 'tool_use' && block.toolName) {
83
+ const command = block.command ?? commandFromTool(block.toolName, block.input);
84
+ const call = {
85
+ id: block.toolUseId,
86
+ name: block.toolName,
87
+ timestamp: event.timestamp,
88
+ input: block.input,
89
+ command,
90
+ source,
91
+ };
92
+ tools.push(call);
93
+ messageTools.push(call);
94
+ if (call.id)
95
+ toolsById.set(call.id, call);
96
+ if (command)
97
+ commands.push(command);
98
+ }
99
+ else if (block.type === 'tool_result' && block.toolUseId) {
100
+ const call = toolsById.get(block.toolUseId);
101
+ if (call) {
102
+ call.output = block.output;
103
+ call.isError = block.isError;
104
+ }
105
+ }
106
+ }
107
+ if (blocks.length === 0 && !usage && event.type !== 'summary')
108
+ return;
109
+ messages.push({
110
+ id: message?.id,
111
+ role: roleForEvent(event),
112
+ timestamp: event.timestamp,
113
+ model: message?.model,
114
+ sourceLabel: message?.sourceLabel,
115
+ content: blocks,
116
+ text: blocks
117
+ .filter((block) => block.type === 'text' || block.type === 'thinking')
118
+ .map((block) => block.text ?? '')
119
+ .filter(Boolean)
120
+ .join('\n'),
121
+ usage,
122
+ cost,
123
+ tools: messageTools,
124
+ commands: messageTools.flatMap((tool) => (tool.command ? [tool.command] : [])),
125
+ source,
126
+ });
127
+ });
128
+ return {
129
+ provider: options.provider,
130
+ sessionId: options.sessionId,
131
+ sourcePath: options.sourcePath,
132
+ cwd,
133
+ gitBranch,
134
+ startedAt: events[0]?.timestamp,
135
+ endedAt: events.at(-1)?.timestamp,
136
+ models: [...models],
137
+ messages,
138
+ tools,
139
+ commands: [...new Set(commands)],
140
+ usage: {
141
+ events: usageEvents,
142
+ costs: usageCosts,
143
+ totals,
144
+ totalCostUsd: usageCosts.length > 0 && usageCosts.every((cost) => cost.costUsd !== null)
145
+ ? usageCosts.reduce((sum, cost) => sum + (cost.costUsd ?? 0), 0)
146
+ : null,
147
+ pricedCostUsd: usageCosts.reduce((sum, cost) => sum + (cost.costUsd ?? 0), 0),
148
+ unpricedEvents: usageCosts.filter((cost) => cost.costUsd === null).length,
149
+ },
150
+ provenance: { source: 'session-events', fidelity, eventCount: events.length },
151
+ };
152
+ }
153
+ function nonEmpty(value) {
154
+ return typeof value === 'string' && value.trim().length > 0;
155
+ }
156
+ function roleForEvent(event) {
157
+ if (event.type === 'summary')
158
+ return 'summary';
159
+ if (event.type === 'system')
160
+ return 'system';
161
+ if (event.type === 'tool_use' || event.type === 'tool_result')
162
+ return 'tool';
163
+ return event.type;
164
+ }
165
+ function contentBlocks(content, fidelity, maxChars, includeUnknown = false) {
166
+ if (typeof content === 'string')
167
+ return [{ type: 'text', ...boundedText(content, fidelity, maxChars) }];
168
+ if (!Array.isArray(content))
169
+ return [];
170
+ const result = [];
171
+ for (const raw of content) {
172
+ if (!raw || typeof raw !== 'object')
173
+ continue;
174
+ const block = raw;
175
+ if (block.type === 'text' && typeof block.text === 'string') {
176
+ result.push({ type: 'text', ...boundedText(block.text, fidelity, maxChars) });
177
+ }
178
+ else if ((block.type === 'thinking' || block.type === 'reasoning') &&
179
+ (typeof block.thinking === 'string' || typeof block.text === 'string')) {
180
+ result.push({
181
+ type: 'thinking',
182
+ ...boundedText(String(block.thinking ?? block.text), fidelity, maxChars),
183
+ });
184
+ }
185
+ else if (block.type === 'tool_use' && typeof block.name === 'string') {
186
+ result.push({
187
+ type: 'tool_use',
188
+ toolName: block.name,
189
+ toolUseId: typeof block.id === 'string' ? block.id : undefined,
190
+ input: boundedValue(block.input, fidelity, maxChars),
191
+ command: commandFromTool(block.name, block.input),
192
+ });
193
+ }
194
+ else if (block.type === 'tool_result') {
195
+ result.push({
196
+ type: 'tool_result',
197
+ toolUseId: typeof block.tool_use_id === 'string' ? block.tool_use_id : undefined,
198
+ output: boundedValue(block.content, fidelity, maxChars),
199
+ isError: block.is_error === true,
200
+ });
201
+ }
202
+ else if (block.type === 'image') {
203
+ result.push({
204
+ type: 'image',
205
+ text: '[Image content]',
206
+ ...(fidelity === 'full' ? { raw } : {}),
207
+ });
208
+ }
209
+ else if (includeUnknown) {
210
+ result.push({ type: 'unknown', raw: boundedValue(raw, fidelity, maxChars) });
211
+ }
212
+ }
213
+ return result;
214
+ }
215
+ function boundedText(text, fidelity, maxChars) {
216
+ if (fidelity === 'full' || text.length <= maxChars)
217
+ return { text, truncated: false };
218
+ return { text: text.slice(0, maxChars), truncated: true };
219
+ }
220
+ function boundedValue(value, fidelity, maxChars) {
221
+ if (fidelity === 'full' || value === undefined)
222
+ return value;
223
+ if (typeof value === 'string')
224
+ return boundedText(value, fidelity, maxChars).text;
225
+ try {
226
+ const json = JSON.stringify(value);
227
+ return json.length <= maxChars ? value : json.slice(0, maxChars);
228
+ }
229
+ catch {
230
+ return String(value).slice(0, maxChars);
231
+ }
232
+ }
233
+ function commandFromTool(name, input) {
234
+ if (!input || typeof input !== 'object')
235
+ return undefined;
236
+ const value = input;
237
+ if (!['bash', 'shell', 'execcommand', 'exec_command', 'local_shell'].includes(name.toLowerCase())) {
238
+ return undefined;
239
+ }
240
+ if (typeof value.command === 'string')
241
+ return value.command;
242
+ if (Array.isArray(value.command))
243
+ return value.command.map(String).join(' ');
244
+ if (typeof value.cmd === 'string')
245
+ return value.cmd;
246
+ return undefined;
247
+ }
248
+ function addUsage(total, usage) {
249
+ total.uncachedInputTokens += usage.uncachedInputTokens;
250
+ total.cacheReadTokens += usage.cacheReadTokens;
251
+ total.cacheWriteTokens += usage.cacheWriteTokens;
252
+ total.outputTokens += usage.outputTokens;
253
+ total.reasoningTokens += usage.reasoningTokens;
254
+ total.cacheInclusiveInputTokens += usage.cacheInclusiveInputTokens;
255
+ total.billableOutputTokens += usage.billableOutputTokens;
256
+ total.totalTokens += usage.totalTokens;
257
+ }