behavior-wrapped 0.8.1 → 0.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "behavior-wrapped",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "A private, local-first Wrapped report for Claude Code, Cowork, and Codex behavior.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,7 @@
1
1
  import crypto from "node:crypto";
2
2
  import { safeEvidenceText, redactText } from "./privacy.mjs";
3
3
  import { isFrustratedMessage, isGratefulMessage } from "./frustration-card.mjs";
4
+ import { estimateModelUsageCost } from "./model-pricing.mjs";
4
5
  import { displayModelName } from "./model-names.mjs";
5
6
 
6
7
  export { displayModelName } from "./model-names.mjs";
@@ -251,15 +252,6 @@ function finding(kind, title, summary, method, score, evidence) {
251
252
  return { id: crypto.randomUUID(), kind, title, summary, method, confidence: confidence(score), evidence };
252
253
  }
253
254
 
254
- function ratesFor(model, agent) {
255
- const value = String(model || "").toLowerCase();
256
- if (value.includes("opus")) return { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 };
257
- if (value.includes("sonnet")) return { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 };
258
- if (value.includes("haiku")) return { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 };
259
- if (agent === "codex" || value.startsWith("gpt")) return { input: 1.25, output: 10, cacheWrite: 1.25, cacheRead: 0.125 };
260
- return { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 };
261
- }
262
-
263
255
  function analyzeBehavior(sessionRecords) {
264
256
  const findings = [];
265
257
  for (const { sessionId, records } of sessionRecords) {
@@ -403,8 +395,7 @@ export function analyzeSessions(sessionRecords) {
403
395
  tokenBreakdown.reasoning += reasoning;
404
396
  const model = record?.message?.model || `${agent === "codex" ? "Codex" : "Claude"} model`;
405
397
  modelTokens.set(model, (modelTokens.get(model) || 0) + recordTokens);
406
- const rates = ratesFor(model, agent);
407
- estimatedCostUsd += (input * rates.input + (output + reasoning) * rates.output + cacheWrite * rates.cacheWrite + cacheRead * rates.cacheRead) / 1_000_000;
398
+ estimatedCostUsd += estimateModelUsageCost(usage, model, agent);
408
399
  }
409
400
  for (const tool of toolUses(record)) {
410
401
  toolCalls++;
@@ -460,7 +451,7 @@ export function analyzeSessions(sessionRecords) {
460
451
  agents,
461
452
  models,
462
453
  estimatedCostUsd: Number(estimatedCostUsd.toFixed(2)),
463
- costEstimateMethod: "API-equivalent estimate using a local, inspectable model-family rate table.",
454
+ costEstimateMethod: "API-equivalent estimate using current standard list prices by exact model, including distinct cache-read, 5-minute cache-write, and 1-hour cache-write rates.",
464
455
  };
465
456
  return { stats, findings: analyzeBehavior(sessionRecords) };
466
457
  }
@@ -0,0 +1,61 @@
1
+ // Standard API list prices in USD per million tokens, verified 2026-08-26.
2
+ // OpenAI: https://developers.openai.com/api/docs/models/compare
3
+ // Anthropic: https://platform.claude.com/docs/en/about-claude/pricing
4
+ const scaled = (value, multiplier) => Number((value * multiplier).toFixed(12));
5
+
6
+ const price = (input, output, cacheRead, cacheWrite5m, cacheWrite1h) => ({
7
+ input,
8
+ output,
9
+ cacheRead: cacheRead ?? scaled(input, 0.1),
10
+ cacheWrite5m: cacheWrite5m ?? scaled(input, 1.25),
11
+ cacheWrite1h: cacheWrite1h ?? scaled(input, 2),
12
+ });
13
+
14
+ const modelRates = [
15
+ [/gpt-5\.6-sol/, price(4, 20)],
16
+ [/gpt-5\.6-terra/, price(2, 12)],
17
+ [/gpt-5\.6-luna/, price(0.2, 1.2)],
18
+ [/gpt-5\.5-pro/, price(30, 180, 0, 30, 30)],
19
+ [/gpt-5\.5/, price(5, 30)],
20
+ [/gpt-5\.4-pro/, price(30, 180, 0, 30, 30)],
21
+ [/gpt-5\.4-mini/, price(0.75, 4.5)],
22
+ [/gpt-5\.4-nano/, price(0.2, 1.25)],
23
+ [/gpt-5\.4/, price(2.5, 15)],
24
+ [/gpt-5\.3-codex/, price(1.75, 14)],
25
+ [/gpt-5\.2/, price(1.75, 14)],
26
+ [/(?:claude-)?(?:fable|mythos)(?:-|\s)?5/, price(10, 50)],
27
+ [/(?:claude-)?opus(?:-|\s)?5/, price(5, 25)],
28
+ [/(?:claude-)?opus(?:-|\s)?4(?:-|\s)?(?:8|7|6|5)/, price(5, 25)],
29
+ [/(?:claude-)?opus(?:-|\s)?4(?:-|\s)?1/, price(15, 75)],
30
+ [/(?:claude-)?opus(?:-|\s)?4(?:\b|-20)/, price(15, 75)],
31
+ [/(?:claude-)?sonnet(?:-|\s)?5/, price(2, 10)],
32
+ [/(?:claude-)?sonnet(?:-|\s)?4/, price(3, 15)],
33
+ [/(?:claude-)?haiku(?:-|\s)?4(?:-|\s)?5/, price(1, 5)],
34
+ [/(?:claude-)?haiku(?:-|\s)?3(?:-|\s)?5/, price(0.8, 4)],
35
+ ];
36
+
37
+ export function ratesFor(model, agent) {
38
+ const value = String(model || "").toLowerCase();
39
+ const match = modelRates.find(([pattern]) => pattern.test(value));
40
+ if (match) return match[1];
41
+ return agent === "codex" || value.startsWith("gpt") ? price(2.5, 15) : price(3, 15);
42
+ }
43
+
44
+ export function estimateModelUsageCost(usage, model, agent) {
45
+ const input = Number(usage?.input_tokens) || 0;
46
+ const output = Number(usage?.output_tokens) || 0;
47
+ const reasoning = Number(usage?.reasoning_output_tokens) || 0;
48
+ const cacheRead = Number(usage?.cache_read_input_tokens) || 0;
49
+ const cacheWrite = Number(usage?.cache_creation_input_tokens) || 0;
50
+ const declared5m = Number(usage?.cache_creation?.ephemeral_5m_input_tokens) || 0;
51
+ const declared1h = Number(usage?.cache_creation?.ephemeral_1h_input_tokens) || 0;
52
+ const cacheWrite5m = declared5m + Math.max(0, cacheWrite - declared5m - declared1h);
53
+ const rates = ratesFor(model, agent);
54
+ return (
55
+ input * rates.input
56
+ + (output + reasoning) * rates.output
57
+ + cacheRead * rates.cacheRead
58
+ + cacheWrite5m * rates.cacheWrite5m
59
+ + declared1h * rates.cacheWrite1h
60
+ ) / 1_000_000;
61
+ }