@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.
- package/LICENSE +153 -0
- package/README.md +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +261 -0
- package/src/actions/browser/session.ts +506 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +363 -0
- package/src/actions/tools/builtin.ts +950 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +301 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/dashboard-auth.ts +75 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/dashboard-auth.test.ts +170 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +407 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +120 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +218 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +195 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
package/src/llm/groq.ts
ADDED
|
@@ -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
|
+
}
|
package/src/llm/index.ts
ADDED
|
@@ -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';
|