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.
- package/dist/api/anthropic.js +38 -13
- package/dist/api/types.d.ts +1 -0
- package/dist/hooks/useConversation.js +226 -59
- package/dist/hooks/useSnapshotState.d.ts +2 -0
- package/dist/hooks/useToolConfirmation.js +1 -1
- package/dist/mcp/subagent.d.ts +35 -0
- package/dist/mcp/subagent.js +64 -0
- package/dist/ui/components/ChatInput.d.ts +1 -2
- package/dist/ui/components/ChatInput.js +47 -39
- package/dist/ui/components/FileRollbackConfirmation.d.ts +3 -2
- package/dist/ui/components/FileRollbackConfirmation.js +81 -22
- package/dist/ui/components/MessageList.d.ts +7 -1
- package/dist/ui/components/MessageList.js +16 -5
- package/dist/ui/components/ToolResultPreview.js +21 -1
- package/dist/ui/pages/ChatScreen.js +29 -20
- package/dist/ui/pages/ConfigScreen.js +47 -46
- package/dist/ui/pages/ProxyConfigScreen.js +1 -1
- package/dist/ui/pages/SubAgentConfigScreen.d.ts +9 -0
- package/dist/ui/pages/SubAgentConfigScreen.js +352 -0
- package/dist/ui/pages/SubAgentListScreen.d.ts +9 -0
- package/dist/ui/pages/SubAgentListScreen.js +114 -0
- package/dist/ui/pages/WelcomeScreen.js +30 -2
- package/dist/utils/incrementalSnapshot.d.ts +7 -0
- package/dist/utils/incrementalSnapshot.js +34 -0
- package/dist/utils/mcpToolsManager.js +41 -1
- package/dist/utils/retryUtils.js +5 -0
- package/dist/utils/sessionConverter.d.ts +1 -0
- package/dist/utils/sessionConverter.js +192 -89
- package/dist/utils/subAgentConfig.d.ts +43 -0
- package/dist/utils/subAgentConfig.js +126 -0
- package/dist/utils/subAgentExecutor.d.ts +29 -0
- package/dist/utils/subAgentExecutor.js +272 -0
- package/dist/utils/toolExecutor.d.ts +10 -2
- package/dist/utils/toolExecutor.js +46 -5
- package/package.json +1 -1
- package/dist/ui/pages/ConfigProfileScreen.d.ts +0 -7
- 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