changebook 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/usage.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Espejo EXACTO de `supabase/functions/mcp/usage.ts` — la matematica de
3
+ * `atlas_usage`, sin dependencias.
4
+ *
5
+ * TRES COPIAS Y NINGUNA PUEDE MENTIR. Esta, la del hospedado y la de la tarjeta
6
+ * de cuenta de la web (`atlas-web/src/lib/savings.ts`). No pueden compartir
7
+ * modulo -runtimes y despliegues distintos-, asi que la unica defensa es que
8
+ * copiar sea VERIFICABLE: `test/usageParity.test.ts` las fija comparando
9
+ * SALIDAS. Un cambio de precio en un lado sin el otro rompe CI en vez de
10
+ * derivar en silencio.
11
+ *
12
+ * El cuerpo de abajo va copiado letra a letra a proposito: cualquier "mejora"
13
+ * local aqui es exactamente la deriva que el contrato persigue.
14
+ */
15
+ const MODEL_PRICES = [
16
+ { match: /haiku/, input: 1, output: 5 },
17
+ { match: /opus-4-[01]\b/, input: 15, output: 75 },
18
+ { match: /opus/, input: 5, output: 25 },
19
+ { match: /fable|mythos/, input: 10, output: 50 },
20
+ { match: /sonnet/, input: 3, output: 15 },
21
+ ];
22
+ const FALLBACK_PRICE = { input: 3, output: 15 };
23
+ const CACHE_READ_FACTOR = 0.1;
24
+ const CACHE_WRITE_FACTOR = 1.25;
25
+ const BATCH_FACTOR = 0.5;
26
+ const CHARS_PER_TOKEN = 4;
27
+ const READ_EXPLORATION_FACTOR = 3;
28
+ function modelPrice(model) {
29
+ const id = (model ?? "").toLowerCase();
30
+ const hit = MODEL_PRICES.find((p) => p.match.test(id));
31
+ return hit ? { input: hit.input, output: hit.output } : FALLBACK_PRICE;
32
+ }
33
+ function costAtPrice(row, price) {
34
+ return (((row.input_tokens ?? 0) * price.input +
35
+ (row.cache_read_input_tokens ?? 0) * price.input * CACHE_READ_FACTOR +
36
+ (row.cache_creation_input_tokens ?? 0) * price.input * CACHE_WRITE_FACTOR +
37
+ (row.output_tokens ?? 0) * price.output) /
38
+ 1_000_000);
39
+ }
40
+ export function summarizeUsage(rows, skipsCount, readsCount, readsChars) {
41
+ let realCost = 0;
42
+ let compression = 0;
43
+ let cache = 0;
44
+ let batch = 0;
45
+ let routing = 0;
46
+ for (const row of rows) {
47
+ const price = modelPrice(row.model);
48
+ const batchFactor = row.action === "import_batch" ? BATCH_FACTOR : 1;
49
+ const cost = costAtPrice(row, price) * batchFactor;
50
+ realCost += cost;
51
+ if (row.raw_diff_chars != null &&
52
+ row.diff_chars != null &&
53
+ row.raw_diff_chars > row.diff_chars) {
54
+ const tokens = (row.raw_diff_chars - row.diff_chars) / CHARS_PER_TOKEN;
55
+ compression += (tokens * price.input * batchFactor) / 1_000_000;
56
+ }
57
+ cache +=
58
+ ((row.cache_read_input_tokens ?? 0) *
59
+ price.input *
60
+ (1 - CACHE_READ_FACTOR) *
61
+ batchFactor) /
62
+ 1_000_000;
63
+ if (row.action === "import_batch")
64
+ batch += cost;
65
+ if (price.input < FALLBACK_PRICE.input) {
66
+ routing +=
67
+ (costAtPrice(row, FALLBACK_PRICE) - costAtPrice(row, price)) *
68
+ batchFactor;
69
+ }
70
+ }
71
+ const avgCost = rows.length > 0 ? realCost / rows.length : 0;
72
+ const skips = skipsCount * avgCost;
73
+ const savedTotal = compression + cache + batch + routing + skips;
74
+ const explorationEstimate = ((readsChars / CHARS_PER_TOKEN) *
75
+ READ_EXPLORATION_FACTOR *
76
+ FALLBACK_PRICE.input) /
77
+ 1_000_000;
78
+ return {
79
+ analyses: rows.length,
80
+ realCost,
81
+ saved: { compression, cache, batch, routing, skips },
82
+ savedTotal,
83
+ skipsCount,
84
+ reads: { count: readsCount, chars: readsChars, explorationEstimate },
85
+ };
86
+ }
87
+ export function fmtUsd(value) {
88
+ const digits = value > 0 && value < 0.1 ? 3 : 2;
89
+ return `$${value.toFixed(digits)}`;
90
+ }
91
+ /**
92
+ * Espejo EXACTO del hospedado, `Number.isFinite` incluido. La primera version
93
+ * que escribi aqui no filtraba, y un NaN en `latency_ms` habria dado un
94
+ * percentil distinto en cada servidor sobre los mismos datos.
95
+ */
96
+ export function percentile(values, p) {
97
+ const v = values.filter((x) => Number.isFinite(x)).sort((a, b) => a - b);
98
+ if (v.length === 0)
99
+ return null;
100
+ const idx = Math.min(v.length - 1, Math.max(0, Math.ceil((p / 100) * v.length) - 1));
101
+ return v[idx];
102
+ }
103
+ export function latencyByTool(rows) {
104
+ const byTool = new Map();
105
+ for (const r of rows) {
106
+ const t = (r.tool ?? "").trim();
107
+ if (!t || r.latency_ms == null)
108
+ continue;
109
+ byTool.set(t, [...(byTool.get(t) ?? []), r.latency_ms]);
110
+ }
111
+ return [...byTool.entries()]
112
+ .map(([tool, xs]) => ({
113
+ tool,
114
+ n: xs.length,
115
+ p50_ms: percentile(xs, 50),
116
+ p95_ms: percentile(xs, 95),
117
+ }))
118
+ .sort((a, b) => b.n - a.n);
119
+ }
120
+ //# sourceMappingURL=usage.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "changebook",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "mcpName": "io.github.raulbr90/changebook",
5
5
  "description": "Your agent already broke this three times. ChangeBook tells it before the fourth. MCP server + CLI: the history of what broke in your repo, served to Claude Code, Cursor or Codex before they edit.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.raulbr90/changebook",
4
4
  "description": "Your agent already broke this three times. ChangeBook tells it before the fourth.",
5
- "version": "0.7.0",
5
+ "version": "0.8.0",
6
6
  "websiteUrl": "https://changebook.dev",
7
7
  "remotes": [
8
8
  {
@@ -15,7 +15,7 @@
15
15
  "registryType": "npm",
16
16
  "registryBaseUrl": "https://registry.npmjs.org",
17
17
  "identifier": "changebook",
18
- "version": "0.7.0",
18
+ "version": "0.8.0",
19
19
  "transport": {
20
20
  "type": "stdio"
21
21
  }