@remixhq/core 0.1.13 → 0.1.15

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 (78) hide show
  1. package/dist/api.d.ts +76 -1
  2. package/dist/api.js +1 -1
  3. package/dist/auth.js +1 -1
  4. package/dist/binding-WiIRI2fl.d.ts +43 -0
  5. package/dist/binding.d.ts +1 -21
  6. package/dist/binding.js +2 -0
  7. package/dist/{chunk-EVWDYCBL.js → chunk-P6JHXOV4.js} +36 -23
  8. package/dist/{chunk-R7FVSCQW.js → chunk-US5SM7ZC.js} +19 -1
  9. package/dist/collab.d.ts +376 -611
  10. package/dist/collab.js +2381 -376
  11. package/dist/contracts-CHmD-fMj.d.ts +677 -0
  12. package/dist/history.d.ts +64 -0
  13. package/dist/history.js +529 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +2 -2
  16. package/package.json +9 -3
  17. package/dist/chunk-2WGZS7CD.js +0 -0
  18. package/dist/chunk-34WDQCPF.js +0 -242
  19. package/dist/chunk-4276ARDF.js +0 -303
  20. package/dist/chunk-4L3ZBZUQ.js +0 -281
  21. package/dist/chunk-54CBEP2W.js +0 -570
  22. package/dist/chunk-55K5GHAZ.js +0 -252
  23. package/dist/chunk-5H5CZKGN.js +0 -691
  24. package/dist/chunk-5NTOJXEZ.js +0 -223
  25. package/dist/chunk-7WUKH3ZD.js +0 -221
  26. package/dist/chunk-AE2HPMUZ.js +0 -80
  27. package/dist/chunk-AEAOYVIL.js +0 -200
  28. package/dist/chunk-B5S3PUIR.js +0 -388
  29. package/dist/chunk-BJFCN2C3.js +0 -46
  30. package/dist/chunk-BNKPTE2U.js +0 -401
  31. package/dist/chunk-C5NBNU32.js +0 -240
  32. package/dist/chunk-CJFGQE7D.js +0 -46
  33. package/dist/chunk-CUUXZSKW.js +0 -611
  34. package/dist/chunk-DCU3646I.js +0 -12
  35. package/dist/chunk-DEWAIK5X.js +0 -11
  36. package/dist/chunk-DRD6EVTT.js +0 -447
  37. package/dist/chunk-DXCL6I4Q.js +0 -399
  38. package/dist/chunk-E4KAGBU7.js +0 -134
  39. package/dist/chunk-E6AYE22H.js +0 -343
  40. package/dist/chunk-EF3677RE.js +0 -93
  41. package/dist/chunk-EW4PWFHB.js +0 -46
  42. package/dist/chunk-FAZUMWBS.js +0 -93
  43. package/dist/chunk-GEHSFPCD.js +0 -93
  44. package/dist/chunk-GFOBGYW4.js +0 -252
  45. package/dist/chunk-INDDXWAH.js +0 -92
  46. package/dist/chunk-IXWQWFYT.js +0 -342
  47. package/dist/chunk-J3J4PBQ7.js +0 -710
  48. package/dist/chunk-K54U353Z.js +0 -691
  49. package/dist/chunk-K57ZFDGC.js +0 -15
  50. package/dist/chunk-NDA7EJJA.js +0 -286
  51. package/dist/chunk-NK2DA4X6.js +0 -357
  52. package/dist/chunk-OBYR4JHZ.js +0 -374
  53. package/dist/chunk-OJMTW22J.js +0 -286
  54. package/dist/chunk-OMUDRPUI.js +0 -195
  55. package/dist/chunk-ONKKRS2C.js +0 -239
  56. package/dist/chunk-OWFBBWU7.js +0 -196
  57. package/dist/chunk-OZRXDDEL.js +0 -46
  58. package/dist/chunk-P7EM3N73.js +0 -46
  59. package/dist/chunk-POYB6MCQ.js +0 -373
  60. package/dist/chunk-PR5QKMHM.js +0 -46
  61. package/dist/chunk-R44EOUS4.js +0 -288
  62. package/dist/chunk-RIP2MIZL.js +0 -710
  63. package/dist/chunk-RKMMEML5.js +0 -46
  64. package/dist/chunk-RM2BGDBB.js +0 -400
  65. package/dist/chunk-RREREIGW.js +0 -710
  66. package/dist/chunk-TQHLFQY4.js +0 -448
  67. package/dist/chunk-TY3SSQQK.js +0 -688
  68. package/dist/chunk-UGKPOCN5.js +0 -710
  69. package/dist/chunk-UIGKSCTD.js +0 -406
  70. package/dist/chunk-UWIVJRTI.js +0 -343
  71. package/dist/chunk-VA6WXRWB.js +0 -636
  72. package/dist/chunk-XC2FV57P.js +0 -385
  73. package/dist/chunk-XOQIADCH.js +0 -223
  74. package/dist/chunk-ZAQZKEH4.js +0 -46
  75. package/dist/chunk-ZBMOGUSJ.js +0 -17
  76. package/dist/chunk-ZXP6ENQY.js +0 -244
  77. package/dist/index.cjs +0 -1269
  78. package/dist/index.d.cts +0 -482
@@ -0,0 +1,64 @@
1
+ import { T as TurnUsage } from './contracts-CHmD-fMj.js';
2
+
3
+ type TranscriptEvent = Record<string, unknown>;
4
+ type ReadTranscriptResult = {
5
+ ok: true;
6
+ events: TranscriptEvent[];
7
+ } | {
8
+ ok: false;
9
+ reason: "transcript_not_found" | "transcript_unreadable";
10
+ };
11
+ declare function readAndParseTranscript(transcriptPath: string): Promise<ReadTranscriptResult>;
12
+
13
+ declare const HARVESTER_WARNING_CODES: readonly ["cache_split_unavailable", "unknown_server_tool", "transcript_truncated", "subagent_model_differs", "server_tool_count_mismatch"];
14
+ type HarvesterWarningCode = (typeof HARVESTER_WARNING_CODES)[number];
15
+ type HarvestWarning = {
16
+ code: HarvesterWarningCode;
17
+ message: string;
18
+ };
19
+ type HarvestUsageInput = {
20
+ events: TranscriptEvent[];
21
+ sessionId: string;
22
+ promptText: string;
23
+ submittedAt: string;
24
+ checkSubsequentEvent?: boolean;
25
+ nextBoundaryAt?: string | null;
26
+ agent: {
27
+ name: "claude-code";
28
+ version: string | null;
29
+ sessionId: string | null;
30
+ turnId: string | null;
31
+ plan: string | null;
32
+ };
33
+ capturedAt: string;
34
+ extensions: Record<string, unknown> | null;
35
+ };
36
+ type HarvestUsageFailureReason = "no_user_boundary_found" | "no_messages_for_turn";
37
+ type HarvestUsageResult = {
38
+ ok: true;
39
+ usage: TurnUsage;
40
+ boundaryAt: string | null;
41
+ } | {
42
+ ok: false;
43
+ reason: HarvestUsageFailureReason;
44
+ };
45
+ declare function harvestClaudeCodeUsage(input: HarvestUsageInput): HarvestUsageResult;
46
+ type SlicedTurn = {
47
+ sessionId: string;
48
+ promptId: string | null;
49
+ promptText: string | null;
50
+ assistantText: string | null;
51
+ occurredAt: string;
52
+ gitBranch: string | null;
53
+ cwd: string | null;
54
+ usage: TurnUsage;
55
+ };
56
+ type SliceTranscriptInput = {
57
+ events: TranscriptEvent[];
58
+ sessionId: string;
59
+ capturedAt: string;
60
+ extensions?: Record<string, unknown> | null;
61
+ };
62
+ declare function sliceTranscriptIntoTurns(input: SliceTranscriptInput): SlicedTurn[];
63
+
64
+ export { HARVESTER_WARNING_CODES, type HarvestUsageFailureReason, type HarvestUsageInput, type HarvestUsageResult, type HarvestWarning, type HarvesterWarningCode, type ReadTranscriptResult, type SliceTranscriptInput, type SlicedTurn, type TranscriptEvent, harvestClaudeCodeUsage, readAndParseTranscript, sliceTranscriptIntoTurns };
@@ -0,0 +1,529 @@
1
+ // src/history/claudeCodeTranscript.ts
2
+ import fs from "fs/promises";
3
+ async function readAndParseTranscript(transcriptPath) {
4
+ let raw;
5
+ try {
6
+ raw = await fs.readFile(transcriptPath, "utf8");
7
+ } catch (err) {
8
+ const code = err && typeof err === "object" && "code" in err ? err.code : null;
9
+ if (code === "ENOENT") {
10
+ return { ok: false, reason: "transcript_not_found" };
11
+ }
12
+ return { ok: false, reason: "transcript_unreadable" };
13
+ }
14
+ const events = [];
15
+ for (const line of raw.split("\n")) {
16
+ const trimmed = line.trim();
17
+ if (!trimmed) continue;
18
+ try {
19
+ const parsed = JSON.parse(trimmed);
20
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
21
+ events.push(parsed);
22
+ }
23
+ } catch {
24
+ }
25
+ }
26
+ return { ok: true, events };
27
+ }
28
+
29
+ // src/history/claudeCodeUsageHarvester.ts
30
+ var HARVESTER_WARNING_CODES = [
31
+ "cache_split_unavailable",
32
+ "unknown_server_tool",
33
+ "transcript_truncated",
34
+ "subagent_model_differs",
35
+ "server_tool_count_mismatch"
36
+ ];
37
+ var BOUNDARY_TIMESTAMP_TOLERANCE_MS = 500;
38
+ function parseTimestamp(value) {
39
+ if (typeof value !== "string") return null;
40
+ const ms = Date.parse(value);
41
+ return Number.isFinite(ms) ? ms : null;
42
+ }
43
+ function extractUserContentText(message) {
44
+ if (!message || typeof message !== "object") return null;
45
+ const content = message.content;
46
+ if (typeof content === "string") return content;
47
+ if (Array.isArray(content)) {
48
+ const textBlocks = content.filter((block) => Boolean(block) && typeof block === "object").filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text);
49
+ if (textBlocks.length === 0) return null;
50
+ return textBlocks.join("\n");
51
+ }
52
+ return null;
53
+ }
54
+ function isUserBoundary(event) {
55
+ return event.type === "user" && event.isMeta !== true && event.isSidechain !== true;
56
+ }
57
+ function extractAssistantContentText(turnEvents) {
58
+ const chunks = [];
59
+ for (const ev of turnEvents) {
60
+ if (ev.type !== "assistant") continue;
61
+ const text = extractUserContentText(ev.message);
62
+ if (text && text.length > 0) chunks.push(text);
63
+ }
64
+ if (chunks.length === 0) return null;
65
+ const deduped = [];
66
+ for (const chunk of chunks) {
67
+ if (deduped[deduped.length - 1] !== chunk) deduped.push(chunk);
68
+ }
69
+ return deduped.join("\n");
70
+ }
71
+ function findBoundary(sessionEvents, promptText, submittedAt, upperMs) {
72
+ const isWithinUpperBound = (ev) => {
73
+ if (upperMs === null) return true;
74
+ const ms = parseTimestamp(ev.timestamp);
75
+ return ms !== null && ms < upperMs;
76
+ };
77
+ let contentMatch = null;
78
+ for (let i = sessionEvents.length - 1; i >= 0; i--) {
79
+ const ev = sessionEvents[i];
80
+ if (!isUserBoundary(ev)) continue;
81
+ if (!isWithinUpperBound(ev)) continue;
82
+ const text = extractUserContentText(ev.message);
83
+ if (text !== null && text === promptText) {
84
+ contentMatch = ev;
85
+ break;
86
+ }
87
+ }
88
+ if (contentMatch) return { boundary: contentMatch, usedFallback: false };
89
+ const submittedMs = parseTimestamp(submittedAt);
90
+ if (submittedMs === null) return { boundary: null, usedFallback: false };
91
+ for (let i = sessionEvents.length - 1; i >= 0; i--) {
92
+ const ev = sessionEvents[i];
93
+ if (!isUserBoundary(ev)) continue;
94
+ if (!isWithinUpperBound(ev)) continue;
95
+ const ms = parseTimestamp(ev.timestamp);
96
+ if (ms === null) continue;
97
+ if (ms >= submittedMs - BOUNDARY_TIMESTAMP_TOLERANCE_MS) {
98
+ return { boundary: ev, usedFallback: true };
99
+ }
100
+ }
101
+ return { boundary: null, usedFallback: false };
102
+ }
103
+ function asNumberOrNull(value) {
104
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
105
+ }
106
+ function asStringOrNull(value) {
107
+ return typeof value === "string" ? value : null;
108
+ }
109
+ function extractAssistantMessage(event) {
110
+ const message = event.message;
111
+ if (!message || typeof message !== "object") return null;
112
+ const msg = message;
113
+ if (msg.role !== "assistant" && msg.role !== void 0) {
114
+ }
115
+ const content = Array.isArray(msg.content) ? msg.content : [];
116
+ const usage = msg.usage && typeof msg.usage === "object" ? msg.usage : null;
117
+ return {
118
+ id: asStringOrNull(msg.id),
119
+ model: asStringOrNull(msg.model),
120
+ content,
121
+ usage
122
+ };
123
+ }
124
+ function usageIsComplete(usage) {
125
+ if (!usage) return false;
126
+ const hasInput = typeof usage.input_tokens === "number";
127
+ const hasOutput = typeof usage.output_tokens === "number";
128
+ const hasCacheRead = typeof usage.cache_read_input_tokens === "number";
129
+ return hasInput && hasOutput && hasCacheRead;
130
+ }
131
+ function buildModelCall(event, msg) {
132
+ const usage = msg.usage ?? {};
133
+ const cacheCreation = usage.cache_creation && typeof usage.cache_creation === "object" ? usage.cache_creation : null;
134
+ const has5m = cacheCreation && typeof cacheCreation.ephemeral_5m_input_tokens === "number";
135
+ const has1h = cacheCreation && typeof cacheCreation.ephemeral_1h_input_tokens === "number";
136
+ const cacheWrite5mTokens = has5m ? cacheCreation.ephemeral_5m_input_tokens : null;
137
+ const cacheWrite1hTokens = has1h ? cacheCreation.ephemeral_1h_input_tokens : null;
138
+ const splitAvailable = has5m || has1h;
139
+ const cacheWriteTokens = splitAvailable ? null : asNumberOrNull(usage.cache_creation_input_tokens);
140
+ return {
141
+ provider: "anthropic",
142
+ model: msg.model,
143
+ tier: asStringOrNull(usage.service_tier),
144
+ requestId: asStringOrNull(event.requestId),
145
+ timestamp: asStringOrNull(event.timestamp),
146
+ isSidechain: event.isSidechain === true,
147
+ inputTokens: asNumberOrNull(usage.input_tokens),
148
+ outputTokens: asNumberOrNull(usage.output_tokens),
149
+ cacheReadTokens: asNumberOrNull(usage.cache_read_input_tokens),
150
+ cacheWriteTokens,
151
+ cacheWrite5mTokens,
152
+ cacheWrite1hTokens,
153
+ reasoningTokens: null,
154
+ audioInputTokens: null,
155
+ imageInputTokens: null
156
+ };
157
+ }
158
+ function scanServerToolUses(content) {
159
+ const uses = [];
160
+ for (const block of content) {
161
+ if (!block || typeof block !== "object") continue;
162
+ const b = block;
163
+ const id = b.id;
164
+ if (typeof id !== "string" || !id.startsWith("srvtoolu_")) continue;
165
+ const name = typeof b.name === "string" ? b.name : "";
166
+ switch (name) {
167
+ case "web_search":
168
+ uses.push({ tool: "web_search", unit: "per_request", isKnown: true, id, source: "direct" });
169
+ break;
170
+ case "web_fetch":
171
+ uses.push({ tool: "web_fetch", unit: "per_request", isKnown: true, id, source: "direct" });
172
+ break;
173
+ case "code_execution":
174
+ uses.push({ tool: "code_execution", unit: "invocation", isKnown: true, id, source: "direct" });
175
+ break;
176
+ default:
177
+ uses.push({ tool: name || "unknown", unit: "invocation", isKnown: false, id, source: "direct" });
178
+ break;
179
+ }
180
+ }
181
+ return uses;
182
+ }
183
+ function buildClientToolNameMap(turnEvents) {
184
+ const map = /* @__PURE__ */ new Map();
185
+ for (const ev of turnEvents) {
186
+ if (ev.type !== "assistant") continue;
187
+ const msg = ev.message;
188
+ if (!msg || typeof msg !== "object") continue;
189
+ const content = msg.content;
190
+ if (!Array.isArray(content)) continue;
191
+ for (const block of content) {
192
+ if (!block || typeof block !== "object") continue;
193
+ const b = block;
194
+ if (b.type !== "tool_use") continue;
195
+ const id = b.id;
196
+ const name = b.name;
197
+ if (typeof id !== "string" || typeof name !== "string") continue;
198
+ if (!id.startsWith("toolu_")) continue;
199
+ map.set(id, name);
200
+ }
201
+ }
202
+ return map;
203
+ }
204
+ function scanEmbeddedServerToolUses(turnEvents, clientToolNames) {
205
+ const uses = [];
206
+ for (const ev of turnEvents) {
207
+ if (ev.type !== "user") continue;
208
+ const tur = ev.toolUseResult;
209
+ if (!tur || typeof tur !== "object") continue;
210
+ const results = tur.results;
211
+ if (!Array.isArray(results)) continue;
212
+ let parentToolName = "";
213
+ const userMsg = ev.message;
214
+ if (userMsg && typeof userMsg === "object") {
215
+ const userContent = userMsg.content;
216
+ if (Array.isArray(userContent)) {
217
+ for (const block of userContent) {
218
+ if (!block || typeof block !== "object") continue;
219
+ const b = block;
220
+ if (b.type !== "tool_result") continue;
221
+ const parentId = b.tool_use_id;
222
+ if (typeof parentId === "string") {
223
+ parentToolName = clientToolNames.get(parentId) ?? "";
224
+ break;
225
+ }
226
+ }
227
+ }
228
+ }
229
+ for (const entry of results) {
230
+ if (!entry || typeof entry !== "object") continue;
231
+ const srvId = entry.tool_use_id;
232
+ if (typeof srvId !== "string" || !srvId.startsWith("srvtoolu_")) continue;
233
+ if (parentToolName === "WebFetch") {
234
+ uses.push({ tool: "web_fetch", unit: "per_request", isKnown: true, id: srvId, source: "embedded" });
235
+ } else {
236
+ uses.push({ tool: "web_search", unit: "per_request", isKnown: true, id: srvId, source: "embedded" });
237
+ }
238
+ }
239
+ }
240
+ return uses;
241
+ }
242
+ function dedupeByServerToolId(records) {
243
+ const seen = /* @__PURE__ */ new Map();
244
+ for (const r of records) {
245
+ const existing = seen.get(r.id);
246
+ if (!existing) {
247
+ seen.set(r.id, r);
248
+ continue;
249
+ }
250
+ if (existing.source === "embedded" && r.source === "direct") {
251
+ seen.set(r.id, r);
252
+ }
253
+ }
254
+ return Array.from(seen.values());
255
+ }
256
+ function aggregateServerTools(uses) {
257
+ const map = /* @__PURE__ */ new Map();
258
+ let sawUnknown = false;
259
+ let sawEmbedded = false;
260
+ for (const use of uses) {
261
+ if (!use.isKnown) sawUnknown = true;
262
+ if (use.source === "embedded") sawEmbedded = true;
263
+ const key = `anthropic|${use.tool}|${use.unit}`;
264
+ const existing = map.get(key);
265
+ if (existing) {
266
+ existing.quantity += 1;
267
+ } else {
268
+ map.set(key, { provider: "anthropic", tool: use.tool, unit: use.unit, quantity: 1 });
269
+ }
270
+ }
271
+ return { serverTools: Array.from(map.values()), sawUnknown, sawEmbedded };
272
+ }
273
+ function sumCrossCheckCounts(usageBlocks) {
274
+ const totals = /* @__PURE__ */ new Map();
275
+ const keyFor = (raw) => {
276
+ if (raw === "web_search_requests") return "web_search";
277
+ if (raw === "web_fetch_requests") return "web_fetch";
278
+ return null;
279
+ };
280
+ for (const usage of usageBlocks) {
281
+ const stu = usage.server_tool_use;
282
+ if (!stu || typeof stu !== "object") continue;
283
+ for (const [rawKey, rawVal] of Object.entries(stu)) {
284
+ const mapped = keyFor(rawKey);
285
+ if (!mapped) continue;
286
+ if (typeof rawVal !== "number" || !Number.isFinite(rawVal)) continue;
287
+ totals.set(mapped, (totals.get(mapped) ?? 0) + rawVal);
288
+ }
289
+ }
290
+ return totals;
291
+ }
292
+ function primaryToolCounts(serverTools) {
293
+ const map = /* @__PURE__ */ new Map();
294
+ for (const entry of serverTools) {
295
+ map.set(entry.tool, (map.get(entry.tool) ?? 0) + entry.quantity);
296
+ }
297
+ return map;
298
+ }
299
+ function resolveVersion(events) {
300
+ for (const ev of events) {
301
+ if (typeof ev.version === "string" && ev.version.trim()) return ev.version.trim();
302
+ }
303
+ return null;
304
+ }
305
+ function buildTurnUsage(args) {
306
+ const assistantEvents = args.turnEvents.filter((ev) => ev.type === "assistant");
307
+ if (assistantEvents.length === 0) {
308
+ return { ok: false, reason: "no_messages_for_turn" };
309
+ }
310
+ const warnings = [...args.initialWarnings];
311
+ const calls = [];
312
+ const usageBlocks = [];
313
+ let anyIncomplete = false;
314
+ let anyComplete = false;
315
+ let sawLumpSumFallback = false;
316
+ const collectedServerToolUses = [];
317
+ const mainModels = /* @__PURE__ */ new Set();
318
+ const sidechainModels = /* @__PURE__ */ new Set();
319
+ for (const ev of assistantEvents) {
320
+ const msg = extractAssistantMessage(ev);
321
+ if (!msg) continue;
322
+ if (usageIsComplete(msg.usage)) {
323
+ anyComplete = true;
324
+ } else {
325
+ anyIncomplete = true;
326
+ }
327
+ if (msg.usage) usageBlocks.push(msg.usage);
328
+ const call = buildModelCall(ev, msg);
329
+ calls.push(call);
330
+ if (call.cacheWrite5mTokens === null && call.cacheWrite1hTokens === null && call.cacheWriteTokens !== null) {
331
+ sawLumpSumFallback = true;
332
+ }
333
+ collectedServerToolUses.push(...scanServerToolUses(msg.content));
334
+ if (call.model) {
335
+ if (call.isSidechain) sidechainModels.add(call.model);
336
+ else mainModels.add(call.model);
337
+ }
338
+ }
339
+ if (calls.length === 0) {
340
+ return { ok: false, reason: "no_messages_for_turn" };
341
+ }
342
+ if (sawLumpSumFallback) {
343
+ warnings.push({
344
+ code: "cache_split_unavailable",
345
+ message: "Assistant message reported lump-sum cache_creation_input_tokens without the 5m/1h split."
346
+ });
347
+ }
348
+ const clientToolNames = buildClientToolNameMap(args.turnEvents);
349
+ const embeddedUses = scanEmbeddedServerToolUses(args.turnEvents, clientToolNames);
350
+ const merged = dedupeByServerToolId([...collectedServerToolUses, ...embeddedUses]);
351
+ const { serverTools, sawUnknown, sawEmbedded } = aggregateServerTools(merged);
352
+ if (sawUnknown) {
353
+ warnings.push({
354
+ code: "unknown_server_tool",
355
+ message: "Encountered a server tool whose name is not in the known list (web_search, web_fetch, code_execution)."
356
+ });
357
+ }
358
+ const crossCheck = sumCrossCheckCounts(usageBlocks);
359
+ const primary = primaryToolCounts(serverTools);
360
+ const allTools = /* @__PURE__ */ new Set([...crossCheck.keys(), ...primary.keys()]);
361
+ for (const tool of allTools) {
362
+ const crossVal = crossCheck.get(tool) ?? 0;
363
+ const primVal = primary.get(tool) ?? 0;
364
+ if (crossVal === primVal) continue;
365
+ if (sawEmbedded && crossVal === 0) continue;
366
+ warnings.push({
367
+ code: "server_tool_count_mismatch",
368
+ message: `Server-tool ${tool} count mismatch: srvtoolu_ scan=${primVal}, usage.server_tool_use=${crossVal}. Trusting srvtoolu_ count.`
369
+ });
370
+ break;
371
+ }
372
+ const subagentMismatch = mainModels.size > 0 && sidechainModels.size > 0 && [...sidechainModels].some((m) => !mainModels.has(m));
373
+ if (subagentMismatch) {
374
+ warnings.push({
375
+ code: "subagent_model_differs",
376
+ message: "At least one sidechain ModelCall uses a model different from the main-chain model in this turn."
377
+ });
378
+ }
379
+ let confidence;
380
+ if (!anyComplete && anyIncomplete) confidence = "unknown";
381
+ else if (anyIncomplete) confidence = "partial";
382
+ else if (anyComplete) confidence = "exact";
383
+ else confidence = "unknown";
384
+ if (args.checkSubsequentEvent) {
385
+ const lastAssistantMs = (() => {
386
+ const msList = assistantEvents.map((ev) => parseTimestamp(ev.timestamp)).filter((ms) => ms !== null);
387
+ return msList.length ? Math.max(...msList) : null;
388
+ })();
389
+ const hasSubsequent = lastAssistantMs !== null && args.sessionEvents.some((ev) => {
390
+ const ms = parseTimestamp(ev.timestamp);
391
+ if (ms === null || ms <= lastAssistantMs) return false;
392
+ if (args.upperMs !== null && ms >= args.upperMs) return false;
393
+ return true;
394
+ });
395
+ if (!hasSubsequent && confidence === "exact") {
396
+ confidence = "partial";
397
+ warnings.push({
398
+ code: "transcript_truncated",
399
+ message: "Previous turn has no subsequent event after its last assistant message; transcript may be truncated."
400
+ });
401
+ }
402
+ }
403
+ const resolvedVersion = args.agent.version ?? resolveVersion(args.turnEvents) ?? resolveVersion(args.sessionEvents);
404
+ const usage = {
405
+ schemaVersion: 1,
406
+ capturedAt: args.capturedAt,
407
+ captureSource: args.captureSource,
408
+ confidence,
409
+ agent: {
410
+ name: args.agent.name,
411
+ version: resolvedVersion,
412
+ sessionId: args.agent.sessionId,
413
+ turnId: args.agent.turnId,
414
+ plan: args.agent.plan
415
+ },
416
+ calls,
417
+ serverTools,
418
+ warnings,
419
+ extensions: args.extensions
420
+ };
421
+ return { ok: true, usage };
422
+ }
423
+ function harvestClaudeCodeUsage(input) {
424
+ const sessionEvents = input.events.filter((ev) => ev.sessionId === input.sessionId);
425
+ const upperMs = input.nextBoundaryAt ? parseTimestamp(input.nextBoundaryAt) : null;
426
+ const { boundary, usedFallback } = findBoundary(
427
+ sessionEvents,
428
+ input.promptText,
429
+ input.submittedAt,
430
+ upperMs
431
+ );
432
+ if (!boundary) {
433
+ return { ok: false, reason: "no_user_boundary_found" };
434
+ }
435
+ const boundaryMs = parseTimestamp(boundary.timestamp);
436
+ if (boundaryMs === null) {
437
+ return { ok: false, reason: "no_user_boundary_found" };
438
+ }
439
+ const turnEvents = sessionEvents.filter((ev) => {
440
+ const ms = parseTimestamp(ev.timestamp);
441
+ if (ms === null || ms <= boundaryMs) return false;
442
+ if (upperMs !== null && ms >= upperMs) return false;
443
+ return true;
444
+ });
445
+ const initialWarnings = [];
446
+ if (usedFallback) {
447
+ initialWarnings.push({
448
+ code: "transcript_truncated",
449
+ message: "Prompt-text equality match failed; used timestamp-tolerance fallback to locate the user boundary."
450
+ });
451
+ }
452
+ const built = buildTurnUsage({
453
+ sessionEvents,
454
+ turnEvents,
455
+ upperMs,
456
+ initialWarnings,
457
+ agent: input.agent,
458
+ capturedAt: input.capturedAt,
459
+ captureSource: "hook",
460
+ extensions: input.extensions,
461
+ checkSubsequentEvent: input.checkSubsequentEvent === true
462
+ });
463
+ if (!built.ok) return built;
464
+ return {
465
+ ok: true,
466
+ usage: built.usage,
467
+ boundaryAt: typeof boundary.timestamp === "string" ? boundary.timestamp : null
468
+ };
469
+ }
470
+ function sliceTranscriptIntoTurns(input) {
471
+ const sessionEvents = input.events.filter((ev) => ev.sessionId === input.sessionId);
472
+ const boundaries = sessionEvents.filter(isUserBoundary);
473
+ const result = [];
474
+ for (let i = 0; i < boundaries.length; i++) {
475
+ const boundary = boundaries[i];
476
+ const nextBoundary = boundaries[i + 1] ?? null;
477
+ const boundaryMs = parseTimestamp(boundary.timestamp);
478
+ if (boundaryMs === null) continue;
479
+ const occurredAt = asStringOrNull(boundary.timestamp);
480
+ if (occurredAt === null) continue;
481
+ const upperMs = nextBoundary ? parseTimestamp(nextBoundary.timestamp) : null;
482
+ const turnEvents = sessionEvents.filter((ev) => {
483
+ const ms = parseTimestamp(ev.timestamp);
484
+ if (ms === null || ms <= boundaryMs) return false;
485
+ if (upperMs !== null && ms >= upperMs) return false;
486
+ return true;
487
+ });
488
+ const promptId = asStringOrNull(boundary.promptId);
489
+ const promptText = extractUserContentText(boundary.message);
490
+ const assistantText = extractAssistantContentText(turnEvents);
491
+ const gitBranch = asStringOrNull(boundary.gitBranch);
492
+ const cwd = asStringOrNull(boundary.cwd);
493
+ const built = buildTurnUsage({
494
+ sessionEvents,
495
+ turnEvents,
496
+ upperMs,
497
+ initialWarnings: [],
498
+ agent: {
499
+ name: "claude-code",
500
+ version: null,
501
+ sessionId: input.sessionId,
502
+ turnId: promptId,
503
+ plan: null
504
+ },
505
+ capturedAt: input.capturedAt,
506
+ captureSource: "historical_import",
507
+ extensions: input.extensions ?? null,
508
+ checkSubsequentEvent: false
509
+ });
510
+ if (!built.ok) continue;
511
+ result.push({
512
+ sessionId: input.sessionId,
513
+ promptId,
514
+ promptText,
515
+ assistantText,
516
+ occurredAt,
517
+ gitBranch,
518
+ cwd,
519
+ usage: built.usage
520
+ });
521
+ }
522
+ return result;
523
+ }
524
+ export {
525
+ HARVESTER_WARNING_CODES,
526
+ harvestClaudeCodeUsage,
527
+ readAndParseTranscript,
528
+ sliceTranscriptIntoTurns
529
+ };
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { S as SessionStore, a as StoredSession, c as createStoredSessionTokenPro
4
4
  export { createLocalSessionStore, createSupabaseAuthHelpers } from './auth.js';
5
5
  export { AgentMemoryItem, AgentMemoryKind, AgentMemorySearchItem, AgentMemorySearchResponse, AgentMemorySummary, AgentMemoryTimelineResponse, ApiClient, AppContext, AppContextAccessPath, AppReconcileResponse, Bundle, BundlePlatform, BundleStatus, ChangeStepDiffResponse, InitiateBundleRequest, InvitationRecord, MergeRequest, MergeRequestReview, MergeRequestStatus, ReconcilePreflightResponse, SyncLocalResponse, SyncUpstreamResponse, createApiClient } from './api.js';
6
6
  import 'zod';
7
+ import './contracts-CHmD-fMj.js';
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-R7FVSCQW.js";
3
+ } from "./chunk-US5SM7ZC.js";
4
4
  import {
5
5
  createLocalSessionStore,
6
6
  createStoredSessionTokenProvider,
7
7
  createSupabaseAuthHelpers,
8
8
  shouldRefreshSoon,
9
9
  storedSessionSchema
10
- } from "./chunk-EVWDYCBL.js";
10
+ } from "./chunk-P6JHXOV4.js";
11
11
  import {
12
12
  configSchema,
13
13
  resolveConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixhq/core",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Remix core library",
5
5
  "homepage": "https://github.com/RemixDotOne/remix-core",
6
6
  "license": "MIT",
@@ -46,6 +46,10 @@
46
46
  "types": "./dist/errors.d.ts",
47
47
  "import": "./dist/errors.js"
48
48
  },
49
+ "./history": {
50
+ "types": "./dist/history.d.ts",
51
+ "import": "./dist/history.js"
52
+ },
49
53
  "./repo": {
50
54
  "types": "./dist/repo.d.ts",
51
55
  "import": "./dist/repo.js"
@@ -55,8 +59,9 @@
55
59
  "dist"
56
60
  ],
57
61
  "scripts": {
58
- "build": "tsup src/index.ts src/api.ts src/auth.ts src/binding.ts src/collab.ts src/config.ts src/errors.ts src/repo.ts --dts --format esm",
62
+ "build": "tsup src/index.ts src/api.ts src/auth.ts src/binding.ts src/collab.ts src/config.ts src/errors.ts src/history.ts src/repo.ts --dts --format esm --clean",
59
63
  "typecheck": "tsc -p tsconfig.json --noEmit",
64
+ "test": "vitest run",
60
65
  "prepack": "npm run build"
61
66
  },
62
67
  "dependencies": {
@@ -67,6 +72,7 @@
67
72
  "devDependencies": {
68
73
  "@types/node": "^24.3.0",
69
74
  "tsup": "^8.5.0",
70
- "typescript": "^5.9.2"
75
+ "typescript": "^5.9.2",
76
+ "vitest": "^2.1.4"
71
77
  }
72
78
  }
File without changes