Skip to content
SlashClone

Llm usage normalizer

@agentcosts/llm-usage-normalizer

Convert Anthropic and OpenAI usage exports, or your own app's token logs, into one usage schema with customers, tasks, cached and uncached tokens, and cost from a price table you edit. Fixes the classic mistake of counting cached tokens twice, and lists unmapped keys and unpriced models.

SkillDatav1.0.0 (latest)Tested
Activity
New: be the first to try it
  1. 1{
  2. 2 "name": "llm-usage-normalizer",
  3. 3 "owner": "agentcosts",
  4. 4 "version": "1.0.0",
  5. 5 "type": "skill",
  6. 6 "description": "Convert Anthropic and OpenAI usage exports, or your own app's token logs, into one usage schema with customers, tasks, cached and uncached tokens, and cost from a price table you edit. Fixes the classic mistake of counting cached tokens twice, and lists unmapped keys and unpriced models.",
  7. 7 "category": "data",
  8. 8 "license": "MIT",
  9. 9 "compat": ["claude-code"],
  10. 10 "permissions": {
  11. 11 "shell": ["node scripts/normalize.mjs"],
  12. 12 "files": {
  13. 13 "read": ["./data/**"],
  14. 14 "write": ["./data/llm-prices.csv", "./data/customer-map.csv", "./out/usage-normalized*"]
  15. 15 }
  16. 16 },
  17. 17 "tests": {
  18. 18 "cases": [
  19. 19 {
  20. 20 "name": "normalizes both example exports and maps keys to customers",
  21. 21 "inputs": {},
  22. 22 "checks": [
  23. 23 {
  24. 24 "type": "command_exits_zero",
  25. 25 "command": "node scripts/normalize.mjs examples/anthropic-usage.csv examples/openai-usage.csv --prices examples/prices.csv --customer-map examples/customer-map.csv --out out/usage-normalized"
  26. 26 },
  27. 27 { "type": "file_contains", "path": "out/usage-normalized.csv", "text": "timestamp,customer,task,run_id,provider,model,input_tokens,output_tokens,cache_read_tokens,cache_write_tokens,cost_usd,outcome" },
  28. 28 { "type": "file_contains", "path": "out/usage-normalized.csv", "text": "bluepeak,code_review,,openai,gpt-5-2025-08-07,432000,98000,180000,0,1.5425," },
  29. 29 { "type": "file_contains", "path": "out/usage-normalized-report.md", "text": "`key-staging` (1 row)" },
  30. 30 {
  31. 31 "type": "output_matches_schema",
  32. 32 "path": "out/usage-normalized-report.json",
  33. 33 "schema": {
  34. 34 "type": "object",
  35. 35 "required": ["rows", "total_cost", "by_customer", "unmapped_keys", "unpriced_models"],
  36. 36 "properties": {
  37. 37 "rows": { "const": 12 },
  38. 38 "total_cost": { "const": 11.59 },
  39. 39 "by_customer": { "type": "object", "properties": { "cobalt-labs": { "const": 2 }, "internal": { "const": 1.4 } } },
  40. 40 "unmapped_keys": { "const": { "key-staging": 1 } },
  41. 41 "unpriced_models": { "const": {} }
  42. 42 }
  43. 43 }
  44. 44 }
  45. 45 ]
  46. 46 },
  47. 47 {
  48. 48 "name": "reads app logs with OpenAI-style usage fields and flags unknown models",
  49. 49 "inputs": {
  50. 50 "data/app-log.csv": "created_at,customer_id,workflow,trace_id,model,prompt_tokens,prompt_tokens_details.cached_tokens,completion_tokens,status\n2026-10-05T12:00:00Z,acme,triage,t1,gpt-5-mini,1000000,400000,100000,success\n2026-10-05T12:01:00Z,acme,triage,t2,new-model-x,1000,0,10,failure\n"
  51. 51 },
  52. 52 "checks": [
  53. 53 { "type": "command_exits_zero", "command": "node scripts/normalize.mjs data/app-log.csv --prices examples/prices.csv --out out/usage-normalized" },
  54. 54 { "type": "file_contains", "path": "out/usage-normalized.csv", "text": "acme,triage,t1,openai,gpt-5-mini,600000,100000,400000,0,0.36,success" },
  55. 55 {
  56. 56 "type": "output_matches_schema",
  57. 57 "path": "out/usage-normalized-report.json",
  58. 58 "schema": { "type": "object", "properties": { "unpriced_models": { "const": { "new-model-x": 1 } }, "total_cost": { "const": 0.36 } } }
  59. 59 }
  60. 60 ]
  61. 61 }
  62. 62 ]
  63. 63 }