snow-ai 0.2.18 → 0.2.20
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/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +87 -30
- package/dist/cli.js +25 -0
- package/dist/hooks/useCommandHandler.js +24 -16
- package/dist/hooks/useConversation.js +87 -136
- package/dist/hooks/useKeyboardInput.js +9 -0
- package/dist/mcp/aceCodeSearch.d.ts +314 -0
- package/dist/mcp/aceCodeSearch.js +822 -0
- package/dist/mcp/filesystem.d.ts +63 -79
- package/dist/mcp/filesystem.js +321 -165
- package/dist/ui/components/DiffViewer.d.ts +2 -1
- package/dist/ui/components/DiffViewer.js +33 -16
- package/dist/ui/components/MarkdownRenderer.js +65 -16
- package/dist/ui/components/ToolConfirmation.js +9 -0
- package/dist/ui/components/ToolResultPreview.js +146 -24
- package/dist/ui/pages/ChatScreen.js +54 -7
- package/dist/utils/commandExecutor.d.ts +1 -0
- package/dist/utils/commands/ide.js +12 -12
- package/dist/utils/mcpToolsManager.js +54 -9
- package/dist/utils/sessionConverter.js +8 -2
- package/dist/utils/vscodeConnection.d.ts +1 -9
- package/dist/utils/vscodeConnection.js +24 -68
- package/package.json +1 -1
|
@@ -10,15 +10,13 @@ import { getOpenAiConfig } from '../utils/apiConfig.js';
|
|
|
10
10
|
import { sessionManager } from '../utils/sessionManager.js';
|
|
11
11
|
import { formatTodoContext } from '../utils/todoPreprocessor.js';
|
|
12
12
|
import { formatToolCallMessage } from '../utils/messageFormatter.js';
|
|
13
|
-
import { vscodeConnection } from '../utils/vscodeConnection.js';
|
|
14
|
-
import { filesystemService } from '../mcp/filesystem.js';
|
|
15
13
|
/**
|
|
16
14
|
* Handle conversation with streaming and tool calls
|
|
17
15
|
*/
|
|
18
16
|
export async function handleConversationWithTools(options) {
|
|
19
17
|
const { userContent, imageContents, controller,
|
|
20
18
|
// messages, // No longer used - we load from session instead to get complete history with tool calls
|
|
21
|
-
saveMessage, setMessages, setStreamTokenCount, setCurrentTodos, requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved, yoloMode, setContextUsage, setIsReasoning, setRetryStatus } = options;
|
|
19
|
+
saveMessage, setMessages, setStreamTokenCount, setCurrentTodos, requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved, yoloMode, setContextUsage, setIsReasoning, setRetryStatus, } = options;
|
|
22
20
|
// Step 1: Ensure session exists and get existing TODOs
|
|
23
21
|
let currentSession = sessionManager.getCurrentSession();
|
|
24
22
|
if (!currentSession) {
|
|
@@ -35,14 +33,14 @@ export async function handleConversationWithTools(options) {
|
|
|
35
33
|
const mcpTools = await collectAllMCPTools();
|
|
36
34
|
// Build conversation history with TODO context as pinned user message
|
|
37
35
|
let conversationMessages = [
|
|
38
|
-
{ role: 'system', content: SYSTEM_PROMPT }
|
|
36
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
39
37
|
];
|
|
40
38
|
// If there are TODOs, add pinned context message at the front
|
|
41
39
|
if (existingTodoList && existingTodoList.todos.length > 0) {
|
|
42
40
|
const todoContext = formatTodoContext(existingTodoList.todos);
|
|
43
41
|
conversationMessages.push({
|
|
44
42
|
role: 'user',
|
|
45
|
-
content: todoContext
|
|
43
|
+
content: todoContext,
|
|
46
44
|
});
|
|
47
45
|
}
|
|
48
46
|
// Add history messages from session (includes tool_calls and tool results)
|
|
@@ -56,13 +54,13 @@ export async function handleConversationWithTools(options) {
|
|
|
56
54
|
conversationMessages.push({
|
|
57
55
|
role: 'user',
|
|
58
56
|
content: userContent,
|
|
59
|
-
images: imageContents
|
|
57
|
+
images: imageContents,
|
|
60
58
|
});
|
|
61
59
|
// Save user message (directly save API format message)
|
|
62
60
|
saveMessage({
|
|
63
61
|
role: 'user',
|
|
64
62
|
content: userContent,
|
|
65
|
-
images: imageContents
|
|
63
|
+
images: imageContents,
|
|
66
64
|
}).catch(error => {
|
|
67
65
|
console.error('Failed to save user message:', error);
|
|
68
66
|
});
|
|
@@ -77,8 +75,8 @@ export async function handleConversationWithTools(options) {
|
|
|
77
75
|
setStreamTokenCount(0);
|
|
78
76
|
const config = getOpenAiConfig();
|
|
79
77
|
const model = options.useBasicModel
|
|
80
|
-
?
|
|
81
|
-
:
|
|
78
|
+
? config.basicModel || config.advancedModel || 'gpt-4.1'
|
|
79
|
+
: config.advancedModel || 'gpt-4.1';
|
|
82
80
|
// Tool calling loop (no limit on rounds)
|
|
83
81
|
let finalAssistantMessage = null;
|
|
84
82
|
// Local set to track approved tools in this conversation (solves async setState issue)
|
|
@@ -104,7 +102,7 @@ export async function handleConversationWithTools(options) {
|
|
|
104
102
|
isRetrying: true,
|
|
105
103
|
attempt,
|
|
106
104
|
nextDelay,
|
|
107
|
-
errorMessage: error.message
|
|
105
|
+
errorMessage: error.message,
|
|
108
106
|
});
|
|
109
107
|
}
|
|
110
108
|
};
|
|
@@ -115,14 +113,14 @@ export async function handleConversationWithTools(options) {
|
|
|
115
113
|
temperature: 0,
|
|
116
114
|
max_tokens: config.maxTokens || 4096,
|
|
117
115
|
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
118
|
-
sessionId: currentSession?.id
|
|
116
|
+
sessionId: currentSession?.id,
|
|
119
117
|
}, controller.signal, onRetry)
|
|
120
118
|
: config.requestMethod === 'gemini'
|
|
121
119
|
? createStreamingGeminiCompletion({
|
|
122
120
|
model,
|
|
123
121
|
messages: conversationMessages,
|
|
124
122
|
temperature: 0,
|
|
125
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined
|
|
123
|
+
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
126
124
|
}, controller.signal, onRetry)
|
|
127
125
|
: config.requestMethod === 'responses'
|
|
128
126
|
? createStreamingResponse({
|
|
@@ -130,13 +128,13 @@ export async function handleConversationWithTools(options) {
|
|
|
130
128
|
messages: conversationMessages,
|
|
131
129
|
temperature: 0,
|
|
132
130
|
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
133
|
-
prompt_cache_key: cacheKey // Use session ID as cache key
|
|
131
|
+
prompt_cache_key: cacheKey, // Use session ID as cache key
|
|
134
132
|
}, controller.signal, onRetry)
|
|
135
133
|
: createStreamingChatCompletion({
|
|
136
134
|
model,
|
|
137
135
|
messages: conversationMessages,
|
|
138
136
|
temperature: 0,
|
|
139
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined
|
|
137
|
+
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
140
138
|
}, controller.signal, onRetry);
|
|
141
139
|
for await (const chunk of streamGenerator) {
|
|
142
140
|
if (controller.signal.aborted)
|
|
@@ -215,9 +213,9 @@ export async function handleConversationWithTools(options) {
|
|
|
215
213
|
type: 'function',
|
|
216
214
|
function: {
|
|
217
215
|
name: tc.function.name,
|
|
218
|
-
arguments: tc.function.arguments
|
|
219
|
-
}
|
|
220
|
-
}))
|
|
216
|
+
arguments: tc.function.arguments,
|
|
217
|
+
},
|
|
218
|
+
})),
|
|
221
219
|
};
|
|
222
220
|
conversationMessages.push(assistantMessage);
|
|
223
221
|
// Save assistant message with tool calls
|
|
@@ -234,25 +232,29 @@ export async function handleConversationWithTools(options) {
|
|
|
234
232
|
catch (e) {
|
|
235
233
|
toolArgs = {};
|
|
236
234
|
}
|
|
237
|
-
setMessages(prev => [
|
|
235
|
+
setMessages(prev => [
|
|
236
|
+
...prev,
|
|
237
|
+
{
|
|
238
238
|
role: 'assistant',
|
|
239
239
|
content: `⚡ ${toolDisplay.toolName}`,
|
|
240
240
|
streaming: false,
|
|
241
241
|
toolCall: {
|
|
242
242
|
name: toolCall.function.name,
|
|
243
|
-
arguments: toolArgs
|
|
243
|
+
arguments: toolArgs,
|
|
244
244
|
},
|
|
245
245
|
toolDisplay,
|
|
246
246
|
toolCallId: toolCall.id, // Store tool call ID for later update
|
|
247
|
-
toolPending: true // Mark as pending execution
|
|
248
|
-
}
|
|
247
|
+
toolPending: true, // Mark as pending execution
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
249
250
|
}
|
|
250
251
|
// Filter tools that need confirmation (not in always-approved list OR session-approved list)
|
|
251
252
|
const toolsNeedingConfirmation = [];
|
|
252
253
|
const autoApprovedTools = [];
|
|
253
254
|
for (const toolCall of receivedToolCalls) {
|
|
254
255
|
// Check both global approved list and session-approved list
|
|
255
|
-
if (isToolAutoApproved(toolCall.function.name) ||
|
|
256
|
+
if (isToolAutoApproved(toolCall.function.name) ||
|
|
257
|
+
sessionApprovedTools.has(toolCall.function.name)) {
|
|
256
258
|
autoApprovedTools.push(toolCall);
|
|
257
259
|
}
|
|
258
260
|
else {
|
|
@@ -267,105 +269,42 @@ export async function handleConversationWithTools(options) {
|
|
|
267
269
|
}
|
|
268
270
|
else if (toolsNeedingConfirmation.length > 0) {
|
|
269
271
|
const firstTool = toolsNeedingConfirmation[0]; // Safe: length > 0 guarantees this exists
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
encoder.free();
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (confirmation === 'approve_always') {
|
|
296
|
-
addMultipleToAlwaysApproved([firstTool.function.name]);
|
|
297
|
-
sessionApprovedTools.add(firstTool.function.name);
|
|
298
|
-
}
|
|
299
|
-
approvedTools.push(...toolsNeedingConfirmation);
|
|
300
|
-
usedDiffApply = true;
|
|
301
|
-
}
|
|
302
|
-
else if (firstTool.function.name === 'filesystem-edit') {
|
|
303
|
-
// For edit, read the file first to get old content
|
|
304
|
-
const fileContent = await filesystemService.getFileContent(args.filePath, args.startLine, args.endLine);
|
|
305
|
-
const confirmation = await vscodeConnection.requestDiffApply(args.filePath, fileContent.content, args.newContent);
|
|
306
|
-
if (confirmation === 'reject') {
|
|
307
|
-
// Remove pending tool messages
|
|
308
|
-
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
309
|
-
setMessages(prev => [...prev, {
|
|
310
|
-
role: 'assistant',
|
|
311
|
-
content: 'Tool call rejected, session ended',
|
|
312
|
-
streaming: false
|
|
313
|
-
}]);
|
|
314
|
-
if (options.setIsStreaming) {
|
|
315
|
-
options.setIsStreaming(false);
|
|
316
|
-
}
|
|
317
|
-
encoder.free();
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (confirmation === 'approve_always') {
|
|
321
|
-
addMultipleToAlwaysApproved([firstTool.function.name]);
|
|
322
|
-
sessionApprovedTools.add(firstTool.function.name);
|
|
323
|
-
}
|
|
324
|
-
approvedTools.push(...toolsNeedingConfirmation);
|
|
325
|
-
usedDiffApply = true;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
// If DIFF+APPLY fails, fall back to regular confirmation
|
|
330
|
-
console.error('Failed to use DIFF+APPLY:', error);
|
|
331
|
-
}
|
|
272
|
+
// Use regular CLI confirmation
|
|
273
|
+
// Pass all tools for proper display in confirmation UI
|
|
274
|
+
const allTools = toolsNeedingConfirmation.length > 1
|
|
275
|
+
? toolsNeedingConfirmation
|
|
276
|
+
: undefined;
|
|
277
|
+
// Use first tool for confirmation UI, but apply result to all
|
|
278
|
+
const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
|
|
279
|
+
if (confirmation === 'reject') {
|
|
280
|
+
// Remove pending tool messages
|
|
281
|
+
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
282
|
+
// User rejected - end conversation
|
|
283
|
+
setMessages(prev => [
|
|
284
|
+
...prev,
|
|
285
|
+
{
|
|
286
|
+
role: 'assistant',
|
|
287
|
+
content: 'Tool call rejected, session ended',
|
|
288
|
+
streaming: false,
|
|
289
|
+
},
|
|
290
|
+
]);
|
|
291
|
+
// End streaming immediately
|
|
292
|
+
if (options.setIsStreaming) {
|
|
293
|
+
options.setIsStreaming(false);
|
|
332
294
|
}
|
|
295
|
+
encoder.free();
|
|
296
|
+
return; // Exit the conversation loop
|
|
333
297
|
}
|
|
334
|
-
// If
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
|
|
342
|
-
if (confirmation === 'reject') {
|
|
343
|
-
// Remove pending tool messages
|
|
344
|
-
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
345
|
-
// User rejected - end conversation
|
|
346
|
-
setMessages(prev => [...prev, {
|
|
347
|
-
role: 'assistant',
|
|
348
|
-
content: 'Tool call rejected, session ended',
|
|
349
|
-
streaming: false
|
|
350
|
-
}]);
|
|
351
|
-
// End streaming immediately
|
|
352
|
-
if (options.setIsStreaming) {
|
|
353
|
-
options.setIsStreaming(false);
|
|
354
|
-
}
|
|
355
|
-
encoder.free();
|
|
356
|
-
return; // Exit the conversation loop
|
|
357
|
-
}
|
|
358
|
-
// If approved_always, add ALL these tools to both global and session-approved sets
|
|
359
|
-
if (confirmation === 'approve_always') {
|
|
360
|
-
const toolNamesToAdd = toolsNeedingConfirmation.map(t => t.function.name);
|
|
361
|
-
// Add to global state (async, for future sessions)
|
|
362
|
-
addMultipleToAlwaysApproved(toolNamesToAdd);
|
|
363
|
-
// Add to local session set (sync, for this conversation)
|
|
364
|
-
toolNamesToAdd.forEach(name => sessionApprovedTools.add(name));
|
|
365
|
-
}
|
|
366
|
-
// Add all tools to approved list
|
|
367
|
-
approvedTools.push(...toolsNeedingConfirmation);
|
|
298
|
+
// If approved_always, add ALL these tools to both global and session-approved sets
|
|
299
|
+
if (confirmation === 'approve_always') {
|
|
300
|
+
const toolNamesToAdd = toolsNeedingConfirmation.map(t => t.function.name);
|
|
301
|
+
// Add to global state (async, for future sessions)
|
|
302
|
+
addMultipleToAlwaysApproved(toolNamesToAdd);
|
|
303
|
+
// Add to local session set (sync, for this conversation)
|
|
304
|
+
toolNamesToAdd.forEach(name => sessionApprovedTools.add(name));
|
|
368
305
|
}
|
|
306
|
+
// Add all tools to approved list
|
|
307
|
+
approvedTools.push(...toolsNeedingConfirmation);
|
|
369
308
|
}
|
|
370
309
|
// Execute approved tools
|
|
371
310
|
const toolResults = await executeToolCalls(approvedTools);
|
|
@@ -390,14 +329,19 @@ export async function handleConversationWithTools(options) {
|
|
|
390
329
|
const statusText = isError ? `\n └─ ${result.content}` : '';
|
|
391
330
|
// Check if this is an edit tool with diff data
|
|
392
331
|
let editDiffData;
|
|
393
|
-
if (toolCall.function.name === 'filesystem-edit'
|
|
332
|
+
if ((toolCall.function.name === 'filesystem-edit' ||
|
|
333
|
+
toolCall.function.name === 'filesystem-edit_search') &&
|
|
334
|
+
!isError) {
|
|
394
335
|
try {
|
|
395
336
|
const resultData = JSON.parse(result.content);
|
|
396
337
|
if (resultData.oldContent && resultData.newContent) {
|
|
397
338
|
editDiffData = {
|
|
398
339
|
oldContent: resultData.oldContent,
|
|
399
340
|
newContent: resultData.newContent,
|
|
400
|
-
filename: JSON.parse(toolCall.function.arguments).filePath
|
|
341
|
+
filename: JSON.parse(toolCall.function.arguments).filePath,
|
|
342
|
+
completeOldContent: resultData.completeOldContent,
|
|
343
|
+
completeNewContent: resultData.completeNewContent,
|
|
344
|
+
contextStartLine: resultData.contextStartLine,
|
|
401
345
|
};
|
|
402
346
|
}
|
|
403
347
|
}
|
|
@@ -415,7 +359,7 @@ export async function handleConversationWithTools(options) {
|
|
|
415
359
|
stdout: resultData.stdout || '',
|
|
416
360
|
stderr: resultData.stderr || '',
|
|
417
361
|
exitCode: resultData.exitCode || 0,
|
|
418
|
-
command: resultData.command
|
|
362
|
+
command: resultData.command,
|
|
419
363
|
};
|
|
420
364
|
}
|
|
421
365
|
}
|
|
@@ -430,15 +374,19 @@ export async function handleConversationWithTools(options) {
|
|
|
430
374
|
...msg,
|
|
431
375
|
content: `${statusIcon} ${toolCall.function.name}${statusText}`,
|
|
432
376
|
toolPending: false,
|
|
433
|
-
toolCall: editDiffData
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
377
|
+
toolCall: editDiffData
|
|
378
|
+
? {
|
|
379
|
+
name: toolCall.function.name,
|
|
380
|
+
arguments: editDiffData,
|
|
381
|
+
}
|
|
382
|
+
: terminalResultData
|
|
383
|
+
? {
|
|
384
|
+
name: toolCall.function.name,
|
|
385
|
+
arguments: terminalResultData,
|
|
386
|
+
}
|
|
387
|
+
: msg.toolCall,
|
|
440
388
|
// Store tool result for preview rendering
|
|
441
|
-
toolResult: !isError ? result.content : undefined
|
|
389
|
+
toolResult: !isError ? result.content : undefined,
|
|
442
390
|
};
|
|
443
391
|
}
|
|
444
392
|
return msg;
|
|
@@ -458,8 +406,8 @@ export async function handleConversationWithTools(options) {
|
|
|
458
406
|
role: 'assistant',
|
|
459
407
|
content: '',
|
|
460
408
|
streaming: false,
|
|
461
|
-
showTodoTree: true
|
|
462
|
-
}
|
|
409
|
+
showTodoTree: true,
|
|
410
|
+
},
|
|
463
411
|
]);
|
|
464
412
|
}
|
|
465
413
|
// Check if there are pending user messages to insert
|
|
@@ -471,17 +419,20 @@ export async function handleConversationWithTools(options) {
|
|
|
471
419
|
// Combine multiple pending messages into one
|
|
472
420
|
const combinedMessage = pendingMessages.join('\n\n');
|
|
473
421
|
// Add user message to UI
|
|
474
|
-
const userMessage = {
|
|
422
|
+
const userMessage = {
|
|
423
|
+
role: 'user',
|
|
424
|
+
content: combinedMessage,
|
|
425
|
+
};
|
|
475
426
|
setMessages(prev => [...prev, userMessage]);
|
|
476
427
|
// Add user message to conversation history
|
|
477
428
|
conversationMessages.push({
|
|
478
429
|
role: 'user',
|
|
479
|
-
content: combinedMessage
|
|
430
|
+
content: combinedMessage,
|
|
480
431
|
});
|
|
481
432
|
// Save user message
|
|
482
433
|
saveMessage({
|
|
483
434
|
role: 'user',
|
|
484
|
-
content: combinedMessage
|
|
435
|
+
content: combinedMessage,
|
|
485
436
|
}).catch(error => {
|
|
486
437
|
console.error('Failed to save pending user message:', error);
|
|
487
438
|
});
|
|
@@ -496,13 +447,13 @@ export async function handleConversationWithTools(options) {
|
|
|
496
447
|
role: 'assistant',
|
|
497
448
|
content: streamedContent.trim(),
|
|
498
449
|
streaming: false,
|
|
499
|
-
discontinued: controller.signal.aborted
|
|
450
|
+
discontinued: controller.signal.aborted,
|
|
500
451
|
};
|
|
501
452
|
setMessages(prev => [...prev, finalAssistantMessage]);
|
|
502
453
|
// Add to conversation history and save
|
|
503
454
|
const assistantMessage = {
|
|
504
455
|
role: 'assistant',
|
|
505
|
-
content: streamedContent.trim()
|
|
456
|
+
content: streamedContent.trim(),
|
|
506
457
|
};
|
|
507
458
|
conversationMessages.push(assistantMessage);
|
|
508
459
|
saveMessage(assistantMessage).catch(error => {
|
|
@@ -26,6 +26,15 @@ export function useKeyboardInput(options) {
|
|
|
26
26
|
useInput((input, key) => {
|
|
27
27
|
if (disabled)
|
|
28
28
|
return;
|
|
29
|
+
// Shift+Tab - Toggle YOLO mode
|
|
30
|
+
if (key.shift && key.tab) {
|
|
31
|
+
executeCommand('yolo').then(result => {
|
|
32
|
+
if (onCommand) {
|
|
33
|
+
onCommand('yolo', result);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
29
38
|
// Handle escape key for double-ESC history navigation
|
|
30
39
|
if (key.escape) {
|
|
31
40
|
// Close file picker if open
|