@johpaz/hive-sdk 0.1.3 → 0.1.5

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 (272) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +78 -23
  3. package/bunfig.toml +4 -2
  4. package/docs/API-AGENTS.md +78 -27
  5. package/docs/API-CONTEXT-COMPILER.md +31 -34
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  7. package/docs/HIVE-HARNESS.md +1 -1
  8. package/docs/INDEX.md +4 -4
  9. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  10. package/package.json +9 -4
  11. package/packages/cli/package.json +2 -2
  12. package/packages/cli/src/commands/create-app.test.ts +36 -7
  13. package/packages/cli/src/commands/init.ts +3 -3
  14. package/packages/cli/src/commands/run.ts +1 -1
  15. package/packages/cli/src/commands/test.ts +37 -25
  16. package/packages/cli/src/commands/trace.ts +30 -28
  17. package/packages/cli/templates/hive-app/.env.example +10 -2
  18. package/packages/cli/templates/hive-app/README.md +103 -0
  19. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  20. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  21. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  22. package/packages/core/package.json +5 -4
  23. package/packages/core/src/agent/acceptance-checks.ts +166 -0
  24. package/packages/core/src/agent/agent-catalog.ts +348 -0
  25. package/packages/core/src/agent/agent-loop.ts +1373 -0
  26. package/packages/core/src/agent/capability-search.ts +186 -0
  27. package/packages/core/src/agent/catalog-selector.ts +103 -0
  28. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  29. package/packages/core/src/agent/context-compiler.ts +689 -0
  30. package/packages/core/src/agent/conversation-store.ts +381 -0
  31. package/packages/core/src/agent/curator.ts +276 -0
  32. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  33. package/packages/core/src/agent/goal-runner.ts +323 -0
  34. package/packages/core/src/agent/index.ts +17 -12
  35. package/packages/core/src/agent/llm-client.ts +266 -0
  36. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  37. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  38. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  39. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  40. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  41. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  42. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  43. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  44. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  45. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  47. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  48. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  49. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  50. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  51. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  52. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  54. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  55. package/packages/core/src/agent/playbook-selector.ts +119 -0
  56. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  57. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  58. package/packages/core/src/agent/providers/index.ts +35 -16
  59. package/packages/core/src/agent/reflector.ts +320 -0
  60. package/packages/core/src/agent/routing-intent.ts +22 -0
  61. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  62. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  63. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  64. package/packages/core/src/agent/skill-selector.ts +374 -0
  65. package/packages/core/src/agent/stuck-loop.ts +209 -0
  66. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  67. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  68. package/packages/core/src/api/createAgent.test.ts +139 -27
  69. package/packages/core/src/api/createAgent.ts +232 -44
  70. package/packages/core/src/artifacts/store.ts +162 -0
  71. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  72. package/packages/core/src/canvas/canvas.test.ts +8 -4
  73. package/packages/core/src/canvas/emitter.ts +131 -80
  74. package/packages/core/src/canvas/index.ts +1 -3
  75. package/packages/core/src/channels/base.ts +9 -1
  76. package/packages/core/src/channels/discord.ts +5 -4
  77. package/packages/core/src/channels/manager.ts +122 -30
  78. package/packages/core/src/channels/slack.ts +5 -4
  79. package/packages/core/src/channels/telegram.ts +36 -6
  80. package/packages/core/src/channels/webchat.ts +11 -10
  81. package/packages/core/src/channels/whatsapp.ts +23 -7
  82. package/packages/core/src/config/index.ts +13 -2
  83. package/packages/core/src/config/loader.ts +71 -29
  84. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  85. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  86. package/packages/core/src/events/agent-bus.ts +44 -68
  87. package/packages/core/src/events/channel-narration.ts +150 -0
  88. package/packages/core/src/events/narration.ts +82 -0
  89. package/packages/core/src/events/tool-narration.ts +62 -0
  90. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  91. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  92. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  93. package/packages/core/src/gateway/lane-queue.ts +173 -0
  94. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  95. package/packages/core/src/gateway/server.ts +1 -1
  96. package/packages/core/src/harness/index.ts +46 -27
  97. package/packages/core/src/index.ts +33 -20
  98. package/packages/core/src/mcp/hot-reload.ts +32 -23
  99. package/packages/core/src/mcp/index.ts +6 -3
  100. package/packages/core/src/mcp/singleton.ts +1 -4
  101. package/packages/core/src/mcp/tool-sync.ts +138 -0
  102. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  103. package/packages/core/src/memory/Scratchpad.ts +27 -34
  104. package/packages/core/src/multimodal/vision-service.ts +44 -38
  105. package/packages/core/src/resilience/retry.ts +95 -0
  106. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  107. package/packages/core/src/scheduler/index.ts +9 -7
  108. package/packages/core/src/scheduler/integration.ts +46 -26
  109. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  110. package/packages/core/src/scheduler/types.ts +7 -2
  111. package/packages/core/src/security/Pairing.ts +1 -1
  112. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  115. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  116. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  117. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  118. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  119. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  120. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  121. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  122. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  123. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  124. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  125. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  128. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  129. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  130. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  131. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  132. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  133. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  134. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  135. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  136. package/packages/core/src/skills/skills.test.ts +52 -11
  137. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  138. package/packages/core/src/storage/bootstrap.ts +151 -0
  139. package/packages/core/src/storage/causal-events.ts +84 -0
  140. package/packages/core/src/storage/collections.ts +680 -0
  141. package/packages/core/src/storage/crypto.ts +205 -74
  142. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  143. package/packages/core/src/storage/hivedb.ts +61 -0
  144. package/packages/core/src/storage/index.ts +111 -17
  145. package/packages/core/src/storage/model-id.ts +53 -0
  146. package/packages/core/src/storage/onboarding.ts +540 -972
  147. package/packages/core/src/storage/reconcile.ts +238 -0
  148. package/packages/core/src/storage/seed.ts +572 -406
  149. package/packages/core/src/storage/usage.ts +285 -225
  150. package/packages/core/src/storage/user-email.ts +11 -0
  151. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  152. package/packages/core/src/swarm/EventBridge.ts +1 -1
  153. package/packages/core/src/swarm/index.ts +12 -9
  154. package/packages/core/src/tool-runtime/index.ts +146 -23
  155. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  156. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  157. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  158. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  159. package/packages/core/src/tools/agents/index.ts +784 -292
  160. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  161. package/packages/core/src/tools/api/api-request.ts +174 -0
  162. package/packages/core/src/tools/api/index.ts +16 -0
  163. package/packages/core/src/tools/cli/index.ts +4 -0
  164. package/packages/core/src/tools/core/index.ts +281 -112
  165. package/packages/core/src/tools/cron/index.ts +121 -124
  166. package/packages/core/src/tools/index.ts +63 -78
  167. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  168. package/packages/core/src/tools/types.ts +3 -1
  169. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  170. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  171. package/packages/core/src/tools/web/browser-service.ts +5 -0
  172. package/packages/core/src/tools/web/browser-type.ts +3 -8
  173. package/packages/core/src/tools/web/index.ts +4 -4
  174. package/packages/core/src/voice/index.ts +89 -63
  175. package/packages/core/src/workers/agent.worker.ts +2 -2
  176. package/packages/core/src/workers/workers.test.ts +3 -10
  177. package/scripts/bump-version.ts +248 -0
  178. package/scripts/generate-skill-bundle.ts +108 -0
  179. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  180. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  181. package/test/causal-events.test.ts +117 -0
  182. package/test/compaction.test.ts +105 -0
  183. package/test/context-compiler.test.ts +269 -0
  184. package/test/curator.test.ts +130 -0
  185. package/test/durable-queue.test.ts +114 -0
  186. package/test/harness-barrel.test.ts +64 -0
  187. package/test/hive-helpers.test.ts +130 -0
  188. package/test/hivedb-search.test.ts +189 -0
  189. package/test/internal-turns.test.ts +166 -0
  190. package/test/job-idempotency.test.ts +68 -0
  191. package/test/job-retry-backoff.test.ts +184 -0
  192. package/test/job-store.test.ts +381 -0
  193. package/test/llm-retry.test.ts +97 -0
  194. package/test/memory-perf.test.ts +774 -0
  195. package/test/minimal-loadout.test.ts +78 -0
  196. package/test/model-catalog.test.ts +105 -0
  197. package/test/preload.ts +12 -0
  198. package/test/reflector.test.ts +320 -0
  199. package/test/retention-cap.test.ts +91 -0
  200. package/test/retired-capabilities-pruned.test.ts +192 -0
  201. package/test/run-store.test.ts +355 -0
  202. package/test/scratchpad.test.ts +74 -0
  203. package/test/secrets-durability.test.ts +119 -0
  204. package/test/seed-model-reseed.test.ts +155 -0
  205. package/test/setup-agent-seed.test.ts +264 -0
  206. package/test/tool-inventory.test.ts +65 -0
  207. package/test/tool-runtime.test.ts +258 -0
  208. package/test/toon.test.ts +429 -0
  209. package/tsconfig.json +2 -0
  210. package/packages/core/src/ace/Curator.ts +0 -158
  211. package/packages/core/src/ace/Reflector.ts +0 -200
  212. package/packages/core/src/ace/index.ts +0 -4
  213. package/packages/core/src/agent/AgentRunner.ts +0 -711
  214. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  215. package/packages/core/src/agent/ContextGuard.ts +0 -91
  216. package/packages/core/src/agent/ConversationStore.ts +0 -254
  217. package/packages/core/src/agent/Hooks.ts +0 -166
  218. package/packages/core/src/agent/StuckLoop.ts +0 -133
  219. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  220. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  221. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  222. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  223. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  224. package/packages/core/src/agent/selectors/index.ts +0 -6
  225. package/packages/core/src/auth/auth.ts +0 -121
  226. package/packages/core/src/auth/index.ts +0 -1
  227. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  228. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  229. package/packages/core/src/harness/collections.ts +0 -98
  230. package/packages/core/src/harness/goal-verifier.ts +0 -141
  231. package/packages/core/src/harness/harness.test.ts +0 -236
  232. package/packages/core/src/harness/reconcile.ts +0 -149
  233. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  234. package/packages/core/src/multimodal/VisionService.ts +0 -293
  235. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  236. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  237. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  238. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  239. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  240. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  241. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  242. package/packages/core/src/scheduler/dag/index.ts +0 -26
  243. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  244. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  245. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  246. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  247. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  248. package/packages/core/src/storage/hiveSeed.ts +0 -308
  249. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  250. package/packages/core/src/storage/schema.ts +0 -689
  251. package/packages/core/src/storage/storage.test.ts +0 -37
  252. package/packages/core/src/swarm/AgentBus.ts +0 -460
  253. package/packages/core/src/swarm/EventBus.ts +0 -169
  254. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  255. package/packages/core/src/tools/bridge-events.ts +0 -26
  256. package/packages/core/src/tools/canvas/index.ts +0 -375
  257. package/packages/core/src/tools/codebridge/index.ts +0 -342
  258. package/packages/core/src/tools/meeting/index.ts +0 -353
  259. package/packages/core/src/tools/projects/index.ts +0 -37
  260. package/packages/core/src/tools/projects/project-create.ts +0 -94
  261. package/packages/core/src/tools/projects/project-done.ts +0 -66
  262. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  263. package/packages/core/src/tools/projects/project-list.ts +0 -96
  264. package/packages/core/src/tools/projects/project-update.ts +0 -72
  265. package/packages/core/src/tools/projects/task-create.ts +0 -68
  266. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  267. package/packages/core/src/tools/projects/task-update.ts +0 -93
  268. package/packages/core/src/tools/voice/index.ts +0 -104
  269. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  270. package/packages/core/src/tools/web/api-request.ts +0 -239
  271. package/test/setup-db.ts +0 -216
  272. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -1,78 +1,137 @@
1
- import { getHiveDB } from "./HiveDBStorage.ts";
2
- import { randomUUID } from "crypto";
3
- import { logger } from "../utils/logger.ts";
1
+ import { col, nextId, bumpRollup } from "./hive";
2
+ import type { ModelDoc, UsageRecordDoc, UsageRollupDoc } from "./collections";
3
+ import { logger } from "../utils/logger";
4
4
 
5
5
  const log = logger.child("usage");
6
6
 
7
- const MODEL_PRICING: Record<string, { inputPer1M: number; outputPer1M: number }> = {
8
- "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
9
- "claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
10
- "claude-haiku-4-5-20251001": { inputPer1M: 1, outputPer1M: 5 },
11
- "anthropic/claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
12
- "anthropic/claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
13
- "gpt-4o": { inputPer1M: 2.5, outputPer1M: 10 },
14
- "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6 },
15
- "gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
16
- "gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180 },
17
- "gpt-5.3": { inputPer1M: 1.75, outputPer1M: 14 },
18
- "gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14 },
19
- "o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4 },
20
- "openai/gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
21
- "openai/gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180 },
22
- "openai/gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14 },
23
- "openai/gpt-oss-120b": { inputPer1M: 0.15, outputPer1M: 0.6 },
24
- "openai/gpt-oss-20b": { inputPer1M: 0.075, outputPer1M: 0.3 },
25
- "gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12 },
26
- "gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5 },
27
- "gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3 },
28
- "gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10 },
29
- "gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6 },
30
- "gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4 },
31
- "gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3 },
32
- "google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12 },
33
- "google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5 },
34
- "google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3 },
35
- "google/gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6 },
36
- "mistral-large-2512": { inputPer1M: 0.5, outputPer1M: 1.5 },
37
- "devstral-2512": { inputPer1M: 0.4, outputPer1M: 2 },
38
- "ministral-14b-2512": { inputPer1M: 0.2, outputPer1M: 0.2 },
39
- "ministral-8b-2512": { inputPer1M: 0.15, outputPer1M: 0.15 },
40
- "codestral-2508": { inputPer1M: 0.2, outputPer1M: 0.6 },
41
- "mistral-small-3.2-24b-instruct": { inputPer1M: 0.1, outputPer1M: 0.3 },
42
- "mistral-large-latest": { inputPer1M: 0.5, outputPer1M: 1.5 },
43
- "codestral-latest": { inputPer1M: 0.2, outputPer1M: 0.6 },
44
- "deepseek-chat": { inputPer1M: 0.28, outputPer1M: 0.42 },
45
- "deepseek-reasoner": { inputPer1M: 0.28, outputPer1M: 0.42 },
46
- "deepseek/deepseek-v3.2": { inputPer1M: 0.25, outputPer1M: 0.4 },
47
- "deepseek/deepseek-r1:free": { inputPer1M: 0, outputPer1M: 0 },
48
- "kimi-k2.5": { inputPer1M: 0.45, outputPer1M: 2.2 },
49
- "kimi-k2": { inputPer1M: 0.45, outputPer1M: 2.2 },
50
- "moonshot-v1-8k": { inputPer1M: 1.67, outputPer1M: 1.67 },
51
- "moonshot-v1-32k": { inputPer1M: 3.33, outputPer1M: 3.33 },
52
- "moonshot-v1-128k": { inputPer1M: 8.33, outputPer1M: 8.33 },
53
- "moonshotai/kimi-k2.5": { inputPer1M: 0.45, outputPer1M: 2.2 },
54
- "moonshotai/kimi-k2-instruct-0905": { inputPer1M: 0.45, outputPer1M: 2.2 },
55
- "meta-llama/llama-3.3-70b-instruct": { inputPer1M: 0.88, outputPer1M: 0.88 },
56
- "meta-llama/llama-4-maverick": { inputPer1M: 0.2, outputPer1M: 0.8 },
57
- "qwen/qwen3.5-plus-02-15": { inputPer1M: 0.26, outputPer1M: 1.56 },
58
- "qwen/qwen3.5-flash-02-23": { inputPer1M: 0.1, outputPer1M: 0.4 },
59
- "qwen/qwen3-32b": { inputPer1M: 0, outputPer1M: 0 },
60
- "llama-3.3-70b-versatile": { inputPer1M: 0.59, outputPer1M: 0.79 },
61
- "llama-3.1-8b-instant": { inputPer1M: 0.05, outputPer1M: 0.08 },
62
- "groq/compound": { inputPer1M: 0, outputPer1M: 0 },
63
- "groq/compound-mini": { inputPer1M: 0, outputPer1M: 0 },
64
- "qwen3:4b": { inputPer1M: 0, outputPer1M: 0 },
65
- "qwen3:8b": { inputPer1M: 0, outputPer1M: 0 },
66
- "qwen3:14b": { inputPer1M: 0, outputPer1M: 0 },
67
- "llama3.2:3b": { inputPer1M: 0, outputPer1M: 0 },
68
- "gemma3:9b": { inputPer1M: 0, outputPer1M: 0 },
69
- };
70
-
71
- function calculateCost(model: string, inputTokens: number, outputTokens: number): number {
72
- const pricing = MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
73
- const inputCost = (inputTokens / 1_000_000) * pricing.inputPer1M;
74
- const outputCost = (outputTokens / 1_000_000) * pricing.outputPer1M;
75
- return inputCost + outputCost;
7
+ /**
8
+ * Costo de una llamada, en USD.
9
+ *
10
+ * El precio vive en la propia fila del modelo (`ModelDoc.input_per_1m` /
11
+ * `output_per_1m`), sembrada desde SEED_DATA.models. Antes había acá un mapa
12
+ * `MODEL_PRICING` hardcodeado en paralelo al catálogo: mantener dos listas en
13
+ * sincronía fallaba en silencio — un modelo sembrado sin entrada en el mapa
14
+ * costaba $0 sin avisar.
15
+ */
16
+
17
+ /** Precio por id de modelo. Se llena bajo demanda y lo invalida el re-seed. */
18
+ let pricingCache: Map<string, { input: number; output: number } | null> | null = null;
19
+
20
+ /** Llamar cuando el catálogo cambie, para que el próximo costo relea de la BD. */
21
+ export function invalidateModelPricingCache(): void {
22
+ pricingCache = null;
23
+ }
24
+
25
+ async function loadPricing(): Promise<Map<string, { input: number; output: number } | null>> {
26
+ if (pricingCache) return pricingCache;
27
+ const cache = new Map<string, { input: number; output: number } | null>();
28
+ try {
29
+ const modelsCol = await col<ModelDoc>("models");
30
+ for (const row of await modelsCol.scan({})) {
31
+ const { input_per_1m: i, output_per_1m: o } = row.doc;
32
+ cache.set(row.id, i == null && o == null ? null : { input: i ?? 0, output: o ?? 0 });
33
+ }
34
+ pricingCache = cache;
35
+ } catch {
36
+ // La BD puede no estar lista todavía (arranque temprano): no cachear el
37
+ // resultado vacío o el costo quedaría en 0 para toda la vida del proceso.
38
+ return cache;
39
+ }
40
+ return cache;
41
+ }
42
+
43
+ /** Modelos ya avisados, para no repetir el warning en cada llamada. */
44
+ const unpricedModels = new Set<string>();
45
+
46
+ export async function calculateCost(
47
+ provider: string,
48
+ model: string,
49
+ inputTokens: number,
50
+ outputTokens: number
51
+ ): Promise<number> {
52
+ const pricing = (await loadPricing()).get(model);
53
+
54
+ if (!pricing) {
55
+ // Sin esto un modelo sin tarifa aparece como $0.00 en el dashboard, que es
56
+ // indistinguible de un modelo realmente gratuito.
57
+ const key = `${provider}/${model}`;
58
+ if (!unpricedModels.has(key)) {
59
+ unpricedModels.add(key);
60
+ log.warn(`[usage] Sin tarifa para ${key} su costo se contará como $0. Agregá inputPer1M/outputPer1M en SEED_DATA.models (storage/seed.ts).`);
61
+ }
62
+ return 0;
63
+ }
64
+
65
+ return (inputTokens / 1_000_000) * pricing.input + (outputTokens / 1_000_000) * pricing.output;
66
+ }
67
+
68
+ /** Hourly bucket key ("2026-07-09T14") lexicographic order matches chronological order. */
69
+ export function hourBucket(ts: number): string {
70
+ return new Date(ts).toISOString().slice(0, 13);
71
+ }
72
+
73
+ function emptyRollup(): UsageRollupDoc {
74
+ return {
75
+ inputTokens: 0, outputTokens: 0, costUsd: 0,
76
+ toonSavedTokens: 0, toonSavedCost: 0, toonSavedBytes: 0,
77
+ toonJsonTokens: 0, toonToonTokens: 0, toonJsonBytes: 0,
78
+ byProvider: {}, byModel: {},
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Applies the top-level token/cost delta exactly once, plus both the
84
+ * byProvider and byModel nested breakdowns, in a single read-modify-write.
85
+ * `bumpRollup`'s generic helper only supports one nested dimension per call —
86
+ * calling it twice here would double-apply the top-level delta.
87
+ */
88
+ async function bumpUsageRollup(
89
+ hour: string,
90
+ delta: { inputTokens: number; outputTokens: number; costUsd: number },
91
+ provider: string,
92
+ model: string
93
+ ): Promise<void> {
94
+ const rollupsCol = await col<UsageRollupDoc>("usageRollups");
95
+ const MAX_RETRIES = 5;
96
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
97
+ const existing = await rollupsCol.get(hour);
98
+ // Merge over emptyRollup() defaults, not just the raw existing doc — a rollup
99
+ // for this hour may have been created by recordToonSavings()'s generic
100
+ // bumpRollup() call, which never initializes byProvider/byModel.
101
+ const doc = existing ? { ...emptyRollup(), ...existing.doc } : emptyRollup();
102
+
103
+ doc.inputTokens += delta.inputTokens;
104
+ doc.outputTokens += delta.outputTokens;
105
+ doc.costUsd += delta.costUsd;
106
+
107
+ const curProvider = doc.byProvider[provider] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };
108
+ doc.byProvider = {
109
+ ...doc.byProvider,
110
+ [provider]: {
111
+ inputTokens: curProvider.inputTokens + delta.inputTokens,
112
+ outputTokens: curProvider.outputTokens + delta.outputTokens,
113
+ costUsd: curProvider.costUsd + delta.costUsd,
114
+ },
115
+ };
116
+
117
+ const curModel = doc.byModel[model] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };
118
+ doc.byModel = {
119
+ ...doc.byModel,
120
+ [model]: {
121
+ inputTokens: curModel.inputTokens + delta.inputTokens,
122
+ outputTokens: curModel.outputTokens + delta.outputTokens,
123
+ costUsd: curModel.costUsd + delta.costUsd,
124
+ },
125
+ };
126
+
127
+ try {
128
+ await rollupsCol.put(hour, doc, { expectedVersion: existing?.version ?? 0 });
129
+ return;
130
+ } catch {
131
+ // Version conflict — retry with a fresh read.
132
+ }
133
+ }
134
+ log.warn(`[USAGE] bumpUsageRollup: too much contention on usageRollups/${hour}`);
76
135
  }
77
136
 
78
137
  export interface UsageRecord {
@@ -112,163 +171,145 @@ export interface UsageSummary {
112
171
  recentRecords: UsageRecord[];
113
172
  }
114
173
 
115
- export async function recordUsage(options: {
174
+ export function recordUsage(options: {
116
175
  provider: string;
117
176
  model: string;
118
177
  inputTokens: number;
119
178
  outputTokens: number;
120
179
  latencyMs?: number;
121
- }): Promise<void> {
122
- try {
123
- const db = await getHiveDB();
124
- const col = db.collection<UsageRecord>("usage_records");
125
- const costUsd = calculateCost(options.model, options.inputTokens, options.outputTokens);
126
-
127
- await col.put(randomUUID(), {
128
- id: randomUUID(),
129
- provider: options.provider,
130
- model: options.model,
131
- input_tokens: options.inputTokens,
132
- output_tokens: options.outputTokens,
133
- cost_usd: costUsd,
134
- latency_ms: options.latencyMs ?? null,
135
- toon_saved_tokens: 0,
136
- toon_saved_cost: 0,
137
- toon_json_bytes: 0,
138
- toon_toon_bytes: 0,
139
- toon_saved_bytes: 0,
140
- toon_saved_percent: 0,
141
- toon_json_tokens: 0,
142
- toon_toon_tokens: 0,
143
- toon_saved_tokens_pct: 0,
144
- created_at: Math.floor(Date.now() / 1000),
145
- });
146
-
147
- log.info(`[USAGE RECORDED] provider=${options.provider} model=${options.model} input=${options.inputTokens} output=${options.outputTokens} cost=$${costUsd.toFixed(4)}`);
148
- } catch (error) {
149
- console.error("Failed to record usage:", error);
180
+ }): void {
181
+ // Fire-and-forget to avoid blocking the LLM call path.
182
+ Promise.resolve().then(async () => {
183
+ try {
184
+ const costUsd = await calculateCost(options.provider, options.model, options.inputTokens, options.outputTokens);
185
+ const now = Date.now();
186
+
187
+ const id = await nextId("usageRecords");
188
+ const recordsCol = await col<UsageRecordDoc>("usageRecords");
189
+ await recordsCol.put(id, {
190
+ id,
191
+ provider: options.provider,
192
+ model: options.model,
193
+ input_tokens: options.inputTokens,
194
+ output_tokens: options.outputTokens,
195
+ cost_usd: costUsd,
196
+ latency_ms: options.latencyMs || null,
197
+ toon_saved_tokens: 0,
198
+ toon_saved_cost: 0,
199
+ toon_json_bytes: 0,
200
+ toon_toon_bytes: 0,
201
+ toon_saved_bytes: 0,
202
+ toon_saved_percent: 0,
203
+ toon_json_tokens: 0,
204
+ toon_toon_tokens: 0,
205
+ toon_saved_tokens_pct: 0,
206
+ created_at: now,
207
+ }, { expectedVersion: 0 });
208
+
209
+ const hour = hourBucket(now);
210
+ const delta = { inputTokens: options.inputTokens, outputTokens: options.outputTokens, costUsd };
211
+ await bumpUsageRollup(hour, delta, options.provider, options.model);
212
+
213
+ log.info(`[USAGE RECORDED] provider=${options.provider} model=${options.model} input=${options.inputTokens} output=${options.outputTokens} cost=$${costUsd.toFixed(4)}`);
214
+ } catch (error) {
215
+ console.error("Failed to record usage:", error);
216
+ }
217
+ });
218
+ }
219
+
220
+ /** Every hour bucket key from `hours` ago through now, oldest first. */
221
+ function hourBucketsSince(hours: number): string[] {
222
+ const now = Date.now();
223
+ const buckets: string[] = [];
224
+ for (let t = now - hours * 3600_000; t <= now; t += 3600_000) {
225
+ buckets.push(hourBucket(t));
150
226
  }
227
+ return buckets;
151
228
  }
152
229
 
153
230
  export async function getUsageStats(hours: number = 24): Promise<UsageSummary> {
154
231
  log.info(`[USAGE STATS] Fetching stats for last ${hours} hours`);
155
- const db = await getHiveDB();
156
- const col = db.collection<UsageRecord>("usage_records");
157
- const since = Math.floor(Date.now() / 1000) - (hours * 3600);
158
-
159
- const entries = await col.scan();
160
- const records = entries.map(e => e.doc).filter(r => r.created_at >= since);
161
-
162
- const totals = records.reduce((acc, r) => ({
163
- total_input: acc.total_input + r.input_tokens,
164
- total_output: acc.total_output + r.output_tokens,
165
- total_cost: acc.total_cost + r.cost_usd,
166
- toon_saved_tokens: acc.toon_saved_tokens + r.toon_saved_tokens,
167
- toon_saved_cost: acc.toon_saved_cost + r.toon_saved_cost,
168
- toon_saved_bytes: acc.toon_saved_bytes + r.toon_saved_bytes,
169
- toon_saved_percent: acc.toon_saved_percent + r.toon_saved_percent,
170
- toon_json_tokens: acc.toon_json_tokens + r.toon_json_tokens,
171
- toon_toon_tokens: acc.toon_toon_tokens + r.toon_toon_tokens,
172
- }), {
173
- total_input: 0,
174
- total_output: 0,
175
- total_cost: 0,
176
- toon_saved_tokens: 0,
177
- toon_saved_cost: 0,
178
- toon_saved_bytes: 0,
179
- toon_saved_percent: 0,
180
- toon_json_tokens: 0,
181
- toon_toon_tokens: 0,
182
- });
232
+
233
+ const rollupsCol = await col<UsageRollupDoc>("usageRollups");
234
+ const buckets = hourBucketsSince(hours);
235
+ const rollups = (await Promise.all(buckets.map((id) => rollupsCol.get(id))))
236
+ .map((e) => e?.doc ?? emptyRollup());
183
237
 
184
238
  const providerMap: UsageSummary["byProvider"] = {};
185
239
  const modelMap: UsageSummary["byModel"] = {};
240
+ let totalInput = 0, totalOutput = 0, totalCost = 0;
241
+ let toonSavedTokens = 0, toonSavedCost = 0, toonSavedBytes = 0;
242
+ let toonJsonTokens = 0, toonToonTokens = 0, toonJsonBytes = 0;
186
243
 
187
- for (const r of records) {
188
- if (r.provider === "toon") continue;
189
- if (!providerMap[r.provider]) {
190
- providerMap[r.provider] = { inputTokens: 0, outputTokens: 0, tokens: 0, costUsd: 0 };
191
- }
192
- providerMap[r.provider].inputTokens += r.input_tokens;
193
- providerMap[r.provider].outputTokens += r.output_tokens;
194
- providerMap[r.provider].tokens += r.input_tokens + r.output_tokens;
195
- providerMap[r.provider].costUsd += r.cost_usd;
244
+ for (const r of rollups) {
245
+ totalInput += r.inputTokens;
246
+ totalOutput += r.outputTokens;
247
+ totalCost += r.costUsd;
248
+ toonSavedTokens += r.toonSavedTokens;
249
+ toonSavedCost += r.toonSavedCost;
250
+ toonSavedBytes += r.toonSavedBytes;
251
+ toonJsonTokens += r.toonJsonTokens;
252
+ toonToonTokens += r.toonToonTokens;
253
+ toonJsonBytes += r.toonJsonBytes;
196
254
 
197
- if (!modelMap[r.model]) {
198
- modelMap[r.model] = { provider: r.provider, inputTokens: 0, outputTokens: 0, tokens: 0, costUsd: 0 };
255
+ for (const [provider, p] of Object.entries(r.byProvider ?? {})) {
256
+ const cur = providerMap[provider] ?? { tokens: 0, costUsd: 0, inputTokens: 0, outputTokens: 0 };
257
+ cur.inputTokens += p.inputTokens;
258
+ cur.outputTokens += p.outputTokens;
259
+ cur.tokens += p.inputTokens + p.outputTokens;
260
+ cur.costUsd += p.costUsd;
261
+ providerMap[provider] = cur;
262
+ }
263
+ for (const [model, m] of Object.entries(r.byModel ?? {})) {
264
+ const cur = modelMap[model] ?? { provider: "unknown", tokens: 0, costUsd: 0, inputTokens: 0, outputTokens: 0 };
265
+ cur.inputTokens += m.inputTokens;
266
+ cur.outputTokens += m.outputTokens;
267
+ cur.tokens += m.inputTokens + m.outputTokens;
268
+ cur.costUsd += m.costUsd;
269
+ modelMap[model] = cur;
199
270
  }
200
- modelMap[r.model].inputTokens += r.input_tokens;
201
- modelMap[r.model].outputTokens += r.output_tokens;
202
- modelMap[r.model].tokens += r.input_tokens + r.output_tokens;
203
- modelMap[r.model].costUsd += r.cost_usd;
204
271
  }
205
272
 
206
- const recentRecords = records
207
- .filter(r => r.created_at >= since)
208
- .sort((a, b) => b.created_at - a.created_at)
209
- .slice(0, 20);
273
+ const sinceMs = Date.now() - hours * 3600_000;
274
+ const recordsCol = await col<UsageRecordDoc>("usageRecords");
275
+ const recentRecords = (await recordsCol.scan({ reverse: true, limit: 20 }))
276
+ .map((e) => e.doc)
277
+ .filter((r) => r.created_at >= sinceMs);
210
278
 
211
- const totalTokens = totals.total_input + totals.total_output;
212
- const totalIncludingSaved = totalTokens + totals.toon_saved_tokens;
279
+ const totalTokens = totalInput + totalOutput;
280
+ const totalIncludingSaved = totalTokens + toonSavedTokens;
213
281
  const toonSavingsPercent = totalIncludingSaved > 0
214
- ? (totals.toon_saved_tokens / totalIncludingSaved) * 100
282
+ ? (toonSavedTokens / totalIncludingSaved) * 100
215
283
  : 0;
216
284
 
217
- const toonSavedBytesPercent = totals.toon_toon_tokens > 0
218
- ? (totals.toon_saved_bytes / totals.toon_toon_tokens) * 100
285
+ // Ratio-of-sums (not mean-of-per-record-percentages) avoids bias from varying record sizes.
286
+ const toonSavedBytesPercent = toonJsonBytes > 0
287
+ ? (toonSavedBytes / toonJsonBytes) * 100
219
288
  : 0;
220
289
 
221
290
  return {
222
291
  totalTokens,
223
- totalInputTokens: totals.total_input,
224
- totalOutputTokens: totals.total_output,
225
- totalCostUsd: totals.total_cost,
226
- toonSavedTokens: totals.toon_saved_tokens,
227
- toonSavedCost: totals.toon_saved_cost,
228
- toonSavedBytes: totals.toon_saved_bytes,
292
+ totalInputTokens: totalInput,
293
+ totalOutputTokens: totalOutput,
294
+ totalCostUsd: totalCost,
295
+ toonSavedTokens,
296
+ toonSavedCost,
297
+ toonSavedBytes,
229
298
  toonSavedBytesPercent,
230
- toonJsonTokens: totals.toon_json_tokens,
231
- toonToonTokens: totals.toon_toon_tokens,
299
+ toonJsonTokens,
300
+ toonToonTokens,
232
301
  toonSavingsPercent,
233
302
  byProvider: providerMap,
234
303
  byModel: modelMap,
235
- recentRecords,
304
+ recentRecords
236
305
  };
237
306
  }
238
307
 
239
- export function getProviderPricing(provider: string, model: string): { inputPer1M: number; outputPer1M: number } {
240
- return MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
241
- }
242
-
243
- export function estimateCostForTokens(model: string, tokens: number): number {
244
- const pricing = MODEL_PRICING[model] || { inputPer1M: 0, outputPer1M: 0 };
245
- return (tokens / 1_000_000) * pricing.inputPer1M;
246
- }
247
-
248
- export function getAverageTokenCost(model: string): number {
249
- let pricing = MODEL_PRICING[model];
250
-
251
- if (!pricing) {
252
- const slashIdx = model.indexOf('/');
253
- if (slashIdx !== -1) {
254
- pricing = MODEL_PRICING[model.slice(slashIdx + 1)];
255
- }
256
- }
257
-
258
- if (!pricing) {
259
- for (const [key, p] of Object.entries(MODEL_PRICING)) {
260
- if (model.includes(key) || key.includes(model)) {
261
- pricing = p;
262
- break;
263
- }
264
- }
265
- }
266
-
267
- if (!pricing) return 0;
268
- return (pricing.inputPer1M + pricing.outputPer1M) / 2 / 1_000_000;
269
- }
270
-
271
- export async function recordToonSavings(
308
+ /**
309
+ * Record TOON savings for metrics tracking
310
+ * This updates the usage_records table with complete TOON compression metrics
311
+ */
312
+ export function recordToonSavings(
272
313
  analysis: {
273
314
  jsonBytes: number;
274
315
  toonBytes: number;
@@ -281,33 +322,52 @@ export async function recordToonSavings(
281
322
  },
282
323
  costSaved: number,
283
324
  category: string
284
- ): Promise<void> {
285
- try {
286
- const db = await getHiveDB();
287
- const col = db.collection<UsageRecord>("usage_records");
288
-
289
- await col.put(`toon_${category}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, {
290
- id: `toon_${category}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
291
- provider: "toon",
292
- model: category,
293
- input_tokens: 0,
294
- output_tokens: 0,
295
- cost_usd: 0,
296
- latency_ms: null,
297
- toon_saved_tokens: Math.max(0, analysis.savedTokens),
298
- toon_saved_cost: costSaved,
299
- toon_json_bytes: analysis.jsonBytes,
300
- toon_toon_bytes: analysis.toonBytes,
301
- toon_saved_bytes: analysis.savedBytes,
302
- toon_saved_percent: Math.max(0, analysis.savedPercent),
303
- toon_json_tokens: analysis.jsonTokens,
304
- toon_toon_tokens: analysis.toonTokens,
305
- toon_saved_tokens_pct: Math.max(0, analysis.savedTokensPercent),
306
- created_at: Math.floor(Date.now() / 1000),
307
- });
308
-
309
- log.debug(`[TOON] Recorded ${analysis.savedTokens} tokens ($${costSaved.toFixed(6)}) saved for ${category}`);
310
- } catch (error) {
311
- log.warn(`[TOON] Failed to record savings:`, error);
312
- }
325
+ ): void {
326
+ // Fire-and-forget to avoid blocking
327
+ Promise.resolve().then(async () => {
328
+ try {
329
+ const now = Date.now();
330
+ const savedTokens = Math.max(0, analysis.savedTokens);
331
+ const savedPercent = Math.max(0, analysis.savedPercent);
332
+ const savedTokensPct = Math.max(0, analysis.savedTokensPercent);
333
+
334
+ // Insert TOON savings record with complete metrics
335
+ const id = await nextId("usageRecords");
336
+ const recordsCol = await col<UsageRecordDoc>("usageRecords");
337
+ await recordsCol.put(id, {
338
+ id,
339
+ provider: "toon",
340
+ model: category,
341
+ input_tokens: 0,
342
+ output_tokens: 0,
343
+ cost_usd: 0,
344
+ latency_ms: null,
345
+ toon_saved_tokens: savedTokens,
346
+ toon_saved_cost: costSaved,
347
+ toon_json_bytes: analysis.jsonBytes,
348
+ toon_toon_bytes: analysis.toonBytes,
349
+ toon_saved_bytes: analysis.savedBytes,
350
+ toon_saved_percent: savedPercent,
351
+ toon_json_tokens: analysis.jsonTokens,
352
+ toon_toon_tokens: analysis.toonTokens,
353
+ toon_saved_tokens_pct: savedTokensPct,
354
+ created_at: now,
355
+ }, { expectedVersion: 0 });
356
+
357
+ // Only the toon-specific fields go into the rollup — no byProvider/byModel
358
+ // breakdown for these (they carry no real token/cost data of their own).
359
+ await bumpRollup("usageRollups", hourBucket(now), {
360
+ toonSavedTokens: savedTokens,
361
+ toonSavedCost: costSaved,
362
+ toonSavedBytes: analysis.savedBytes,
363
+ toonJsonTokens: analysis.jsonTokens,
364
+ toonToonTokens: analysis.toonTokens,
365
+ toonJsonBytes: analysis.jsonBytes,
366
+ });
367
+
368
+ log.debug(`[TOON] Recorded ${analysis.savedTokens} tokens ($${costSaved.toFixed(6)}) saved for ${category}`)
369
+ } catch (error) {
370
+ log.warn(`[TOON] Failed to record savings:`, error)
371
+ }
372
+ })
313
373
  }
@@ -0,0 +1,11 @@
1
+ const SIMPLE_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2
+
3
+ export function normalizeUserEmail(value: unknown): string {
4
+ if (typeof value !== "string") throw new Error("El correo electrónico es obligatorio.");
5
+ const email = value.trim().toLowerCase();
6
+ if (!email) throw new Error("El correo electrónico es obligatorio.");
7
+ if (email.length > 254 || !SIMPLE_EMAIL_PATTERN.test(email)) {
8
+ throw new Error("Ingresa un correo electrónico válido.");
9
+ }
10
+ return email;
11
+ }
@@ -10,7 +10,7 @@
10
10
  * concurrently without awaiting each one serially.
11
11
  */
12
12
 
13
- import { runAgentIsolated } from "../agent/AgentRunner.ts"
13
+ import { runAgentIsolated } from "../agent/agent-loop.ts"
14
14
  import { TaskNode } from "./TaskNode"
15
15
  import { TaskTimeoutError } from "./errors"
16
16
 
@@ -7,7 +7,7 @@
7
7
  * Also emits canvas:node_update events so the UI reflects task state in real time.
8
8
  */
9
9
 
10
- import { agentBus } from "../swarm/AgentBus.ts"
10
+ import { agentBus } from "../events/agent-bus.ts"
11
11
  import { emitCanvas } from "../canvas/emitter.ts"
12
12
  import { TaskNode } from "./TaskNode"
13
13
  import { DAGResult } from "./TaskResult"
@@ -11,15 +11,18 @@ export { EventBridge } from "./EventBridge.ts";
11
11
 
12
12
  export { CyclicDependencyError, TaskTimeoutError, TaskFailureError } from "./errors.ts";
13
13
 
14
- export type { AgentBusEventMap, AgentBusEventKey, AgentBusEventHandler, AgentBusMessage } from "./AgentBus.ts";
15
- export { getUnreadMessagesForWorker, getProjectMessageHistory, agentBus } from "./AgentBus.ts";
16
- export type { AgentBus } from "./AgentBus.ts";
17
-
18
- export type { EventMap, EventKey, EventHandler } from "./EventBus.ts";
19
- export { eventBus } from "./EventBus.ts";
20
- export type { TypedEventBus } from "./EventBus.ts";
21
-
22
- export { setSchedulerForCleanup, executeScheduledTask, notifyTaskCompletion, createTaskHandler } from "./WorkerPool.ts";
14
+ // Los buses viven en events/. `swarm/AgentBus.ts` y `swarm/EventBus.ts` eran
15
+ // copias que escribían a SQLite; se re-exportan desde acá para no romper a quien
16
+ // los importaba por el subpath ./swarm.
17
+ export type { AgentBusEventMap, AgentBusEventKey, AgentBusEventHandler, AgentBusMessage, AgentBus } from "../events/agent-bus.ts";
18
+ export { getUnreadMessagesForWorker, agentBus } from "../events/agent-bus.ts";
19
+
20
+ export type { EventMap, EventKey, EventHandler, TypedEventBus } from "../events/event-bus.ts";
21
+ export { eventBus } from "../events/event-bus.ts";
22
+
23
+ // Ejecución de tareas agendadas: `swarm/WorkerPool.ts` era una copia rezagada de
24
+ // scheduler/integration.ts (mismo archivo, 18 líneas de deriva).
25
+ export { setSchedulerForCleanup, notifyTaskCompletion, createTaskHandler } from "../scheduler/integration.ts";
23
26
 
24
27
  export type { ExecutionStrategy } from "./strategies/index.ts";
25
28
  export { ParallelStrategy, PriorityStrategy } from "./strategies/index.ts";