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.
@@ -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
- ? (config.basicModel || config.advancedModel || 'gpt-4.1')
81
- : (config.advancedModel || 'gpt-4.1');
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 => [...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) || sessionApprovedTools.has(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
- // Check if we should use DIFF+APPLY for filesystem tools
271
- let usedDiffApply = false;
272
- if (vscodeConnection.isConnected()) {
273
- // Try to use DIFF+APPLY for filesystem_create or filesystem_edit
274
- if (firstTool.function.name === 'filesystem-create' || firstTool.function.name === 'filesystem-edit') {
275
- try {
276
- const args = JSON.parse(firstTool.function.arguments);
277
- if (firstTool.function.name === 'filesystem-create') {
278
- // For create, show diff with empty old content
279
- const confirmation = await vscodeConnection.requestDiffApply(args.filePath, '', // Empty old content for new file
280
- args.content);
281
- if (confirmation === 'reject') {
282
- // Remove pending tool messages
283
- setMessages(prev => prev.filter(msg => !msg.toolPending));
284
- setMessages(prev => [...prev, {
285
- role: 'assistant',
286
- content: 'Tool call rejected, session ended',
287
- streaming: false
288
- }]);
289
- if (options.setIsStreaming) {
290
- options.setIsStreaming(false);
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 we didn't use DIFF+APPLY, fall back to regular CLI confirmation
335
- if (!usedDiffApply) {
336
- // Pass all tools for proper display in confirmation UI
337
- const allTools = toolsNeedingConfirmation.length > 1
338
- ? toolsNeedingConfirmation
339
- : undefined;
340
- // Use first tool for confirmation UI, but apply result to all
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' && !isError) {
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
- name: toolCall.function.name,
435
- arguments: editDiffData
436
- } : terminalResultData ? {
437
- name: toolCall.function.name,
438
- arguments: terminalResultData
439
- } : msg.toolCall,
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 = { role: 'user', content: combinedMessage };
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