reasonix 0.32.0 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/chat-Q5ZCVIOO.js +39 -0
- package/dist/cli/chunk-2AWTGJ2C.js +110 -0
- package/dist/cli/chunk-2AWTGJ2C.js.map +1 -0
- package/dist/cli/chunk-3Q3C4W66.js +30 -0
- package/dist/cli/chunk-3Q3C4W66.js.map +1 -0
- package/dist/cli/chunk-4DCHFFEY.js +149 -0
- package/dist/cli/chunk-4DCHFFEY.js.map +1 -0
- package/dist/cli/chunk-5X7LZJDE.js +36 -0
- package/dist/cli/chunk-5X7LZJDE.js.map +1 -0
- package/dist/cli/chunk-63KAV5DX.js +106 -0
- package/dist/cli/chunk-63KAV5DX.js.map +1 -0
- package/dist/cli/chunk-6TMHAK5D.js +576 -0
- package/dist/cli/chunk-6TMHAK5D.js.map +1 -0
- package/dist/cli/chunk-APPB3ZPQ.js +43 -0
- package/dist/cli/chunk-APPB3ZPQ.js.map +1 -0
- package/dist/cli/chunk-BQNUJJN7.js +42 -0
- package/dist/cli/chunk-BQNUJJN7.js.map +1 -0
- package/dist/cli/chunk-CPOV2O73.js +39 -0
- package/dist/cli/chunk-CPOV2O73.js.map +1 -0
- package/dist/cli/chunk-D5DKXIP5.js +368 -0
- package/dist/cli/chunk-D5DKXIP5.js.map +1 -0
- package/dist/cli/chunk-DFP4YSVM.js +247 -0
- package/dist/cli/chunk-DFP4YSVM.js.map +1 -0
- package/dist/cli/chunk-DULSP7JH.js +410 -0
- package/dist/cli/chunk-DULSP7JH.js.map +1 -0
- package/dist/cli/chunk-FM57FNPJ.js +46 -0
- package/dist/cli/chunk-FM57FNPJ.js.map +1 -0
- package/dist/cli/chunk-FWGEHRB7.js +54 -0
- package/dist/cli/chunk-FWGEHRB7.js.map +1 -0
- package/dist/cli/chunk-FXGQ5NHE.js +513 -0
- package/dist/cli/chunk-FXGQ5NHE.js.map +1 -0
- package/dist/cli/chunk-G3XNWSFN.js +53 -0
- package/dist/cli/chunk-G3XNWSFN.js.map +1 -0
- package/dist/cli/chunk-I6YIAK6C.js +757 -0
- package/dist/cli/chunk-I6YIAK6C.js.map +1 -0
- package/dist/cli/chunk-J5VLP23S.js +94 -0
- package/dist/cli/chunk-J5VLP23S.js.map +1 -0
- package/dist/cli/chunk-KMWKGPFZ.js +303 -0
- package/dist/cli/chunk-KMWKGPFZ.js.map +1 -0
- package/dist/cli/chunk-MDHVWCJ4.js +14965 -0
- package/dist/cli/chunk-MDHVWCJ4.js.map +1 -0
- package/dist/cli/chunk-MHDNZXJJ.js +48 -0
- package/dist/cli/chunk-MHDNZXJJ.js.map +1 -0
- package/dist/cli/chunk-ORM6PK57.js +140 -0
- package/dist/cli/chunk-ORM6PK57.js.map +1 -0
- package/dist/cli/chunk-Q6YFXW7H.js +4986 -0
- package/dist/cli/chunk-Q6YFXW7H.js.map +1 -0
- package/dist/cli/chunk-QGE6AF76.js +1467 -0
- package/dist/cli/chunk-QGE6AF76.js.map +1 -0
- package/dist/cli/chunk-RFX7TYVV.js +28 -0
- package/dist/cli/chunk-RFX7TYVV.js.map +1 -0
- package/dist/cli/chunk-RZILUXUC.js +940 -0
- package/dist/cli/chunk-RZILUXUC.js.map +1 -0
- package/dist/cli/chunk-SDE5U32Z.js +535 -0
- package/dist/cli/chunk-SDE5U32Z.js.map +1 -0
- package/dist/cli/chunk-SOZE7V7V.js +340 -0
- package/dist/cli/chunk-SOZE7V7V.js.map +1 -0
- package/dist/cli/chunk-U3V2ZQ5J.js +479 -0
- package/dist/cli/chunk-U3V2ZQ5J.js.map +1 -0
- package/dist/cli/chunk-W4LDFAZ6.js +1544 -0
- package/dist/cli/chunk-W4LDFAZ6.js.map +1 -0
- package/dist/cli/chunk-WBDE4IRI.js +208 -0
- package/dist/cli/chunk-WBDE4IRI.js.map +1 -0
- package/dist/cli/chunk-XHQIK7B6.js +189 -0
- package/dist/cli/chunk-XHQIK7B6.js.map +1 -0
- package/dist/cli/chunk-XJLZ4HKU.js +307 -0
- package/dist/cli/chunk-XJLZ4HKU.js.map +1 -0
- package/dist/cli/chunk-ZPTSJGX5.js +88 -0
- package/dist/cli/chunk-ZPTSJGX5.js.map +1 -0
- package/dist/cli/chunk-ZTLZO42A.js +231 -0
- package/dist/cli/chunk-ZTLZO42A.js.map +1 -0
- package/dist/cli/code-DLR77NPZ.js +151 -0
- package/dist/cli/code-DLR77NPZ.js.map +1 -0
- package/dist/cli/commands-JWT2MWVH.js +352 -0
- package/dist/cli/commands-JWT2MWVH.js.map +1 -0
- package/dist/cli/commit-RPZBOZS2.js +288 -0
- package/dist/cli/commit-RPZBOZS2.js.map +1 -0
- package/dist/cli/diff-NTEHCSDW.js +145 -0
- package/dist/cli/diff-NTEHCSDW.js.map +1 -0
- package/dist/cli/doctor-3TGB2NZN.js +19 -0
- package/dist/cli/doctor-3TGB2NZN.js.map +1 -0
- package/dist/cli/events-P27CX7LN.js +338 -0
- package/dist/cli/events-P27CX7LN.js.map +1 -0
- package/dist/cli/index.js +80 -33693
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-ARTNQ24O.js +266 -0
- package/dist/cli/mcp-ARTNQ24O.js.map +1 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js +163 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js.map +1 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js +103 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js.map +1 -0
- package/dist/cli/{prompt-XHICFAYN.js → prompt-V47QKSAR.js} +3 -2
- package/dist/cli/prompt-V47QKSAR.js.map +1 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js +42 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js.map +1 -0
- package/dist/cli/replay-Q43DSMG6.js +273 -0
- package/dist/cli/replay-Q43DSMG6.js.map +1 -0
- package/dist/cli/run-JMEOTQCG.js +215 -0
- package/dist/cli/run-JMEOTQCG.js.map +1 -0
- package/dist/cli/server-SYC3OVOP.js +2967 -0
- package/dist/cli/server-SYC3OVOP.js.map +1 -0
- package/dist/cli/sessions-MOJAALJI.js +102 -0
- package/dist/cli/sessions-MOJAALJI.js.map +1 -0
- package/dist/cli/setup-CCJZAWTY.js +404 -0
- package/dist/cli/setup-CCJZAWTY.js.map +1 -0
- package/dist/cli/stats-5RJCATCE.js +12 -0
- package/dist/cli/stats-5RJCATCE.js.map +1 -0
- package/dist/cli/update-4TJWRUIN.js +90 -0
- package/dist/cli/update-4TJWRUIN.js.map +1 -0
- package/dist/cli/version-3MYFE4G6.js +29 -0
- package/dist/cli/version-3MYFE4G6.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +493 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-VWFJNLIK.js +0 -1031
- package/dist/cli/chunk-VWFJNLIK.js.map +0 -1
- /package/dist/cli/{prompt-XHICFAYN.js.map → chat-Q5ZCVIOO.js.map} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
Usage
|
|
4
|
+
} from "./chunk-KMWKGPFZ.js";
|
|
5
|
+
import {
|
|
6
|
+
claudeEquivalentCost,
|
|
7
|
+
costUsd,
|
|
8
|
+
inputCostUsd,
|
|
9
|
+
outputCostUsd
|
|
10
|
+
} from "./chunk-ORM6PK57.js";
|
|
11
|
+
|
|
12
|
+
// src/transcript/log.ts
|
|
13
|
+
import { createWriteStream, readFileSync } from "fs";
|
|
14
|
+
function recordFromLoopEvent(ev, extra) {
|
|
15
|
+
const rec = {
|
|
16
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17
|
+
turn: ev.turn,
|
|
18
|
+
role: ev.role,
|
|
19
|
+
content: ev.content
|
|
20
|
+
};
|
|
21
|
+
if (ev.toolName !== void 0) rec.tool = ev.toolName;
|
|
22
|
+
if (ev.toolArgs !== void 0) rec.args = ev.toolArgs;
|
|
23
|
+
if (ev.error !== void 0) rec.error = ev.error;
|
|
24
|
+
if (ev.stats) {
|
|
25
|
+
rec.usage = {
|
|
26
|
+
prompt_tokens: ev.stats.usage.promptTokens,
|
|
27
|
+
completion_tokens: ev.stats.usage.completionTokens,
|
|
28
|
+
total_tokens: ev.stats.usage.totalTokens,
|
|
29
|
+
prompt_cache_hit_tokens: ev.stats.usage.promptCacheHitTokens,
|
|
30
|
+
prompt_cache_miss_tokens: ev.stats.usage.promptCacheMissTokens
|
|
31
|
+
};
|
|
32
|
+
rec.cost = ev.stats.cost;
|
|
33
|
+
rec.model = ev.stats.model;
|
|
34
|
+
rec.prefixHash = extra.prefixHash;
|
|
35
|
+
} else if (ev.role === "assistant_final") {
|
|
36
|
+
rec.model = extra.model;
|
|
37
|
+
rec.prefixHash = extra.prefixHash;
|
|
38
|
+
}
|
|
39
|
+
return rec;
|
|
40
|
+
}
|
|
41
|
+
function writeRecord(stream, record) {
|
|
42
|
+
stream.write(`${JSON.stringify(record)}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
function writeMeta(stream, meta) {
|
|
46
|
+
const line = { role: "_meta", meta };
|
|
47
|
+
stream.write(`${JSON.stringify(line)}
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
function openTranscriptFile(path, meta) {
|
|
51
|
+
const stream = createWriteStream(path, { flags: "a" });
|
|
52
|
+
writeMeta(stream, meta);
|
|
53
|
+
return stream;
|
|
54
|
+
}
|
|
55
|
+
function readTranscript(path) {
|
|
56
|
+
const raw = readFileSync(path, "utf8");
|
|
57
|
+
return parseTranscript(raw);
|
|
58
|
+
}
|
|
59
|
+
function parseTranscript(raw) {
|
|
60
|
+
const out = { meta: null, records: [] };
|
|
61
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
62
|
+
const trimmed = line.trim();
|
|
63
|
+
if (!trimmed) continue;
|
|
64
|
+
let obj;
|
|
65
|
+
try {
|
|
66
|
+
obj = JSON.parse(trimmed);
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!obj || typeof obj !== "object") continue;
|
|
71
|
+
const rec = obj;
|
|
72
|
+
if (rec.role === "_meta" && rec.meta && typeof rec.meta === "object") {
|
|
73
|
+
out.meta = rec.meta;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (typeof rec.ts === "string" && typeof rec.turn === "number" && typeof rec.role === "string" && typeof rec.content === "string") {
|
|
77
|
+
out.records.push(rec);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/transcript/replay.ts
|
|
84
|
+
function groupRecordsByTurn(records) {
|
|
85
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const rec of records) {
|
|
87
|
+
const list = byTurn.get(rec.turn);
|
|
88
|
+
if (list) list.push(rec);
|
|
89
|
+
else byTurn.set(rec.turn, [rec]);
|
|
90
|
+
}
|
|
91
|
+
return [...byTurn.entries()].sort(([a], [b]) => a - b).map(([turn, records2]) => ({ turn, records: records2 }));
|
|
92
|
+
}
|
|
93
|
+
function computeCumulativeStats(pages, upToIdx) {
|
|
94
|
+
if (upToIdx < 0) return computeReplayStats([]);
|
|
95
|
+
const flat = [];
|
|
96
|
+
for (let i = 0; i <= upToIdx && i < pages.length; i++) {
|
|
97
|
+
const records = pages[i]?.records;
|
|
98
|
+
if (records) flat.push(...records);
|
|
99
|
+
}
|
|
100
|
+
return computeReplayStats(flat);
|
|
101
|
+
}
|
|
102
|
+
function replayFromFile(path) {
|
|
103
|
+
const parsed = readTranscript(path);
|
|
104
|
+
return { parsed, stats: computeReplayStats(parsed.records) };
|
|
105
|
+
}
|
|
106
|
+
function computeReplayStats(records) {
|
|
107
|
+
const turns = [];
|
|
108
|
+
const models = /* @__PURE__ */ new Set();
|
|
109
|
+
const prefixHashes = /* @__PURE__ */ new Set();
|
|
110
|
+
let userTurns = 0;
|
|
111
|
+
let toolCalls = 0;
|
|
112
|
+
for (const rec of records) {
|
|
113
|
+
if (rec.role === "user") userTurns++;
|
|
114
|
+
else if (rec.role === "tool") toolCalls++;
|
|
115
|
+
else if (rec.role === "assistant_final") {
|
|
116
|
+
if (rec.model) models.add(rec.model);
|
|
117
|
+
if (rec.prefixHash) prefixHashes.add(rec.prefixHash);
|
|
118
|
+
if (rec.usage && rec.model) {
|
|
119
|
+
const u = new Usage(
|
|
120
|
+
rec.usage.prompt_tokens ?? 0,
|
|
121
|
+
rec.usage.completion_tokens ?? 0,
|
|
122
|
+
rec.usage.total_tokens ?? 0,
|
|
123
|
+
rec.usage.prompt_cache_hit_tokens ?? 0,
|
|
124
|
+
rec.usage.prompt_cache_miss_tokens ?? 0
|
|
125
|
+
);
|
|
126
|
+
turns.push({
|
|
127
|
+
turn: rec.turn,
|
|
128
|
+
model: rec.model,
|
|
129
|
+
usage: u,
|
|
130
|
+
// `rec.cost` wins when present — honors whatever the writer computed
|
|
131
|
+
// even if pricing tables have since changed. Only recompute when
|
|
132
|
+
// the transcript didn't record it (old format).
|
|
133
|
+
cost: rec.cost ?? costUsd(rec.model, u),
|
|
134
|
+
cacheHitRatio: u.cacheHitRatio
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
perTurn: turns,
|
|
141
|
+
models: [...models],
|
|
142
|
+
prefixHashes: [...prefixHashes],
|
|
143
|
+
userTurns,
|
|
144
|
+
toolCalls,
|
|
145
|
+
...summarizeTurns(turns)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function summarizeTurns(turns) {
|
|
149
|
+
const totalCost = turns.reduce((s, t) => s + t.cost, 0);
|
|
150
|
+
const totalInput = turns.reduce((s, t) => s + inputCostUsd(t.model, t.usage), 0);
|
|
151
|
+
const totalOutput = turns.reduce((s, t) => s + outputCostUsd(t.model, t.usage), 0);
|
|
152
|
+
const totalClaude = turns.reduce((s, t) => s + claudeEquivalentCost(t.usage), 0);
|
|
153
|
+
let hit = 0;
|
|
154
|
+
let miss = 0;
|
|
155
|
+
for (const t of turns) {
|
|
156
|
+
hit += t.usage.promptCacheHitTokens;
|
|
157
|
+
miss += t.usage.promptCacheMissTokens;
|
|
158
|
+
}
|
|
159
|
+
const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;
|
|
160
|
+
const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;
|
|
161
|
+
const lastTurn = turns[turns.length - 1];
|
|
162
|
+
return {
|
|
163
|
+
turns: turns.length,
|
|
164
|
+
totalCostUsd: round(totalCost, 6),
|
|
165
|
+
totalInputCostUsd: round(totalInput, 6),
|
|
166
|
+
totalOutputCostUsd: round(totalOutput, 6),
|
|
167
|
+
claudeEquivalentUsd: round(totalClaude, 6),
|
|
168
|
+
savingsVsClaudePct: round(savingsVsClaude * 100, 2),
|
|
169
|
+
cacheHitRatio: round(cacheHitRatio, 4),
|
|
170
|
+
lastPromptTokens: lastTurn?.usage.promptTokens ?? 0,
|
|
171
|
+
lastTurnCostUsd: round(lastTurn?.cost ?? 0, 6)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function round(n, digits) {
|
|
175
|
+
const f = 10 ** digits;
|
|
176
|
+
return Math.round(n * f) / f;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
recordFromLoopEvent,
|
|
181
|
+
writeRecord,
|
|
182
|
+
openTranscriptFile,
|
|
183
|
+
readTranscript,
|
|
184
|
+
groupRecordsByTurn,
|
|
185
|
+
computeCumulativeStats,
|
|
186
|
+
replayFromFile,
|
|
187
|
+
computeReplayStats
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=chunk-XHQIK7B6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/transcript/log.ts","../../src/transcript/replay.ts"],"sourcesContent":["/** Transcripts are receipts (cost/usage/prefix); sessions are memory (ChatMessages). Don't conflate. */\n\nimport { type WriteStream, createWriteStream, readFileSync } from \"node:fs\";\nimport type { LoopEvent } from \"../loop.js\";\nimport type { RawUsage } from \"../types.js\";\n\nexport interface TranscriptRecord {\n /** ISO-8601 timestamp at emit time. */\n ts: string;\n /** 1-based turn number within the session. */\n turn: number;\n /** LoopEvent role — \"assistant_delta\" | \"assistant_final\" | \"tool\" | \"done\" | ... */\n role: string;\n /** For assistant events, the final (or delta) text; for tool events, the tool result. */\n content: string;\n /** Tool name (role === \"tool\"). */\n tool?: string;\n /** JSON-string args the model sent for a tool call (role === \"tool\"). Persisted so diff can explain *why* two runs made different calls. */\n args?: string;\n /** DeepSeek token-usage snapshot (role === \"assistant_final\"). */\n usage?: RawUsage;\n /** USD cost of this turn (role === \"assistant_final\"). */\n cost?: number;\n /** Model id that produced this turn. */\n model?: string;\n /** Lets diff attribute cache-hit delta to log stability vs prompt change. */\n prefixHash?: string;\n /** Optional error message (role === \"error\"). */\n error?: string;\n}\n\nexport interface TranscriptMeta {\n version: 1;\n source: string; // e.g. \"reasonix chat\", \"bench/baseline\", \"bench/reasonix\"\n model?: string;\n task?: string;\n mode?: string;\n repeat?: number;\n startedAt: string;\n}\n\ninterface MetaLine {\n role: \"_meta\";\n meta: TranscriptMeta;\n}\n\nexport interface ReadTranscriptResult {\n meta: TranscriptMeta | null;\n records: TranscriptRecord[];\n}\n\nexport function recordFromLoopEvent(\n ev: LoopEvent,\n extra: { model: string; prefixHash: string },\n): TranscriptRecord {\n const rec: TranscriptRecord = {\n ts: new Date().toISOString(),\n turn: ev.turn,\n role: ev.role,\n content: ev.content,\n };\n if (ev.toolName !== undefined) rec.tool = ev.toolName;\n if (ev.toolArgs !== undefined) rec.args = ev.toolArgs;\n if (ev.error !== undefined) rec.error = ev.error;\n if (ev.stats) {\n rec.usage = {\n prompt_tokens: ev.stats.usage.promptTokens,\n completion_tokens: ev.stats.usage.completionTokens,\n total_tokens: ev.stats.usage.totalTokens,\n prompt_cache_hit_tokens: ev.stats.usage.promptCacheHitTokens,\n prompt_cache_miss_tokens: ev.stats.usage.promptCacheMissTokens,\n };\n rec.cost = ev.stats.cost;\n rec.model = ev.stats.model;\n rec.prefixHash = extra.prefixHash;\n } else if (ev.role === \"assistant_final\") {\n // assistant_final without stats (shouldn't happen in the live loop but\n // might in test fixtures) — still persist model + prefix for continuity.\n rec.model = extra.model;\n rec.prefixHash = extra.prefixHash;\n }\n return rec;\n}\n\n/**\n * Append a record to an open write stream. Caller owns the stream lifecycle.\n */\nexport function writeRecord(stream: WriteStream, record: TranscriptRecord): void {\n stream.write(`${JSON.stringify(record)}\\n`);\n}\n\n/**\n * Write a _meta line to an open write stream. Call exactly once, at the top.\n */\nexport function writeMeta(stream: WriteStream, meta: TranscriptMeta): void {\n const line: MetaLine = { role: \"_meta\", meta };\n stream.write(`${JSON.stringify(line)}\\n`);\n}\n\n/**\n * Convenience: open a stream, write meta, return stream.\n */\nexport function openTranscriptFile(path: string, meta: TranscriptMeta): WriteStream {\n const stream = createWriteStream(path, { flags: \"a\" });\n writeMeta(stream, meta);\n return stream;\n}\n\n/** Tolerant: empty / malformed lines skipped, missing optionals OK — live chats may be mid-write. */\nexport function readTranscript(path: string): ReadTranscriptResult {\n const raw = readFileSync(path, \"utf8\");\n return parseTranscript(raw);\n}\n\nexport function parseTranscript(raw: string): ReadTranscriptResult {\n const out: ReadTranscriptResult = { meta: null, records: [] };\n for (const line of raw.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n let obj: unknown;\n try {\n obj = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (!obj || typeof obj !== \"object\") continue;\n const rec = obj as Record<string, unknown>;\n if (rec.role === \"_meta\" && rec.meta && typeof rec.meta === \"object\") {\n out.meta = rec.meta as TranscriptMeta;\n continue;\n }\n if (\n typeof rec.ts === \"string\" &&\n typeof rec.turn === \"number\" &&\n typeof rec.role === \"string\" &&\n typeof rec.content === \"string\"\n ) {\n out.records.push(rec as unknown as TranscriptRecord);\n }\n }\n return out;\n}\n","/** Reconstruct session economics from a transcript alone — offline audit, no API key. */\n\nimport { Usage } from \"../client.js\";\nimport {\n type SessionSummary,\n type TurnStats,\n claudeEquivalentCost,\n costUsd,\n inputCostUsd,\n outputCostUsd,\n} from \"../telemetry/stats.js\";\nimport { type ReadTranscriptResult, type TranscriptRecord, readTranscript } from \"./log.js\";\n\nexport interface TurnPage {\n turn: number;\n records: TranscriptRecord[];\n}\n\nexport function groupRecordsByTurn(records: TranscriptRecord[]): TurnPage[] {\n const byTurn = new Map<number, TranscriptRecord[]>();\n for (const rec of records) {\n const list = byTurn.get(rec.turn);\n if (list) list.push(rec);\n else byTurn.set(rec.turn, [rec]);\n }\n return [...byTurn.entries()]\n .sort(([a], [b]) => a - b)\n .map(([turn, records]) => ({ turn, records }));\n}\n\nexport function computeCumulativeStats(pages: TurnPage[], upToIdx: number): ReplayStats {\n if (upToIdx < 0) return computeReplayStats([]);\n const flat: TranscriptRecord[] = [];\n for (let i = 0; i <= upToIdx && i < pages.length; i++) {\n const records = pages[i]?.records;\n if (records) flat.push(...records);\n }\n return computeReplayStats(flat);\n}\n\nexport interface ReplayStats extends SessionSummary {\n /** Per-turn stats, in turn order. Only assistant_final records contribute. */\n perTurn: TurnStats[];\n /** Unique models that appeared in the transcript's assistant_final records. */\n models: string[];\n /** Unique prefix hashes that appeared. Length > 1 means the prefix churned (cache-hostile). */\n prefixHashes: string[];\n /** Count of user-role records (user turns issued). */\n userTurns: number;\n /** Count of tool-role records (tool calls executed). */\n toolCalls: number;\n}\n\nexport function replayFromFile(path: string): { parsed: ReadTranscriptResult; stats: ReplayStats } {\n const parsed = readTranscript(path);\n return { parsed, stats: computeReplayStats(parsed.records) };\n}\n\nexport function computeReplayStats(records: TranscriptRecord[]): ReplayStats {\n const turns: TurnStats[] = [];\n const models = new Set<string>();\n const prefixHashes = new Set<string>();\n let userTurns = 0;\n let toolCalls = 0;\n\n for (const rec of records) {\n if (rec.role === \"user\") userTurns++;\n else if (rec.role === \"tool\") toolCalls++;\n else if (rec.role === \"assistant_final\") {\n if (rec.model) models.add(rec.model);\n if (rec.prefixHash) prefixHashes.add(rec.prefixHash);\n if (rec.usage && rec.model) {\n const u = new Usage(\n rec.usage.prompt_tokens ?? 0,\n rec.usage.completion_tokens ?? 0,\n rec.usage.total_tokens ?? 0,\n rec.usage.prompt_cache_hit_tokens ?? 0,\n rec.usage.prompt_cache_miss_tokens ?? 0,\n );\n turns.push({\n turn: rec.turn,\n model: rec.model,\n usage: u,\n // `rec.cost` wins when present — honors whatever the writer computed\n // even if pricing tables have since changed. Only recompute when\n // the transcript didn't record it (old format).\n cost: rec.cost ?? costUsd(rec.model, u),\n cacheHitRatio: u.cacheHitRatio,\n });\n }\n }\n }\n\n return {\n perTurn: turns,\n models: [...models],\n prefixHashes: [...prefixHashes],\n userTurns,\n toolCalls,\n ...summarizeTurns(turns),\n };\n}\n\nfunction summarizeTurns(turns: TurnStats[]): SessionSummary {\n const totalCost = turns.reduce((s, t) => s + t.cost, 0);\n const totalInput = turns.reduce((s, t) => s + inputCostUsd(t.model, t.usage), 0);\n const totalOutput = turns.reduce((s, t) => s + outputCostUsd(t.model, t.usage), 0);\n const totalClaude = turns.reduce((s, t) => s + claudeEquivalentCost(t.usage), 0);\n let hit = 0;\n let miss = 0;\n for (const t of turns) {\n hit += t.usage.promptCacheHitTokens;\n miss += t.usage.promptCacheMissTokens;\n }\n const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;\n const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;\n const lastTurn = turns[turns.length - 1];\n return {\n turns: turns.length,\n totalCostUsd: round(totalCost, 6),\n totalInputCostUsd: round(totalInput, 6),\n totalOutputCostUsd: round(totalOutput, 6),\n claudeEquivalentUsd: round(totalClaude, 6),\n savingsVsClaudePct: round(savingsVsClaude * 100, 2),\n cacheHitRatio: round(cacheHitRatio, 4),\n lastPromptTokens: lastTurn?.usage.promptTokens ?? 0,\n lastTurnCostUsd: round(lastTurn?.cost ?? 0, 6),\n };\n}\n\nfunction round(n: number, digits: number): number {\n const f = 10 ** digits;\n return Math.round(n * f) / f;\n}\n"],"mappings":"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","names":["records"]}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
computeReplayStats
|
|
4
|
+
} from "./chunk-XHQIK7B6.js";
|
|
5
|
+
|
|
6
|
+
// src/transcript/diff.ts
|
|
7
|
+
function findNextDivergence(pairs, fromIdx) {
|
|
8
|
+
for (let i = fromIdx + 1; i < pairs.length; i++) {
|
|
9
|
+
if (pairs[i].kind !== "match") return i;
|
|
10
|
+
}
|
|
11
|
+
return -1;
|
|
12
|
+
}
|
|
13
|
+
function findPrevDivergence(pairs, fromIdx) {
|
|
14
|
+
const start = Math.min(fromIdx - 1, pairs.length - 1);
|
|
15
|
+
for (let i = start; i >= 0; i--) {
|
|
16
|
+
if (pairs[i].kind !== "match") return i;
|
|
17
|
+
}
|
|
18
|
+
return -1;
|
|
19
|
+
}
|
|
20
|
+
function diffTranscripts(a, b) {
|
|
21
|
+
const aSide = {
|
|
22
|
+
label: a.label,
|
|
23
|
+
meta: a.parsed.meta,
|
|
24
|
+
records: a.parsed.records,
|
|
25
|
+
stats: computeReplayStats(a.parsed.records)
|
|
26
|
+
};
|
|
27
|
+
const bSide = {
|
|
28
|
+
label: b.label,
|
|
29
|
+
meta: b.parsed.meta,
|
|
30
|
+
records: b.parsed.records,
|
|
31
|
+
stats: computeReplayStats(b.parsed.records)
|
|
32
|
+
};
|
|
33
|
+
const aByTurn = groupByTurn(a.parsed.records);
|
|
34
|
+
const bByTurn = groupByTurn(b.parsed.records);
|
|
35
|
+
const turns = [.../* @__PURE__ */ new Set([...aByTurn.keys(), ...bByTurn.keys()])].sort((x, y) => x - y);
|
|
36
|
+
const pairs = [];
|
|
37
|
+
let firstDivergenceTurn = null;
|
|
38
|
+
for (const turn of turns) {
|
|
39
|
+
const aGroup = aByTurn.get(turn) ?? { assistant: void 0, tools: [] };
|
|
40
|
+
const bGroup = bByTurn.get(turn) ?? { assistant: void 0, tools: [] };
|
|
41
|
+
const aAssistant = aGroup.assistant;
|
|
42
|
+
const bAssistant = bGroup.assistant;
|
|
43
|
+
const aTools = aGroup.tools;
|
|
44
|
+
const bTools = bGroup.tools;
|
|
45
|
+
let kind;
|
|
46
|
+
let divergenceNote;
|
|
47
|
+
if (!aAssistant && bAssistant) kind = "only_in_b";
|
|
48
|
+
else if (aAssistant && !bAssistant) kind = "only_in_a";
|
|
49
|
+
else if (!aAssistant && !bAssistant)
|
|
50
|
+
kind = "diverge";
|
|
51
|
+
else {
|
|
52
|
+
divergenceNote = classifyDivergence(aAssistant, bAssistant, aTools, bTools);
|
|
53
|
+
kind = divergenceNote ? "diverge" : "match";
|
|
54
|
+
}
|
|
55
|
+
if (kind !== "match" && firstDivergenceTurn === null) firstDivergenceTurn = turn;
|
|
56
|
+
pairs.push({ turn, aAssistant, bAssistant, aTools, bTools, kind, divergenceNote });
|
|
57
|
+
}
|
|
58
|
+
return { a: aSide, b: bSide, pairs, firstDivergenceTurn };
|
|
59
|
+
}
|
|
60
|
+
function classifyDivergence(a, b, aTools, bTools) {
|
|
61
|
+
const aNames = aTools.map((t) => t.tool ?? "").sort();
|
|
62
|
+
const bNames = bTools.map((t) => t.tool ?? "").sort();
|
|
63
|
+
if (aNames.join(",") !== bNames.join(",")) {
|
|
64
|
+
return `tool calls differ: A=[${aNames.join(",") || "\u2014"}] B=[${bNames.join(",") || "\u2014"}]`;
|
|
65
|
+
}
|
|
66
|
+
for (let i = 0; i < aTools.length; i++) {
|
|
67
|
+
const at = aTools[i];
|
|
68
|
+
const bt = bTools[i];
|
|
69
|
+
if (at.tool !== bt.tool) continue;
|
|
70
|
+
if ((at.args ?? "") !== (bt.args ?? "")) {
|
|
71
|
+
return `"${at.tool}" args differ`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const simRatio = similarity(a.content, b.content);
|
|
75
|
+
if (simRatio < 0.75) return `text similarity ${(simRatio * 100).toFixed(0)}%`;
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
function similarity(a, b) {
|
|
79
|
+
if (a === b) return 1;
|
|
80
|
+
if (!a && !b) return 1;
|
|
81
|
+
if (!a || !b) return 0;
|
|
82
|
+
const maxLen = Math.max(a.length, b.length);
|
|
83
|
+
if (maxLen > 2e3) return tokenOverlap(a, b);
|
|
84
|
+
const dist = levenshtein(a, b);
|
|
85
|
+
return 1 - dist / maxLen;
|
|
86
|
+
}
|
|
87
|
+
function tokenOverlap(a, b) {
|
|
88
|
+
const ta = new Set(a.toLowerCase().split(/\s+/).filter(Boolean));
|
|
89
|
+
const tb = new Set(b.toLowerCase().split(/\s+/).filter(Boolean));
|
|
90
|
+
if (ta.size === 0 && tb.size === 0) return 1;
|
|
91
|
+
let shared = 0;
|
|
92
|
+
for (const t of ta) if (tb.has(t)) shared++;
|
|
93
|
+
return 2 * shared / (ta.size + tb.size);
|
|
94
|
+
}
|
|
95
|
+
function levenshtein(a, b) {
|
|
96
|
+
const m = a.length;
|
|
97
|
+
const n = b.length;
|
|
98
|
+
if (m === 0) return n;
|
|
99
|
+
if (n === 0) return m;
|
|
100
|
+
let prev = new Array(n + 1);
|
|
101
|
+
let curr = new Array(n + 1);
|
|
102
|
+
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
103
|
+
for (let i = 1; i <= m; i++) {
|
|
104
|
+
curr[0] = i;
|
|
105
|
+
for (let j = 1; j <= n; j++) {
|
|
106
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
107
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
108
|
+
}
|
|
109
|
+
[prev, curr] = [curr, prev];
|
|
110
|
+
}
|
|
111
|
+
return prev[n];
|
|
112
|
+
}
|
|
113
|
+
function groupByTurn(records) {
|
|
114
|
+
const out = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const rec of records) {
|
|
116
|
+
if (rec.role === "user") continue;
|
|
117
|
+
const g = out.get(rec.turn) ?? { tools: [] };
|
|
118
|
+
if (rec.role === "assistant_final") g.assistant = rec;
|
|
119
|
+
else if (rec.role === "tool") g.tools.push(rec);
|
|
120
|
+
out.set(rec.turn, g);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
function renderSummaryTable(report, _opts = {}) {
|
|
125
|
+
const a = report.a;
|
|
126
|
+
const b = report.b;
|
|
127
|
+
const lines = [];
|
|
128
|
+
lines.push("Comparing:");
|
|
129
|
+
lines.push(` A ${a.label}`);
|
|
130
|
+
lines.push(` B ${b.label}`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
lines.push(row(["", "A", "B", "\u0394"], [20, 14, 14, 14]));
|
|
133
|
+
lines.push(
|
|
134
|
+
row(["\u2500".repeat(20), "\u2500".repeat(14), "\u2500".repeat(14), "\u2500".repeat(14)], [20, 14, 14, 14])
|
|
135
|
+
);
|
|
136
|
+
lines.push(statRow("model calls", a.stats.turns, b.stats.turns));
|
|
137
|
+
lines.push(statRow("user turns", a.stats.userTurns, b.stats.userTurns));
|
|
138
|
+
lines.push(statRow("tool calls", a.stats.toolCalls, b.stats.toolCalls));
|
|
139
|
+
lines.push(
|
|
140
|
+
row(
|
|
141
|
+
[
|
|
142
|
+
"cache hit",
|
|
143
|
+
`${pct(a.stats.cacheHitRatio)}`,
|
|
144
|
+
`${pct(b.stats.cacheHitRatio)}`,
|
|
145
|
+
signPct(b.stats.cacheHitRatio - a.stats.cacheHitRatio)
|
|
146
|
+
],
|
|
147
|
+
[20, 14, 14, 14]
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
lines.push(
|
|
151
|
+
row(
|
|
152
|
+
[
|
|
153
|
+
"cost (USD)",
|
|
154
|
+
`$${a.stats.totalCostUsd.toFixed(6)}`,
|
|
155
|
+
`$${b.stats.totalCostUsd.toFixed(6)}`,
|
|
156
|
+
costDelta(a.stats.totalCostUsd, b.stats.totalCostUsd)
|
|
157
|
+
],
|
|
158
|
+
[20, 14, 14, 14]
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
lines.push(statRow("prefix hashes", a.stats.prefixHashes.length, b.stats.prefixHashes.length));
|
|
162
|
+
lines.push("");
|
|
163
|
+
const aPrefixStable = a.stats.prefixHashes.length <= 1;
|
|
164
|
+
const bPrefixStable = b.stats.prefixHashes.length <= 1;
|
|
165
|
+
if (aPrefixStable !== bPrefixStable) {
|
|
166
|
+
const stable = aPrefixStable ? "A" : "B";
|
|
167
|
+
const churn = aPrefixStable ? "B" : "A";
|
|
168
|
+
const churnCount = aPrefixStable ? b.stats.prefixHashes.length : a.stats.prefixHashes.length;
|
|
169
|
+
lines.push(
|
|
170
|
+
`prefix stability: ${stable} stayed byte-stable across ${Math.max(
|
|
171
|
+
a.stats.turns,
|
|
172
|
+
b.stats.turns
|
|
173
|
+
)} turns; ${churn} churned ${churnCount} distinct prefixes.`
|
|
174
|
+
);
|
|
175
|
+
lines.push("");
|
|
176
|
+
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
177
|
+
lines.push(
|
|
178
|
+
`prefix: A and B share the same prefix hash (${a.stats.prefixHashes[0].slice(0, 12)}\u2026) \u2014 cache delta is attributable to log stability, not prompt change.`
|
|
179
|
+
);
|
|
180
|
+
lines.push("");
|
|
181
|
+
}
|
|
182
|
+
if (report.firstDivergenceTurn !== null) {
|
|
183
|
+
const p = report.pairs.find((p2) => p2.turn === report.firstDivergenceTurn);
|
|
184
|
+
lines.push(
|
|
185
|
+
`first divergence: turn ${report.firstDivergenceTurn} \u2014 ${p?.divergenceNote ?? "?"}`
|
|
186
|
+
);
|
|
187
|
+
if (p?.aAssistant) lines.push(` A \u2192 ${truncate(p.aAssistant.content, 100)}`);
|
|
188
|
+
if (p?.bAssistant) lines.push(` B \u2192 ${truncate(p.bAssistant.content, 100)}`);
|
|
189
|
+
} else {
|
|
190
|
+
lines.push("no material divergence detected (texts within similarity threshold).");
|
|
191
|
+
}
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
function renderMarkdown(report) {
|
|
195
|
+
const a = report.a;
|
|
196
|
+
const b = report.b;
|
|
197
|
+
const out = [];
|
|
198
|
+
out.push(`# Transcript diff: ${a.label} vs ${b.label}`);
|
|
199
|
+
out.push("");
|
|
200
|
+
if (a.meta || b.meta) {
|
|
201
|
+
out.push("## Meta");
|
|
202
|
+
out.push("");
|
|
203
|
+
out.push(`| | ${a.label} | ${b.label} |`);
|
|
204
|
+
out.push("|---|---|---|");
|
|
205
|
+
out.push(`| source | ${a.meta?.source ?? "\u2014"} | ${b.meta?.source ?? "\u2014"} |`);
|
|
206
|
+
out.push(`| model | ${a.meta?.model ?? "\u2014"} | ${b.meta?.model ?? "\u2014"} |`);
|
|
207
|
+
out.push(`| task | ${a.meta?.task ?? "\u2014"} | ${b.meta?.task ?? "\u2014"} |`);
|
|
208
|
+
out.push(`| startedAt | ${a.meta?.startedAt ?? "\u2014"} | ${b.meta?.startedAt ?? "\u2014"} |`);
|
|
209
|
+
out.push("");
|
|
210
|
+
}
|
|
211
|
+
out.push("## Summary");
|
|
212
|
+
out.push("");
|
|
213
|
+
out.push(`| metric | ${a.label} | ${b.label} | delta |`);
|
|
214
|
+
out.push("|---|---:|---:|---:|");
|
|
215
|
+
out.push(
|
|
216
|
+
`| model calls | ${a.stats.turns} | ${b.stats.turns} | ${signed(b.stats.turns - a.stats.turns)} |`
|
|
217
|
+
);
|
|
218
|
+
out.push(
|
|
219
|
+
`| user turns | ${a.stats.userTurns} | ${b.stats.userTurns} | ${signed(b.stats.userTurns - a.stats.userTurns)} |`
|
|
220
|
+
);
|
|
221
|
+
out.push(
|
|
222
|
+
`| tool calls | ${a.stats.toolCalls} | ${b.stats.toolCalls} | ${signed(b.stats.toolCalls - a.stats.toolCalls)} |`
|
|
223
|
+
);
|
|
224
|
+
out.push(
|
|
225
|
+
`| cache hit | ${pct(a.stats.cacheHitRatio)} | ${pct(b.stats.cacheHitRatio)} | **${signPct(b.stats.cacheHitRatio - a.stats.cacheHitRatio)}** |`
|
|
226
|
+
);
|
|
227
|
+
out.push(
|
|
228
|
+
`| cost (USD) | $${a.stats.totalCostUsd.toFixed(6)} | $${b.stats.totalCostUsd.toFixed(6)} | ${costDelta(a.stats.totalCostUsd, b.stats.totalCostUsd)} |`
|
|
229
|
+
);
|
|
230
|
+
out.push(
|
|
231
|
+
`| prefix hashes | ${a.stats.prefixHashes.length} | ${b.stats.prefixHashes.length} | \u2014 |`
|
|
232
|
+
);
|
|
233
|
+
out.push("");
|
|
234
|
+
out.push("## Turn-by-turn");
|
|
235
|
+
out.push("");
|
|
236
|
+
out.push(`| turn | kind | ${a.label} tool calls | ${b.label} tool calls | note |`);
|
|
237
|
+
out.push("|---:|:---:|---|---|---|");
|
|
238
|
+
for (const p of report.pairs) {
|
|
239
|
+
const aTools = p.aTools.map((t) => t.tool).filter(Boolean).join(", ") || "\u2014";
|
|
240
|
+
const bTools = p.bTools.map((t) => t.tool).filter(Boolean).join(", ") || "\u2014";
|
|
241
|
+
out.push(`| ${p.turn} | ${p.kind} | ${aTools} | ${bTools} | ${p.divergenceNote ?? ""} |`);
|
|
242
|
+
}
|
|
243
|
+
out.push("");
|
|
244
|
+
if (report.firstDivergenceTurn !== null) {
|
|
245
|
+
const p = report.pairs.find((x) => x.turn === report.firstDivergenceTurn);
|
|
246
|
+
out.push(`## First divergence (turn ${report.firstDivergenceTurn})`);
|
|
247
|
+
out.push("");
|
|
248
|
+
out.push(p?.divergenceNote ?? "");
|
|
249
|
+
out.push("");
|
|
250
|
+
if (p?.aAssistant) {
|
|
251
|
+
out.push(`**${a.label}:**`);
|
|
252
|
+
out.push("");
|
|
253
|
+
out.push("```");
|
|
254
|
+
out.push(p.aAssistant.content);
|
|
255
|
+
out.push("```");
|
|
256
|
+
out.push("");
|
|
257
|
+
}
|
|
258
|
+
if (p?.bAssistant) {
|
|
259
|
+
out.push(`**${b.label}:**`);
|
|
260
|
+
out.push("");
|
|
261
|
+
out.push("```");
|
|
262
|
+
out.push(p.bAssistant.content);
|
|
263
|
+
out.push("```");
|
|
264
|
+
out.push("");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return out.join("\n");
|
|
268
|
+
}
|
|
269
|
+
function row(cols, widths) {
|
|
270
|
+
return cols.map((c, i) => padRight(c, widths[i] ?? c.length)).join(" ");
|
|
271
|
+
}
|
|
272
|
+
function statRow(label, av, bv) {
|
|
273
|
+
return row([label, `${av}`, `${bv}`, signed(bv - av)], [20, 14, 14, 14]);
|
|
274
|
+
}
|
|
275
|
+
function padRight(s, w) {
|
|
276
|
+
return s.length >= w ? s : s + " ".repeat(w - s.length);
|
|
277
|
+
}
|
|
278
|
+
function signed(n) {
|
|
279
|
+
if (n === 0) return "0";
|
|
280
|
+
return `${n > 0 ? "+" : ""}${n}`;
|
|
281
|
+
}
|
|
282
|
+
function signPct(diff) {
|
|
283
|
+
if (diff === 0) return "0pp";
|
|
284
|
+
const s = (diff * 100).toFixed(1);
|
|
285
|
+
return `${diff > 0 ? "+" : ""}${s}pp`;
|
|
286
|
+
}
|
|
287
|
+
function pct(x) {
|
|
288
|
+
return `${(x * 100).toFixed(1)}%`;
|
|
289
|
+
}
|
|
290
|
+
function costDelta(a, b) {
|
|
291
|
+
if (a === 0 && b === 0) return "\u2014";
|
|
292
|
+
if (a === 0) return "new";
|
|
293
|
+
const pctChange = (b - a) / a * 100;
|
|
294
|
+
return `${pctChange > 0 ? "+" : ""}${pctChange.toFixed(1)}%`;
|
|
295
|
+
}
|
|
296
|
+
function truncate(s, n) {
|
|
297
|
+
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
findNextDivergence,
|
|
302
|
+
findPrevDivergence,
|
|
303
|
+
diffTranscripts,
|
|
304
|
+
renderSummaryTable,
|
|
305
|
+
renderMarkdown
|
|
306
|
+
};
|
|
307
|
+
//# sourceMappingURL=chunk-XJLZ4HKU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/transcript/diff.ts"],"sourcesContent":["/** Transcript diff — pairs assistant_final by turn number; unmatched extras become only_in_a / only_in_b. */\n\nimport type { ReadTranscriptResult, TranscriptRecord } from \"./log.js\";\nimport { type ReplayStats, computeReplayStats } from \"./replay.js\";\n\nexport interface DiffSide {\n label: string;\n meta: ReadTranscriptResult[\"meta\"];\n records: TranscriptRecord[];\n stats: ReplayStats;\n}\n\nexport interface TurnPair {\n turn: number;\n aAssistant?: TranscriptRecord;\n bAssistant?: TranscriptRecord;\n aTools: TranscriptRecord[];\n bTools: TranscriptRecord[];\n kind: \"match\" | \"diverge\" | \"only_in_a\" | \"only_in_b\";\n /** When kind === \"diverge\", a short one-liner pointing at what differs. */\n divergenceNote?: string;\n}\n\nexport interface DiffReport {\n a: DiffSide;\n b: DiffSide;\n pairs: TurnPair[];\n firstDivergenceTurn: number | null;\n}\n\nexport function findNextDivergence(pairs: TurnPair[], fromIdx: number): number {\n for (let i = fromIdx + 1; i < pairs.length; i++) {\n if (pairs[i]!.kind !== \"match\") return i;\n }\n return -1;\n}\n\nexport function findPrevDivergence(pairs: TurnPair[], fromIdx: number): number {\n const start = Math.min(fromIdx - 1, pairs.length - 1);\n for (let i = start; i >= 0; i--) {\n if (pairs[i]!.kind !== \"match\") return i;\n }\n return -1;\n}\n\nexport function diffTranscripts(\n a: { label: string; parsed: ReadTranscriptResult },\n b: { label: string; parsed: ReadTranscriptResult },\n): DiffReport {\n const aSide: DiffSide = {\n label: a.label,\n meta: a.parsed.meta,\n records: a.parsed.records,\n stats: computeReplayStats(a.parsed.records),\n };\n const bSide: DiffSide = {\n label: b.label,\n meta: b.parsed.meta,\n records: b.parsed.records,\n stats: computeReplayStats(b.parsed.records),\n };\n\n const aByTurn = groupByTurn(a.parsed.records);\n const bByTurn = groupByTurn(b.parsed.records);\n const turns = [...new Set([...aByTurn.keys(), ...bByTurn.keys()])].sort((x, y) => x - y);\n\n const pairs: TurnPair[] = [];\n let firstDivergenceTurn: number | null = null;\n for (const turn of turns) {\n const aGroup = aByTurn.get(turn) ?? { assistant: undefined, tools: [] };\n const bGroup = bByTurn.get(turn) ?? { assistant: undefined, tools: [] };\n const aAssistant = aGroup.assistant;\n const bAssistant = bGroup.assistant;\n const aTools = aGroup.tools;\n const bTools = bGroup.tools;\n\n let kind: TurnPair[\"kind\"];\n let divergenceNote: string | undefined;\n if (!aAssistant && bAssistant) kind = \"only_in_b\";\n else if (aAssistant && !bAssistant) kind = \"only_in_a\";\n else if (!aAssistant && !bAssistant)\n kind = \"diverge\"; // tool-only turn (rare)\n else {\n divergenceNote = classifyDivergence(aAssistant!, bAssistant!, aTools, bTools);\n kind = divergenceNote ? \"diverge\" : \"match\";\n }\n\n if (kind !== \"match\" && firstDivergenceTurn === null) firstDivergenceTurn = turn;\n pairs.push({ turn, aAssistant, bAssistant, aTools, bTools, kind, divergenceNote });\n }\n\n return { a: aSide, b: bSide, pairs, firstDivergenceTurn };\n}\n\nfunction classifyDivergence(\n a: TranscriptRecord,\n b: TranscriptRecord,\n aTools: TranscriptRecord[],\n bTools: TranscriptRecord[],\n): string | undefined {\n const aNames = aTools.map((t) => t.tool ?? \"\").sort();\n const bNames = bTools.map((t) => t.tool ?? \"\").sort();\n if (aNames.join(\",\") !== bNames.join(\",\")) {\n return `tool calls differ: A=[${aNames.join(\",\") || \"—\"}] B=[${bNames.join(\",\") || \"—\"}]`;\n }\n // Same tool names — did they pass different args?\n for (let i = 0; i < aTools.length; i++) {\n const at = aTools[i]!;\n const bt = bTools[i]!;\n if (at.tool !== bt.tool) continue;\n if ((at.args ?? \"\") !== (bt.args ?? \"\")) {\n return `\"${at.tool}\" args differ`;\n }\n }\n const simRatio = similarity(a.content, b.content);\n if (simRatio < 0.75) return `text similarity ${(simRatio * 100).toFixed(0)}%`;\n return undefined;\n}\n\n/** Falls back to token-overlap above 2000 chars to keep diff fast on chatty transcripts. */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1;\n if (!a && !b) return 1;\n if (!a || !b) return 0;\n const maxLen = Math.max(a.length, b.length);\n if (maxLen > 2000) return tokenOverlap(a, b);\n const dist = levenshtein(a, b);\n return 1 - dist / maxLen;\n}\n\nfunction tokenOverlap(a: string, b: string): number {\n const ta = new Set(a.toLowerCase().split(/\\s+/).filter(Boolean));\n const tb = new Set(b.toLowerCase().split(/\\s+/).filter(Boolean));\n if (ta.size === 0 && tb.size === 0) return 1;\n let shared = 0;\n for (const t of ta) if (tb.has(t)) shared++;\n return (2 * shared) / (ta.size + tb.size);\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n if (m === 0) return n;\n if (n === 0) return m;\n let prev = new Array(n + 1);\n let curr = new Array(n + 1);\n for (let j = 0; j <= n; j++) prev[j] = j;\n for (let i = 1; i <= m; i++) {\n curr[0] = i;\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);\n }\n [prev, curr] = [curr, prev];\n }\n return prev[n];\n}\n\ninterface TurnGroup {\n assistant?: TranscriptRecord;\n tools: TranscriptRecord[];\n}\n\nfunction groupByTurn(records: TranscriptRecord[]): Map<number, TurnGroup> {\n const out = new Map<number, TurnGroup>();\n for (const rec of records) {\n if (rec.role === \"user\") continue; // user msg is input to the turn, not its output\n const g = out.get(rec.turn) ?? { tools: [] };\n if (rec.role === \"assistant_final\") g.assistant = rec;\n else if (rec.role === \"tool\") g.tools.push(rec);\n out.set(rec.turn, g);\n }\n return out;\n}\n\nexport interface RenderOptions {\n /** Monochrome output (for file redirection or piping). Defaults to true. */\n monochrome?: boolean;\n}\n\nexport function renderSummaryTable(report: DiffReport, _opts: RenderOptions = {}): string {\n const a = report.a;\n const b = report.b;\n const lines: string[] = [];\n lines.push(\"Comparing:\");\n lines.push(` A ${a.label}`);\n lines.push(` B ${b.label}`);\n lines.push(\"\");\n lines.push(row([\"\", \"A\", \"B\", \"Δ\"], [20, 14, 14, 14]));\n lines.push(\n row([\"─\".repeat(20), \"─\".repeat(14), \"─\".repeat(14), \"─\".repeat(14)], [20, 14, 14, 14]),\n );\n lines.push(statRow(\"model calls\", a.stats.turns, b.stats.turns));\n lines.push(statRow(\"user turns\", a.stats.userTurns, b.stats.userTurns));\n lines.push(statRow(\"tool calls\", a.stats.toolCalls, b.stats.toolCalls));\n lines.push(\n row(\n [\n \"cache hit\",\n `${pct(a.stats.cacheHitRatio)}`,\n `${pct(b.stats.cacheHitRatio)}`,\n signPct(b.stats.cacheHitRatio - a.stats.cacheHitRatio),\n ],\n [20, 14, 14, 14],\n ),\n );\n lines.push(\n row(\n [\n \"cost (USD)\",\n `$${a.stats.totalCostUsd.toFixed(6)}`,\n `$${b.stats.totalCostUsd.toFixed(6)}`,\n costDelta(a.stats.totalCostUsd, b.stats.totalCostUsd),\n ],\n [20, 14, 14, 14],\n ),\n );\n lines.push(statRow(\"prefix hashes\", a.stats.prefixHashes.length, b.stats.prefixHashes.length));\n lines.push(\"\");\n\n // Prefix stability story — the headline finding when comparing bench modes.\n const aPrefixStable = a.stats.prefixHashes.length <= 1;\n const bPrefixStable = b.stats.prefixHashes.length <= 1;\n if (aPrefixStable !== bPrefixStable) {\n const stable = aPrefixStable ? \"A\" : \"B\";\n const churn = aPrefixStable ? \"B\" : \"A\";\n const churnCount = aPrefixStable ? b.stats.prefixHashes.length : a.stats.prefixHashes.length;\n lines.push(\n `prefix stability: ${stable} stayed byte-stable across ${Math.max(\n a.stats.turns,\n b.stats.turns,\n )} turns; ${churn} churned ${churnCount} distinct prefixes.`,\n );\n lines.push(\"\");\n } else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {\n lines.push(\n `prefix: A and B share the same prefix hash (${a.stats.prefixHashes[0].slice(0, 12)}…) — cache delta is attributable to log stability, not prompt change.`,\n );\n lines.push(\"\");\n }\n\n if (report.firstDivergenceTurn !== null) {\n const p = report.pairs.find((p) => p.turn === report.firstDivergenceTurn);\n lines.push(\n `first divergence: turn ${report.firstDivergenceTurn} — ${p?.divergenceNote ?? \"?\"}`,\n );\n if (p?.aAssistant) lines.push(` A → ${truncate(p.aAssistant.content, 100)}`);\n if (p?.bAssistant) lines.push(` B → ${truncate(p.bAssistant.content, 100)}`);\n } else {\n lines.push(\"no material divergence detected (texts within similarity threshold).\");\n }\n\n return lines.join(\"\\n\");\n}\n\nexport function renderMarkdown(report: DiffReport): string {\n const a = report.a;\n const b = report.b;\n const out: string[] = [];\n out.push(`# Transcript diff: ${a.label} vs ${b.label}`);\n out.push(\"\");\n if (a.meta || b.meta) {\n out.push(\"## Meta\");\n out.push(\"\");\n out.push(`| | ${a.label} | ${b.label} |`);\n out.push(\"|---|---|---|\");\n out.push(`| source | ${a.meta?.source ?? \"—\"} | ${b.meta?.source ?? \"—\"} |`);\n out.push(`| model | ${a.meta?.model ?? \"—\"} | ${b.meta?.model ?? \"—\"} |`);\n out.push(`| task | ${a.meta?.task ?? \"—\"} | ${b.meta?.task ?? \"—\"} |`);\n out.push(`| startedAt | ${a.meta?.startedAt ?? \"—\"} | ${b.meta?.startedAt ?? \"—\"} |`);\n out.push(\"\");\n }\n\n out.push(\"## Summary\");\n out.push(\"\");\n out.push(`| metric | ${a.label} | ${b.label} | delta |`);\n out.push(\"|---|---:|---:|---:|\");\n out.push(\n `| model calls | ${a.stats.turns} | ${b.stats.turns} | ${signed(b.stats.turns - a.stats.turns)} |`,\n );\n out.push(\n `| user turns | ${a.stats.userTurns} | ${b.stats.userTurns} | ${signed(b.stats.userTurns - a.stats.userTurns)} |`,\n );\n out.push(\n `| tool calls | ${a.stats.toolCalls} | ${b.stats.toolCalls} | ${signed(b.stats.toolCalls - a.stats.toolCalls)} |`,\n );\n out.push(\n `| cache hit | ${pct(a.stats.cacheHitRatio)} | ${pct(b.stats.cacheHitRatio)} | **${signPct(b.stats.cacheHitRatio - a.stats.cacheHitRatio)}** |`,\n );\n out.push(\n `| cost (USD) | $${a.stats.totalCostUsd.toFixed(6)} | $${b.stats.totalCostUsd.toFixed(6)} | ${costDelta(a.stats.totalCostUsd, b.stats.totalCostUsd)} |`,\n );\n out.push(\n `| prefix hashes | ${a.stats.prefixHashes.length} | ${b.stats.prefixHashes.length} | — |`,\n );\n out.push(\"\");\n\n out.push(\"## Turn-by-turn\");\n out.push(\"\");\n out.push(`| turn | kind | ${a.label} tool calls | ${b.label} tool calls | note |`);\n out.push(\"|---:|:---:|---|---|---|\");\n for (const p of report.pairs) {\n const aTools =\n p.aTools\n .map((t) => t.tool)\n .filter(Boolean)\n .join(\", \") || \"—\";\n const bTools =\n p.bTools\n .map((t) => t.tool)\n .filter(Boolean)\n .join(\", \") || \"—\";\n out.push(`| ${p.turn} | ${p.kind} | ${aTools} | ${bTools} | ${p.divergenceNote ?? \"\"} |`);\n }\n out.push(\"\");\n\n if (report.firstDivergenceTurn !== null) {\n const p = report.pairs.find((x) => x.turn === report.firstDivergenceTurn);\n out.push(`## First divergence (turn ${report.firstDivergenceTurn})`);\n out.push(\"\");\n out.push(p?.divergenceNote ?? \"\");\n out.push(\"\");\n if (p?.aAssistant) {\n out.push(`**${a.label}:**`);\n out.push(\"\");\n out.push(\"```\");\n out.push(p.aAssistant.content);\n out.push(\"```\");\n out.push(\"\");\n }\n if (p?.bAssistant) {\n out.push(`**${b.label}:**`);\n out.push(\"\");\n out.push(\"```\");\n out.push(p.bAssistant.content);\n out.push(\"```\");\n out.push(\"\");\n }\n }\n return out.join(\"\\n\");\n}\n\nfunction row(cols: string[], widths: number[]): string {\n return cols.map((c, i) => padRight(c, widths[i] ?? c.length)).join(\" \");\n}\n\nfunction statRow(label: string, av: number, bv: number): string {\n return row([label, `${av}`, `${bv}`, signed(bv - av)], [20, 14, 14, 14]);\n}\n\nfunction padRight(s: string, w: number): string {\n return s.length >= w ? s : s + \" \".repeat(w - s.length);\n}\n\nfunction signed(n: number): string {\n if (n === 0) return \"0\";\n return `${n > 0 ? \"+\" : \"\"}${n}`;\n}\n\nfunction signPct(diff: number): string {\n if (diff === 0) return \"0pp\";\n const s = (diff * 100).toFixed(1);\n return `${diff > 0 ? \"+\" : \"\"}${s}pp`;\n}\n\nfunction pct(x: number): string {\n return `${(x * 100).toFixed(1)}%`;\n}\n\nfunction costDelta(a: number, b: number): string {\n if (a === 0 && b === 0) return \"—\";\n if (a === 0) return \"new\";\n const pctChange = ((b - a) / a) * 100;\n return `${pctChange > 0 ? \"+\" : \"\"}${pctChange.toFixed(1)}%`;\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length > n ? `${s.slice(0, n)}…` : s;\n}\n"],"mappings":"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","names":["p"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/ui/theme.ts
|
|
4
|
+
var GRADIENT = [
|
|
5
|
+
"#5eead4",
|
|
6
|
+
// teal
|
|
7
|
+
"#67e8f9",
|
|
8
|
+
// cyan
|
|
9
|
+
"#7dd3fc",
|
|
10
|
+
// sky
|
|
11
|
+
"#93c5fd",
|
|
12
|
+
// blue
|
|
13
|
+
"#a5b4fc",
|
|
14
|
+
// indigo
|
|
15
|
+
"#c4b5fd",
|
|
16
|
+
// violet
|
|
17
|
+
"#d8b4fe",
|
|
18
|
+
// purple
|
|
19
|
+
"#f0abfc"
|
|
20
|
+
// fuchsia
|
|
21
|
+
];
|
|
22
|
+
var COLOR = {
|
|
23
|
+
primary: "#67e8f9",
|
|
24
|
+
// cyan-300
|
|
25
|
+
accent: "#c4b5fd",
|
|
26
|
+
// violet-300
|
|
27
|
+
brand: "#5eead4",
|
|
28
|
+
// teal-300
|
|
29
|
+
user: "#67e8f9",
|
|
30
|
+
// user message glyph + bar
|
|
31
|
+
assistant: "#86efac",
|
|
32
|
+
// green-300, assistant glyph + bar
|
|
33
|
+
tool: "#fcd34d",
|
|
34
|
+
// amber-300, tool ok pill bg
|
|
35
|
+
toolErr: "#fda4af",
|
|
36
|
+
// rose-300, tool err pill bg
|
|
37
|
+
info: "#94a3b8",
|
|
38
|
+
// slate-400, info / dim
|
|
39
|
+
warn: "#fbbf24",
|
|
40
|
+
// amber-400
|
|
41
|
+
err: "#f87171",
|
|
42
|
+
// red-400
|
|
43
|
+
ok: "#4ade80"
|
|
44
|
+
// green-400
|
|
45
|
+
};
|
|
46
|
+
var GLYPH = {
|
|
47
|
+
brand: "\u25C8",
|
|
48
|
+
user: "\u25C7",
|
|
49
|
+
assistant: "\u25C6",
|
|
50
|
+
toolOk: "\u25A3",
|
|
51
|
+
toolErr: "\u25A5",
|
|
52
|
+
warn: "\u25B2",
|
|
53
|
+
err: "\u2726",
|
|
54
|
+
arrow: "\u203A",
|
|
55
|
+
bullet: "\xB7",
|
|
56
|
+
bar: "\u258E",
|
|
57
|
+
thinBar: "\u258F",
|
|
58
|
+
block: "\u2588",
|
|
59
|
+
shade1: "\u2591",
|
|
60
|
+
shade2: "\u2592",
|
|
61
|
+
shade3: "\u2593",
|
|
62
|
+
// Status icons — checkbox-style states used across plan steps,
|
|
63
|
+
// job rows, history entries. Pair with the COLOR semantics:
|
|
64
|
+
// done→ok, cur→primary, pending→info-faint, fail→err.
|
|
65
|
+
done: "\u2713",
|
|
66
|
+
cur: "\u25B8",
|
|
67
|
+
pending: "\u25CB",
|
|
68
|
+
fail: "\u2717",
|
|
69
|
+
running: "\u25CF",
|
|
70
|
+
// Tree-drawing chars for hierarchical lists (plan steps, sub-loops,
|
|
71
|
+
// hook attachments). 1 cell each; render fine in every monospace
|
|
72
|
+
// font we've tested.
|
|
73
|
+
branch: "\u2523",
|
|
74
|
+
branchEnd: "\u2517",
|
|
75
|
+
branchStub: "\u2503",
|
|
76
|
+
rule: "\u2500",
|
|
77
|
+
// Spinner frames — 4-step rotation. Cycle every 200ms via setInterval
|
|
78
|
+
// (Ink's useEffect setState pattern). Equivalent to ink-spinner but
|
|
79
|
+
// with our own cadence + character set.
|
|
80
|
+
spinFrames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"]
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
GRADIENT,
|
|
85
|
+
COLOR,
|
|
86
|
+
GLYPH
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-ZPTSJGX5.js.map
|