MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / analyzeMessages

Function analyzeMessages

src/diagnostics/token-analyzer.ts:119–227  ·  view source on GitHub ↗
(
  sessionId: string,
  messages: Message[],
  opts: AnalyzeOptions,
)

Source from the content-addressed store, hash-verified

117
118/** Run the analysis. Pure function — no I/O. */
119export function analyzeMessages(
120 sessionId: string,
121 messages: Message[],
122 opts: AnalyzeOptions,
123): TokenReport {
124 const turns = groupIntoTurns(messages);
125 const breakdowns: TurnBreakdown[] = [];
126
127 // Per-tool aggregation
128 const toolStats = new Map<string, { calls: number; tokens: number }>();
129 // Per-file aggregation (best-effort: matches tool calls whose args contain a `path`/`file_path`)
130 const fileStats = new Map<string, { reads: number; tokens: number }>();
131
132 for (let i = 0; i < turns.length; i++) {
133 const turn = turns[i]!;
134 let userTok = 0;
135 let assistantTok = 0;
136 let resultTok = 0;
137
138 for (const m of turn) {
139 if (m.role === 'user') {
140 userTok += estimateTokens(m.content);
141 } else if (m.role === 'assistant') {
142 assistantTok += estimateTokens(m.content);
143 if (m.tool_calls) {
144 assistantTok += estimateTokensJson(m.tool_calls);
145 // Track per-tool / per-file usage from the EMITTED calls (results come in `tool` messages)
146 for (const tc of m.tool_calls) {
147 const toolName = tc.function?.name ?? '(unknown)';
148 const entry = toolStats.get(toolName) ?? { calls: 0, tokens: 0 };
149 entry.calls += 1;
150 toolStats.set(toolName, entry);
151 // Extract file path heuristically
152 try {
153 const args = JSON.parse(tc.function?.arguments ?? '{}');
154 const p = (args?.path ?? args?.file_path ?? args?.filename) as string | undefined;
155 if (typeof p === 'string' && p.length < 256) {
156 const e = fileStats.get(p) ?? { reads: 0, tokens: 0 };
157 e.reads += 1;
158 fileStats.set(p, e);
159 }
160 } catch (err) {
161 // Unparseable tool-call args mean this call's file/path attribution is
162 // skipped — log so the resulting undercount in diagnostics is traceable.
163 logger.debug('token-analyzer: failed to parse tool-call arguments', { tool: toolName, err });
164 }
165 }
166 }
167 } else if (m.role === 'tool') {
168 const tokens = estimateTokens(m.content);
169 resultTok += tokens;
170 // Attribute these tokens to the tool that produced them (lookup by tool_call_id is complex,
171 // but `name` is set on tool messages when the agent records them).
172 if ((m as any).name) {
173 const t = (m as any).name as string;
174 const entry = toolStats.get(t) ?? { calls: 0, tokens: 0 };
175 entry.tokens += tokens;
176 toolStats.set(t, entry);

Callers 3

index.tsFile · 0.85
handleSlashCommandFunction · 0.85

Calls 8

groupIntoTurnsFunction · 0.85
estimateTokensJsonFunction · 0.85
buildRecommendationsFunction · 0.85
debugMethod · 0.80
estimateTokensFunction · 0.70
getMethod · 0.45
setMethod · 0.45
pushMethod · 0.45

Tested by

no test coverage detected