@johpaz/hive-sdk 0.0.12 → 0.0.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 (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,374 @@
1
+ import { getDb } from "./SQLiteStorage.ts";
2
+ import { randomUUID } from "crypto";
3
+ import { logger } from "../utils/logger.ts";
4
+
5
+ const log = logger.child("usage");
6
+
7
+ // Precios en USD por millón de tokens (input / output)
8
+ // Fuentes: docs.anthropic.com, openrouter.ai/api/v1/models, api-docs.deepseek.com, console.groq.com
9
+ const MODEL_PRICING: Record<string, { inputPer1M: number; outputPer1M: number }> = {
10
+ // ── Anthropic (fuente: docs.anthropic.com) ──
11
+ "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
12
+ "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
13
+ "claude-haiku-4-5-20251001": { inputPer1M: 1, outputPer1M: 5 },
14
+ "anthropic/claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
15
+ "anthropic/claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
16
+
17
+ // ── OpenAI (fuente: openrouter.ai/api/v1/models) ──
18
+ "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10 },
19
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6 },
20
+ "gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
21
+ "gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180 },
22
+ "gpt-5.3": { inputPer1M: 1.75, outputPer1M: 14 },
23
+ "gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14 },
24
+ "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4 },
25
+ "openai/gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
26
+ "openai/gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180 },
27
+ "openai/gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14 },
28
+ // Groq OSS (fuente: console.groq.com)
29
+ "openai/gpt-oss-120b": { inputPer1M: 0.15, outputPer1M: 0.6 },
30
+ "openai/gpt-oss-20b": { inputPer1M: 0.075, outputPer1M: 0.3 },
31
+
32
+ // ── Google Gemini (fuente: openrouter.ai/api/v1/models) ──
33
+ "gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12 },
34
+ "gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5 },
35
+ "gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3 },
36
+ "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10 },
37
+ "gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6 },
38
+ "gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4 },
39
+ "gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3 },
40
+ "google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12 },
41
+ "google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5 },
42
+ "google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3 },
43
+ "google/gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6 },
44
+
45
+ // ── Mistral (fuente: openrouter.ai/api/v1/models) ──
46
+ "mistral-large-2512": { inputPer1M: 0.5, outputPer1M: 1.5 },
47
+ "devstral-2512": { inputPer1M: 0.4, outputPer1M: 2 },
48
+ "ministral-14b-2512": { inputPer1M: 0.2, outputPer1M: 0.2 },
49
+ "ministral-8b-2512": { inputPer1M: 0.15, outputPer1M: 0.15 },
50
+ "codestral-2508": { inputPer1M: 0.2, outputPer1M: 0.6 },
51
+ "mistral-small-3.2-24b-instruct": { inputPer1M: 0.1, outputPer1M: 0.3 },
52
+ "mistral-large-latest": { inputPer1M: 0.5, outputPer1M: 1.5 },
53
+ "codestral-latest": { inputPer1M: 0.2, outputPer1M: 0.6 },
54
+
55
+ // ── DeepSeek (fuente: api-docs.deepseek.com/quick_start/pricing) ──
56
+ "deepseek-chat": { inputPer1M: 0.28, outputPer1M: 0.42 },
57
+ "deepseek-reasoner": { inputPer1M: 0.28, outputPer1M: 0.42 },
58
+ "deepseek/deepseek-v3.2": { inputPer1M: 0.25, outputPer1M: 0.4 },
59
+ "deepseek/deepseek-r1:free": { inputPer1M: 0, outputPer1M: 0 },
60
+
61
+ // ── Kimi / Moonshot (fuente: openrouter.ai/moonshotai) ──
62
+ "kimi-k2.5": { inputPer1M: 0.45, outputPer1M: 2.2 },
63
+ "kimi-k2": { inputPer1M: 0.45, outputPer1M: 2.2 },
64
+ "moonshot-v1-8k": { inputPer1M: 1.67, outputPer1M: 1.67 },
65
+ "moonshot-v1-32k": { inputPer1M: 3.33, outputPer1M: 3.33 },
66
+ "moonshot-v1-128k": { inputPer1M: 8.33, outputPer1M: 8.33 },
67
+ "moonshotai/kimi-k2.5": { inputPer1M: 0.45, outputPer1M: 2.2 },
68
+ "moonshotai/kimi-k2-instruct-0905": { inputPer1M: 0.45, outputPer1M: 2.2 },
69
+
70
+ // ── Meta Llama (vía OpenRouter) ──
71
+ "meta-llama/llama-3.3-70b-instruct": { inputPer1M: 0.88, outputPer1M: 0.88 },
72
+ "meta-llama/llama-4-maverick": { inputPer1M: 0.2, outputPer1M: 0.8 },
73
+
74
+ // ── Qwen (vía OpenRouter) ──
75
+ "qwen/qwen3.5-plus-02-15": { inputPer1M: 0.26, outputPer1M: 1.56 },
76
+ "qwen/qwen3.5-flash-02-23": { inputPer1M: 0.1, outputPer1M: 0.4 },
77
+ "qwen/qwen3-32b": { inputPer1M: 0, outputPer1M: 0 },
78
+
79
+ // ── Groq (fuente: console.groq.com/docs/models) ──
80
+ "llama-3.3-70b-versatile": { inputPer1M: 0.59, outputPer1M: 0.79 },
81
+ "llama-3.1-8b-instant": { inputPer1M: 0.05, outputPer1M: 0.08 },
82
+ "groq/compound": { inputPer1M: 0, outputPer1M: 0 },
83
+ "groq/compound-mini": { inputPer1M: 0, outputPer1M: 0 },
84
+
85
+ // ── Ollama local = siempre free ──
86
+ "qwen3:4b": { inputPer1M: 0, outputPer1M: 0 },
87
+ "qwen3:8b": { inputPer1M: 0, outputPer1M: 0 },
88
+ "qwen3:14b": { inputPer1M: 0, outputPer1M: 0 },
89
+ "llama3.2:3b": { inputPer1M: 0, outputPer1M: 0 },
90
+ "gemma3:9b": { inputPer1M: 0, outputPer1M: 0 },
91
+ };
92
+
93
+ function calculateCost(model: string, inputTokens: number, outputTokens: number): number {
94
+ const pricing = MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
95
+ const inputCost = (inputTokens / 1_000_000) * pricing.inputPer1M;
96
+ const outputCost = (outputTokens / 1_000_000) * pricing.outputPer1M;
97
+ return inputCost + outputCost;
98
+ }
99
+
100
+ export interface UsageRecord {
101
+ id: string;
102
+ provider: string;
103
+ model: string;
104
+ input_tokens: number;
105
+ output_tokens: number;
106
+ cost_usd: number;
107
+ latency_ms: number | null;
108
+ toon_saved_tokens: number;
109
+ toon_saved_cost: number;
110
+ toon_json_bytes: number;
111
+ toon_toon_bytes: number;
112
+ toon_saved_bytes: number;
113
+ toon_saved_percent: number;
114
+ toon_json_tokens: number;
115
+ toon_toon_tokens: number;
116
+ toon_saved_tokens_pct: number;
117
+ created_at: number;
118
+ }
119
+
120
+ export interface UsageSummary {
121
+ totalTokens: number;
122
+ totalInputTokens: number;
123
+ totalOutputTokens: number;
124
+ totalCostUsd: number;
125
+ toonSavedTokens: number;
126
+ toonSavedCost: number;
127
+ toonSavedBytes: number;
128
+ toonSavedBytesPercent: number;
129
+ toonJsonTokens: number;
130
+ toonToonTokens: number;
131
+ toonSavingsPercent: number;
132
+ byProvider: Record<string, { tokens: number; costUsd: number; inputTokens: number; outputTokens: number }>;
133
+ byModel: Record<string, { tokens: number; costUsd: number; provider: string; inputTokens: number; outputTokens: number }>;
134
+ recentRecords: UsageRecord[];
135
+ }
136
+
137
+ export function recordUsage(options: {
138
+ provider: string;
139
+ model: string;
140
+ inputTokens: number;
141
+ outputTokens: number;
142
+ latencyMs?: number;
143
+ }): void {
144
+ try {
145
+ const db = getDb();
146
+ const costUsd = calculateCost(options.model, options.inputTokens, options.outputTokens);
147
+
148
+ db.prepare(`
149
+ INSERT INTO usage_records (id, provider, model, input_tokens, output_tokens, cost_usd, latency_ms, created_at)
150
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
151
+ `).run(
152
+ randomUUID(),
153
+ options.provider,
154
+ options.model,
155
+ options.inputTokens,
156
+ options.outputTokens,
157
+ costUsd,
158
+ options.latencyMs || null,
159
+ Math.floor(Date.now() / 1000)
160
+ );
161
+ log.info(`[USAGE RECORDED] provider=${options.provider} model=${options.model} input=${options.inputTokens} output=${options.outputTokens} cost=$${costUsd.toFixed(4)}`);
162
+ } catch (error) {
163
+ console.error("Failed to record usage:", error);
164
+ }
165
+ }
166
+
167
+ export function getUsageStats(hours: number = 24): UsageSummary {
168
+ log.info(`[USAGE STATS] Fetching stats for last ${hours} hours`);
169
+ const db = getDb();
170
+ const since = Math.floor(Date.now() / 1000) - (hours * 3600);
171
+
172
+ const totals = db.prepare(`
173
+ SELECT
174
+ COALESCE(SUM(input_tokens), 0) as total_input,
175
+ COALESCE(SUM(output_tokens), 0) as total_output,
176
+ COALESCE(SUM(cost_usd), 0) as total_cost,
177
+ COALESCE(SUM(toon_saved_tokens), 0) as toon_saved_tokens,
178
+ COALESCE(SUM(toon_saved_cost), 0) as toon_saved_cost,
179
+ COALESCE(SUM(toon_saved_bytes), 0) as toon_saved_bytes,
180
+ COALESCE(SUM(toon_saved_percent), 0) as toon_saved_percent,
181
+ COALESCE(SUM(toon_json_tokens), 0) as toon_json_tokens,
182
+ COALESCE(SUM(toon_toon_tokens), 0) as toon_toon_tokens
183
+ FROM usage_records
184
+ WHERE created_at >= ?
185
+ `).get(since) as {
186
+ total_input: number;
187
+ total_output: number;
188
+ total_cost: number;
189
+ toon_saved_tokens: number;
190
+ toon_saved_cost: number;
191
+ toon_saved_bytes: number;
192
+ toon_saved_percent: number;
193
+ toon_json_tokens: number;
194
+ toon_toon_tokens: number;
195
+ };
196
+
197
+ const byProvider = db.prepare(`
198
+ SELECT
199
+ provider,
200
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
201
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
202
+ COALESCE(SUM(cost_usd), 0) as cost_usd
203
+ FROM usage_records
204
+ WHERE created_at >= ? AND provider != 'toon'
205
+ GROUP BY provider
206
+ `).all(since) as Array<{ provider: string; input_tokens: number; output_tokens: number; cost_usd: number }>;
207
+
208
+ const byModel = db.prepare(`
209
+ SELECT
210
+ model,
211
+ provider,
212
+ COALESCE(SUM(input_tokens), 0) as input_tokens,
213
+ COALESCE(SUM(output_tokens), 0) as output_tokens,
214
+ COALESCE(SUM(cost_usd), 0) as cost_usd
215
+ FROM usage_records
216
+ WHERE created_at >= ? AND provider != 'toon'
217
+ GROUP BY model
218
+ ORDER BY cost_usd DESC
219
+ `).all(since) as Array<{ model: string; provider: string; input_tokens: number; output_tokens: number; cost_usd: number }>;
220
+
221
+ const recentRecords = db.prepare(`
222
+ SELECT * FROM usage_records
223
+ WHERE created_at >= ?
224
+ ORDER BY created_at DESC
225
+ LIMIT 20
226
+ `).all(since) as UsageRecord[];
227
+
228
+ const providerMap: UsageSummary["byProvider"] = {};
229
+ for (const p of byProvider) {
230
+ providerMap[p.provider] = {
231
+ inputTokens: p.input_tokens,
232
+ outputTokens: p.output_tokens,
233
+ tokens: p.input_tokens + p.output_tokens,
234
+ costUsd: p.cost_usd
235
+ };
236
+ }
237
+
238
+ const modelMap: UsageSummary["byModel"] = {};
239
+ for (const m of byModel) {
240
+ modelMap[m.model] = {
241
+ provider: m.provider,
242
+ inputTokens: m.input_tokens,
243
+ outputTokens: m.output_tokens,
244
+ tokens: m.input_tokens + m.output_tokens,
245
+ costUsd: m.cost_usd
246
+ };
247
+ }
248
+
249
+ const totalTokens = totals.total_input + totals.total_output;
250
+ const totalIncludingSaved = totalTokens + totals.toon_saved_tokens;
251
+ const toonSavingsPercent = totalIncludingSaved > 0
252
+ ? (totals.toon_saved_tokens / totalIncludingSaved) * 100
253
+ : 0;
254
+
255
+ // Calculate average bytes savings percent
256
+ const toonSavedBytesPercent = totals.toon_toon_tokens > 0
257
+ ? (totals.toon_saved_bytes / totals.toon_toon_tokens) * 100
258
+ : 0;
259
+
260
+ return {
261
+ totalTokens,
262
+ totalInputTokens: totals.total_input,
263
+ totalOutputTokens: totals.total_output,
264
+ totalCostUsd: totals.total_cost,
265
+ toonSavedTokens: totals.toon_saved_tokens,
266
+ toonSavedCost: totals.toon_saved_cost,
267
+ toonSavedBytes: totals.toon_saved_bytes,
268
+ toonSavedBytesPercent,
269
+ toonJsonTokens: totals.toon_json_tokens,
270
+ toonToonTokens: totals.toon_toon_tokens,
271
+ toonSavingsPercent,
272
+ byProvider: providerMap,
273
+ byModel: modelMap,
274
+ recentRecords
275
+ };
276
+ }
277
+
278
+ export function getProviderPricing(provider: string, model: string): { inputPer1M: number; outputPer1M: number } {
279
+ return MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
280
+ }
281
+
282
+ export function estimateCostForTokens(model: string, tokens: number): number {
283
+ const pricing = MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
284
+ return (tokens / 1_000_000) * pricing.inputPer1M;
285
+ }
286
+
287
+ /**
288
+ * Get average cost per token for a model (input + output average)
289
+ */
290
+ export function getAverageTokenCost(model: string): number {
291
+ // 1. Exact match
292
+ let pricing = MODEL_PRICING[model];
293
+
294
+ // 2. Try stripping a single provider prefix (e.g. "openrouter/moonshotai/kimi" → "moonshotai/kimi")
295
+ if (!pricing) {
296
+ const slashIdx = model.indexOf('/');
297
+ if (slashIdx !== -1) {
298
+ pricing = MODEL_PRICING[model.slice(slashIdx + 1)];
299
+ }
300
+ }
301
+
302
+ // 3. Partial match — find any key whose name is contained in the model string
303
+ if (!pricing) {
304
+ for (const [key, p] of Object.entries(MODEL_PRICING)) {
305
+ if (model.includes(key) || key.includes(model)) {
306
+ pricing = p;
307
+ break;
308
+ }
309
+ }
310
+ }
311
+
312
+ if (!pricing) return 0;
313
+ // Average of input and output cost per token
314
+ return (pricing.inputPer1M + pricing.outputPer1M) / 2 / 1_000_000;
315
+ }
316
+
317
+ /**
318
+ * Record TOON savings for metrics tracking
319
+ * This updates the usage_records table with complete TOON compression metrics
320
+ */
321
+ export function recordToonSavings(
322
+ analysis: {
323
+ jsonBytes: number;
324
+ toonBytes: number;
325
+ savedBytes: number;
326
+ savedPercent: number;
327
+ jsonTokens: number;
328
+ toonTokens: number;
329
+ savedTokens: number;
330
+ savedTokensPercent: number;
331
+ },
332
+ costSaved: number,
333
+ category: string
334
+ ): void {
335
+ // Fire-and-forget to avoid blocking
336
+ Promise.resolve().then(async () => {
337
+ try {
338
+ const db = getDb();
339
+
340
+ // Insert TOON savings record with complete metrics
341
+ db.query(`
342
+ INSERT INTO usage_records (
343
+ id, provider, model, input_tokens, output_tokens, cost_usd,
344
+ toon_saved_tokens, toon_saved_cost,
345
+ toon_json_bytes, toon_toon_bytes, toon_saved_bytes, toon_saved_percent,
346
+ toon_json_tokens, toon_toon_tokens, toon_saved_tokens_pct,
347
+ created_at
348
+ )
349
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
350
+ `).run(
351
+ `toon_${category}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
352
+ 'toon',
353
+ category,
354
+ 0,
355
+ 0,
356
+ 0,
357
+ Math.max(0, analysis.savedTokens),
358
+ costSaved,
359
+ analysis.jsonBytes,
360
+ analysis.toonBytes,
361
+ analysis.savedBytes,
362
+ Math.max(0, analysis.savedPercent),
363
+ analysis.jsonTokens,
364
+ analysis.toonTokens,
365
+ Math.max(0, analysis.savedTokensPercent),
366
+ Math.floor(Date.now() / 1000),
367
+ )
368
+
369
+ log.debug(`[TOON] Recorded ${analysis.savedTokens} tokens ($${costSaved.toFixed(6)}) saved for ${category}`)
370
+ } catch (error) {
371
+ log.warn(`[TOON] Failed to record savings:`, error)
372
+ }
373
+ })
374
+ }