@benzsiangco/jarvis 1.0.0 → 1.1.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/README.md +5 -0
- package/bin/{jarvis.js → jarvis} +1 -1
- package/dist/cli.js +476 -350
- package/dist/electron/main.js +160 -0
- package/dist/electron/preload.js +19 -0
- package/package.json +21 -8
- package/skills.md +147 -0
- package/src/agents/index.ts +248 -0
- package/src/brain/loader.ts +136 -0
- package/src/cli.ts +411 -0
- package/src/config/index.ts +363 -0
- package/src/core/executor.ts +222 -0
- package/src/core/plugins.ts +148 -0
- package/src/core/types.ts +217 -0
- package/src/electron/main.ts +192 -0
- package/src/electron/preload.ts +25 -0
- package/src/electron/types.d.ts +20 -0
- package/src/index.ts +12 -0
- package/src/providers/antigravity-loader.ts +233 -0
- package/src/providers/antigravity.ts +585 -0
- package/src/providers/index.ts +523 -0
- package/src/sessions/index.ts +194 -0
- package/src/tools/index.ts +436 -0
- package/src/tui/index.tsx +784 -0
- package/src/utils/auth-prompt.ts +394 -0
- package/src/utils/index.ts +180 -0
- package/src/utils/native-picker.ts +71 -0
- package/src/utils/skills.ts +99 -0
- package/src/utils/table-integration-examples.ts +617 -0
- package/src/utils/table-utils.ts +401 -0
- package/src/web/build-ui.ts +27 -0
- package/src/web/server.ts +674 -0
- package/src/web/ui/dist/.gitkeep +0 -0
- package/src/web/ui/dist/main.css +1 -0
- package/src/web/ui/dist/main.js +320 -0
- package/src/web/ui/dist/main.js.map +20 -0
- package/src/web/ui/index.html +46 -0
- package/src/web/ui/src/App.tsx +143 -0
- package/src/web/ui/src/Modules/Safety/GuardianModal.tsx +83 -0
- package/src/web/ui/src/components/Layout/ContextPanel.tsx +243 -0
- package/src/web/ui/src/components/Layout/Header.tsx +91 -0
- package/src/web/ui/src/components/Layout/ModelSelector.tsx +235 -0
- package/src/web/ui/src/components/Layout/SessionStats.tsx +369 -0
- package/src/web/ui/src/components/Layout/Sidebar.tsx +895 -0
- package/src/web/ui/src/components/Modules/Chat/ChatStage.tsx +620 -0
- package/src/web/ui/src/components/Modules/Chat/MessageItem.tsx +446 -0
- package/src/web/ui/src/components/Modules/Editor/CommandInspector.tsx +71 -0
- package/src/web/ui/src/components/Modules/Editor/DiffViewer.tsx +83 -0
- package/src/web/ui/src/components/Modules/Terminal/TabbedTerminal.tsx +202 -0
- package/src/web/ui/src/components/Settings/SettingsModal.tsx +935 -0
- package/src/web/ui/src/config/models.ts +70 -0
- package/src/web/ui/src/main.tsx +13 -0
- package/src/web/ui/src/store/agentStore.ts +41 -0
- package/src/web/ui/src/store/uiStore.ts +64 -0
- package/src/web/ui/src/types/index.ts +54 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// Configuration loader for Jarvis
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
|
+
import type { JarvisConfig, ProviderConfig, ModelConfig } from '../core/types';
|
|
6
|
+
|
|
7
|
+
// Re-export JarvisConfig for use by other modules
|
|
8
|
+
export type { JarvisConfig } from '../core/types';
|
|
9
|
+
|
|
10
|
+
const CONFIG_FILE_NAME = 'jarvis.json';
|
|
11
|
+
const CONFIG_DIR = join(homedir(), '.config', 'jarvis');
|
|
12
|
+
|
|
13
|
+
// Default Antigravity models from opencode-antigravity-auth
|
|
14
|
+
const defaultAntigravityModels: Record<string, ModelConfig> = {
|
|
15
|
+
'antigravity-gemini-3-pro': {
|
|
16
|
+
name: 'Gemini 3 Pro (Antigravity)',
|
|
17
|
+
limit: { context: 1048576, output: 65535 },
|
|
18
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
19
|
+
variants: {
|
|
20
|
+
low: { thinkingLevel: 'low' },
|
|
21
|
+
high: { thinkingLevel: 'high' },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
'antigravity-gemini-3-flash': {
|
|
25
|
+
name: 'Gemini 3 Flash (Antigravity)',
|
|
26
|
+
limit: { context: 1048576, output: 65536 },
|
|
27
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
28
|
+
variants: {
|
|
29
|
+
minimal: { thinkingLevel: 'minimal' },
|
|
30
|
+
low: { thinkingLevel: 'low' },
|
|
31
|
+
medium: { thinkingLevel: 'medium' },
|
|
32
|
+
high: { thinkingLevel: 'high' },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
'antigravity-claude-sonnet-4-5': {
|
|
36
|
+
name: 'Claude Sonnet 4.5 (Antigravity)',
|
|
37
|
+
limit: { context: 200000, output: 64000 },
|
|
38
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
39
|
+
},
|
|
40
|
+
'antigravity-claude-sonnet-4-5-thinking': {
|
|
41
|
+
name: 'Claude Sonnet 4.5 Thinking (Antigravity)',
|
|
42
|
+
limit: { context: 200000, output: 64000 },
|
|
43
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
44
|
+
variants: {
|
|
45
|
+
low: { thinkingConfig: { thinkingBudget: 8192 } },
|
|
46
|
+
max: { thinkingConfig: { thinkingBudget: 32768 } },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
'antigravity-claude-opus-4-5-thinking': {
|
|
50
|
+
name: 'Claude Opus 4.5 Thinking (Antigravity)',
|
|
51
|
+
limit: { context: 200000, output: 64000 },
|
|
52
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
53
|
+
variants: {
|
|
54
|
+
low: { thinkingConfig: { thinkingBudget: 8192 } },
|
|
55
|
+
max: { thinkingConfig: { thinkingBudget: 32768 } },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
'gemini-2.5-flash': {
|
|
59
|
+
name: 'Gemini 2.5 Flash (Gemini CLI)',
|
|
60
|
+
limit: { context: 1048576, output: 65536 },
|
|
61
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
62
|
+
},
|
|
63
|
+
'gemini-2.5-pro': {
|
|
64
|
+
name: 'Gemini 2.5 Pro (Gemini CLI)',
|
|
65
|
+
limit: { context: 1048576, output: 65536 },
|
|
66
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
67
|
+
},
|
|
68
|
+
'gemini-3-flash-preview': {
|
|
69
|
+
name: 'Gemini 3 Flash Preview (Gemini CLI)',
|
|
70
|
+
limit: { context: 1048576, output: 65536 },
|
|
71
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
72
|
+
},
|
|
73
|
+
'gemini-3-pro-preview': {
|
|
74
|
+
name: 'Gemini 3 Pro Preview (Gemini CLI)',
|
|
75
|
+
limit: { context: 1048576, output: 65535 },
|
|
76
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Default configuration
|
|
81
|
+
const defaultConfig: JarvisConfig = {
|
|
82
|
+
$schema: 'https://opencode.ai/config.json',
|
|
83
|
+
model: 'google/antigravity-claude-sonnet-4-5',
|
|
84
|
+
plugin: ['opencode-antigravity-auth@latest'],
|
|
85
|
+
provider: {
|
|
86
|
+
google: {
|
|
87
|
+
models: defaultAntigravityModels,
|
|
88
|
+
},
|
|
89
|
+
anthropic: {
|
|
90
|
+
models: {
|
|
91
|
+
'claude-sonnet-4-20250514': {
|
|
92
|
+
name: 'Claude Sonnet 4',
|
|
93
|
+
limit: { context: 200000, output: 64000 },
|
|
94
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
95
|
+
},
|
|
96
|
+
'claude-haiku-4-20250514': {
|
|
97
|
+
name: 'Claude Haiku 4',
|
|
98
|
+
limit: { context: 200000, output: 64000 },
|
|
99
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
100
|
+
},
|
|
101
|
+
'claude-opus-4-20250514': {
|
|
102
|
+
name: 'Claude Opus 4',
|
|
103
|
+
limit: { context: 200000, output: 64000 },
|
|
104
|
+
modalities: { input: ['text', 'image', 'pdf'], output: ['text'] },
|
|
105
|
+
},
|
|
106
|
+
'claude-3-5-sonnet-20241022': {
|
|
107
|
+
name: 'Claude 3.5 Sonnet',
|
|
108
|
+
limit: { context: 200000, output: 8192 },
|
|
109
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
openai: {
|
|
114
|
+
models: {
|
|
115
|
+
'gpt-4o': {
|
|
116
|
+
name: 'GPT-4o',
|
|
117
|
+
limit: { context: 128000, output: 16384 },
|
|
118
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
119
|
+
},
|
|
120
|
+
'gpt-4o-mini': {
|
|
121
|
+
name: 'GPT-4o Mini',
|
|
122
|
+
limit: { context: 128000, output: 16384 },
|
|
123
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
124
|
+
},
|
|
125
|
+
'gpt-4-turbo': {
|
|
126
|
+
name: 'GPT-4 Turbo',
|
|
127
|
+
limit: { context: 128000, output: 4096 },
|
|
128
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
129
|
+
},
|
|
130
|
+
'o1-preview': {
|
|
131
|
+
name: 'o1 Preview',
|
|
132
|
+
limit: { context: 128000, output: 32768 },
|
|
133
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
134
|
+
},
|
|
135
|
+
'o1-mini': {
|
|
136
|
+
name: 'o1 Mini',
|
|
137
|
+
limit: { context: 128000, output: 65536 },
|
|
138
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
139
|
+
},
|
|
140
|
+
'o3-mini': {
|
|
141
|
+
name: 'o3 Mini',
|
|
142
|
+
limit: { context: 128000, output: 65536 },
|
|
143
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
groq: {
|
|
148
|
+
models: {
|
|
149
|
+
'llama-3.3-70b-versatile': {
|
|
150
|
+
name: 'Llama 3.3 70B',
|
|
151
|
+
limit: { context: 128000, output: 32768 },
|
|
152
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
153
|
+
},
|
|
154
|
+
'llama-3.1-8b-instant': {
|
|
155
|
+
name: 'Llama 3.1 8B',
|
|
156
|
+
limit: { context: 128000, output: 8192 },
|
|
157
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
158
|
+
},
|
|
159
|
+
'mixtral-8x7b-32768': {
|
|
160
|
+
name: 'Mixtral 8x7B',
|
|
161
|
+
limit: { context: 32768, output: 32768 },
|
|
162
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
163
|
+
},
|
|
164
|
+
'gemma2-9b-it': {
|
|
165
|
+
name: 'Gemma 2 9B',
|
|
166
|
+
limit: { context: 8192, output: 8192 },
|
|
167
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
mistral: {
|
|
172
|
+
models: {
|
|
173
|
+
'mistral-large-latest': {
|
|
174
|
+
name: 'Mistral Large',
|
|
175
|
+
limit: { context: 128000, output: 8192 },
|
|
176
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
177
|
+
},
|
|
178
|
+
'mistral-medium-latest': {
|
|
179
|
+
name: 'Mistral Medium',
|
|
180
|
+
limit: { context: 32000, output: 8192 },
|
|
181
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
182
|
+
},
|
|
183
|
+
'codestral-latest': {
|
|
184
|
+
name: 'Codestral',
|
|
185
|
+
limit: { context: 32000, output: 8192 },
|
|
186
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
187
|
+
},
|
|
188
|
+
'open-mistral-nemo': {
|
|
189
|
+
name: 'Mistral Nemo',
|
|
190
|
+
limit: { context: 128000, output: 8192 },
|
|
191
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
deepseek: {
|
|
196
|
+
models: {
|
|
197
|
+
'deepseek-chat': {
|
|
198
|
+
name: 'DeepSeek Chat',
|
|
199
|
+
limit: { context: 64000, output: 8192 },
|
|
200
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
201
|
+
},
|
|
202
|
+
'deepseek-reasoner': {
|
|
203
|
+
name: 'DeepSeek Reasoner (R1)',
|
|
204
|
+
limit: { context: 64000, output: 8192 },
|
|
205
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
xai: {
|
|
210
|
+
models: {
|
|
211
|
+
'grok-3': {
|
|
212
|
+
name: 'Grok 3',
|
|
213
|
+
limit: { context: 131072, output: 8192 },
|
|
214
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
215
|
+
},
|
|
216
|
+
'grok-3-mini': {
|
|
217
|
+
name: 'Grok 3 Mini',
|
|
218
|
+
limit: { context: 131072, output: 8192 },
|
|
219
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
220
|
+
},
|
|
221
|
+
'grok-2-1212': {
|
|
222
|
+
name: 'Grok 2',
|
|
223
|
+
limit: { context: 131072, output: 8192 },
|
|
224
|
+
modalities: { input: ['text', 'image'], output: ['text'] },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
together: {
|
|
229
|
+
models: {
|
|
230
|
+
'meta-llama/Llama-3.3-70B-Instruct-Turbo': {
|
|
231
|
+
name: 'Llama 3.3 70B Turbo',
|
|
232
|
+
limit: { context: 131072, output: 8192 },
|
|
233
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
234
|
+
},
|
|
235
|
+
'deepseek-ai/DeepSeek-R1': {
|
|
236
|
+
name: 'DeepSeek R1',
|
|
237
|
+
limit: { context: 128000, output: 8192 },
|
|
238
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
239
|
+
},
|
|
240
|
+
'Qwen/Qwen2.5-Coder-32B-Instruct': {
|
|
241
|
+
name: 'Qwen 2.5 Coder 32B',
|
|
242
|
+
limit: { context: 32768, output: 8192 },
|
|
243
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
tools: {
|
|
249
|
+
bash: true,
|
|
250
|
+
read: true,
|
|
251
|
+
write: true,
|
|
252
|
+
edit: true,
|
|
253
|
+
glob: true,
|
|
254
|
+
grep: true,
|
|
255
|
+
webfetch: true,
|
|
256
|
+
task: true,
|
|
257
|
+
todoread: true,
|
|
258
|
+
todowrite: true,
|
|
259
|
+
question: true,
|
|
260
|
+
},
|
|
261
|
+
permission: {
|
|
262
|
+
edit: 'allow',
|
|
263
|
+
bash: 'allow',
|
|
264
|
+
webfetch: 'allow',
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export function getConfigDir(): string {
|
|
269
|
+
return CONFIG_DIR;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function getGlobalConfigPath(): string {
|
|
273
|
+
return join(CONFIG_DIR, CONFIG_FILE_NAME);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function getProjectConfigPath(projectDir?: string): string {
|
|
277
|
+
const dir = projectDir || process.cwd();
|
|
278
|
+
return join(dir, CONFIG_FILE_NAME);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function ensureConfigDir(): void {
|
|
282
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
283
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function loadConfig(projectDir?: string): JarvisConfig {
|
|
288
|
+
ensureConfigDir();
|
|
289
|
+
|
|
290
|
+
let config: JarvisConfig = { ...defaultConfig };
|
|
291
|
+
|
|
292
|
+
// Load global config
|
|
293
|
+
const globalConfigPath = getGlobalConfigPath();
|
|
294
|
+
if (existsSync(globalConfigPath)) {
|
|
295
|
+
try {
|
|
296
|
+
const globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf-8'));
|
|
297
|
+
config = mergeConfigs(config, globalConfig);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error(`Error loading global config: ${error}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Load project config
|
|
304
|
+
const projectConfigPath = getProjectConfigPath(projectDir);
|
|
305
|
+
if (existsSync(projectConfigPath)) {
|
|
306
|
+
try {
|
|
307
|
+
const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf-8'));
|
|
308
|
+
config = mergeConfigs(config, projectConfig);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(`Error loading project config: ${error}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return config;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function saveGlobalConfig(config: JarvisConfig): void {
|
|
318
|
+
ensureConfigDir();
|
|
319
|
+
writeFileSync(getGlobalConfigPath(), JSON.stringify(config, null, 2));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function saveProjectConfig(config: Partial<JarvisConfig>, projectDir?: string): void {
|
|
323
|
+
const configPath = getProjectConfigPath(projectDir);
|
|
324
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function mergeConfigs(base: JarvisConfig, override: Partial<JarvisConfig>): JarvisConfig {
|
|
328
|
+
return {
|
|
329
|
+
...base,
|
|
330
|
+
...override,
|
|
331
|
+
provider: {
|
|
332
|
+
...base.provider,
|
|
333
|
+
...override.provider,
|
|
334
|
+
},
|
|
335
|
+
agent: {
|
|
336
|
+
...base.agent,
|
|
337
|
+
...override.agent,
|
|
338
|
+
},
|
|
339
|
+
tools: {
|
|
340
|
+
...base.tools,
|
|
341
|
+
...override.tools,
|
|
342
|
+
},
|
|
343
|
+
permission: {
|
|
344
|
+
...base.permission,
|
|
345
|
+
...override.permission,
|
|
346
|
+
},
|
|
347
|
+
plugin: override.plugin ?? base.plugin,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function parseModelId(modelId: string): { providerId: string; modelId: string } {
|
|
352
|
+
const parts = modelId.split('/');
|
|
353
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
354
|
+
return { providerId: parts[0], modelId: parts[1] };
|
|
355
|
+
}
|
|
356
|
+
// Default to google provider if no provider specified
|
|
357
|
+
return { providerId: 'google', modelId };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function getModelConfig(config: JarvisConfig, modelId: string): ModelConfig | undefined {
|
|
361
|
+
const { providerId, modelId: model } = parseModelId(modelId);
|
|
362
|
+
return config.provider?.[providerId]?.models?.[model];
|
|
363
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { streamText, generateText, type CoreMessage, type Tool as AITool } from 'ai';
|
|
2
|
+
import type { AgentConfig, Message, Session, Tool, ToolContext, ToolResult } from '../core/types';
|
|
3
|
+
import { getModel } from '../providers';
|
|
4
|
+
import { getTool, getAllTools } from '../tools';
|
|
5
|
+
import { getAgentSystemPrompt, isToolEnabled } from '../agents';
|
|
6
|
+
import { addMessage, getSession, setTodos, getTodos } from '../sessions';
|
|
7
|
+
import { loadConfig } from '../config';
|
|
8
|
+
|
|
9
|
+
// Convert our tools to AI SDK format
|
|
10
|
+
function convertToolsToAIFormat(agent: AgentConfig, context: ToolContext): Record<string, AITool<any, any>> {
|
|
11
|
+
const aiTools: Record<string, AITool<any, any>> = {};
|
|
12
|
+
const allTools = getAllTools();
|
|
13
|
+
|
|
14
|
+
for (const tool of allTools) {
|
|
15
|
+
if (!isToolEnabled(agent, tool.name)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
aiTools[tool.name] = {
|
|
20
|
+
description: tool.description,
|
|
21
|
+
parameters: tool.parameters as any,
|
|
22
|
+
execute: async (args: Record<string, unknown>) => {
|
|
23
|
+
// Handle security permissions
|
|
24
|
+
const permission = (agent.permission as any)?.[tool.name] || 'allow';
|
|
25
|
+
if (permission === 'deny') {
|
|
26
|
+
return { error: `Permission denied for tool: ${tool.name}` };
|
|
27
|
+
}
|
|
28
|
+
// In web/cli mode 'ask' defaults to 'allow' for now unless we implement interactive prompt
|
|
29
|
+
|
|
30
|
+
console.log(`[EXECUTOR] Executing tool: ${tool.name}`);
|
|
31
|
+
return await tool.execute(args, context);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return aiTools;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildMessages(session: Session): CoreMessage[] {
|
|
40
|
+
const messages: CoreMessage[] = [];
|
|
41
|
+
for (const msg of session.messages) {
|
|
42
|
+
if (msg.role === 'user') {
|
|
43
|
+
messages.push({ role: 'user', content: msg.content });
|
|
44
|
+
} else if (msg.role === 'assistant') {
|
|
45
|
+
messages.push({ role: 'assistant', content: msg.content });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return messages;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ExecutorOptions {
|
|
52
|
+
session: Session;
|
|
53
|
+
agent: AgentConfig;
|
|
54
|
+
modelOptions?: Record<string, unknown>;
|
|
55
|
+
onText?: (text: string) => void;
|
|
56
|
+
onToolStart?: (toolName: string, args: Record<string, unknown>) => void;
|
|
57
|
+
onToolEnd?: (toolName: string, result: ToolResult) => void;
|
|
58
|
+
onThinking?: (thinking: string) => void;
|
|
59
|
+
onError?: (error: Error) => void;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ExecutorResult {
|
|
64
|
+
message: Message;
|
|
65
|
+
toolCalls: Array<{
|
|
66
|
+
name: string;
|
|
67
|
+
args: Record<string, unknown>;
|
|
68
|
+
result: ToolResult;
|
|
69
|
+
}>;
|
|
70
|
+
usage?: any;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function executeAgent(
|
|
74
|
+
userMessage: string,
|
|
75
|
+
options: ExecutorOptions
|
|
76
|
+
): Promise<ExecutorResult> {
|
|
77
|
+
const { session, agent, modelOptions, onText, onToolStart, onToolEnd, onThinking, onError, signal } = options;
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
|
|
80
|
+
const modelId = agent.model || config.model || 'google/antigravity-claude-sonnet-4-5-thinking';
|
|
81
|
+
const model = getModel(modelId, modelOptions);
|
|
82
|
+
|
|
83
|
+
if (!model) {
|
|
84
|
+
throw new Error(`Model not found: ${modelId}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
addMessage(session.id, { role: 'user', content: userMessage });
|
|
88
|
+
|
|
89
|
+
const toolContext: ToolContext = {
|
|
90
|
+
workdir: session.workdir || process.cwd(),
|
|
91
|
+
session,
|
|
92
|
+
signal,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const tools = convertToolsToAIFormat(agent, toolContext);
|
|
96
|
+
const messages = buildMessages(session);
|
|
97
|
+
const systemPrompt = getAgentSystemPrompt(agent);
|
|
98
|
+
|
|
99
|
+
const toolCalls: ExecutorResult['toolCalls'] = [];
|
|
100
|
+
let fullText = '';
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const result = await streamText({
|
|
104
|
+
model,
|
|
105
|
+
system: systemPrompt,
|
|
106
|
+
messages,
|
|
107
|
+
tools,
|
|
108
|
+
maxSteps: agent.maxSteps || 50,
|
|
109
|
+
temperature: agent.temperature,
|
|
110
|
+
abortSignal: signal,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
for await (const part of result.fullStream) {
|
|
114
|
+
if (part.type === 'text-delta') {
|
|
115
|
+
if (onText) onText(part.textDelta);
|
|
116
|
+
fullText += part.textDelta;
|
|
117
|
+
} else if (part.type === 'tool-call') {
|
|
118
|
+
if (onToolStart) onToolStart(part.toolName, part.args as Record<string, unknown>);
|
|
119
|
+
} else if (part.type === 'reasoning') {
|
|
120
|
+
if (onThinking) onThinking(part.textDelta);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const steps = await result.steps;
|
|
125
|
+
for (const step of steps) {
|
|
126
|
+
for (const tc of step.toolCalls) {
|
|
127
|
+
const foundResult = step.toolResults.find((tr: any) => tr.toolCallId === tc.toolCallId);
|
|
128
|
+
toolCalls.push({
|
|
129
|
+
name: tc.toolName,
|
|
130
|
+
args: tc.args as Record<string, unknown>,
|
|
131
|
+
result: (foundResult as any)?.result as ToolResult,
|
|
132
|
+
});
|
|
133
|
+
if (onToolEnd) onToolEnd(tc.toolName, (foundResult as any)?.result as ToolResult);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const usage = await result.usage;
|
|
138
|
+
const assistantMessage = addMessage(session.id, {
|
|
139
|
+
role: 'assistant',
|
|
140
|
+
content: fullText,
|
|
141
|
+
parts: toolCalls.map(tc => ({
|
|
142
|
+
type: 'tool_call' as const,
|
|
143
|
+
content: JSON.stringify(tc.result),
|
|
144
|
+
toolName: tc.name,
|
|
145
|
+
toolArgs: tc.args,
|
|
146
|
+
toolResult: tc.result,
|
|
147
|
+
})),
|
|
148
|
+
metadata: { usage },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return { message: assistantMessage, toolCalls, usage };
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (onError) onError(err as Error);
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function executeAgentSync(
|
|
159
|
+
userMessage: string,
|
|
160
|
+
options: Omit<ExecutorOptions, 'onText' | 'onToolStart' | 'onToolEnd' | 'onThinking'>
|
|
161
|
+
): Promise<ExecutorResult> {
|
|
162
|
+
const { session, agent, signal } = options;
|
|
163
|
+
const config = loadConfig();
|
|
164
|
+
|
|
165
|
+
const modelId = agent.model || config.model || 'google/antigravity-claude-sonnet-4-5-thinking';
|
|
166
|
+
const model = getModel(modelId);
|
|
167
|
+
|
|
168
|
+
if (!model) {
|
|
169
|
+
throw new Error(`Model not found: ${modelId}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
addMessage(session.id, { role: 'user', content: userMessage });
|
|
173
|
+
|
|
174
|
+
const toolContext: ToolContext = {
|
|
175
|
+
workdir: session.workdir || process.cwd(),
|
|
176
|
+
session,
|
|
177
|
+
signal,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const tools = convertToolsToAIFormat(agent, toolContext);
|
|
181
|
+
const messages = buildMessages(session);
|
|
182
|
+
const systemPrompt = getAgentSystemPrompt(agent);
|
|
183
|
+
|
|
184
|
+
const result = await generateText({
|
|
185
|
+
model,
|
|
186
|
+
system: systemPrompt,
|
|
187
|
+
messages,
|
|
188
|
+
tools,
|
|
189
|
+
maxSteps: agent.maxSteps || 50,
|
|
190
|
+
temperature: agent.temperature,
|
|
191
|
+
abortSignal: signal,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const toolCalls: ExecutorResult['toolCalls'] = [];
|
|
195
|
+
for (const step of result.steps) {
|
|
196
|
+
for (const tc of step.toolCalls) {
|
|
197
|
+
const toolResult = step.toolResults.find((tr: any) => tr.toolCallId === tc.toolCallId);
|
|
198
|
+
if (toolResult) {
|
|
199
|
+
toolCalls.push({
|
|
200
|
+
name: tc.toolName,
|
|
201
|
+
args: tc.args as Record<string, unknown>,
|
|
202
|
+
result: (toolResult as any).result as ToolResult,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const assistantMessage = addMessage(session.id, {
|
|
209
|
+
role: 'assistant',
|
|
210
|
+
content: result.text,
|
|
211
|
+
parts: toolCalls.map(tc => ({
|
|
212
|
+
type: 'tool_call' as const,
|
|
213
|
+
content: JSON.stringify(tc.result),
|
|
214
|
+
toolName: tc.name,
|
|
215
|
+
toolArgs: tc.args,
|
|
216
|
+
toolResult: tc.result,
|
|
217
|
+
})),
|
|
218
|
+
metadata: { usage: result.usage },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return { message: assistantMessage, toolCalls, usage: result.usage };
|
|
222
|
+
}
|