@johpaz/hive-sdk 0.1.4 → 0.1.6

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 (278) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +78 -23
  3. package/bun.lock +55 -29
  4. package/bunfig.toml +4 -2
  5. package/docs/API-AGENTS.md +78 -27
  6. package/docs/API-CONTEXT-COMPILER.md +31 -34
  7. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  8. package/docs/HIVE-HARNESS.md +1 -1
  9. package/docs/INDEX.md +4 -4
  10. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  11. package/package.json +17 -12
  12. package/packages/cli/package.json +2 -2
  13. package/packages/cli/src/commands/create-app.test.ts +36 -7
  14. package/packages/cli/src/commands/init.ts +3 -3
  15. package/packages/cli/src/commands/run.ts +1 -1
  16. package/packages/cli/src/commands/test.ts +37 -25
  17. package/packages/cli/src/commands/trace.ts +30 -28
  18. package/packages/cli/templates/hive-app/.env.example +10 -2
  19. package/packages/cli/templates/hive-app/README.md +103 -0
  20. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  21. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  22. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  23. package/packages/core/package.json +13 -12
  24. package/packages/core/src/agent/acceptance-checks.ts +172 -0
  25. package/packages/core/src/agent/agent-catalog.ts +348 -0
  26. package/packages/core/src/agent/agent-loop.ts +1373 -0
  27. package/packages/core/src/agent/capability-search.ts +186 -0
  28. package/packages/core/src/agent/catalog-selector.ts +103 -0
  29. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  30. package/packages/core/src/agent/context-compiler.ts +689 -0
  31. package/packages/core/src/agent/conversation-store.ts +381 -0
  32. package/packages/core/src/agent/curator.ts +276 -0
  33. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  34. package/packages/core/src/agent/goal-runner.ts +323 -0
  35. package/packages/core/src/agent/index.ts +17 -12
  36. package/packages/core/src/agent/llm-client.ts +266 -0
  37. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  38. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  39. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  40. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  41. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  42. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  43. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  44. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  45. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  47. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  48. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  49. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  50. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  51. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  52. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  54. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  55. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  56. package/packages/core/src/agent/playbook-selector.ts +119 -0
  57. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  58. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  59. package/packages/core/src/agent/providers/index.ts +35 -16
  60. package/packages/core/src/agent/reflector.ts +320 -0
  61. package/packages/core/src/agent/routing-intent.ts +22 -0
  62. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  63. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  64. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  65. package/packages/core/src/agent/skill-selector.ts +374 -0
  66. package/packages/core/src/agent/stuck-loop.ts +209 -0
  67. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  68. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  69. package/packages/core/src/api/createAgent.test.ts +139 -27
  70. package/packages/core/src/api/createAgent.ts +232 -44
  71. package/packages/core/src/artifacts/store.ts +162 -0
  72. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  73. package/packages/core/src/canvas/canvas.test.ts +8 -4
  74. package/packages/core/src/canvas/emitter.ts +131 -80
  75. package/packages/core/src/canvas/index.ts +1 -3
  76. package/packages/core/src/channels/base.ts +9 -1
  77. package/packages/core/src/channels/discord.ts +5 -4
  78. package/packages/core/src/channels/manager.ts +122 -30
  79. package/packages/core/src/channels/slack.ts +5 -4
  80. package/packages/core/src/channels/telegram.ts +36 -6
  81. package/packages/core/src/channels/webchat.ts +11 -10
  82. package/packages/core/src/channels/whatsapp.ts +23 -7
  83. package/packages/core/src/config/index.ts +13 -2
  84. package/packages/core/src/config/loader.ts +76 -29
  85. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  86. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  87. package/packages/core/src/events/agent-bus.ts +44 -68
  88. package/packages/core/src/events/channel-narration.ts +150 -0
  89. package/packages/core/src/events/narration.ts +82 -0
  90. package/packages/core/src/events/tool-narration.ts +62 -0
  91. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  92. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  93. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  94. package/packages/core/src/gateway/lane-queue.ts +173 -0
  95. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  96. package/packages/core/src/gateway/server.ts +1 -1
  97. package/packages/core/src/harness/index.ts +46 -27
  98. package/packages/core/src/index.ts +33 -27
  99. package/packages/core/src/mcp/hot-reload.ts +32 -23
  100. package/packages/core/src/mcp/index.ts +6 -3
  101. package/packages/core/src/mcp/singleton.ts +1 -4
  102. package/packages/core/src/mcp/tool-sync.ts +138 -0
  103. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  104. package/packages/core/src/memory/Scratchpad.ts +27 -34
  105. package/packages/core/src/multimodal/vision-service.ts +44 -38
  106. package/packages/core/src/resilience/retry.ts +95 -0
  107. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  108. package/packages/core/src/scheduler/index.ts +9 -7
  109. package/packages/core/src/scheduler/integration.ts +46 -26
  110. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  111. package/packages/core/src/scheduler/types.ts +7 -2
  112. package/packages/core/src/security/Pairing.ts +1 -1
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  115. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  116. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  117. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  118. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  119. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  120. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  121. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  122. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  123. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  124. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  125. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  128. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  129. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  130. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  131. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  132. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  133. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  134. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  135. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  136. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  137. package/packages/core/src/skills/skills.test.ts +52 -11
  138. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  139. package/packages/core/src/storage/bootstrap.ts +151 -0
  140. package/packages/core/src/storage/causal-events.ts +84 -0
  141. package/packages/core/src/storage/collections.ts +680 -0
  142. package/packages/core/src/storage/crypto.ts +205 -74
  143. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  144. package/packages/core/src/storage/hivedb.ts +61 -0
  145. package/packages/core/src/storage/index.ts +111 -18
  146. package/packages/core/src/storage/model-id.ts +53 -0
  147. package/packages/core/src/storage/onboarding.ts +540 -972
  148. package/packages/core/src/storage/reconcile.ts +238 -0
  149. package/packages/core/src/storage/seed.ts +572 -406
  150. package/packages/core/src/storage/usage.ts +285 -225
  151. package/packages/core/src/storage/user-email.ts +11 -0
  152. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  153. package/packages/core/src/swarm/EventBridge.ts +1 -1
  154. package/packages/core/src/swarm/index.ts +12 -9
  155. package/packages/core/src/tool-runtime/index.ts +146 -23
  156. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  157. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  158. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  159. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  160. package/packages/core/src/tools/agents/index.ts +784 -292
  161. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  162. package/packages/core/src/tools/api/api-request.ts +174 -0
  163. package/packages/core/src/tools/api/index.ts +16 -0
  164. package/packages/core/src/tools/cli/index.ts +4 -0
  165. package/packages/core/src/tools/core/index.ts +281 -112
  166. package/packages/core/src/tools/cron/index.ts +121 -124
  167. package/packages/core/src/tools/index.ts +63 -78
  168. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  169. package/packages/core/src/tools/types.ts +3 -1
  170. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  171. package/packages/core/src/tools/web/browser-backend.ts +129 -0
  172. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  173. package/packages/core/src/tools/web/browser-service.ts +80 -35
  174. package/packages/core/src/tools/web/browser-type.ts +3 -8
  175. package/packages/core/src/tools/web/index.ts +4 -4
  176. package/packages/core/src/tools/web/webview-backend.ts +412 -0
  177. package/packages/core/src/voice/index.ts +89 -63
  178. package/packages/core/src/workers/agent.worker.ts +2 -2
  179. package/packages/core/src/workers/workers.test.ts +3 -10
  180. package/scripts/bump-version.ts +248 -0
  181. package/scripts/generate-skill-bundle.ts +108 -0
  182. package/test/acceptance-checks.test.ts +403 -0
  183. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  184. package/test/browser-backend.test.ts +308 -0
  185. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  186. package/test/causal-events.test.ts +117 -0
  187. package/test/compaction.test.ts +105 -0
  188. package/test/context-compiler.test.ts +269 -0
  189. package/test/curator.test.ts +130 -0
  190. package/test/durable-queue.test.ts +114 -0
  191. package/test/harness-barrel.test.ts +64 -0
  192. package/test/hive-helpers.test.ts +130 -0
  193. package/test/hivedb-search.test.ts +189 -0
  194. package/test/internal-turns.test.ts +166 -0
  195. package/test/job-idempotency.test.ts +68 -0
  196. package/test/job-retry-backoff.test.ts +184 -0
  197. package/test/job-store.test.ts +381 -0
  198. package/test/llm-retry.test.ts +97 -0
  199. package/test/memory-perf.test.ts +774 -0
  200. package/test/minimal-loadout.test.ts +78 -0
  201. package/test/model-catalog.test.ts +105 -0
  202. package/test/preload.ts +12 -0
  203. package/test/reflector.test.ts +320 -0
  204. package/test/retention-cap.test.ts +91 -0
  205. package/test/retired-capabilities-pruned.test.ts +192 -0
  206. package/test/run-store.test.ts +355 -0
  207. package/test/scratchpad.test.ts +74 -0
  208. package/test/secrets-durability.test.ts +119 -0
  209. package/test/seed-model-reseed.test.ts +155 -0
  210. package/test/setup-agent-seed.test.ts +264 -0
  211. package/test/tool-inventory.test.ts +65 -0
  212. package/test/tool-runtime.test.ts +258 -0
  213. package/test/tool-selector-runtime-tools.test.ts +117 -0
  214. package/test/toon.test.ts +429 -0
  215. package/tsconfig.json +2 -0
  216. package/packages/core/src/ace/Curator.ts +0 -158
  217. package/packages/core/src/ace/Reflector.ts +0 -200
  218. package/packages/core/src/ace/index.ts +0 -4
  219. package/packages/core/src/agent/AgentRunner.ts +0 -711
  220. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  221. package/packages/core/src/agent/ContextGuard.ts +0 -91
  222. package/packages/core/src/agent/ConversationStore.ts +0 -254
  223. package/packages/core/src/agent/Hooks.ts +0 -166
  224. package/packages/core/src/agent/StuckLoop.ts +0 -133
  225. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  226. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  227. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  228. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  229. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  230. package/packages/core/src/agent/selectors/index.ts +0 -6
  231. package/packages/core/src/auth/auth.ts +0 -121
  232. package/packages/core/src/auth/index.ts +0 -1
  233. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  234. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  235. package/packages/core/src/harness/collections.ts +0 -98
  236. package/packages/core/src/harness/goal-verifier.ts +0 -141
  237. package/packages/core/src/harness/harness.test.ts +0 -236
  238. package/packages/core/src/harness/reconcile.ts +0 -149
  239. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  240. package/packages/core/src/multimodal/VisionService.ts +0 -293
  241. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  242. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  243. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  244. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  245. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  246. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  247. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  248. package/packages/core/src/scheduler/dag/index.ts +0 -26
  249. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  250. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  251. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  252. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  253. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  254. package/packages/core/src/storage/hiveSeed.ts +0 -308
  255. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  256. package/packages/core/src/storage/schema.ts +0 -689
  257. package/packages/core/src/storage/storage.test.ts +0 -37
  258. package/packages/core/src/swarm/AgentBus.ts +0 -460
  259. package/packages/core/src/swarm/EventBus.ts +0 -169
  260. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  261. package/packages/core/src/tools/bridge-events.ts +0 -26
  262. package/packages/core/src/tools/canvas/index.ts +0 -375
  263. package/packages/core/src/tools/codebridge/index.ts +0 -342
  264. package/packages/core/src/tools/meeting/index.ts +0 -353
  265. package/packages/core/src/tools/projects/index.ts +0 -37
  266. package/packages/core/src/tools/projects/project-create.ts +0 -94
  267. package/packages/core/src/tools/projects/project-done.ts +0 -66
  268. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  269. package/packages/core/src/tools/projects/project-list.ts +0 -96
  270. package/packages/core/src/tools/projects/project-update.ts +0 -72
  271. package/packages/core/src/tools/projects/task-create.ts +0 -68
  272. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  273. package/packages/core/src/tools/projects/task-update.ts +0 -93
  274. package/packages/core/src/tools/voice/index.ts +0 -104
  275. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  276. package/packages/core/src/tools/web/api-request.ts +0 -239
  277. package/test/setup-db.ts +0 -216
  278. /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
- }