@remixhq/claude-plugin 0.1.19 → 0.1.21
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.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/remix-collab.md +10 -10
- package/dist/hook-post-collab.cjs +3 -7
- package/dist/hook-post-collab.cjs.map +1 -1
- package/dist/hook-pre-git.cjs +6 -6
- package/dist/hook-pre-git.cjs.map +1 -1
- package/dist/hook-stop-collab.cjs +34 -40
- package/dist/hook-stop-collab.cjs.map +1 -1
- package/dist/hook-user-prompt.cjs +9 -13
- package/dist/hook-user-prompt.cjs.map +1 -1
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +17 -352
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +3 -7
- package/skills/review-merge-request/SKILL.md +2 -2
- package/skills/safe-collab-workflow/SKILL.md +13 -15
- package/skills/submit-change-step/SKILL.md +25 -54
- package/skills/sync-and-reconcile/SKILL.md +2 -2
- package/dist/historical.d.ts +0 -31
- package/dist/historical.js +0 -443
- package/dist/historical.js.map +0 -1
package/dist/historical.js
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/usage/claudeCodeTranscript.ts
|
|
4
|
-
import fs from "fs/promises";
|
|
5
|
-
async function readAndParseTranscript(transcriptPath) {
|
|
6
|
-
let raw;
|
|
7
|
-
try {
|
|
8
|
-
raw = await fs.readFile(transcriptPath, "utf8");
|
|
9
|
-
} catch (err) {
|
|
10
|
-
const code = err && typeof err === "object" && "code" in err ? err.code : null;
|
|
11
|
-
if (code === "ENOENT") {
|
|
12
|
-
return { ok: false, reason: "transcript_not_found" };
|
|
13
|
-
}
|
|
14
|
-
return { ok: false, reason: "transcript_unreadable" };
|
|
15
|
-
}
|
|
16
|
-
const events = [];
|
|
17
|
-
for (const line of raw.split("\n")) {
|
|
18
|
-
const trimmed = line.trim();
|
|
19
|
-
if (!trimmed) continue;
|
|
20
|
-
try {
|
|
21
|
-
const parsed = JSON.parse(trimmed);
|
|
22
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
23
|
-
events.push(parsed);
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return { ok: true, events };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/usage/claudeCodeUsageHarvester.ts
|
|
32
|
-
function parseTimestamp(value) {
|
|
33
|
-
if (typeof value !== "string") return null;
|
|
34
|
-
const ms = Date.parse(value);
|
|
35
|
-
return Number.isFinite(ms) ? ms : null;
|
|
36
|
-
}
|
|
37
|
-
function extractUserContentText(message) {
|
|
38
|
-
if (!message || typeof message !== "object") return null;
|
|
39
|
-
const content = message.content;
|
|
40
|
-
if (typeof content === "string") return content;
|
|
41
|
-
if (Array.isArray(content)) {
|
|
42
|
-
const textBlocks = content.filter((block) => Boolean(block) && typeof block === "object").filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text);
|
|
43
|
-
if (textBlocks.length === 0) return null;
|
|
44
|
-
return textBlocks.join("\n");
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
function isUserBoundary(event) {
|
|
49
|
-
return event.type === "user" && event.isMeta !== true && event.isSidechain !== true;
|
|
50
|
-
}
|
|
51
|
-
function extractAssistantContentText(turnEvents) {
|
|
52
|
-
const chunks = [];
|
|
53
|
-
for (const ev of turnEvents) {
|
|
54
|
-
if (ev.type !== "assistant") continue;
|
|
55
|
-
const text = extractUserContentText(ev.message);
|
|
56
|
-
if (text && text.length > 0) chunks.push(text);
|
|
57
|
-
}
|
|
58
|
-
if (chunks.length === 0) return null;
|
|
59
|
-
const deduped = [];
|
|
60
|
-
for (const chunk of chunks) {
|
|
61
|
-
if (deduped[deduped.length - 1] !== chunk) deduped.push(chunk);
|
|
62
|
-
}
|
|
63
|
-
return deduped.join("\n");
|
|
64
|
-
}
|
|
65
|
-
function asNumberOrNull(value) {
|
|
66
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
67
|
-
}
|
|
68
|
-
function asStringOrNull(value) {
|
|
69
|
-
return typeof value === "string" ? value : null;
|
|
70
|
-
}
|
|
71
|
-
function extractAssistantMessage(event) {
|
|
72
|
-
const message = event.message;
|
|
73
|
-
if (!message || typeof message !== "object") return null;
|
|
74
|
-
const msg = message;
|
|
75
|
-
if (msg.role !== "assistant" && msg.role !== void 0) {
|
|
76
|
-
}
|
|
77
|
-
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
78
|
-
const usage = msg.usage && typeof msg.usage === "object" ? msg.usage : null;
|
|
79
|
-
return {
|
|
80
|
-
id: asStringOrNull(msg.id),
|
|
81
|
-
model: asStringOrNull(msg.model),
|
|
82
|
-
content,
|
|
83
|
-
usage
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function usageIsComplete(usage) {
|
|
87
|
-
if (!usage) return false;
|
|
88
|
-
const hasInput = typeof usage.input_tokens === "number";
|
|
89
|
-
const hasOutput = typeof usage.output_tokens === "number";
|
|
90
|
-
const hasCacheRead = typeof usage.cache_read_input_tokens === "number";
|
|
91
|
-
return hasInput && hasOutput && hasCacheRead;
|
|
92
|
-
}
|
|
93
|
-
function buildModelCall(event, msg) {
|
|
94
|
-
const usage = msg.usage ?? {};
|
|
95
|
-
const cacheCreation = usage.cache_creation && typeof usage.cache_creation === "object" ? usage.cache_creation : null;
|
|
96
|
-
const has5m = cacheCreation && typeof cacheCreation.ephemeral_5m_input_tokens === "number";
|
|
97
|
-
const has1h = cacheCreation && typeof cacheCreation.ephemeral_1h_input_tokens === "number";
|
|
98
|
-
const cacheWrite5mTokens = has5m ? cacheCreation.ephemeral_5m_input_tokens : null;
|
|
99
|
-
const cacheWrite1hTokens = has1h ? cacheCreation.ephemeral_1h_input_tokens : null;
|
|
100
|
-
const splitAvailable = has5m || has1h;
|
|
101
|
-
const cacheWriteTokens = splitAvailable ? null : asNumberOrNull(usage.cache_creation_input_tokens);
|
|
102
|
-
return {
|
|
103
|
-
provider: "anthropic",
|
|
104
|
-
model: msg.model,
|
|
105
|
-
tier: asStringOrNull(usage.service_tier),
|
|
106
|
-
requestId: asStringOrNull(event.requestId),
|
|
107
|
-
timestamp: asStringOrNull(event.timestamp),
|
|
108
|
-
isSidechain: event.isSidechain === true,
|
|
109
|
-
inputTokens: asNumberOrNull(usage.input_tokens),
|
|
110
|
-
outputTokens: asNumberOrNull(usage.output_tokens),
|
|
111
|
-
cacheReadTokens: asNumberOrNull(usage.cache_read_input_tokens),
|
|
112
|
-
cacheWriteTokens,
|
|
113
|
-
cacheWrite5mTokens,
|
|
114
|
-
cacheWrite1hTokens,
|
|
115
|
-
reasoningTokens: null,
|
|
116
|
-
audioInputTokens: null,
|
|
117
|
-
imageInputTokens: null
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function scanServerToolUses(content) {
|
|
121
|
-
const uses = [];
|
|
122
|
-
for (const block of content) {
|
|
123
|
-
if (!block || typeof block !== "object") continue;
|
|
124
|
-
const b = block;
|
|
125
|
-
const id = b.id;
|
|
126
|
-
if (typeof id !== "string" || !id.startsWith("srvtoolu_")) continue;
|
|
127
|
-
const name = typeof b.name === "string" ? b.name : "";
|
|
128
|
-
switch (name) {
|
|
129
|
-
case "web_search":
|
|
130
|
-
uses.push({ tool: "web_search", unit: "per_request", isKnown: true, id, source: "direct" });
|
|
131
|
-
break;
|
|
132
|
-
case "web_fetch":
|
|
133
|
-
uses.push({ tool: "web_fetch", unit: "per_request", isKnown: true, id, source: "direct" });
|
|
134
|
-
break;
|
|
135
|
-
case "code_execution":
|
|
136
|
-
uses.push({ tool: "code_execution", unit: "invocation", isKnown: true, id, source: "direct" });
|
|
137
|
-
break;
|
|
138
|
-
default:
|
|
139
|
-
uses.push({ tool: name || "unknown", unit: "invocation", isKnown: false, id, source: "direct" });
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return uses;
|
|
144
|
-
}
|
|
145
|
-
function buildClientToolNameMap(turnEvents) {
|
|
146
|
-
const map = /* @__PURE__ */ new Map();
|
|
147
|
-
for (const ev of turnEvents) {
|
|
148
|
-
if (ev.type !== "assistant") continue;
|
|
149
|
-
const msg = ev.message;
|
|
150
|
-
if (!msg || typeof msg !== "object") continue;
|
|
151
|
-
const content = msg.content;
|
|
152
|
-
if (!Array.isArray(content)) continue;
|
|
153
|
-
for (const block of content) {
|
|
154
|
-
if (!block || typeof block !== "object") continue;
|
|
155
|
-
const b = block;
|
|
156
|
-
if (b.type !== "tool_use") continue;
|
|
157
|
-
const id = b.id;
|
|
158
|
-
const name = b.name;
|
|
159
|
-
if (typeof id !== "string" || typeof name !== "string") continue;
|
|
160
|
-
if (!id.startsWith("toolu_")) continue;
|
|
161
|
-
map.set(id, name);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return map;
|
|
165
|
-
}
|
|
166
|
-
function scanEmbeddedServerToolUses(turnEvents, clientToolNames) {
|
|
167
|
-
const uses = [];
|
|
168
|
-
for (const ev of turnEvents) {
|
|
169
|
-
if (ev.type !== "user") continue;
|
|
170
|
-
const tur = ev.toolUseResult;
|
|
171
|
-
if (!tur || typeof tur !== "object") continue;
|
|
172
|
-
const results = tur.results;
|
|
173
|
-
if (!Array.isArray(results)) continue;
|
|
174
|
-
let parentToolName = "";
|
|
175
|
-
const userMsg = ev.message;
|
|
176
|
-
if (userMsg && typeof userMsg === "object") {
|
|
177
|
-
const userContent = userMsg.content;
|
|
178
|
-
if (Array.isArray(userContent)) {
|
|
179
|
-
for (const block of userContent) {
|
|
180
|
-
if (!block || typeof block !== "object") continue;
|
|
181
|
-
const b = block;
|
|
182
|
-
if (b.type !== "tool_result") continue;
|
|
183
|
-
const parentId = b.tool_use_id;
|
|
184
|
-
if (typeof parentId === "string") {
|
|
185
|
-
parentToolName = clientToolNames.get(parentId) ?? "";
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
for (const entry of results) {
|
|
192
|
-
if (!entry || typeof entry !== "object") continue;
|
|
193
|
-
const srvId = entry.tool_use_id;
|
|
194
|
-
if (typeof srvId !== "string" || !srvId.startsWith("srvtoolu_")) continue;
|
|
195
|
-
if (parentToolName === "WebFetch") {
|
|
196
|
-
uses.push({ tool: "web_fetch", unit: "per_request", isKnown: true, id: srvId, source: "embedded" });
|
|
197
|
-
} else {
|
|
198
|
-
uses.push({ tool: "web_search", unit: "per_request", isKnown: true, id: srvId, source: "embedded" });
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return uses;
|
|
203
|
-
}
|
|
204
|
-
function dedupeByServerToolId(records) {
|
|
205
|
-
const seen = /* @__PURE__ */ new Map();
|
|
206
|
-
for (const r of records) {
|
|
207
|
-
const existing = seen.get(r.id);
|
|
208
|
-
if (!existing) {
|
|
209
|
-
seen.set(r.id, r);
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (existing.source === "embedded" && r.source === "direct") {
|
|
213
|
-
seen.set(r.id, r);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return Array.from(seen.values());
|
|
217
|
-
}
|
|
218
|
-
function aggregateServerTools(uses) {
|
|
219
|
-
const map = /* @__PURE__ */ new Map();
|
|
220
|
-
let sawUnknown = false;
|
|
221
|
-
let sawEmbedded = false;
|
|
222
|
-
for (const use of uses) {
|
|
223
|
-
if (!use.isKnown) sawUnknown = true;
|
|
224
|
-
if (use.source === "embedded") sawEmbedded = true;
|
|
225
|
-
const key = `anthropic|${use.tool}|${use.unit}`;
|
|
226
|
-
const existing = map.get(key);
|
|
227
|
-
if (existing) {
|
|
228
|
-
existing.quantity += 1;
|
|
229
|
-
} else {
|
|
230
|
-
map.set(key, { provider: "anthropic", tool: use.tool, unit: use.unit, quantity: 1 });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return { serverTools: Array.from(map.values()), sawUnknown, sawEmbedded };
|
|
234
|
-
}
|
|
235
|
-
function sumCrossCheckCounts(usageBlocks) {
|
|
236
|
-
const totals = /* @__PURE__ */ new Map();
|
|
237
|
-
const keyFor = (raw) => {
|
|
238
|
-
if (raw === "web_search_requests") return "web_search";
|
|
239
|
-
if (raw === "web_fetch_requests") return "web_fetch";
|
|
240
|
-
return null;
|
|
241
|
-
};
|
|
242
|
-
for (const usage of usageBlocks) {
|
|
243
|
-
const stu = usage.server_tool_use;
|
|
244
|
-
if (!stu || typeof stu !== "object") continue;
|
|
245
|
-
for (const [rawKey, rawVal] of Object.entries(stu)) {
|
|
246
|
-
const mapped = keyFor(rawKey);
|
|
247
|
-
if (!mapped) continue;
|
|
248
|
-
if (typeof rawVal !== "number" || !Number.isFinite(rawVal)) continue;
|
|
249
|
-
totals.set(mapped, (totals.get(mapped) ?? 0) + rawVal);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return totals;
|
|
253
|
-
}
|
|
254
|
-
function primaryToolCounts(serverTools) {
|
|
255
|
-
const map = /* @__PURE__ */ new Map();
|
|
256
|
-
for (const entry of serverTools) {
|
|
257
|
-
map.set(entry.tool, (map.get(entry.tool) ?? 0) + entry.quantity);
|
|
258
|
-
}
|
|
259
|
-
return map;
|
|
260
|
-
}
|
|
261
|
-
function resolveVersion(events) {
|
|
262
|
-
for (const ev of events) {
|
|
263
|
-
if (typeof ev.version === "string" && ev.version.trim()) return ev.version.trim();
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
function buildTurnUsage(args) {
|
|
268
|
-
const assistantEvents = args.turnEvents.filter((ev) => ev.type === "assistant");
|
|
269
|
-
if (assistantEvents.length === 0) {
|
|
270
|
-
return { ok: false, reason: "no_messages_for_turn" };
|
|
271
|
-
}
|
|
272
|
-
const warnings = [...args.initialWarnings];
|
|
273
|
-
const calls = [];
|
|
274
|
-
const usageBlocks = [];
|
|
275
|
-
let anyIncomplete = false;
|
|
276
|
-
let anyComplete = false;
|
|
277
|
-
let sawLumpSumFallback = false;
|
|
278
|
-
const collectedServerToolUses = [];
|
|
279
|
-
const mainModels = /* @__PURE__ */ new Set();
|
|
280
|
-
const sidechainModels = /* @__PURE__ */ new Set();
|
|
281
|
-
for (const ev of assistantEvents) {
|
|
282
|
-
const msg = extractAssistantMessage(ev);
|
|
283
|
-
if (!msg) continue;
|
|
284
|
-
if (usageIsComplete(msg.usage)) {
|
|
285
|
-
anyComplete = true;
|
|
286
|
-
} else {
|
|
287
|
-
anyIncomplete = true;
|
|
288
|
-
}
|
|
289
|
-
if (msg.usage) usageBlocks.push(msg.usage);
|
|
290
|
-
const call = buildModelCall(ev, msg);
|
|
291
|
-
calls.push(call);
|
|
292
|
-
if (call.cacheWrite5mTokens === null && call.cacheWrite1hTokens === null && call.cacheWriteTokens !== null) {
|
|
293
|
-
sawLumpSumFallback = true;
|
|
294
|
-
}
|
|
295
|
-
collectedServerToolUses.push(...scanServerToolUses(msg.content));
|
|
296
|
-
if (call.model) {
|
|
297
|
-
if (call.isSidechain) sidechainModels.add(call.model);
|
|
298
|
-
else mainModels.add(call.model);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (calls.length === 0) {
|
|
302
|
-
return { ok: false, reason: "no_messages_for_turn" };
|
|
303
|
-
}
|
|
304
|
-
if (sawLumpSumFallback) {
|
|
305
|
-
warnings.push({
|
|
306
|
-
code: "cache_split_unavailable",
|
|
307
|
-
message: "Assistant message reported lump-sum cache_creation_input_tokens without the 5m/1h split."
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
const clientToolNames = buildClientToolNameMap(args.turnEvents);
|
|
311
|
-
const embeddedUses = scanEmbeddedServerToolUses(args.turnEvents, clientToolNames);
|
|
312
|
-
const merged = dedupeByServerToolId([...collectedServerToolUses, ...embeddedUses]);
|
|
313
|
-
const { serverTools, sawUnknown, sawEmbedded } = aggregateServerTools(merged);
|
|
314
|
-
if (sawUnknown) {
|
|
315
|
-
warnings.push({
|
|
316
|
-
code: "unknown_server_tool",
|
|
317
|
-
message: "Encountered a server tool whose name is not in the known list (web_search, web_fetch, code_execution)."
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
const crossCheck = sumCrossCheckCounts(usageBlocks);
|
|
321
|
-
const primary = primaryToolCounts(serverTools);
|
|
322
|
-
const allTools = /* @__PURE__ */ new Set([...crossCheck.keys(), ...primary.keys()]);
|
|
323
|
-
for (const tool of allTools) {
|
|
324
|
-
const crossVal = crossCheck.get(tool) ?? 0;
|
|
325
|
-
const primVal = primary.get(tool) ?? 0;
|
|
326
|
-
if (crossVal === primVal) continue;
|
|
327
|
-
if (sawEmbedded && crossVal === 0) continue;
|
|
328
|
-
warnings.push({
|
|
329
|
-
code: "server_tool_count_mismatch",
|
|
330
|
-
message: `Server-tool ${tool} count mismatch: srvtoolu_ scan=${primVal}, usage.server_tool_use=${crossVal}. Trusting srvtoolu_ count.`
|
|
331
|
-
});
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
const subagentMismatch = mainModels.size > 0 && sidechainModels.size > 0 && [...sidechainModels].some((m) => !mainModels.has(m));
|
|
335
|
-
if (subagentMismatch) {
|
|
336
|
-
warnings.push({
|
|
337
|
-
code: "subagent_model_differs",
|
|
338
|
-
message: "At least one sidechain ModelCall uses a model different from the main-chain model in this turn."
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
let confidence;
|
|
342
|
-
if (!anyComplete && anyIncomplete) confidence = "unknown";
|
|
343
|
-
else if (anyIncomplete) confidence = "partial";
|
|
344
|
-
else if (anyComplete) confidence = "exact";
|
|
345
|
-
else confidence = "unknown";
|
|
346
|
-
if (args.checkSubsequentEvent) {
|
|
347
|
-
const lastAssistantMs = (() => {
|
|
348
|
-
const msList = assistantEvents.map((ev) => parseTimestamp(ev.timestamp)).filter((ms) => ms !== null);
|
|
349
|
-
return msList.length ? Math.max(...msList) : null;
|
|
350
|
-
})();
|
|
351
|
-
const hasSubsequent = lastAssistantMs !== null && args.sessionEvents.some((ev) => {
|
|
352
|
-
const ms = parseTimestamp(ev.timestamp);
|
|
353
|
-
if (ms === null || ms <= lastAssistantMs) return false;
|
|
354
|
-
if (args.upperMs !== null && ms >= args.upperMs) return false;
|
|
355
|
-
return true;
|
|
356
|
-
});
|
|
357
|
-
if (!hasSubsequent && confidence === "exact") {
|
|
358
|
-
confidence = "partial";
|
|
359
|
-
warnings.push({
|
|
360
|
-
code: "transcript_truncated",
|
|
361
|
-
message: "Previous turn has no subsequent event after its last assistant message; transcript may be truncated."
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const resolvedVersion = args.agent.version ?? resolveVersion(args.turnEvents) ?? resolveVersion(args.sessionEvents);
|
|
366
|
-
const usage = {
|
|
367
|
-
schemaVersion: 1,
|
|
368
|
-
capturedAt: args.capturedAt,
|
|
369
|
-
captureSource: args.captureSource,
|
|
370
|
-
confidence,
|
|
371
|
-
agent: {
|
|
372
|
-
name: args.agent.name,
|
|
373
|
-
version: resolvedVersion,
|
|
374
|
-
sessionId: args.agent.sessionId,
|
|
375
|
-
turnId: args.agent.turnId,
|
|
376
|
-
plan: args.agent.plan
|
|
377
|
-
},
|
|
378
|
-
calls,
|
|
379
|
-
serverTools,
|
|
380
|
-
warnings,
|
|
381
|
-
extensions: args.extensions
|
|
382
|
-
};
|
|
383
|
-
return { ok: true, usage };
|
|
384
|
-
}
|
|
385
|
-
function sliceTranscriptIntoTurns(input) {
|
|
386
|
-
const sessionEvents = input.events.filter((ev) => ev.sessionId === input.sessionId);
|
|
387
|
-
const boundaries = sessionEvents.filter(isUserBoundary);
|
|
388
|
-
const result = [];
|
|
389
|
-
for (let i = 0; i < boundaries.length; i++) {
|
|
390
|
-
const boundary = boundaries[i];
|
|
391
|
-
const nextBoundary = boundaries[i + 1] ?? null;
|
|
392
|
-
const boundaryMs = parseTimestamp(boundary.timestamp);
|
|
393
|
-
if (boundaryMs === null) continue;
|
|
394
|
-
const occurredAt = asStringOrNull(boundary.timestamp);
|
|
395
|
-
if (occurredAt === null) continue;
|
|
396
|
-
const upperMs = nextBoundary ? parseTimestamp(nextBoundary.timestamp) : null;
|
|
397
|
-
const turnEvents = sessionEvents.filter((ev) => {
|
|
398
|
-
const ms = parseTimestamp(ev.timestamp);
|
|
399
|
-
if (ms === null || ms <= boundaryMs) return false;
|
|
400
|
-
if (upperMs !== null && ms >= upperMs) return false;
|
|
401
|
-
return true;
|
|
402
|
-
});
|
|
403
|
-
const promptId = asStringOrNull(boundary.promptId);
|
|
404
|
-
const promptText = extractUserContentText(boundary.message);
|
|
405
|
-
const assistantText = extractAssistantContentText(turnEvents);
|
|
406
|
-
const gitBranch = asStringOrNull(boundary.gitBranch);
|
|
407
|
-
const cwd = asStringOrNull(boundary.cwd);
|
|
408
|
-
const built = buildTurnUsage({
|
|
409
|
-
sessionEvents,
|
|
410
|
-
turnEvents,
|
|
411
|
-
upperMs,
|
|
412
|
-
initialWarnings: [],
|
|
413
|
-
agent: {
|
|
414
|
-
name: "claude-code",
|
|
415
|
-
version: null,
|
|
416
|
-
sessionId: input.sessionId,
|
|
417
|
-
turnId: promptId,
|
|
418
|
-
plan: null
|
|
419
|
-
},
|
|
420
|
-
capturedAt: input.capturedAt,
|
|
421
|
-
captureSource: "historical_import",
|
|
422
|
-
extensions: input.extensions ?? null,
|
|
423
|
-
checkSubsequentEvent: false
|
|
424
|
-
});
|
|
425
|
-
if (!built.ok) continue;
|
|
426
|
-
result.push({
|
|
427
|
-
sessionId: input.sessionId,
|
|
428
|
-
promptId,
|
|
429
|
-
promptText,
|
|
430
|
-
assistantText,
|
|
431
|
-
occurredAt,
|
|
432
|
-
gitBranch,
|
|
433
|
-
cwd,
|
|
434
|
-
usage: built.usage
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
return result;
|
|
438
|
-
}
|
|
439
|
-
export {
|
|
440
|
-
readAndParseTranscript,
|
|
441
|
-
sliceTranscriptIntoTurns
|
|
442
|
-
};
|
|
443
|
-
//# sourceMappingURL=historical.js.map
|
package/dist/historical.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/usage/claudeCodeTranscript.ts","../src/usage/claudeCodeUsageHarvester.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\n\nexport type TranscriptEvent = Record<string, unknown>;\n\nexport type ReadTranscriptResult =\n | { ok: true; events: TranscriptEvent[] }\n | { ok: false; reason: \"transcript_not_found\" | \"transcript_unreadable\" };\n\nexport async function readAndParseTranscript(transcriptPath: string): Promise<ReadTranscriptResult> {\n let raw: string;\n try {\n raw = await fs.readFile(transcriptPath, \"utf8\");\n } catch (err) {\n const code = err && typeof err === \"object\" && \"code\" in err ? (err as { code?: unknown }).code : null;\n if (code === \"ENOENT\") {\n return { ok: false, reason: \"transcript_not_found\" };\n }\n return { ok: false, reason: \"transcript_unreadable\" };\n }\n\n const events: TranscriptEvent[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n events.push(parsed as TranscriptEvent);\n }\n } catch {\n // skip malformed lines\n }\n }\n return { ok: true, events };\n}\n","// Field-name map verified against real Claude Code transcripts under\n// ~/.claude/projects/**/*.jsonl (CC version 2.1.114):\n// event.sessionId, event.type, event.isMeta, event.isSidechain,\n// event.uuid, event.timestamp, event.requestId (assistant only, Anthropic req_...),\n// event.version (Claude Code version, e.g. \"2.1.114\"),\n// event.message.{id, model, role, content, usage}\n// usage.{input_tokens, output_tokens, cache_read_input_tokens,\n// cache_creation_input_tokens, cache_creation.{ephemeral_5m_input_tokens,\n// ephemeral_1h_input_tokens}, server_tool_use.{web_search_requests,\n// web_fetch_requests, ...}, service_tier}\n//\n// Prompt mutation: for plain typed prompts, event.message.content is the\n// literal string the user submitted (verified). For slash commands it becomes\n// \"<command-name>/foo</command-name>...\" wrapping. The boundary walker matches\n// on exact string equality first; a timestamp-tolerance fallback covers the\n// slash-command case without needing a special path.\n//\n// Sidechain marker: boolean event.isSidechain at event top level. Task-spawned\n// subagent assistant messages carry isSidechain: true and may use a different\n// model — billed separately.\n\nimport type {\n ModelCall,\n ServerToolUsage,\n TurnUsage,\n TurnUsageCaptureSource,\n} from \"@remixhq/core/collab\";\nimport type { TranscriptEvent } from \"./claudeCodeTranscript.js\";\n\nexport const HARVESTER_WARNING_CODES = [\n \"cache_split_unavailable\",\n \"unknown_server_tool\",\n \"transcript_truncated\",\n \"subagent_model_differs\",\n \"server_tool_count_mismatch\",\n] as const;\nexport type HarvesterWarningCode = (typeof HARVESTER_WARNING_CODES)[number];\n\nexport type HarvestWarning = { code: HarvesterWarningCode; message: string };\n\nexport type HarvestUsageInput = {\n events: TranscriptEvent[];\n sessionId: string;\n promptText: string;\n submittedAt: string;\n checkSubsequentEvent?: boolean;\n // Exclusive upper bound on transcript events considered part of this turn.\n // Used by the previous-turn harvest to avoid bleeding into the current\n // turn's user message + assistant response when prompts repeat (e.g. user\n // says \"hi\" twice in a row). Defaults to no upper bound (current-turn\n // harvest behavior).\n nextBoundaryAt?: string | null;\n agent: {\n name: \"claude-code\";\n version: string | null;\n sessionId: string | null;\n turnId: string | null;\n plan: string | null;\n };\n capturedAt: string;\n extensions: Record<string, unknown> | null;\n};\n\nexport type HarvestUsageFailureReason = \"no_user_boundary_found\" | \"no_messages_for_turn\";\n\nexport type HarvestUsageResult =\n | {\n ok: true;\n usage: TurnUsage;\n // Timestamp of the user-event boundary that was matched in the\n // transcript (NOT the hook's submittedAt). Callers use this as the\n // upper bound for a subsequent previous-turn harvest so the cutoff\n // sits exactly on this turn's user message rather than on the hook's\n // (slightly later) fire time.\n boundaryAt: string | null;\n }\n | { ok: false; reason: HarvestUsageFailureReason };\n\nconst BOUNDARY_TIMESTAMP_TOLERANCE_MS = 500;\n\nfunction parseTimestamp(value: unknown): number | null {\n if (typeof value !== \"string\") return null;\n const ms = Date.parse(value);\n return Number.isFinite(ms) ? ms : null;\n}\n\nfunction extractUserContentText(message: unknown): string | null {\n if (!message || typeof message !== \"object\") return null;\n const content = (message as { content?: unknown }).content;\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n const textBlocks = content\n .filter((block): block is { type?: string; text?: string } => Boolean(block) && typeof block === \"object\")\n .filter((block) => block.type === \"text\" && typeof block.text === \"string\")\n .map((block) => block.text as string);\n if (textBlocks.length === 0) return null;\n return textBlocks.join(\"\\n\");\n }\n return null;\n}\n\nfunction isUserBoundary(event: TranscriptEvent): boolean {\n return (\n event.type === \"user\" &&\n event.isMeta !== true &&\n event.isSidechain !== true\n );\n}\n\n// Concatenate the text content of every assistant event inside a single\n// turn into one string. Mirrors `extractUserContentText` but iterates\n// over a list (turns can have many assistant events: streaming chunks,\n// tool-use cycles, sidechain delegated agents, etc).\n//\n// We DELIBERATELY include sidechain assistant events here because they\n// represent real model output the user paid for and would expect to see\n// in the dashboard. The slicer's \"no sidechain user boundary\" rule\n// (isUserBoundary) is about turn segmentation, not about hiding the\n// model's response text.\n//\n// We DELIBERATELY drop tool_use / tool_result / thinking blocks: this\n// field is meant to render in a \"what did the agent say\" pane, not to\n// reproduce the entire structured tool log. Tool calls are surfaced\n// elsewhere (turnUsage in workspace_metadata).\nfunction extractAssistantContentText(turnEvents: TranscriptEvent[]): string | null {\n const chunks: string[] = [];\n for (const ev of turnEvents) {\n if (ev.type !== \"assistant\") continue;\n const text = extractUserContentText(ev.message);\n if (text && text.length > 0) chunks.push(text);\n }\n if (chunks.length === 0) return null;\n // Strip duplicate adjacent chunks that some Claude transcripts emit\n // (streaming-restart artefacts where the assistant resumes mid-text\n // and the harvester sees the prefix twice). Cheap to do here, hard\n // to undo at read time.\n const deduped: string[] = [];\n for (const chunk of chunks) {\n if (deduped[deduped.length - 1] !== chunk) deduped.push(chunk);\n }\n return deduped.join(\"\\n\");\n}\n\nfunction findBoundary(\n sessionEvents: TranscriptEvent[],\n promptText: string,\n submittedAt: string,\n upperMs: number | null,\n): { boundary: TranscriptEvent | null; usedFallback: boolean } {\n // Exclude any event at or after the upper bound. Used by the previous-turn\n // harvest so a repeated prompt text can't lock onto the current turn's user\n // message.\n const isWithinUpperBound = (ev: TranscriptEvent): boolean => {\n if (upperMs === null) return true;\n const ms = parseTimestamp(ev.timestamp);\n return ms !== null && ms < upperMs;\n };\n\n let contentMatch: TranscriptEvent | null = null;\n for (let i = sessionEvents.length - 1; i >= 0; i--) {\n const ev = sessionEvents[i];\n if (!isUserBoundary(ev)) continue;\n if (!isWithinUpperBound(ev)) continue;\n const text = extractUserContentText(ev.message);\n if (text !== null && text === promptText) {\n contentMatch = ev;\n break;\n }\n }\n if (contentMatch) return { boundary: contentMatch, usedFallback: false };\n\n const submittedMs = parseTimestamp(submittedAt);\n if (submittedMs === null) return { boundary: null, usedFallback: false };\n\n for (let i = sessionEvents.length - 1; i >= 0; i--) {\n const ev = sessionEvents[i];\n if (!isUserBoundary(ev)) continue;\n if (!isWithinUpperBound(ev)) continue;\n const ms = parseTimestamp(ev.timestamp);\n if (ms === null) continue;\n if (ms >= submittedMs - BOUNDARY_TIMESTAMP_TOLERANCE_MS) {\n return { boundary: ev, usedFallback: true };\n }\n }\n return { boundary: null, usedFallback: false };\n}\n\nfunction asNumberOrNull(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction asStringOrNull(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\n\ntype AssistantMessage = {\n id: string | null;\n model: string | null;\n content: unknown[];\n usage: Record<string, unknown> | null;\n};\n\nfunction extractAssistantMessage(event: TranscriptEvent): AssistantMessage | null {\n const message = event.message;\n if (!message || typeof message !== \"object\") return null;\n const msg = message as Record<string, unknown>;\n if (msg.role !== \"assistant\" && msg.role !== undefined) {\n // some transcripts may omit role on assistant events; keep permissive\n }\n const content = Array.isArray(msg.content) ? (msg.content as unknown[]) : [];\n const usage = msg.usage && typeof msg.usage === \"object\" ? (msg.usage as Record<string, unknown>) : null;\n return {\n id: asStringOrNull(msg.id),\n model: asStringOrNull(msg.model),\n content,\n usage,\n };\n}\n\nfunction usageIsComplete(usage: Record<string, unknown> | null): boolean {\n if (!usage) return false;\n const hasInput = typeof usage.input_tokens === \"number\";\n const hasOutput = typeof usage.output_tokens === \"number\";\n const hasCacheRead = typeof usage.cache_read_input_tokens === \"number\";\n return hasInput && hasOutput && hasCacheRead;\n}\n\nfunction buildModelCall(event: TranscriptEvent, msg: AssistantMessage): ModelCall {\n const usage = msg.usage ?? {};\n const cacheCreation =\n usage.cache_creation && typeof usage.cache_creation === \"object\"\n ? (usage.cache_creation as Record<string, unknown>)\n : null;\n const has5m = cacheCreation && typeof cacheCreation.ephemeral_5m_input_tokens === \"number\";\n const has1h = cacheCreation && typeof cacheCreation.ephemeral_1h_input_tokens === \"number\";\n const cacheWrite5mTokens = has5m ? (cacheCreation!.ephemeral_5m_input_tokens as number) : null;\n const cacheWrite1hTokens = has1h ? (cacheCreation!.ephemeral_1h_input_tokens as number) : null;\n const splitAvailable = has5m || has1h;\n const cacheWriteTokens = splitAvailable ? null : asNumberOrNull(usage.cache_creation_input_tokens);\n\n return {\n provider: \"anthropic\",\n model: msg.model,\n tier: asStringOrNull(usage.service_tier),\n requestId: asStringOrNull(event.requestId),\n timestamp: asStringOrNull(event.timestamp),\n isSidechain: event.isSidechain === true,\n inputTokens: asNumberOrNull(usage.input_tokens),\n outputTokens: asNumberOrNull(usage.output_tokens),\n cacheReadTokens: asNumberOrNull(usage.cache_read_input_tokens),\n cacheWriteTokens,\n cacheWrite5mTokens,\n cacheWrite1hTokens,\n reasoningTokens: null,\n audioInputTokens: null,\n imageInputTokens: null,\n };\n}\n\ntype ServerToolUseRecord = {\n tool: string;\n unit: string;\n isKnown: boolean;\n // Stable identifier so we can dedupe across \"direct\" (raw API server_tool_use\n // blocks in assistant content) and \"embedded\" (Claude Code's WebSearch\n // wrapper, where the underlying srvtoolu_* lives in tool_result.toolUseResult)\n // discovery paths.\n id: string;\n source: \"direct\" | \"embedded\";\n};\n\nfunction scanServerToolUses(content: unknown[]): ServerToolUseRecord[] {\n const uses: ServerToolUseRecord[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n const id = b.id;\n if (typeof id !== \"string\" || !id.startsWith(\"srvtoolu_\")) continue;\n const name = typeof b.name === \"string\" ? b.name : \"\";\n // Units MUST match the values stored in the backend's `server_tool_pricing`\n // table — the rate lookup is exact-match on (provider, tool, unit). The\n // seed uses `per_request` for billable web tools (anthropic web_search +\n // web_fetch are $0.01/req); emitting plain `request` here used to silently\n // zero out server_tool_cost_usd because the lookup found no row.\n switch (name) {\n case \"web_search\":\n uses.push({ tool: \"web_search\", unit: \"per_request\", isKnown: true, id, source: \"direct\" });\n break;\n case \"web_fetch\":\n uses.push({ tool: \"web_fetch\", unit: \"per_request\", isKnown: true, id, source: \"direct\" });\n break;\n case \"code_execution\":\n uses.push({ tool: \"code_execution\", unit: \"invocation\", isKnown: true, id, source: \"direct\" });\n break;\n default:\n uses.push({ tool: name || \"unknown\", unit: \"invocation\", isKnown: false, id, source: \"direct\" });\n break;\n }\n }\n return uses;\n}\n\n// Claude Code's `WebSearch` / `WebFetch` tools never appear as `server_tool_use`\n// blocks on the assistant message — they show up as ordinary `tool_use` blocks\n// with `toolu_*` IDs and a capitalized `name` (\"WebSearch\"). The actual server\n// tool that Anthropic bills (with the `srvtoolu_*` ID) is invoked from a\n// secondary Haiku sub-call (see leaked source analysis at\n// trevorfox.com/2026/04/how-claude-code-search-actually-works/), and Claude\n// Code mirrors the underlying `srvtoolu_*` ID into the matching tool_result\n// user message under `event.toolUseResult.results[].tool_use_id`.\n//\n// Because the Haiku sub-call is its own API request, the main turn's\n// `usage.server_tool_use.web_search_requests` is always 0 — so the existing\n// \"direct\" scan misses these completely. We recover them here by walking\n// tool_result events for embedded srvtoolu_ IDs.\nfunction buildClientToolNameMap(turnEvents: TranscriptEvent[]): Map<string, string> {\n const map = new Map<string, string>();\n for (const ev of turnEvents) {\n if (ev.type !== \"assistant\") continue;\n const msg = ev.message;\n if (!msg || typeof msg !== \"object\") continue;\n const content = (msg as { content?: unknown }).content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type !== \"tool_use\") continue;\n const id = b.id;\n const name = b.name;\n if (typeof id !== \"string\" || typeof name !== \"string\") continue;\n if (!id.startsWith(\"toolu_\")) continue;\n map.set(id, name);\n }\n }\n return map;\n}\n\nfunction scanEmbeddedServerToolUses(\n turnEvents: TranscriptEvent[],\n clientToolNames: Map<string, string>,\n): ServerToolUseRecord[] {\n const uses: ServerToolUseRecord[] = [];\n for (const ev of turnEvents) {\n if (ev.type !== \"user\") continue;\n const tur = (ev as Record<string, unknown>).toolUseResult;\n if (!tur || typeof tur !== \"object\") continue;\n const results = (tur as Record<string, unknown>).results;\n if (!Array.isArray(results)) continue;\n\n // The parent client-side tool's ID (toolu_*) lives on the tool_result\n // content block inside the user message. We use it to attribute the\n // embedded srvtoolu_ to the correct logical tool.\n let parentToolName = \"\";\n const userMsg = ev.message;\n if (userMsg && typeof userMsg === \"object\") {\n const userContent = (userMsg as { content?: unknown }).content;\n if (Array.isArray(userContent)) {\n for (const block of userContent) {\n if (!block || typeof block !== \"object\") continue;\n const b = block as Record<string, unknown>;\n if (b.type !== \"tool_result\") continue;\n const parentId = b.tool_use_id;\n if (typeof parentId === \"string\") {\n parentToolName = clientToolNames.get(parentId) ?? \"\";\n break;\n }\n }\n }\n }\n\n for (const entry of results) {\n if (!entry || typeof entry !== \"object\") continue;\n const srvId = (entry as Record<string, unknown>).tool_use_id;\n if (typeof srvId !== \"string\" || !srvId.startsWith(\"srvtoolu_\")) continue;\n // Today, Claude Code only delegates `WebSearch` to Anthropic's\n // server-side `web_search` tool. WebFetch summarizes via Haiku without\n // emitting a srvtoolu_. If a future Claude Code release adds another\n // wrapper we'll see it via the parent tool name.\n if (parentToolName === \"WebFetch\") {\n uses.push({ tool: \"web_fetch\", unit: \"per_request\", isKnown: true, id: srvId, source: \"embedded\" });\n } else {\n uses.push({ tool: \"web_search\", unit: \"per_request\", isKnown: true, id: srvId, source: \"embedded\" });\n }\n }\n }\n return uses;\n}\n\nfunction dedupeByServerToolId(records: ServerToolUseRecord[]): ServerToolUseRecord[] {\n const seen = new Map<string, ServerToolUseRecord>();\n for (const r of records) {\n const existing = seen.get(r.id);\n if (!existing) {\n seen.set(r.id, r);\n continue;\n }\n // Direct path is preferred (it carries the canonical name from Anthropic's\n // own server_tool_use block). Embedded only wins if direct is absent.\n if (existing.source === \"embedded\" && r.source === \"direct\") {\n seen.set(r.id, r);\n }\n }\n return Array.from(seen.values());\n}\n\nfunction aggregateServerTools(\n uses: ServerToolUseRecord[],\n): { serverTools: ServerToolUsage[]; sawUnknown: boolean; sawEmbedded: boolean } {\n const map = new Map<string, ServerToolUsage>();\n let sawUnknown = false;\n let sawEmbedded = false;\n for (const use of uses) {\n if (!use.isKnown) sawUnknown = true;\n if (use.source === \"embedded\") sawEmbedded = true;\n const key = `anthropic|${use.tool}|${use.unit}`;\n const existing = map.get(key);\n if (existing) {\n existing.quantity += 1;\n } else {\n map.set(key, { provider: \"anthropic\", tool: use.tool, unit: use.unit, quantity: 1 });\n }\n }\n return { serverTools: Array.from(map.values()), sawUnknown, sawEmbedded };\n}\n\nfunction sumCrossCheckCounts(usageBlocks: Record<string, unknown>[]): Map<string, number> {\n const totals = new Map<string, number>();\n const keyFor = (raw: string): string | null => {\n if (raw === \"web_search_requests\") return \"web_search\";\n if (raw === \"web_fetch_requests\") return \"web_fetch\";\n return null;\n };\n for (const usage of usageBlocks) {\n const stu = usage.server_tool_use;\n if (!stu || typeof stu !== \"object\") continue;\n for (const [rawKey, rawVal] of Object.entries(stu as Record<string, unknown>)) {\n const mapped = keyFor(rawKey);\n if (!mapped) continue;\n if (typeof rawVal !== \"number\" || !Number.isFinite(rawVal)) continue;\n totals.set(mapped, (totals.get(mapped) ?? 0) + rawVal);\n }\n }\n return totals;\n}\n\nfunction primaryToolCounts(serverTools: ServerToolUsage[]): Map<string, number> {\n const map = new Map<string, number>();\n for (const entry of serverTools) {\n map.set(entry.tool, (map.get(entry.tool) ?? 0) + entry.quantity);\n }\n return map;\n}\n\nfunction resolveVersion(events: TranscriptEvent[]): string | null {\n for (const ev of events) {\n if (typeof ev.version === \"string\" && ev.version.trim()) return ev.version.trim();\n }\n return null;\n}\n\n// Shared core: takes a pre-computed turn slice and assembles the TurnUsage\n// payload. Used by both the live hook path (`harvestClaudeCodeUsage`, which\n// finds the slice via prompt-text matching) and the historical importer\n// (`sliceTranscriptIntoTurns`, which walks every user boundary in the\n// transcript). Both paths MUST go through this function so the\n// workspace_metadata.turnUsage shape is byte-identical for the same source\n// events; the pricing pipeline reads that shape and silent shape drift\n// between live and historical would corrupt cost numbers without surfacing.\nfunction buildTurnUsage(args: {\n sessionEvents: TranscriptEvent[];\n turnEvents: TranscriptEvent[];\n upperMs: number | null;\n initialWarnings: HarvestWarning[];\n agent: TurnUsage[\"agent\"];\n capturedAt: string;\n captureSource: TurnUsageCaptureSource;\n extensions: Record<string, unknown> | null;\n checkSubsequentEvent: boolean;\n}): { ok: true; usage: TurnUsage } | { ok: false; reason: HarvestUsageFailureReason } {\n const assistantEvents = args.turnEvents.filter((ev) => ev.type === \"assistant\");\n if (assistantEvents.length === 0) {\n return { ok: false, reason: \"no_messages_for_turn\" };\n }\n\n const warnings: HarvestWarning[] = [...args.initialWarnings];\n\n const calls: ModelCall[] = [];\n const usageBlocks: Record<string, unknown>[] = [];\n let anyIncomplete = false;\n let anyComplete = false;\n let sawLumpSumFallback = false;\n const collectedServerToolUses: ServerToolUseRecord[] = [];\n\n const mainModels = new Set<string>();\n const sidechainModels = new Set<string>();\n\n for (const ev of assistantEvents) {\n const msg = extractAssistantMessage(ev);\n if (!msg) continue;\n if (usageIsComplete(msg.usage)) {\n anyComplete = true;\n } else {\n anyIncomplete = true;\n }\n if (msg.usage) usageBlocks.push(msg.usage);\n\n const call = buildModelCall(ev, msg);\n calls.push(call);\n\n if (call.cacheWrite5mTokens === null && call.cacheWrite1hTokens === null && call.cacheWriteTokens !== null) {\n sawLumpSumFallback = true;\n }\n\n collectedServerToolUses.push(...scanServerToolUses(msg.content));\n\n if (call.model) {\n if (call.isSidechain) sidechainModels.add(call.model);\n else mainModels.add(call.model);\n }\n }\n\n if (calls.length === 0) {\n return { ok: false, reason: \"no_messages_for_turn\" };\n }\n\n if (sawLumpSumFallback) {\n warnings.push({\n code: \"cache_split_unavailable\",\n message: \"Assistant message reported lump-sum cache_creation_input_tokens without the 5m/1h split.\",\n });\n }\n\n // Pull in server-tool usage from Claude Code's WebSearch/WebFetch wrappers,\n // where the billable srvtoolu_ ID lives in the tool_result event rather than\n // on the assistant message. Dedupe by srvtoolu_ ID so a future Claude Code\n // version that surfaces the ID in BOTH places won't double-count.\n const clientToolNames = buildClientToolNameMap(args.turnEvents);\n const embeddedUses = scanEmbeddedServerToolUses(args.turnEvents, clientToolNames);\n const merged = dedupeByServerToolId([...collectedServerToolUses, ...embeddedUses]);\n const { serverTools, sawUnknown, sawEmbedded } = aggregateServerTools(merged);\n if (sawUnknown) {\n warnings.push({\n code: \"unknown_server_tool\",\n message: \"Encountered a server tool whose name is not in the known list (web_search, web_fetch, code_execution).\",\n });\n }\n\n const crossCheck = sumCrossCheckCounts(usageBlocks);\n const primary = primaryToolCounts(serverTools);\n const allTools = new Set<string>([...crossCheck.keys(), ...primary.keys()]);\n for (const tool of allTools) {\n const crossVal = crossCheck.get(tool) ?? 0;\n const primVal = primary.get(tool) ?? 0;\n if (crossVal === primVal) continue;\n if (sawEmbedded && crossVal === 0) continue;\n warnings.push({\n code: \"server_tool_count_mismatch\",\n message: `Server-tool ${tool} count mismatch: srvtoolu_ scan=${primVal}, usage.server_tool_use=${crossVal}. Trusting srvtoolu_ count.`,\n });\n break;\n }\n\n const subagentMismatch =\n mainModels.size > 0 &&\n sidechainModels.size > 0 &&\n [...sidechainModels].some((m) => !mainModels.has(m));\n if (subagentMismatch) {\n warnings.push({\n code: \"subagent_model_differs\",\n message: \"At least one sidechain ModelCall uses a model different from the main-chain model in this turn.\",\n });\n }\n\n let confidence: TurnUsage[\"confidence\"];\n if (!anyComplete && anyIncomplete) confidence = \"unknown\";\n else if (anyIncomplete) confidence = \"partial\";\n else if (anyComplete) confidence = \"exact\";\n else confidence = \"unknown\";\n\n if (args.checkSubsequentEvent) {\n const lastAssistantMs = (() => {\n const msList = assistantEvents\n .map((ev) => parseTimestamp(ev.timestamp))\n .filter((ms): ms is number => ms !== null);\n return msList.length ? Math.max(...msList) : null;\n })();\n const hasSubsequent =\n lastAssistantMs !== null &&\n args.sessionEvents.some((ev) => {\n const ms = parseTimestamp(ev.timestamp);\n if (ms === null || ms <= lastAssistantMs) return false;\n if (args.upperMs !== null && ms >= args.upperMs) return false;\n return true;\n });\n if (!hasSubsequent && confidence === \"exact\") {\n confidence = \"partial\";\n warnings.push({\n code: \"transcript_truncated\",\n message: \"Previous turn has no subsequent event after its last assistant message; transcript may be truncated.\",\n });\n }\n }\n\n const resolvedVersion =\n args.agent.version ?? resolveVersion(args.turnEvents) ?? resolveVersion(args.sessionEvents);\n\n const usage: TurnUsage = {\n schemaVersion: 1,\n capturedAt: args.capturedAt,\n captureSource: args.captureSource,\n confidence,\n agent: {\n name: args.agent.name,\n version: resolvedVersion,\n sessionId: args.agent.sessionId,\n turnId: args.agent.turnId,\n plan: args.agent.plan,\n },\n calls,\n serverTools,\n warnings,\n extensions: args.extensions,\n };\n return { ok: true, usage };\n}\n\nexport function harvestClaudeCodeUsage(input: HarvestUsageInput): HarvestUsageResult {\n const sessionEvents = input.events.filter((ev) => ev.sessionId === input.sessionId);\n const upperMs = input.nextBoundaryAt ? parseTimestamp(input.nextBoundaryAt) : null;\n const { boundary, usedFallback } = findBoundary(\n sessionEvents,\n input.promptText,\n input.submittedAt,\n upperMs,\n );\n if (!boundary) {\n return { ok: false, reason: \"no_user_boundary_found\" };\n }\n const boundaryMs = parseTimestamp(boundary.timestamp);\n if (boundaryMs === null) {\n return { ok: false, reason: \"no_user_boundary_found\" };\n }\n\n const turnEvents = sessionEvents.filter((ev) => {\n const ms = parseTimestamp(ev.timestamp);\n if (ms === null || ms <= boundaryMs) return false;\n if (upperMs !== null && ms >= upperMs) return false;\n return true;\n });\n\n const initialWarnings: HarvestWarning[] = [];\n if (usedFallback) {\n initialWarnings.push({\n code: \"transcript_truncated\",\n message: \"Prompt-text equality match failed; used timestamp-tolerance fallback to locate the user boundary.\",\n });\n }\n\n const built = buildTurnUsage({\n sessionEvents,\n turnEvents,\n upperMs,\n initialWarnings,\n agent: input.agent,\n capturedAt: input.capturedAt,\n captureSource: \"hook\",\n extensions: input.extensions,\n checkSubsequentEvent: input.checkSubsequentEvent === true,\n });\n\n if (!built.ok) return built;\n return {\n ok: true,\n usage: built.usage,\n boundaryAt: typeof boundary.timestamp === \"string\" ? boundary.timestamp : null,\n };\n}\n\n// =============================================================================\n// Historical-import slicer\n// =============================================================================\n// Walks every user-boundary event in a session's transcript and produces one\n// SlicedTurn per boundary, calling buildTurnUsage with captureSource =\n// \"historical_import\". This is the read-side primitive used by\n// `remix history import`.\n//\n// Sidechain policy (matches live hook behavior, enforced by isUserBoundary):\n// * Sidechain user events (isSidechain: true) DO NOT start a new turn.\n// * Sidechain assistant events ARE captured into the parent turn's\n// `calls` array with isSidechain: true preserved (handled inside\n// buildTurnUsage via buildModelCall).\n//\n// `gitBranch`, `cwd`, and `promptId` are read from the user-boundary event\n// directly (verified present on real Claude transcripts, CC version 2.1.116).\n// They're returned alongside the TurnUsage so the importer can compute the\n// dedup key, the repo fingerprint, and the workspace_metadata.branch.\n\nexport type SlicedTurn = {\n sessionId: string;\n promptId: string | null;\n promptText: string | null;\n // Concatenated text of every assistant message inside this turn\n // (post-prompt, pre-next-prompt). Tool-use / tool-result / thinking\n // blocks are excluded — see extractAssistantContentText for the\n // reasoning. The CLI runner gates whether this gets uploaded with\n // the same opt-in as promptText (--include-prompt-text), since the\n // sensitivity is identical: this is the user's literal AI dialogue.\n assistantText: string | null;\n occurredAt: string;\n gitBranch: string | null;\n cwd: string | null;\n usage: TurnUsage;\n};\n\nexport type SliceTranscriptInput = {\n events: TranscriptEvent[];\n sessionId: string;\n capturedAt: string;\n extensions?: Record<string, unknown> | null;\n};\n\nexport function sliceTranscriptIntoTurns(input: SliceTranscriptInput): SlicedTurn[] {\n const sessionEvents = input.events.filter((ev) => ev.sessionId === input.sessionId);\n const boundaries = sessionEvents.filter(isUserBoundary);\n const result: SlicedTurn[] = [];\n\n for (let i = 0; i < boundaries.length; i++) {\n const boundary = boundaries[i];\n const nextBoundary = boundaries[i + 1] ?? null;\n const boundaryMs = parseTimestamp(boundary.timestamp);\n if (boundaryMs === null) continue;\n const occurredAt = asStringOrNull(boundary.timestamp);\n if (occurredAt === null) continue;\n\n const upperMs = nextBoundary ? parseTimestamp(nextBoundary.timestamp) : null;\n\n const turnEvents = sessionEvents.filter((ev) => {\n const ms = parseTimestamp(ev.timestamp);\n if (ms === null || ms <= boundaryMs) return false;\n if (upperMs !== null && ms >= upperMs) return false;\n return true;\n });\n\n const promptId = asStringOrNull((boundary as Record<string, unknown>).promptId);\n const promptText = extractUserContentText(boundary.message);\n const assistantText = extractAssistantContentText(turnEvents);\n const gitBranch = asStringOrNull((boundary as Record<string, unknown>).gitBranch);\n const cwd = asStringOrNull((boundary as Record<string, unknown>).cwd);\n\n const built = buildTurnUsage({\n sessionEvents,\n turnEvents,\n upperMs,\n initialWarnings: [],\n agent: {\n name: \"claude-code\",\n version: null,\n sessionId: input.sessionId,\n turnId: promptId,\n plan: null,\n },\n capturedAt: input.capturedAt,\n captureSource: \"historical_import\",\n extensions: input.extensions ?? null,\n checkSubsequentEvent: false,\n });\n\n if (!built.ok) continue;\n\n result.push({\n sessionId: input.sessionId,\n promptId,\n promptText,\n assistantText,\n occurredAt,\n gitBranch,\n cwd,\n usage: built.usage,\n });\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
|