@shykaruu/jarvis-brain 0.4.0

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 (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,406 @@
1
+ import type {
2
+ LLMProvider,
3
+ LLMMessage,
4
+ LLMOptions,
5
+ LLMResponse,
6
+ LLMStreamEvent,
7
+ LLMTool,
8
+ LLMToolCall,
9
+ } from './provider.ts';
10
+ import { compactHistory, calculateHistoryBudget } from './history.ts';
11
+
12
+ type GroqMessage = {
13
+ role: 'system' | 'user' | 'assistant' | 'tool';
14
+ content: string | null;
15
+ tool_calls?: GroqToolCall[];
16
+ tool_call_id?: string;
17
+ };
18
+
19
+ type GroqToolDef = {
20
+ type: 'function';
21
+ function: {
22
+ name: string;
23
+ description: string;
24
+ parameters: Record<string, unknown>;
25
+ };
26
+ };
27
+
28
+ type GroqToolCall = {
29
+ id: string;
30
+ type: 'function';
31
+ function: {
32
+ name: string;
33
+ arguments: string;
34
+ };
35
+ };
36
+
37
+ type GroqResponse = {
38
+ id: string;
39
+ object: 'chat.completion';
40
+ created: number;
41
+ model: string;
42
+ choices: Array<{
43
+ index: number;
44
+ message: {
45
+ role: 'assistant';
46
+ content: string | null;
47
+ tool_calls?: GroqToolCall[];
48
+ };
49
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
50
+ }>;
51
+ usage: {
52
+ prompt_tokens: number;
53
+ completion_tokens: number;
54
+ total_tokens: number;
55
+ };
56
+ };
57
+
58
+ type GroqStreamChunk = {
59
+ id: string;
60
+ object: 'chat.completion.chunk';
61
+ created: number;
62
+ model: string;
63
+ choices: Array<{
64
+ index: number;
65
+ delta: {
66
+ role?: 'assistant';
67
+ content?: string;
68
+ tool_calls?: Array<{
69
+ index: number;
70
+ id?: string;
71
+ type?: 'function';
72
+ function?: {
73
+ name?: string;
74
+ arguments?: string;
75
+ };
76
+ }>;
77
+ };
78
+ finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter' | null;
79
+ }>;
80
+ };
81
+
82
+ export class GroqProvider implements LLMProvider {
83
+ name = 'groq';
84
+ private apiKey: string;
85
+ private defaultModel: string;
86
+ private apiUrl = 'https://api.groq.com/openai/v1/chat/completions';
87
+ private static readonly SAFE_PROMPT_CHAR_BUDGET = 24_000;
88
+ private static readonly SAFE_TOOL_OVERHEAD_CHARS = 8_000;
89
+
90
+ constructor(apiKey: string, defaultModel = 'llama-3.3-70b-versatile') {
91
+ this.apiKey = apiKey;
92
+ this.defaultModel = defaultModel;
93
+ }
94
+
95
+ async chat(messages: LLMMessage[], options: LLMOptions = {}): Promise<LLMResponse> {
96
+ const body = this.buildRequestBody(messages, options, false);
97
+
98
+ const response = await fetch(this.apiUrl, {
99
+ method: 'POST',
100
+ headers: {
101
+ 'Authorization': `Bearer ${this.apiKey}`,
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ body: JSON.stringify(body),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ const errorText = await response.text();
109
+ throw new Error(`Groq API error (${response.status}): ${errorText}`);
110
+ }
111
+
112
+ const data = await response.json() as GroqResponse;
113
+ return this.convertResponse(data);
114
+ }
115
+
116
+ async *stream(messages: LLMMessage[], options: LLMOptions = {}): AsyncIterable<LLMStreamEvent> {
117
+ const body = this.buildRequestBody(messages, options, true);
118
+ const responseModel = typeof body.model === 'string' ? body.model : this.defaultModel;
119
+
120
+ const response = await fetch(this.apiUrl, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Authorization': `Bearer ${this.apiKey}`,
124
+ 'Content-Type': 'application/json',
125
+ },
126
+ body: JSON.stringify(body),
127
+ });
128
+
129
+ if (!response.ok) {
130
+ const errorText = await response.text();
131
+ yield { type: 'error', error: `Groq API error (${response.status}): ${errorText}` };
132
+ return;
133
+ }
134
+
135
+ if (!response.body) {
136
+ yield { type: 'error', error: 'No response body' };
137
+ return;
138
+ }
139
+
140
+ let accumulatedText = '';
141
+ const toolCalls: LLMToolCall[] = [];
142
+ const toolCallBuilders: Map<number, { id: string; name: string; arguments: string }> = new Map();
143
+ let finishReason: string | null = null;
144
+ let streamedModel = responseModel;
145
+
146
+ try {
147
+ const reader = response.body.getReader();
148
+ const decoder = new TextDecoder();
149
+ let buffer = '';
150
+
151
+ while (true) {
152
+ const { done, value } = await reader.read();
153
+ if (done) break;
154
+
155
+ buffer += decoder.decode(value, { stream: true });
156
+ const lines = buffer.split('\n');
157
+ buffer = lines.pop() || '';
158
+
159
+ for (const line of lines) {
160
+ if (!line.trim() || !line.startsWith('data: ')) continue;
161
+
162
+ const data = line.slice(6);
163
+ if (data === '[DONE]') continue;
164
+
165
+ try {
166
+ const chunk = JSON.parse(data) as GroqStreamChunk;
167
+ if (chunk.choices && chunk.choices.length > 0) {
168
+ const choice = chunk.choices[0];
169
+ streamedModel = chunk.model;
170
+
171
+ if (choice!.delta.content) {
172
+ accumulatedText += choice!.delta.content;
173
+ yield { type: 'text', text: choice!.delta.content };
174
+ }
175
+
176
+ if (choice!.delta.tool_calls) {
177
+ for (const toolCallDelta of choice!.delta.tool_calls) {
178
+ const index = toolCallDelta.index;
179
+ let builder = toolCallBuilders.get(index);
180
+
181
+ if (!builder) {
182
+ builder = {
183
+ id: toolCallDelta.id || '',
184
+ name: toolCallDelta.function?.name || '',
185
+ arguments: '',
186
+ };
187
+ toolCallBuilders.set(index, builder);
188
+ }
189
+
190
+ if (toolCallDelta.id) builder.id = toolCallDelta.id;
191
+ if (toolCallDelta.function?.name) builder.name = toolCallDelta.function.name;
192
+ if (toolCallDelta.function?.arguments) {
193
+ builder.arguments += toolCallDelta.function.arguments;
194
+ }
195
+ }
196
+ }
197
+
198
+ if (choice!.finish_reason) {
199
+ finishReason = choice!.finish_reason;
200
+ }
201
+ }
202
+ } catch (err) {
203
+ // Skip invalid JSON lines
204
+ console.error('Failed to parse SSE chunk:', err);
205
+ }
206
+ }
207
+ }
208
+
209
+ // Convert accumulated tool calls
210
+ for (const builder of toolCallBuilders.values()) {
211
+ try {
212
+ const toolCall: LLMToolCall = {
213
+ id: builder.id,
214
+ name: builder.name,
215
+ arguments: JSON.parse(builder.arguments),
216
+ };
217
+ toolCalls.push(toolCall);
218
+ yield { type: 'tool_call', tool_call: toolCall };
219
+ } catch (err) {
220
+ yield { type: 'error', error: `Failed to parse tool call arguments: ${err}` };
221
+ }
222
+ }
223
+
224
+ const mappedFinishReason = this.mapFinishReason(finishReason);
225
+ yield {
226
+ type: 'done',
227
+ response: {
228
+ content: accumulatedText,
229
+ tool_calls: toolCalls,
230
+ usage: { input_tokens: 0, output_tokens: 0 },
231
+ model: streamedModel,
232
+ finish_reason: mappedFinishReason,
233
+ },
234
+ };
235
+ } catch (err) {
236
+ yield { type: 'error', error: `Stream error: ${err}` };
237
+ }
238
+ }
239
+
240
+ async listModels(): Promise<string[]> {
241
+ try {
242
+ const response = await fetch('https://api.groq.com/openai/v1/models', {
243
+ headers: {
244
+ 'Authorization': `Bearer ${this.apiKey}`,
245
+ },
246
+ });
247
+
248
+ if (!response.ok) {
249
+ throw new Error(`Failed to list models: ${response.status}`);
250
+ }
251
+
252
+ const data = await response.json() as { data: Array<{ id: string }> };
253
+ return data.data.map(m => m.id).sort();
254
+ } catch (_err) {
255
+ return [
256
+ 'llama-3.3-70b-versatile',
257
+ 'llama-3.1-8b-instant',
258
+ 'qwen/qwen3-32b',
259
+ 'deepseek-r1-distill-llama-70b',
260
+ ];
261
+ }
262
+ }
263
+
264
+ private buildRequestBody(messages: LLMMessage[], options: LLMOptions, stream: boolean): Record<string, unknown> {
265
+ const { model = this.defaultModel, temperature, max_tokens, tools } = options;
266
+ const body: Record<string, unknown> = {
267
+ model,
268
+ messages: this.convertMessages(this.compactMessages(messages, tools)),
269
+ };
270
+
271
+ if (stream) body.stream = true;
272
+ if (temperature !== undefined) body.temperature = temperature;
273
+ if (max_tokens !== undefined) body.max_completion_tokens = max_tokens;
274
+ if (tools && tools.length > 0) {
275
+ body.tools = this.convertTools(tools);
276
+ body.tool_choice = 'auto';
277
+ body.parallel_tool_calls = true;
278
+ }
279
+
280
+ return body;
281
+ }
282
+
283
+ private convertMessages(messages: LLMMessage[]): GroqMessage[] {
284
+ return messages.map(m => {
285
+ const text = typeof m.content === 'string'
286
+ ? m.content
287
+ : m.content.map((b) => b.type === 'text' ? b.text : '[image]').join('\n');
288
+ const hasToolCalls = !!(m.tool_calls && m.tool_calls.length > 0);
289
+ const msg: GroqMessage = {
290
+ role: m.role as 'system' | 'user' | 'assistant' | 'tool',
291
+ content: hasToolCalls && text.trim().length === 0 ? null : text,
292
+ };
293
+ if (m.tool_calls && m.tool_calls.length > 0) {
294
+ msg.tool_calls = m.tool_calls.map(tc => ({
295
+ id: tc.id,
296
+ type: 'function' as const,
297
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
298
+ }));
299
+ }
300
+ if (m.tool_call_id) {
301
+ msg.tool_call_id = m.tool_call_id;
302
+ }
303
+ return msg;
304
+ });
305
+ }
306
+
307
+ private compactMessages(messages: LLMMessage[], tools?: LLMTool[]): LLMMessage[] {
308
+ if (messages.length <= 2) return messages;
309
+
310
+ const toolOverhead = tools && tools.length > 0
311
+ ? Math.min(
312
+ GroqProvider.SAFE_TOOL_OVERHEAD_CHARS,
313
+ JSON.stringify(this.convertTools(tools)).length,
314
+ )
315
+ : 0;
316
+ const budget = Math.max(8_000, GroqProvider.SAFE_PROMPT_CHAR_BUDGET - toolOverhead);
317
+ const systemMessage = messages[0]?.role === 'system' ? messages[0] : null;
318
+ const compacted: LLMMessage[] = [];
319
+ let used = systemMessage ? this.measureMessage(systemMessage) : 0;
320
+
321
+ if (systemMessage) compacted.push(systemMessage);
322
+
323
+ const startIndex = systemMessage ? 1 : 0;
324
+ const keptTail: LLMMessage[] = [];
325
+
326
+ for (let i = messages.length - 1; i >= startIndex; i--) {
327
+ const current = messages[i]!;
328
+ const size = this.measureMessage(current);
329
+ if (keptTail.length > 0 && used + size > budget) {
330
+ break;
331
+ }
332
+ keptTail.push(current);
333
+ used += size;
334
+ }
335
+
336
+ keptTail.reverse();
337
+ compacted.push(...keptTail);
338
+ return compacted;
339
+ }
340
+
341
+ private measureMessage(message: LLMMessage): number {
342
+ const content = typeof message.content === 'string'
343
+ ? message.content
344
+ : message.content.map((b) => b.type === 'text' ? b.text : '[image]').join('\n');
345
+ const toolCallsSize = message.tool_calls ? JSON.stringify(message.tool_calls).length : 0;
346
+ return content.length + toolCallsSize + 128;
347
+ }
348
+
349
+ private convertTools(tools: LLMTool[]): GroqToolDef[] {
350
+ return tools.map(tool => ({
351
+ type: 'function',
352
+ function: {
353
+ name: tool.name,
354
+ description: tool.description,
355
+ parameters: tool.parameters,
356
+ },
357
+ }));
358
+ }
359
+
360
+ private convertResponse(response: GroqResponse): LLMResponse {
361
+ const choice = response.choices[0]!;
362
+ const message = choice.message;
363
+ const content = message.content || '';
364
+ const tool_calls: LLMToolCall[] = [];
365
+
366
+ if (message.tool_calls) {
367
+ for (const toolCall of message.tool_calls) {
368
+ try {
369
+ tool_calls.push({
370
+ id: toolCall.id,
371
+ name: toolCall.function.name,
372
+ arguments: JSON.parse(toolCall.function.arguments),
373
+ });
374
+ } catch (err) {
375
+ console.error('Failed to parse tool call arguments:', err);
376
+ }
377
+ }
378
+ }
379
+
380
+ return {
381
+ content,
382
+ tool_calls,
383
+ usage: {
384
+ input_tokens: response.usage.prompt_tokens,
385
+ output_tokens: response.usage.completion_tokens,
386
+ },
387
+ model: response.model,
388
+ finish_reason: this.mapFinishReason(choice!.finish_reason),
389
+ };
390
+ }
391
+
392
+ private mapFinishReason(finishReason: string | null): 'stop' | 'tool_use' | 'length' | 'error' {
393
+ switch (finishReason) {
394
+ case 'stop':
395
+ return 'stop';
396
+ case 'tool_calls':
397
+ return 'tool_use';
398
+ case 'length':
399
+ return 'length';
400
+ case 'content_filter':
401
+ return 'error';
402
+ default:
403
+ return 'stop';
404
+ }
405
+ }
406
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Message History Compaction — Tool-Call Aware
3
+ *
4
+ * Intelligently trims long message histories while preserving:
5
+ * - System prompt (first message)
6
+ * - Latest conversation turns
7
+ * - Complete tool-call exchange chains
8
+ *
9
+ * This prevents "request too large" errors and orphaned tool messages
10
+ * that break LLM tool-calling APIs.
11
+ */
12
+
13
+ import type { LLMMessage } from './provider.ts';
14
+
15
+ const SYSTEM_RESERVE = 500; // Tokens reserved for system prompt
16
+ const MINIMUM_BUDGET_PER_TURN = 100; // Minimum tokens per turn
17
+
18
+ /**
19
+ * Compact message history for LLM API requests.
20
+ *
21
+ * @param messages - Full message history starting with system prompt
22
+ * @param budgetTokens - Token budget (typically max_tokens * 3 to leave room for output)
23
+ * @returns Compacted message list: system prompt + latest turns that fit budget
24
+ */
25
+ export function compactHistory(messages: LLMMessage[], budgetTokens: number): LLMMessage[] {
26
+ if (messages.length === 0) return [];
27
+ if (messages.length === 1) return messages; // Only system prompt
28
+
29
+ const compacted: LLMMessage[] = [];
30
+
31
+ // Always keep system prompt
32
+ if (messages[0]?.role === 'system') {
33
+ compacted.push(messages[0]);
34
+ }
35
+
36
+ const budget = budgetTokens - SYSTEM_RESERVE;
37
+ let used = compacted[0] ? measureMessage(compacted[0]) : 0;
38
+
39
+ // Group remaining messages into atomic chunks
40
+ // Each chunk = assistant with tool_calls + all subsequent tool results
41
+ // Or just individual regular messages
42
+ const chunks = chunkMessages(messages.slice(1));
43
+ const keptChunks: LLMMessage[][] = [];
44
+
45
+ // Work backwards from latest messages
46
+ for (let i = chunks.length - 1; i >= 0; i--) {
47
+ const chunk = chunks[i]!;
48
+ const size = measureChunk(chunk);
49
+
50
+ // Stop if adding this chunk exceeds budget (with previous chunks kept)
51
+ if (keptChunks.length > 0 && used + size > budget) {
52
+ break;
53
+ }
54
+
55
+ keptChunks.push(chunk);
56
+ used += size;
57
+ }
58
+
59
+ // Reverse back to chronological order and add to compacted
60
+ keptChunks.reverse();
61
+ for (const chunk of keptChunks) {
62
+ compacted.push(...chunk);
63
+ }
64
+
65
+ return compacted;
66
+ }
67
+
68
+ /**
69
+ * Group messages into atomic chunks for preservation during compaction.
70
+ *
71
+ * A chunk is either:
72
+ * - An assistant message with tool_calls + all its subsequent tool result messages
73
+ * - A single regular message
74
+ *
75
+ * This ensures tool-call exchanges stay together (required by OpenAI/Groq/etc).
76
+ */
77
+ function chunkMessages(messages: LLMMessage[]): LLMMessage[][] {
78
+ const chunks: LLMMessage[][] = [];
79
+
80
+ for (let i = 0; i < messages.length; i++) {
81
+ const current = messages[i]!;
82
+
83
+ // Start of a tool-use exchange
84
+ if (current.role === 'assistant' && current.tool_calls && current.tool_calls.length > 0) {
85
+ const chunk: LLMMessage[] = [current];
86
+ i++;
87
+
88
+ // Collect all subsequent tool result messages
89
+ while (i < messages.length && messages[i]!.role === 'tool') {
90
+ chunk.push(messages[i]!);
91
+ i++;
92
+ }
93
+
94
+ // Back up one because the loop will increment
95
+ i--;
96
+ chunks.push(chunk);
97
+ } else {
98
+ // Regular message (user, system, or assistant without tool_calls)
99
+ chunks.push([current]);
100
+ }
101
+ }
102
+
103
+ return chunks;
104
+ }
105
+
106
+ /**
107
+ * Estimate token count for a message (rough heuristic).
108
+ * 1 token ≈ 4 characters + fixed overhead per message
109
+ */
110
+ function measureMessage(message: LLMMessage): number {
111
+ const contentStr = typeof message.content === 'string'
112
+ ? message.content
113
+ : message.content.map(b => b.type === 'text' ? b.text : '[image]').join('\n');
114
+
115
+ let size = Math.ceil(contentStr.length / 4) + 10; // 10 token overhead
116
+
117
+ if (message.tool_calls) {
118
+ for (const tc of message.tool_calls) {
119
+ const argsStr = JSON.stringify(tc.arguments);
120
+ size += Math.ceil(argsStr.length / 4) + 5;
121
+ }
122
+ }
123
+
124
+ return size;
125
+ }
126
+
127
+ /**
128
+ * Estimate token count for a message chunk (multiple messages).
129
+ */
130
+ function measureChunk(messages: LLMMessage[]): number {
131
+ return messages.reduce((total, msg) => total + measureMessage(msg), 0);
132
+ }
133
+
134
+ /**
135
+ * Calculate effective budget for history compaction.
136
+ * Reserve space in token limit for: system prompt + response generation
137
+ */
138
+ export function calculateHistoryBudget(
139
+ requestTokenLimit: number,
140
+ systemPromptTokens: number = 500,
141
+ responseReserve: number = 1000,
142
+ ): number {
143
+ return Math.max(
144
+ requestTokenLimit - systemPromptTokens - responseReserve,
145
+ 0
146
+ );
147
+ }
@@ -0,0 +1,21 @@
1
+ // Provider types and interfaces
2
+ export type {
3
+ LLMMessage,
4
+ LLMTool,
5
+ LLMToolCall,
6
+ LLMResponse,
7
+ LLMStreamEvent,
8
+ LLMOptions,
9
+ LLMProvider,
10
+ } from './provider.ts';
11
+
12
+ // Provider implementations
13
+ export { AnthropicProvider } from './anthropic.ts';
14
+ export { OpenAIProvider } from './openai.ts';
15
+ export { GroqProvider } from './groq.ts';
16
+ export { GeminiProvider } from './gemini.ts';
17
+ export { OllamaProvider } from './ollama.ts';
18
+ export { OpenRouterProvider } from './openrouter.ts';
19
+
20
+ // Manager
21
+ export { LLMManager } from './manager.ts';