@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,212 +0,0 @@
1
- import { logger } from "../../utils/logger.ts"
2
- import type { LLMCallOptions, LLMProvider, LLMResponse, LLMToolCall } from "./interface"
3
- import type { ContentPart, LLMMessage } from "./LLMClient.ts"
4
-
5
- const log = logger.child("llm-client")
6
-
7
- // Models that support extended thinking (claude-3-7+ and claude-4.x).
8
- const THINKING_CAPABLE_MODELS = new Set([
9
- "claude-3-7-sonnet-20250219",
10
- "claude-sonnet-4-5",
11
- "claude-sonnet-4-6",
12
- "claude-opus-4-5",
13
- "claude-opus-4-6",
14
- "claude-opus-4-7",
15
- "claude-haiku-4-5",
16
- "claude-haiku-4-5-20251001",
17
- ])
18
-
19
- function supportsThinking(model: string): boolean {
20
- if (THINKING_CAPABLE_MODELS.has(model)) return true
21
- // Also match any claude-4.x or claude-3-7+ by prefix
22
- return /^claude-(4|3-7)/.test(model)
23
- }
24
-
25
- export class AnthropicProvider implements LLMProvider {
26
- private _convertContentPart(part: ContentPart): any {
27
- switch (part.type) {
28
- case "text":
29
- return { type: "text", text: part.text }
30
- case "image_url": {
31
- const url = part.image_url.url
32
- if (url.startsWith("data:")) {
33
- const match = url.match(/^data:([^;]+);base64,(.+)$/)
34
- if (match) return { type: "image", source: { type: "base64", media_type: match[1], data: match[2] } }
35
- }
36
- return { type: "image", source: { type: "url", url } }
37
- }
38
- case "image_base64":
39
- return { type: "image", source: { type: "base64", media_type: part.mimeType, data: part.base64 } }
40
- case "document":
41
- return { type: "document", source: { type: "base64", media_type: part.mimeType, data: part.base64 } }
42
- default:
43
- return { type: "text", text: JSON.stringify(part) }
44
- }
45
- }
46
-
47
- private _convertUserContent(msg: LLMMessage): any[] {
48
- if (Array.isArray(msg.content)) {
49
- return msg.content.map(p => this._convertContentPart(p))
50
- }
51
- return [{ type: "text", text: msg.content }]
52
- }
53
-
54
- async call(options: LLMCallOptions): Promise<LLMResponse> {
55
- const Anthropic = await import("@anthropic-ai/sdk")
56
- const client = new Anthropic.default({ apiKey: options.apiKey })
57
-
58
- const systemText = options.messages
59
- .filter((m) => m.role === "system")
60
- .map((m) => m.content)
61
- .join("\n\n")
62
-
63
- const anthropicMessages: any[] = []
64
-
65
- for (const msg of options.messages) {
66
- if (msg.role === "system") continue
67
-
68
- if (msg.role === "tool") {
69
- const block = { type: "tool_result", tool_use_id: msg.tool_call_id, content: msg.content }
70
- const last = anthropicMessages[anthropicMessages.length - 1]
71
- if (last?.role === "user" && Array.isArray(last.content)) {
72
- last.content.push(block)
73
- } else {
74
- anthropicMessages.push({ role: "user", content: [block] })
75
- }
76
- continue
77
- }
78
-
79
- if (msg.role === "assistant" && msg.tool_calls?.length) {
80
- const content: any[] = []
81
- if (msg.content) content.push({ type: "text", text: msg.content })
82
- for (const tc of msg.tool_calls) {
83
- let input: Record<string, unknown>
84
- try { input = JSON.parse(tc.function.arguments || "{}") } catch { input = {} }
85
- content.push({ type: "tool_use", id: tc.id, name: tc.function.name, input })
86
- }
87
- anthropicMessages.push({ role: "assistant", content })
88
- continue
89
- }
90
-
91
- anthropicMessages.push({ role: msg.role, content: Array.isArray(msg.content) ? this._convertUserContent(msg) : msg.content })
92
- }
93
-
94
- const tools: any[] = (options.tools ?? []).map((t) => ({
95
- name: t.function.name,
96
- description: t.function.description,
97
- input_schema: t.function.parameters,
98
- }))
99
-
100
- const body: any = {
101
- model: options.model,
102
- max_tokens: options.maxTokens ?? 16384,
103
- messages: anthropicMessages,
104
- }
105
- if (systemText) body.system = systemText
106
- if (tools.length) body.tools = tools
107
-
108
- // Extended thinking — only for supported models
109
- const thinkingEnabled = options.thinking?.enabled && supportsThinking(options.model)
110
- if (thinkingEnabled) {
111
- body.thinking = { type: "enabled", budget_tokens: options.thinking?.budget_tokens ?? 10000 }
112
- }
113
-
114
- log.info(
115
- `[llm-client] anthropic/${options.model} — ${anthropicMessages.length} msgs, ${tools.length} tools` +
116
- (thinkingEnabled ? ` thinking=${body.thinking.budget_tokens}tok` : "")
117
- )
118
-
119
- let content = ""
120
- let thinking_content = ""
121
- const tool_calls: LLMToolCall[] = []
122
-
123
- // Streaming via messages.stream()
124
- const useStream = true // Always stream for better UX
125
- if (useStream) {
126
- const stream = client.messages.stream({ ...body, ...(options.signal ? {} : {}) })
127
-
128
- // Track partial tool inputs by index
129
- const partialInputs: Record<number, string> = {}
130
- const toolMeta: Record<number, { id: string; name: string }> = {}
131
-
132
- for await (const event of stream) {
133
- if (event.type === "content_block_start") {
134
- if (event.content_block.type === "tool_use") {
135
- toolMeta[event.index] = { id: event.content_block.id, name: event.content_block.name }
136
- partialInputs[event.index] = ""
137
- }
138
- } else if (event.type === "content_block_delta") {
139
- if (event.delta.type === "text_delta") {
140
- content += event.delta.text
141
- if (options.onToken) options.onToken(event.delta.text)
142
- } else if (event.delta.type === "thinking_delta") {
143
- thinking_content += event.delta.thinking
144
- } else if (event.delta.type === "input_json_delta") {
145
- if (partialInputs[event.index] !== undefined) {
146
- partialInputs[event.index] += event.delta.partial_json
147
- }
148
- }
149
- }
150
- }
151
-
152
- const finalMsg = await stream.finalMessage()
153
-
154
- // Build tool_calls from accumulated partial inputs
155
- for (const [idx, meta] of Object.entries(toolMeta)) {
156
- const args = partialInputs[Number(idx)] ?? "{}"
157
- tool_calls.push({
158
- id: meta.id,
159
- type: "function",
160
- function: { name: meta.name, arguments: args },
161
- })
162
- }
163
-
164
- const usage = finalMsg.usage
165
- return {
166
- content,
167
- thinking_content: thinking_content || undefined,
168
- tool_calls: tool_calls.length ? tool_calls : undefined,
169
- stop_reason:
170
- finalMsg.stop_reason === "tool_use" ? "tool_calls"
171
- : finalMsg.stop_reason === "max_tokens" ? "max_tokens"
172
- : "stop",
173
- usage: {
174
- input_tokens: usage.input_tokens,
175
- output_tokens: usage.output_tokens,
176
- thinking_tokens: (usage as any).thinking_tokens ?? 0,
177
- },
178
- }
179
- }
180
-
181
- // Non-streaming fallback (kept for reference, unreachable with useStream=true)
182
- const response = await client.messages.create(body)
183
-
184
- for (const block of response.content) {
185
- if (block.type === "text") content = block.text
186
- if (block.type === "thinking") thinking_content = (block as any).thinking ?? ""
187
- if (block.type === "tool_use") {
188
- let args: string
189
- try { args = JSON.stringify(block.input) } catch { args = "{}" }
190
- tool_calls.push({
191
- id: block.id,
192
- type: "function",
193
- function: { name: block.name, arguments: args },
194
- })
195
- }
196
- }
197
-
198
- return {
199
- content,
200
- thinking_content: thinking_content || undefined,
201
- tool_calls: tool_calls.length ? tool_calls : undefined,
202
- stop_reason:
203
- response.stop_reason === "tool_use" ? "tool_calls"
204
- : response.stop_reason === "max_tokens" ? "max_tokens"
205
- : "stop",
206
- usage: {
207
- input_tokens: response.usage.input_tokens,
208
- output_tokens: response.usage.output_tokens,
209
- },
210
- }
211
- }
212
- }
@@ -1,231 +0,0 @@
1
- import { logger } from "../../utils/logger.ts"
2
- import {
3
- sanitizeMessages, requiresTemperature1, OPENAI_COMPAT_BASE_URLS,
4
- getProviderProfile, modelSupportsTools, normalizeToolName, normalizeToolSchema,
5
- } from "./interface"
6
- import type { LLMCallOptions, LLMProvider, LLMResponse, LLMToolCall } from "./interface"
7
- import type { ContentPart, LLMMessage } from "./LLMClient.ts"
8
-
9
- const log = logger.child("llm-client")
10
-
11
- export class OpenAICompatProvider implements LLMProvider {
12
- private _convertContentPart(part: ContentPart): any {
13
- switch (part.type) {
14
- case "text":
15
- return { type: "text", text: part.text }
16
- case "image_url":
17
- return { type: "image_url", image_url: { url: part.image_url.url } }
18
- case "image_base64":
19
- return { type: "image_url", image_url: { url: `data:${part.mimeType};base64,${part.base64}` } }
20
- case "document":
21
- return { type: "text", text: `[Document: ${part.fileName || "file"}] (base64 content not displayed)` }
22
- default:
23
- return { type: "text", text: JSON.stringify(part) }
24
- }
25
- }
26
-
27
- private _convertMessage(msg: LLMMessage): any {
28
- if (Array.isArray(msg.content)) {
29
- return { ...msg, content: msg.content.map(p => this._convertContentPart(p)) }
30
- }
31
- return msg
32
- }
33
-
34
- async call(options: LLMCallOptions): Promise<LLMResponse> {
35
- const { default: OpenAI } = await import("openai")
36
-
37
- const baseURL = options.baseUrl?.trim() || OPENAI_COMPAT_BASE_URLS[options.provider] || undefined
38
- const isLocal = baseURL?.includes("localhost") || baseURL?.includes("127.0.0.1") || baseURL?.includes("::1")
39
- const apiKey = options.apiKey || (isLocal ? "ollama" : undefined)
40
-
41
- if (!apiKey) {
42
- throw new Error(`API key missing for provider: ${options.provider}. Configure it in Settings → Providers.`)
43
- }
44
-
45
- const client = new OpenAI({ apiKey, baseURL })
46
-
47
- const isKimi = options.provider === "kimi"
48
- const isDeepSeek = options.provider === "deepseek"
49
- // Kimi K2 and DeepSeek reasoner require reasoning_content to be round-tripped
50
- const needsReasoningRoundtrip = isKimi || isDeepSeek
51
-
52
- const sanitized = sanitizeMessages(options.messages)
53
- const rawMessages = needsReasoningRoundtrip
54
- ? sanitized
55
- : sanitized.map(({ reasoning_content: _rc, ...rest }) => rest as typeof sanitized[number])
56
- const messagesForProvider = rawMessages.map(m => this._convertMessage(m))
57
-
58
- const providerPrefix = new RegExp(`^${options.provider}\\/`, "i")
59
- const body: any = {
60
- model: options.model.replace(providerPrefix, ""),
61
- messages: messagesForProvider,
62
- temperature: requiresTemperature1(options.provider, options.model) ? 1 : (options.temperature ?? 0.7),
63
- }
64
- if (options.maxTokens) body.max_tokens = options.maxTokens
65
- if (options.numCtx && isLocal) body.num_ctx = options.numCtx
66
-
67
- // Per-provider profile drives tool call behavior
68
- const profile = getProviderProfile(options.provider)
69
- const sendTools = modelSupportsTools(options.provider, options.model) && !!(options.tools?.length)
70
-
71
- // Map from wire name (normalized) → original name for denormalizing responses
72
- const toolNameMap = new Map<string, string>()
73
-
74
- if (sendTools) {
75
- const preparedTools = options.tools!.map((t) => {
76
- const originalName = t.function.name
77
- const wireName = profile.normalizeToolNames
78
- ? normalizeToolName(originalName, profile.toolNameReplacement)
79
- : originalName
80
- if (wireName !== originalName) toolNameMap.set(wireName, originalName)
81
- return {
82
- ...t,
83
- function: {
84
- ...t.function,
85
- name: wireName,
86
- parameters: normalizeToolSchema(t.function.parameters as Record<string, unknown>, profile),
87
- },
88
- }
89
- })
90
- body.tools = preparedTools
91
- body.tool_choice = profile.toolChoiceAuto
92
- if (profile.disableParallelToolCalls) body.parallel_tool_calls = false
93
- }
94
-
95
- log.info(`[llm-client] ${options.provider}/${body.model} — ${options.messages.length} msgs, ${options.tools?.length ?? 0} tools${sendTools ? "" : " (tools suppressed)"}`)
96
-
97
- if (options.onToken) {
98
- return this._streamCall(client, body, options, toolNameMap, sendTools, profile)
99
- }
100
-
101
- let response
102
- try {
103
- response = await client.chat.completions.create(body)
104
- } catch (err: any) {
105
- const status = err?.status ?? err?.response?.status
106
- if (sendTools && profile.retryWithoutToolsOnCodes.includes(status)) {
107
- log.warn(`[llm-client] ${options.provider}: tools rejected (HTTP ${status}) — retrying without tools`)
108
- const bodyNoTools = { ...body }
109
- delete bodyNoTools.tools
110
- delete bodyNoTools.tool_choice
111
- delete bodyNoTools.parallel_tool_calls
112
- response = await client.chat.completions.create(bodyNoTools)
113
- } else {
114
- throw err
115
- }
116
- }
117
-
118
- const choice = response.choices[0]
119
- const msg = choice.message
120
-
121
- const tool_calls: LLMToolCall[] | undefined = (msg.tool_calls as any[])?.map((tc: any) => ({
122
- id: tc.id,
123
- type: "function" as const,
124
- function: {
125
- name: toolNameMap.get(tc.function.name) ?? tc.function.name,
126
- arguments: tc.function.arguments,
127
- },
128
- }))
129
-
130
- return {
131
- content: msg.content ?? "",
132
- tool_calls: tool_calls?.length ? tool_calls : undefined,
133
- reasoning_content: (msg as any).reasoning_content ?? undefined,
134
- stop_reason:
135
- choice.finish_reason === "tool_calls" ? "tool_calls"
136
- : choice.finish_reason === "length" ? "max_tokens"
137
- : "stop",
138
- usage: response.usage ? {
139
- input_tokens: response.usage.prompt_tokens,
140
- output_tokens: response.usage.completion_tokens,
141
- } : undefined,
142
- }
143
- }
144
-
145
- private async _streamCall(
146
- client: any,
147
- body: any,
148
- options: LLMCallOptions,
149
- toolNameMap: Map<string, string>,
150
- sendTools: boolean,
151
- profile: ReturnType<typeof getProviderProfile>,
152
- ): Promise<LLMResponse> {
153
- let stream
154
- try {
155
- stream = await client.chat.completions.create({ ...body, stream: true })
156
- } catch (err: any) {
157
- const status = err?.status ?? err?.response?.status
158
- if (sendTools && profile.retryWithoutToolsOnCodes.includes(status)) {
159
- log.warn(`[llm-client] ${options.provider}: tools rejected (HTTP ${status}) — retrying stream without tools`)
160
- const bodyNoTools = { ...body }
161
- delete bodyNoTools.tools
162
- delete bodyNoTools.tool_choice
163
- delete bodyNoTools.parallel_tool_calls
164
- stream = await client.chat.completions.create({ ...bodyNoTools, stream: true })
165
- } else {
166
- throw err
167
- }
168
- }
169
-
170
- let content = ""
171
- let reasoning_content = ""
172
- let finish_reason = "stop"
173
- const toolCallMap: Map<number, { id: string; name: string; arguments: string }> = new Map()
174
- let input_tokens = 0
175
- let output_tokens = 0
176
-
177
- for await (const chunk of stream) {
178
- const choice = chunk.choices?.[0]
179
- if (!choice) continue
180
-
181
- const delta = choice.delta as any
182
- if (delta.content) {
183
- content += delta.content
184
- options.onToken!(delta.content)
185
- }
186
- if (delta.reasoning_content) {
187
- reasoning_content += delta.reasoning_content
188
- }
189
- if (delta.tool_calls) {
190
- for (const tc of delta.tool_calls) {
191
- const idx: number = tc.index
192
- if (!toolCallMap.has(idx)) {
193
- toolCallMap.set(idx, { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: "" })
194
- }
195
- const entry = toolCallMap.get(idx)!
196
- if (tc.id) entry.id = tc.id
197
- if (tc.function?.name) entry.name = tc.function.name
198
- if (tc.function?.arguments) entry.arguments += tc.function.arguments
199
- }
200
- }
201
- if (choice.finish_reason) finish_reason = choice.finish_reason
202
-
203
- if (chunk.usage) {
204
- input_tokens = chunk.usage.prompt_tokens ?? 0
205
- output_tokens = chunk.usage.completion_tokens ?? 0
206
- }
207
- }
208
-
209
- const tool_calls: LLMToolCall[] = [...toolCallMap.values()].map((tc) => ({
210
- id: tc.id,
211
- type: "function" as const,
212
- function: {
213
- name: toolNameMap.get(tc.name) ?? tc.name,
214
- arguments: tc.arguments || "{}",
215
- },
216
- }))
217
-
218
- return {
219
- content,
220
- tool_calls: tool_calls.length ? tool_calls : undefined,
221
- reasoning_content: reasoning_content || undefined,
222
- stop_reason:
223
- finish_reason === "tool_calls" ? "tool_calls"
224
- : finish_reason === "length" ? "max_tokens"
225
- : "stop",
226
- usage: input_tokens > 0 || output_tokens > 0
227
- ? { input_tokens, output_tokens }
228
- : undefined,
229
- }
230
- }
231
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * HiveDB-based Playbook Rules Selector (ACE Curator)
3
- *
4
- * Uses HiveDB hybrid search over the playbook index.
5
- */
6
-
7
- import { getHiveDB } from "../../storage/HiveDBStorage.ts"
8
- import { logger } from "../../utils/logger.ts"
9
- import type { HivePlaybookDoc } from "../../storage/hiveSeed.ts"
10
- import type { IndexDoc } from "@johpaz/hive-db"
11
-
12
- const log = logger.child("playbook-selector")
13
-
14
- // ─── Types ───────────────────────────────────────────────────────────────────────
15
-
16
- export interface PlaybookRule {
17
- id: string
18
- rule: string
19
- category: string
20
- applicable_to?: string
21
- }
22
-
23
- // ─── Configuration ─────────────────────────────────────────────────────────────
24
-
25
- const MAX_RULES_PER_TURN = 5
26
-
27
- const MIN_RELEVANCE_THRESHOLD = 0.5
28
-
29
- // ─── Selection Logic ───────────────────────────────────────────────────────────
30
-
31
- function toRule(id: string, doc: HivePlaybookDoc): PlaybookRule {
32
- return {
33
- id,
34
- rule: doc.rule,
35
- category: doc.category,
36
- applicable_to: doc.applicableTo ? doc.applicableTo.join(",") : undefined,
37
- }
38
- }
39
-
40
- export async function selectPlaybookRules(message: string): Promise<PlaybookRule[]> {
41
- const db = await getHiveDB()
42
- const startTime = performance.now()
43
-
44
- const keywords = message
45
- .toLowerCase()
46
- .replace(/[^\p{L}\p{N}\s]/gu, " ")
47
- .split(/\s+/)
48
- .filter(w => w.length > 3)
49
- .slice(0, 5)
50
-
51
- if (keywords.length === 0) return []
52
-
53
- const query = keywords.join(" ")
54
-
55
- try {
56
- const hits = await db.queryHybrid({
57
- text: query,
58
- k: MAX_RULES_PER_TURN,
59
- boosts: { body: 5.0, tags: 2.0, name: 1.0 },
60
- })
61
-
62
- const relevantIds = hits
63
- .filter(r => r.score >= MIN_RELEVANCE_THRESHOLD)
64
- .map(r => r.id)
65
-
66
- if (relevantIds.length === 0) return []
67
-
68
- const playbookCol = db.collection<HivePlaybookDoc>("playbook")
69
- const rules: PlaybookRule[] = []
70
- for (const id of relevantIds) {
71
- const entry = await playbookCol.get(id)
72
- if (entry && entry.doc.active) {
73
- rules.push(toRule(id, entry.doc))
74
- }
75
- }
76
-
77
- const timing = performance.now() - startTime
78
- log.info(`[playbook-selector] Selected ${rules.length} rules in ${timing.toFixed(2)}ms`)
79
- if (rules.length > 0) {
80
- log.debug(`[playbook-selector] Rules: ${rules.map(r => `[${r.id}] ${r.rule.substring(0, 60)}`).join(', ')}`)
81
- }
82
-
83
- return rules
84
- } catch (err) {
85
- log.error(`[playbook-selector] Failed to select rules:`, err)
86
- return []
87
- }
88
- }
89
-
90
- // ─── Sync Logic ───────────────────────────────────────────────────────────────
91
-
92
- export async function syncPlaybookToFTS(): Promise<void> {
93
- const db = await getHiveDB()
94
-
95
- try {
96
- const playbookCol = db.collection<HivePlaybookDoc>("playbook")
97
- const entries = await playbookCol.scan()
98
- const rules = entries.map(e => ({ id: e.id, doc: e.doc })).filter(r => r.doc.active)
99
-
100
- if (rules.length === 0) {
101
- log.debug(`[playbook-selector] No rules in playbook to sync`)
102
- return
103
- }
104
-
105
- const docs: IndexDoc[] = rules.map(r => ({
106
- id: r.id,
107
- name: r.doc.category,
108
- body: r.doc.rule,
109
- tags: r.doc.applicableTo ? r.doc.applicableTo.join(" ") : "",
110
- filters: [{ field: "type", value: "playbook" }],
111
- }))
112
-
113
- await db.upsertBatch(docs)
114
-
115
- log.info(`[playbook-selector] Atomic sync complete: ${rules.length} rules indexed in HiveDB`)
116
-
117
- } catch (err) {
118
- log.error(`[playbook-selector] Transactional sync failed:`, err)
119
- throw err
120
- }
121
- }