snow-ai 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/api/anthropic.js +38 -13
  2. package/dist/api/types.d.ts +1 -0
  3. package/dist/hooks/useConversation.js +226 -59
  4. package/dist/hooks/useSnapshotState.d.ts +2 -0
  5. package/dist/hooks/useToolConfirmation.js +1 -1
  6. package/dist/mcp/subagent.d.ts +35 -0
  7. package/dist/mcp/subagent.js +64 -0
  8. package/dist/ui/components/ChatInput.d.ts +1 -2
  9. package/dist/ui/components/ChatInput.js +47 -39
  10. package/dist/ui/components/FileRollbackConfirmation.d.ts +3 -2
  11. package/dist/ui/components/FileRollbackConfirmation.js +81 -22
  12. package/dist/ui/components/MessageList.d.ts +7 -1
  13. package/dist/ui/components/MessageList.js +16 -5
  14. package/dist/ui/components/ToolResultPreview.js +21 -1
  15. package/dist/ui/pages/ChatScreen.js +29 -20
  16. package/dist/ui/pages/ConfigScreen.js +47 -46
  17. package/dist/ui/pages/ProxyConfigScreen.js +1 -1
  18. package/dist/ui/pages/SubAgentConfigScreen.d.ts +9 -0
  19. package/dist/ui/pages/SubAgentConfigScreen.js +352 -0
  20. package/dist/ui/pages/SubAgentListScreen.d.ts +9 -0
  21. package/dist/ui/pages/SubAgentListScreen.js +114 -0
  22. package/dist/ui/pages/WelcomeScreen.js +30 -2
  23. package/dist/utils/incrementalSnapshot.d.ts +7 -0
  24. package/dist/utils/incrementalSnapshot.js +34 -0
  25. package/dist/utils/mcpToolsManager.js +41 -1
  26. package/dist/utils/retryUtils.js +5 -0
  27. package/dist/utils/sessionConverter.d.ts +1 -0
  28. package/dist/utils/sessionConverter.js +192 -89
  29. package/dist/utils/subAgentConfig.d.ts +43 -0
  30. package/dist/utils/subAgentConfig.js +126 -0
  31. package/dist/utils/subAgentExecutor.d.ts +29 -0
  32. package/dist/utils/subAgentExecutor.js +272 -0
  33. package/dist/utils/toolExecutor.d.ts +10 -2
  34. package/dist/utils/toolExecutor.js +46 -5
  35. package/package.json +1 -1
  36. package/dist/ui/pages/ConfigProfileScreen.d.ts +0 -7
  37. package/dist/ui/pages/ConfigProfileScreen.js +0 -300
@@ -0,0 +1,272 @@
1
+ import { createStreamingAnthropicCompletion } from '../api/anthropic.js';
2
+ import { createStreamingResponse } from '../api/responses.js';
3
+ import { createStreamingGeminiCompletion } from '../api/gemini.js';
4
+ import { createStreamingChatCompletion } from '../api/chat.js';
5
+ import { getSubAgent } from './subAgentConfig.js';
6
+ import { collectAllMCPTools, executeMCPTool } from './mcpToolsManager.js';
7
+ import { getOpenAiConfig } from './apiConfig.js';
8
+ import { sessionManager } from './sessionManager.js';
9
+ /**
10
+ * Execute a sub-agent as a tool
11
+ * @param agentId - The ID of the sub-agent to execute
12
+ * @param prompt - The task prompt to send to the sub-agent
13
+ * @param onMessage - Callback for streaming sub-agent messages (for UI display)
14
+ * @param abortSignal - Optional abort signal
15
+ * @param requestToolConfirmation - Callback to request tool confirmation from user
16
+ * @param isToolAutoApproved - Function to check if a tool is auto-approved
17
+ * @param yoloMode - Whether YOLO mode is enabled (auto-approve all tools)
18
+ * @returns The final result from the sub-agent
19
+ */
20
+ export async function executeSubAgent(agentId, prompt, onMessage, abortSignal, requestToolConfirmation, isToolAutoApproved, yoloMode) {
21
+ try {
22
+ // Get sub-agent configuration
23
+ const agent = getSubAgent(agentId);
24
+ if (!agent) {
25
+ return {
26
+ success: false,
27
+ result: '',
28
+ error: `Sub-agent with ID "${agentId}" not found`,
29
+ };
30
+ }
31
+ // Get all available tools
32
+ const allTools = await collectAllMCPTools();
33
+ // Filter tools based on sub-agent's allowed tools
34
+ const allowedTools = allTools.filter((tool) => {
35
+ const toolName = tool.function.name;
36
+ return agent.tools.some(allowedTool => {
37
+ // Normalize both tool names: replace underscores with hyphens for comparison
38
+ const normalizedToolName = toolName.replace(/_/g, '-');
39
+ const normalizedAllowedTool = allowedTool.replace(/_/g, '-');
40
+ // Support both exact match and prefix match (e.g., "filesystem" matches "filesystem-read")
41
+ return (normalizedToolName === normalizedAllowedTool ||
42
+ normalizedToolName.startsWith(`${normalizedAllowedTool}-`));
43
+ });
44
+ });
45
+ if (allowedTools.length === 0) {
46
+ return {
47
+ success: false,
48
+ result: '',
49
+ error: `Sub-agent "${agent.name}" has no valid tools configured`,
50
+ };
51
+ }
52
+ // Build conversation history for sub-agent
53
+ const messages = [
54
+ {
55
+ role: 'user',
56
+ content: prompt,
57
+ },
58
+ ];
59
+ // Stream sub-agent execution
60
+ let finalResponse = '';
61
+ let hasError = false;
62
+ let errorMessage = '';
63
+ const maxIterations = 10; // Prevent infinite loops
64
+ let iteration = 0;
65
+ while (iteration < maxIterations) {
66
+ iteration++;
67
+ // Check abort signal
68
+ if (abortSignal?.aborted) {
69
+ return {
70
+ success: false,
71
+ result: finalResponse,
72
+ error: 'Sub-agent execution aborted',
73
+ };
74
+ }
75
+ // Get API configuration
76
+ const config = getOpenAiConfig();
77
+ const currentSession = sessionManager.getCurrentSession();
78
+ const model = config.advancedModel || 'claude-3-5-sonnet-20241022';
79
+ // Call API with sub-agent's tools - choose API based on config
80
+ const stream = config.requestMethod === 'anthropic'
81
+ ? createStreamingAnthropicCompletion({
82
+ model,
83
+ messages,
84
+ temperature: 0,
85
+ max_tokens: config.maxTokens || 4096,
86
+ tools: allowedTools,
87
+ sessionId: currentSession?.id,
88
+ }, abortSignal)
89
+ : config.requestMethod === 'gemini'
90
+ ? createStreamingGeminiCompletion({
91
+ model,
92
+ messages,
93
+ temperature: 0,
94
+ tools: allowedTools,
95
+ }, abortSignal)
96
+ : config.requestMethod === 'responses'
97
+ ? createStreamingResponse({
98
+ model,
99
+ messages,
100
+ temperature: 0,
101
+ tools: allowedTools,
102
+ prompt_cache_key: currentSession?.id,
103
+ }, abortSignal)
104
+ : createStreamingChatCompletion({
105
+ model,
106
+ messages,
107
+ temperature: 0,
108
+ tools: allowedTools,
109
+ }, abortSignal);
110
+ let currentContent = '';
111
+ let toolCalls = [];
112
+ for await (const event of stream) {
113
+ // Forward message to UI (but don't save to main conversation)
114
+ if (onMessage) {
115
+ onMessage({
116
+ type: 'sub_agent_message',
117
+ agentId: agent.id,
118
+ agentName: agent.name,
119
+ message: event,
120
+ });
121
+ }
122
+ if (event.type === 'content' && event.content) {
123
+ currentContent += event.content;
124
+ }
125
+ else if (event.type === 'tool_calls' && event.tool_calls) {
126
+ toolCalls = event.tool_calls;
127
+ }
128
+ }
129
+ if (hasError) {
130
+ return {
131
+ success: false,
132
+ result: finalResponse,
133
+ error: errorMessage,
134
+ };
135
+ }
136
+ // Add assistant response to conversation
137
+ if (currentContent || toolCalls.length > 0) {
138
+ const assistantMessage = {
139
+ role: 'assistant',
140
+ content: currentContent || '',
141
+ };
142
+ if (toolCalls.length > 0) {
143
+ assistantMessage.tool_calls = toolCalls;
144
+ }
145
+ messages.push(assistantMessage);
146
+ finalResponse = currentContent;
147
+ }
148
+ // If no tool calls, we're done
149
+ if (toolCalls.length === 0) {
150
+ break;
151
+ }
152
+ // Check tool approvals before execution
153
+ const approvedToolCalls = [];
154
+ const rejectedToolCalls = [];
155
+ for (const toolCall of toolCalls) {
156
+ const toolName = toolCall.function.name;
157
+ let args;
158
+ try {
159
+ args = JSON.parse(toolCall.function.arguments);
160
+ }
161
+ catch (e) {
162
+ args = {};
163
+ }
164
+ // Check if tool needs confirmation
165
+ let needsConfirmation = true;
166
+ // In YOLO mode, auto-approve all tools
167
+ if (yoloMode) {
168
+ needsConfirmation = false;
169
+ }
170
+ // Check if tool is in auto-approved list
171
+ else if (isToolAutoApproved && isToolAutoApproved(toolName)) {
172
+ needsConfirmation = false;
173
+ }
174
+ if (needsConfirmation && requestToolConfirmation) {
175
+ // Request confirmation from user
176
+ const confirmation = await requestToolConfirmation(toolName, args);
177
+ if (confirmation === 'reject') {
178
+ rejectedToolCalls.push(toolCall);
179
+ continue;
180
+ }
181
+ // If approved or approved_always, continue execution
182
+ // (approved_always is handled by the main flow's session-approved list)
183
+ }
184
+ approvedToolCalls.push(toolCall);
185
+ }
186
+ // Handle rejected tools
187
+ if (rejectedToolCalls.length > 0) {
188
+ return {
189
+ success: false,
190
+ result: finalResponse,
191
+ error: `User rejected tool execution: ${rejectedToolCalls
192
+ .map(tc => tc.function.name)
193
+ .join(', ')}`,
194
+ };
195
+ }
196
+ // Execute approved tool calls
197
+ const toolResults = [];
198
+ for (const toolCall of approvedToolCalls) {
199
+ try {
200
+ const args = JSON.parse(toolCall.function.arguments);
201
+ const result = await executeMCPTool(toolCall.function.name, args, abortSignal);
202
+ const toolResult = {
203
+ role: 'tool',
204
+ tool_call_id: toolCall.id,
205
+ content: JSON.stringify(result),
206
+ };
207
+ toolResults.push(toolResult);
208
+ // Send tool result to UI
209
+ if (onMessage) {
210
+ onMessage({
211
+ type: 'sub_agent_message',
212
+ agentId: agent.id,
213
+ agentName: agent.name,
214
+ message: {
215
+ type: 'tool_result',
216
+ tool_call_id: toolCall.id,
217
+ tool_name: toolCall.function.name,
218
+ content: JSON.stringify(result),
219
+ },
220
+ });
221
+ }
222
+ }
223
+ catch (error) {
224
+ const errorResult = {
225
+ role: 'tool',
226
+ tool_call_id: toolCall.id,
227
+ content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
228
+ };
229
+ toolResults.push(errorResult);
230
+ // Send error result to UI
231
+ if (onMessage) {
232
+ onMessage({
233
+ type: 'sub_agent_message',
234
+ agentId: agent.id,
235
+ agentName: agent.name,
236
+ message: {
237
+ type: 'tool_result',
238
+ tool_call_id: toolCall.id,
239
+ tool_name: toolCall.function.name,
240
+ content: `Error: ${error instanceof Error
241
+ ? error.message
242
+ : 'Tool execution failed'}`,
243
+ },
244
+ });
245
+ }
246
+ }
247
+ }
248
+ // Add tool results to conversation
249
+ messages.push(...toolResults);
250
+ // Continue to next iteration if there were tool calls
251
+ // The loop will continue until no more tool calls or max iterations
252
+ }
253
+ if (iteration >= maxIterations) {
254
+ return {
255
+ success: false,
256
+ result: finalResponse,
257
+ error: 'Sub-agent exceeded maximum iterations',
258
+ };
259
+ }
260
+ return {
261
+ success: true,
262
+ result: finalResponse,
263
+ };
264
+ }
265
+ catch (error) {
266
+ return {
267
+ success: false,
268
+ result: '',
269
+ error: error instanceof Error ? error.message : 'Unknown error',
270
+ };
271
+ }
272
+ }
@@ -1,3 +1,4 @@
1
+ import type { SubAgentMessage } from './subAgentExecutor.js';
1
2
  export interface ToolCall {
2
3
  id: string;
3
4
  type: 'function';
@@ -11,11 +12,18 @@ export interface ToolResult {
11
12
  role: 'tool';
12
13
  content: string;
13
14
  }
15
+ export type SubAgentMessageCallback = (message: SubAgentMessage) => void;
16
+ export interface ToolConfirmationCallback {
17
+ (toolCall: ToolCall, batchToolNames?: string, allTools?: ToolCall[]): Promise<string>;
18
+ }
19
+ export interface ToolApprovalChecker {
20
+ (toolName: string): boolean;
21
+ }
14
22
  /**
15
23
  * Execute a single tool call and return the result
16
24
  */
17
- export declare function executeToolCall(toolCall: ToolCall, abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void): Promise<ToolResult>;
25
+ export declare function executeToolCall(toolCall: ToolCall, abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void, onSubAgentMessage?: SubAgentMessageCallback, requestToolConfirmation?: ToolConfirmationCallback, isToolAutoApproved?: ToolApprovalChecker, yoloMode?: boolean): Promise<ToolResult>;
18
26
  /**
19
27
  * Execute multiple tool calls in parallel
20
28
  */
21
- export declare function executeToolCalls(toolCalls: ToolCall[], abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void): Promise<ToolResult[]>;
29
+ export declare function executeToolCalls(toolCalls: ToolCall[], abortSignal?: AbortSignal, onTokenUpdate?: (tokenCount: number) => void, onSubAgentMessage?: SubAgentMessageCallback, requestToolConfirmation?: ToolConfirmationCallback, isToolAutoApproved?: ToolApprovalChecker, yoloMode?: boolean): Promise<ToolResult[]>;
@@ -1,28 +1,69 @@
1
1
  import { executeMCPTool } from './mcpToolsManager.js';
2
+ import { subAgentService } from '../mcp/subagent.js';
2
3
  /**
3
4
  * Execute a single tool call and return the result
4
5
  */
5
- export async function executeToolCall(toolCall, abortSignal, onTokenUpdate) {
6
+ export async function executeToolCall(toolCall, abortSignal, onTokenUpdate, onSubAgentMessage, requestToolConfirmation, isToolAutoApproved, yoloMode) {
6
7
  try {
7
8
  const args = JSON.parse(toolCall.function.arguments);
9
+ // Check if this is a sub-agent tool
10
+ if (toolCall.function.name.startsWith('subagent-')) {
11
+ const agentId = toolCall.function.name.substring('subagent-'.length);
12
+ // Create a tool confirmation adapter for sub-agent
13
+ const subAgentToolConfirmation = requestToolConfirmation
14
+ ? async (toolName, toolArgs) => {
15
+ // Create a fake tool call for confirmation
16
+ const fakeToolCall = {
17
+ id: 'subagent-tool',
18
+ type: 'function',
19
+ function: {
20
+ name: toolName,
21
+ arguments: JSON.stringify(toolArgs),
22
+ },
23
+ };
24
+ return await requestToolConfirmation(fakeToolCall);
25
+ }
26
+ : undefined;
27
+ const result = await subAgentService.execute({
28
+ agentId,
29
+ prompt: args.prompt,
30
+ onMessage: onSubAgentMessage,
31
+ abortSignal,
32
+ requestToolConfirmation: subAgentToolConfirmation
33
+ ? async (toolCall) => {
34
+ // Use the adapter to convert to the expected signature
35
+ const args = JSON.parse(toolCall.function.arguments);
36
+ return await subAgentToolConfirmation(toolCall.function.name, args);
37
+ }
38
+ : undefined,
39
+ isToolAutoApproved,
40
+ yoloMode,
41
+ });
42
+ return {
43
+ tool_call_id: toolCall.id,
44
+ role: 'tool',
45
+ content: JSON.stringify(result),
46
+ };
47
+ }
48
+ // Regular tool execution
8
49
  const result = await executeMCPTool(toolCall.function.name, args, abortSignal, onTokenUpdate);
9
50
  return {
10
51
  tool_call_id: toolCall.id,
11
52
  role: 'tool',
12
- content: JSON.stringify(result)
53
+ content: JSON.stringify(result),
13
54
  };
14
55
  }
15
56
  catch (error) {
16
57
  return {
17
58
  tool_call_id: toolCall.id,
18
59
  role: 'tool',
19
- content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`
60
+ content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
20
61
  };
21
62
  }
22
63
  }
23
64
  /**
24
65
  * Execute multiple tool calls in parallel
25
66
  */
26
- export async function executeToolCalls(toolCalls, abortSignal, onTokenUpdate) {
27
- return Promise.all(toolCalls.map(tc => executeToolCall(tc, abortSignal, onTokenUpdate)));
67
+ export async function executeToolCalls(toolCalls, abortSignal, onTokenUpdate, onSubAgentMessage, requestToolConfirmation, isToolAutoApproved, yoloMode) {
68
+ return Promise.all(toolCalls.map(tc => executeToolCall(tc, abortSignal, onTokenUpdate, onSubAgentMessage, requestToolConfirmation, isToolAutoApproved, yoloMode)));
28
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- type Props = {
3
- onBack: () => void;
4
- onSelectProfile: (profileName: string) => void;
5
- };
6
- export default function ConfigProfileScreen({ onBack, onSelectProfile, }: Props): React.JSX.Element;
7
- export {};