snow-ai 0.3.12 → 0.3.14
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/agents/compactAgent.js +6 -5
- package/dist/agents/reviewAgent.js +1 -0
- package/dist/agents/summaryAgent.js +1 -0
- package/dist/api/anthropic.d.ts +7 -1
- package/dist/api/anthropic.js +74 -7
- package/dist/api/chat.js +1 -1
- package/dist/api/responses.d.ts +1 -1
- package/dist/api/responses.js +6 -3
- package/dist/api/systemPrompt.js +11 -5
- package/dist/api/types.d.ts +5 -0
- package/dist/hooks/useConversation.js +48 -11
- package/dist/hooks/useToolConfirmation.js +22 -11
- package/dist/mcp/subagent.d.ts +1 -0
- package/dist/mcp/subagent.js +2 -2
- package/dist/ui/pages/ChatScreen.js +186 -78
- package/dist/ui/pages/ConfigScreen.js +245 -110
- package/dist/ui/pages/HeadlessModeScreen.js +3 -1
- package/dist/utils/apiConfig.d.ts +5 -0
- package/dist/utils/apiConfig.js +9 -3
- package/dist/utils/commands/ide.js +6 -10
- package/dist/utils/contextCompressor.js +7 -2
- package/dist/utils/subAgentExecutor.d.ts +4 -1
- package/dist/utils/subAgentExecutor.js +18 -6
- package/dist/utils/toolExecutor.d.ts +5 -2
- package/dist/utils/toolExecutor.js +4 -3
- package/package.json +1 -1
|
@@ -102,6 +102,7 @@ export class CompactAgent {
|
|
|
102
102
|
messages,
|
|
103
103
|
max_tokens: 4096,
|
|
104
104
|
includeBuiltinSystemPrompt: false, // 不需要内置系统提示词
|
|
105
|
+
disableThinking: true, // Agents 不使用 Extended Thinking
|
|
105
106
|
}, abortSignal);
|
|
106
107
|
break;
|
|
107
108
|
case 'gemini':
|
|
@@ -192,14 +193,14 @@ export class CompactAgent {
|
|
|
192
193
|
stack: streamError.stack,
|
|
193
194
|
name: streamError.name,
|
|
194
195
|
chunkCount,
|
|
195
|
-
contentLength: completeContent.length
|
|
196
|
+
contentLength: completeContent.length,
|
|
196
197
|
});
|
|
197
198
|
}
|
|
198
199
|
else {
|
|
199
200
|
logger.error('Compact agent: Unknown streaming error:', {
|
|
200
201
|
error: streamError,
|
|
201
202
|
chunkCount,
|
|
202
|
-
contentLength: completeContent.length
|
|
203
|
+
contentLength: completeContent.length,
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
206
|
throw streamError;
|
|
@@ -220,14 +221,14 @@ export class CompactAgent {
|
|
|
220
221
|
stack: error.stack,
|
|
221
222
|
name: error.name,
|
|
222
223
|
requestMethod: this.requestMethod,
|
|
223
|
-
modelName: this.modelName
|
|
224
|
+
modelName: this.modelName,
|
|
224
225
|
});
|
|
225
226
|
}
|
|
226
227
|
else {
|
|
227
228
|
logger.error('Compact agent: Unknown API error:', {
|
|
228
229
|
error,
|
|
229
230
|
requestMethod: this.requestMethod,
|
|
230
|
-
modelName: this.modelName
|
|
231
|
+
modelName: this.modelName,
|
|
231
232
|
});
|
|
232
233
|
}
|
|
233
234
|
throw error;
|
|
@@ -291,7 +292,7 @@ Provide the extracted content below:`;
|
|
|
291
292
|
logger.warn('Compact agent extraction failed, using original content:', {
|
|
292
293
|
error: error.message,
|
|
293
294
|
stack: error.stack,
|
|
294
|
-
name: error.name
|
|
295
|
+
name: error.name,
|
|
295
296
|
});
|
|
296
297
|
}
|
|
297
298
|
else {
|
package/dist/api/anthropic.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ export interface AnthropicOptions {
|
|
|
7
7
|
tools?: ChatCompletionTool[];
|
|
8
8
|
sessionId?: string;
|
|
9
9
|
includeBuiltinSystemPrompt?: boolean;
|
|
10
|
+
disableThinking?: boolean;
|
|
10
11
|
}
|
|
11
12
|
export interface AnthropicStreamChunk {
|
|
12
|
-
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage';
|
|
13
|
+
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage' | 'reasoning_started' | 'reasoning_delta';
|
|
13
14
|
content?: string;
|
|
14
15
|
tool_calls?: Array<{
|
|
15
16
|
id: string;
|
|
@@ -21,6 +22,11 @@ export interface AnthropicStreamChunk {
|
|
|
21
22
|
}>;
|
|
22
23
|
delta?: string;
|
|
23
24
|
usage?: UsageInfo;
|
|
25
|
+
thinking?: {
|
|
26
|
+
type: 'thinking';
|
|
27
|
+
thinking: string;
|
|
28
|
+
signature?: string;
|
|
29
|
+
};
|
|
24
30
|
}
|
|
25
31
|
export interface AnthropicTool {
|
|
26
32
|
name: string;
|
package/dist/api/anthropic.js
CHANGED
|
@@ -20,6 +20,7 @@ function getAnthropicConfig() {
|
|
|
20
20
|
: 'https://api.anthropic.com/v1',
|
|
21
21
|
customHeaders,
|
|
22
22
|
anthropicBeta: config.anthropicBeta,
|
|
23
|
+
thinking: config.thinking,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
return anthropicConfig;
|
|
@@ -124,6 +125,11 @@ function convertToAnthropicMessages(messages, includeBuiltinSystemPrompt = true)
|
|
|
124
125
|
msg.tool_calls &&
|
|
125
126
|
msg.tool_calls.length > 0) {
|
|
126
127
|
const content = [];
|
|
128
|
+
// When thinking is enabled, thinking block must come first
|
|
129
|
+
if (msg.thinking) {
|
|
130
|
+
// Use the complete thinking block object (includes signature)
|
|
131
|
+
content.push(msg.thinking);
|
|
132
|
+
}
|
|
127
133
|
if (msg.content) {
|
|
128
134
|
content.push({
|
|
129
135
|
type: 'text',
|
|
@@ -145,10 +151,29 @@ function convertToAnthropicMessages(messages, includeBuiltinSystemPrompt = true)
|
|
|
145
151
|
continue;
|
|
146
152
|
}
|
|
147
153
|
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
content
|
|
151
|
-
|
|
154
|
+
// For assistant messages with thinking, convert to structured format
|
|
155
|
+
if (msg.role === 'assistant' && msg.thinking) {
|
|
156
|
+
const content = [];
|
|
157
|
+
// Thinking block must come first - use complete block object (includes signature)
|
|
158
|
+
content.push(msg.thinking);
|
|
159
|
+
// Then text content
|
|
160
|
+
if (msg.content) {
|
|
161
|
+
content.push({
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: msg.content,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
anthropicMessages.push({
|
|
167
|
+
role: 'assistant',
|
|
168
|
+
content,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
anthropicMessages.push({
|
|
173
|
+
role: msg.role,
|
|
174
|
+
content: msg.content,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
152
177
|
}
|
|
153
178
|
}
|
|
154
179
|
// 如果配置了自定义系统提示词(最高优先级,始终添加)
|
|
@@ -266,7 +291,6 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
266
291
|
const requestBody = {
|
|
267
292
|
model: options.model,
|
|
268
293
|
max_tokens: options.max_tokens || 4096,
|
|
269
|
-
temperature: options.temperature ?? 0.7,
|
|
270
294
|
system,
|
|
271
295
|
messages,
|
|
272
296
|
tools: convertToolsToAnthropic(options.tools),
|
|
@@ -275,11 +299,18 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
275
299
|
},
|
|
276
300
|
stream: true,
|
|
277
301
|
};
|
|
302
|
+
// Add thinking configuration if enabled and not explicitly disabled
|
|
303
|
+
// When thinking is enabled, temperature must be 1
|
|
304
|
+
// Note: agents and other internal tools should set disableThinking=true
|
|
305
|
+
if (config.thinking && !options.disableThinking) {
|
|
306
|
+
requestBody.thinking = config.thinking;
|
|
307
|
+
requestBody.temperature = 1;
|
|
308
|
+
}
|
|
278
309
|
// Prepare headers
|
|
279
310
|
const headers = {
|
|
280
311
|
'Content-Type': 'application/json',
|
|
281
312
|
'x-api-key': config.apiKey,
|
|
282
|
-
|
|
313
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
283
314
|
'anthropic-version': '2023-06-01',
|
|
284
315
|
...config.customHeaders,
|
|
285
316
|
};
|
|
@@ -305,10 +336,13 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
305
336
|
throw new Error('No response body from Anthropic API');
|
|
306
337
|
}
|
|
307
338
|
let contentBuffer = '';
|
|
339
|
+
let thinkingTextBuffer = ''; // Accumulate thinking text content
|
|
340
|
+
let thinkingSignature = ''; // Accumulate thinking signature
|
|
308
341
|
let toolCallsBuffer = new Map();
|
|
309
342
|
let hasToolCalls = false;
|
|
310
343
|
let usageData;
|
|
311
344
|
let blockIndexToId = new Map();
|
|
345
|
+
let blockIndexToType = new Map(); // Track block types (text, thinking, tool_use)
|
|
312
346
|
let completedToolBlocks = new Set(); // Track which tool blocks have finished streaming
|
|
313
347
|
for await (const event of parseSSEStream(response.body.getReader())) {
|
|
314
348
|
if (abortSignal?.aborted) {
|
|
@@ -316,9 +350,11 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
316
350
|
}
|
|
317
351
|
if (event.type === 'content_block_start') {
|
|
318
352
|
const block = event.content_block;
|
|
353
|
+
const blockIndex = event.index;
|
|
354
|
+
// Track block type for later reference
|
|
355
|
+
blockIndexToType.set(blockIndex, block.type);
|
|
319
356
|
if (block.type === 'tool_use') {
|
|
320
357
|
hasToolCalls = true;
|
|
321
|
-
const blockIndex = event.index;
|
|
322
358
|
blockIndexToId.set(blockIndex, block.id);
|
|
323
359
|
toolCallsBuffer.set(block.id, {
|
|
324
360
|
id: block.id,
|
|
@@ -333,6 +369,13 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
333
369
|
delta: block.name,
|
|
334
370
|
};
|
|
335
371
|
}
|
|
372
|
+
// Handle thinking block start (Extended Thinking feature)
|
|
373
|
+
else if (block.type === 'thinking') {
|
|
374
|
+
// Thinking block started - emit reasoning_started event
|
|
375
|
+
yield {
|
|
376
|
+
type: 'reasoning_started',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
336
379
|
}
|
|
337
380
|
else if (event.type === 'content_block_delta') {
|
|
338
381
|
const delta = event.delta;
|
|
@@ -344,6 +387,21 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
344
387
|
content: text,
|
|
345
388
|
};
|
|
346
389
|
}
|
|
390
|
+
// Handle thinking_delta (Extended Thinking feature)
|
|
391
|
+
// Emit reasoning_delta event for thinking content
|
|
392
|
+
if (delta.type === 'thinking_delta') {
|
|
393
|
+
const thinkingText = delta.thinking;
|
|
394
|
+
thinkingTextBuffer += thinkingText; // Accumulate thinking text
|
|
395
|
+
yield {
|
|
396
|
+
type: 'reasoning_delta',
|
|
397
|
+
delta: thinkingText,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
// Handle signature_delta (Extended Thinking feature)
|
|
401
|
+
// Signature is required for thinking blocks
|
|
402
|
+
if (delta.type === 'signature_delta') {
|
|
403
|
+
thinkingSignature += delta.signature; // Accumulate signature
|
|
404
|
+
}
|
|
347
405
|
if (delta.type === 'input_json_delta') {
|
|
348
406
|
const jsonDelta = delta.partial_json;
|
|
349
407
|
const blockIndex = event.index;
|
|
@@ -457,8 +515,17 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
457
515
|
usage: usageData,
|
|
458
516
|
};
|
|
459
517
|
}
|
|
518
|
+
// Return complete thinking block with signature if thinking content exists
|
|
519
|
+
const thinkingBlock = thinkingTextBuffer
|
|
520
|
+
? {
|
|
521
|
+
type: 'thinking',
|
|
522
|
+
thinking: thinkingTextBuffer,
|
|
523
|
+
signature: thinkingSignature || undefined,
|
|
524
|
+
}
|
|
525
|
+
: undefined;
|
|
460
526
|
yield {
|
|
461
527
|
type: 'done',
|
|
528
|
+
thinking: thinkingBlock,
|
|
462
529
|
};
|
|
463
530
|
}, {
|
|
464
531
|
abortSignal,
|
package/dist/api/chat.js
CHANGED
|
@@ -185,7 +185,7 @@ export async function* createStreamingChatCompletion(options, abortSignal, onRet
|
|
|
185
185
|
method: 'POST',
|
|
186
186
|
headers: {
|
|
187
187
|
'Content-Type': 'application/json',
|
|
188
|
-
|
|
188
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
189
189
|
...config.customHeaders,
|
|
190
190
|
},
|
|
191
191
|
body: JSON.stringify(requestBody),
|
package/dist/api/responses.d.ts
CHANGED
package/dist/api/responses.js
CHANGED
|
@@ -246,7 +246,10 @@ export async function* createStreamingResponse(options, abortSignal, onRetry) {
|
|
|
246
246
|
tools: convertToolsForResponses(options.tools),
|
|
247
247
|
tool_choice: options.tool_choice,
|
|
248
248
|
parallel_tool_calls: false,
|
|
249
|
-
|
|
249
|
+
// Only add reasoning if not explicitly disabled (null means don't pass it)
|
|
250
|
+
...(options.reasoning !== null && {
|
|
251
|
+
reasoning: options.reasoning || { effort: 'high', summary: 'auto' },
|
|
252
|
+
}),
|
|
250
253
|
store: false,
|
|
251
254
|
stream: true,
|
|
252
255
|
prompt_cache_key: options.prompt_cache_key,
|
|
@@ -256,7 +259,7 @@ export async function* createStreamingResponse(options, abortSignal, onRetry) {
|
|
|
256
259
|
method: 'POST',
|
|
257
260
|
headers: {
|
|
258
261
|
'Content-Type': 'application/json',
|
|
259
|
-
|
|
262
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
260
263
|
...config.customHeaders,
|
|
261
264
|
},
|
|
262
265
|
body: JSON.stringify(requestPayload),
|
|
@@ -442,7 +445,7 @@ export async function* createStreamingResponse(options, abortSignal, onRetry) {
|
|
|
442
445
|
usage: usageData,
|
|
443
446
|
};
|
|
444
447
|
}
|
|
445
|
-
// 发送完成信号
|
|
448
|
+
// 发送完成信号 - For Responses API, thinking content is in reasoning object, not separate thinking field
|
|
446
449
|
yield {
|
|
447
450
|
type: 'done',
|
|
448
451
|
};
|
package/dist/api/systemPrompt.js
CHANGED
|
@@ -33,7 +33,7 @@ const SYSTEM_PROMPT_TEMPLATE = `You are Snow AI CLI, an intelligent command-line
|
|
|
33
33
|
1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query
|
|
34
34
|
2. **ACTION FIRST**: Write code immediately when task is clear - stop overthinking
|
|
35
35
|
3. **Smart Context**: Read what's needed for correctness, skip excessive exploration
|
|
36
|
-
4. **Quality Verification**:
|
|
36
|
+
4. **Quality Verification**: run build/test after changes
|
|
37
37
|
|
|
38
38
|
## 🚀 Execution Strategy - BALANCE ACTION & ANALYSIS
|
|
39
39
|
|
|
@@ -121,13 +121,19 @@ and other shell features. Your capabilities include text processing, data filter
|
|
|
121
121
|
manipulation, workflow automation, and complex command chaining to solve sophisticated
|
|
122
122
|
system administration and data processing challenges.
|
|
123
123
|
|
|
124
|
+
**Sub-Agent:**
|
|
125
|
+
A sub-agent is a separate session isolated from the main session, and a sub-agent may have some of the tools described above to focus on solving a specific problem.
|
|
126
|
+
If you have a sub-agent tool, then you can leave some of the work to the sub-agent to solve.
|
|
127
|
+
For example, if you have a sub-agent of a work plan, you can hand over the work plan to the sub-agent to solve when you receive user requirements.
|
|
128
|
+
This way, the master agent can focus on task fulfillment.
|
|
129
|
+
*If you don't have a sub-agent tool, ignore this feature*
|
|
130
|
+
|
|
124
131
|
## 🔍 Quality Assurance
|
|
125
132
|
|
|
126
133
|
Guidance and recommendations:
|
|
127
|
-
1.
|
|
128
|
-
2.
|
|
129
|
-
3.
|
|
130
|
-
4. Never leave broken code
|
|
134
|
+
1. Run build: \`npm run build\` or \`tsc\`
|
|
135
|
+
2. Fix any errors immediately
|
|
136
|
+
3. Never leave broken code
|
|
131
137
|
|
|
132
138
|
## 📚 Project Context (SNOW.md)
|
|
133
139
|
|
package/dist/api/types.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export async function handleConversationWithTools(options) {
|
|
|
21
21
|
const { userContent, imageContents, controller,
|
|
22
22
|
// messages, // No longer used - we load from session instead to get complete history with tool calls
|
|
23
23
|
saveMessage, setMessages, setStreamTokenCount, setCurrentTodos, requestToolConfirmation, isToolAutoApproved, addMultipleToAlwaysApproved, yoloMode, setContextUsage, setIsReasoning, setRetryStatus, } = options;
|
|
24
|
+
// Create a wrapper function for adding single tool to always-approved list
|
|
25
|
+
const addToAlwaysApproved = (toolName) => {
|
|
26
|
+
addMultipleToAlwaysApproved([toolName]);
|
|
27
|
+
};
|
|
24
28
|
// Step 1: Ensure session exists and get existing TODOs
|
|
25
29
|
let currentSession = sessionManager.getCurrentSession();
|
|
26
30
|
if (!currentSession) {
|
|
@@ -64,13 +68,18 @@ export async function handleConversationWithTools(options) {
|
|
|
64
68
|
images: imageContents,
|
|
65
69
|
});
|
|
66
70
|
// Save user message (directly save API format message)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
// IMPORTANT: await to ensure message is saved before continuing
|
|
72
|
+
// This prevents loss of user message if conversation is interrupted (ESC)
|
|
73
|
+
try {
|
|
74
|
+
await saveMessage({
|
|
75
|
+
role: 'user',
|
|
76
|
+
content: userContent,
|
|
77
|
+
images: imageContents,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
72
81
|
console.error('Failed to save user message:', error);
|
|
73
|
-
}
|
|
82
|
+
}
|
|
74
83
|
// Initialize token encoder with proper cleanup tracking
|
|
75
84
|
let encoder;
|
|
76
85
|
let encoderFreed = false;
|
|
@@ -114,6 +123,7 @@ export async function handleConversationWithTools(options) {
|
|
|
114
123
|
let streamedContent = '';
|
|
115
124
|
let receivedToolCalls;
|
|
116
125
|
let receivedReasoning;
|
|
126
|
+
let receivedThinking; // Accumulate thinking content from all platforms
|
|
117
127
|
// Stream AI response - choose API based on config
|
|
118
128
|
let toolCallAccumulator = ''; // Accumulate tool call deltas for token counting
|
|
119
129
|
let reasoningAccumulator = ''; // Accumulate reasoning summary deltas for token counting (Responses API only)
|
|
@@ -141,6 +151,8 @@ export async function handleConversationWithTools(options) {
|
|
|
141
151
|
max_tokens: config.maxTokens || 4096,
|
|
142
152
|
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
143
153
|
sessionId: currentSession?.id,
|
|
154
|
+
// Disable thinking for basicModel (e.g., init command)
|
|
155
|
+
disableThinking: options.useBasicModel,
|
|
144
156
|
}, controller.signal, onRetry)
|
|
145
157
|
: config.requestMethod === 'gemini'
|
|
146
158
|
? createStreamingGeminiCompletion({
|
|
@@ -157,6 +169,9 @@ export async function handleConversationWithTools(options) {
|
|
|
157
169
|
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
158
170
|
tool_choice: 'auto',
|
|
159
171
|
prompt_cache_key: cacheKey, // Use session ID as cache key
|
|
172
|
+
// Don't pass reasoning for basicModel (small models may not support it)
|
|
173
|
+
// Pass null to explicitly disable reasoning in API call
|
|
174
|
+
reasoning: options.useBasicModel ? null : undefined,
|
|
160
175
|
}, controller.signal, onRetry)
|
|
161
176
|
: createStreamingChatCompletion({
|
|
162
177
|
model,
|
|
@@ -194,6 +209,8 @@ export async function handleConversationWithTools(options) {
|
|
|
194
209
|
}
|
|
195
210
|
else if (chunk.type === 'tool_call_delta' && chunk.delta) {
|
|
196
211
|
// Accumulate tool call deltas and update token count in real-time
|
|
212
|
+
// When tool calls start, reasoning is done (OpenAI generally doesn't output text content during tool calls)
|
|
213
|
+
setIsReasoning?.(false);
|
|
197
214
|
toolCallAccumulator += chunk.delta;
|
|
198
215
|
try {
|
|
199
216
|
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
@@ -222,6 +239,10 @@ export async function handleConversationWithTools(options) {
|
|
|
222
239
|
// Capture reasoning data from Responses API
|
|
223
240
|
receivedReasoning = chunk.reasoning;
|
|
224
241
|
}
|
|
242
|
+
else if (chunk.type === 'done' && chunk.thinking) {
|
|
243
|
+
// Capture thinking content from Anthropic only (includes signature)
|
|
244
|
+
receivedThinking = chunk.thinking;
|
|
245
|
+
}
|
|
225
246
|
else if (chunk.type === 'usage' && chunk.usage) {
|
|
226
247
|
// Capture usage information both in state and locally
|
|
227
248
|
setContextUsage(chunk.usage);
|
|
@@ -256,7 +277,8 @@ export async function handleConversationWithTools(options) {
|
|
|
256
277
|
}
|
|
257
278
|
if (chunk.usage.cached_tokens !== undefined) {
|
|
258
279
|
accumulatedUsage.cached_tokens =
|
|
259
|
-
(accumulatedUsage.cached_tokens || 0) +
|
|
280
|
+
(accumulatedUsage.cached_tokens || 0) +
|
|
281
|
+
chunk.usage.cached_tokens;
|
|
260
282
|
}
|
|
261
283
|
}
|
|
262
284
|
}
|
|
@@ -283,7 +305,8 @@ export async function handleConversationWithTools(options) {
|
|
|
283
305
|
arguments: tc.function.arguments,
|
|
284
306
|
},
|
|
285
307
|
})),
|
|
286
|
-
reasoning: receivedReasoning, // Include reasoning data for caching
|
|
308
|
+
reasoning: receivedReasoning, // Include reasoning data for caching (Responses API)
|
|
309
|
+
thinking: receivedThinking, // Include thinking content (Anthropic/OpenAI)
|
|
287
310
|
};
|
|
288
311
|
conversationMessages.push(assistantMessage);
|
|
289
312
|
// Save assistant message with tool calls
|
|
@@ -402,6 +425,8 @@ export async function handleConversationWithTools(options) {
|
|
|
402
425
|
approvedTools.push(...toolsNeedingConfirmation);
|
|
403
426
|
}
|
|
404
427
|
// Execute approved tools with sub-agent message callback and terminal output callback
|
|
428
|
+
// Track sub-agent content for token counting
|
|
429
|
+
let subAgentContentAccumulator = '';
|
|
405
430
|
const toolResults = await executeToolCalls(approvedTools, controller.signal, setStreamTokenCount, async (subAgentMessage) => {
|
|
406
431
|
// Handle sub-agent messages - display and save to session
|
|
407
432
|
setMessages(prev => {
|
|
@@ -515,9 +540,20 @@ export async function handleConversationWithTools(options) {
|
|
|
515
540
|
let content = '';
|
|
516
541
|
if (subAgentMessage.message.type === 'content') {
|
|
517
542
|
content = subAgentMessage.message.content;
|
|
543
|
+
// Update token count for sub-agent content
|
|
544
|
+
subAgentContentAccumulator += content;
|
|
545
|
+
try {
|
|
546
|
+
const tokens = encoder.encode(subAgentContentAccumulator);
|
|
547
|
+
setStreamTokenCount(tokens.length);
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
// Ignore encoding errors
|
|
551
|
+
}
|
|
518
552
|
}
|
|
519
553
|
else if (subAgentMessage.message.type === 'done') {
|
|
520
|
-
// Mark as complete
|
|
554
|
+
// Mark as complete and reset token counter
|
|
555
|
+
subAgentContentAccumulator = '';
|
|
556
|
+
setStreamTokenCount(0);
|
|
521
557
|
if (existingIndex !== -1) {
|
|
522
558
|
const updated = [...prev];
|
|
523
559
|
const existing = updated[existingIndex];
|
|
@@ -565,7 +601,7 @@ export async function handleConversationWithTools(options) {
|
|
|
565
601
|
}
|
|
566
602
|
return prev;
|
|
567
603
|
});
|
|
568
|
-
}, requestToolConfirmation, isToolAutoApproved, yoloMode);
|
|
604
|
+
}, requestToolConfirmation, isToolAutoApproved, yoloMode, addToAlwaysApproved);
|
|
569
605
|
// Check if aborted during tool execution
|
|
570
606
|
if (controller.signal.aborted) {
|
|
571
607
|
freeEncoder();
|
|
@@ -819,7 +855,8 @@ export async function handleConversationWithTools(options) {
|
|
|
819
855
|
const assistantMessage = {
|
|
820
856
|
role: 'assistant',
|
|
821
857
|
content: streamedContent.trim(),
|
|
822
|
-
reasoning: receivedReasoning, // Include reasoning data for caching
|
|
858
|
+
reasoning: receivedReasoning, // Include reasoning data for caching (Responses API)
|
|
859
|
+
thinking: receivedThinking, // Include thinking content (Anthropic/OpenAI)
|
|
823
860
|
};
|
|
824
861
|
conversationMessages.push(assistantMessage);
|
|
825
862
|
saveMessage(assistantMessage).catch(error => {
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useRef, useCallback } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Hook for managing tool confirmation state and logic
|
|
4
4
|
*/
|
|
5
5
|
export function useToolConfirmation() {
|
|
6
6
|
const [pendingToolConfirmation, setPendingToolConfirmation] = useState(null);
|
|
7
|
+
// Use ref for always-approved tools to ensure closure functions always see latest state
|
|
8
|
+
const alwaysApprovedToolsRef = useRef(new Set());
|
|
7
9
|
const [alwaysApprovedTools, setAlwaysApprovedTools] = useState(new Set());
|
|
8
10
|
/**
|
|
9
11
|
* Request user confirmation for tool execution
|
|
10
12
|
*/
|
|
11
13
|
const requestToolConfirmation = async (toolCall, batchToolNames, allTools) => {
|
|
12
|
-
return new Promise(
|
|
14
|
+
return new Promise(resolve => {
|
|
13
15
|
setPendingToolConfirmation({
|
|
14
16
|
tool: toolCall,
|
|
15
17
|
batchToolNames,
|
|
@@ -17,34 +19,43 @@ export function useToolConfirmation() {
|
|
|
17
19
|
resolve: (result) => {
|
|
18
20
|
setPendingToolConfirmation(null);
|
|
19
21
|
resolve(result);
|
|
20
|
-
}
|
|
22
|
+
},
|
|
21
23
|
});
|
|
22
24
|
});
|
|
23
25
|
};
|
|
24
26
|
/**
|
|
25
27
|
* Check if a tool is auto-approved
|
|
28
|
+
* Uses ref to ensure it always sees the latest approved tools
|
|
26
29
|
*/
|
|
27
|
-
const isToolAutoApproved = (toolName) => {
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
+
const isToolAutoApproved = useCallback((toolName) => {
|
|
31
|
+
return (alwaysApprovedToolsRef.current.has(toolName) ||
|
|
32
|
+
toolName.startsWith('todo-') ||
|
|
33
|
+
toolName.startsWith('subagent-'));
|
|
34
|
+
}, []);
|
|
30
35
|
/**
|
|
31
36
|
* Add a tool to the always-approved list
|
|
32
37
|
*/
|
|
33
|
-
const addToAlwaysApproved = (toolName) => {
|
|
38
|
+
const addToAlwaysApproved = useCallback((toolName) => {
|
|
39
|
+
// Update ref immediately (for closure functions)
|
|
40
|
+
alwaysApprovedToolsRef.current.add(toolName);
|
|
41
|
+
// Update state (for UI reactivity)
|
|
34
42
|
setAlwaysApprovedTools(prev => new Set([...prev, toolName]));
|
|
35
|
-
};
|
|
43
|
+
}, []);
|
|
36
44
|
/**
|
|
37
45
|
* Add multiple tools to the always-approved list
|
|
38
46
|
*/
|
|
39
|
-
const addMultipleToAlwaysApproved = (toolNames) => {
|
|
47
|
+
const addMultipleToAlwaysApproved = useCallback((toolNames) => {
|
|
48
|
+
// Update ref immediately (for closure functions)
|
|
49
|
+
toolNames.forEach(name => alwaysApprovedToolsRef.current.add(name));
|
|
50
|
+
// Update state (for UI reactivity)
|
|
40
51
|
setAlwaysApprovedTools(prev => new Set([...prev, ...toolNames]));
|
|
41
|
-
};
|
|
52
|
+
}, []);
|
|
42
53
|
return {
|
|
43
54
|
pendingToolConfirmation,
|
|
44
55
|
alwaysApprovedTools,
|
|
45
56
|
requestToolConfirmation,
|
|
46
57
|
isToolAutoApproved,
|
|
47
58
|
addToAlwaysApproved,
|
|
48
|
-
addMultipleToAlwaysApproved
|
|
59
|
+
addMultipleToAlwaysApproved,
|
|
49
60
|
};
|
|
50
61
|
}
|
package/dist/mcp/subagent.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface SubAgentToolExecutionOptions {
|
|
|
8
8
|
requestToolConfirmation?: (toolCall: ToolCall, batchToolNames?: string, allTools?: ToolCall[]) => Promise<string>;
|
|
9
9
|
isToolAutoApproved?: (toolName: string) => boolean;
|
|
10
10
|
yoloMode?: boolean;
|
|
11
|
+
addToAlwaysApproved?: (toolName: string) => void;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
14
|
* Sub-Agent MCP Service
|
package/dist/mcp/subagent.js
CHANGED
|
@@ -9,7 +9,7 @@ export class SubAgentService {
|
|
|
9
9
|
* Execute a sub-agent as a tool
|
|
10
10
|
*/
|
|
11
11
|
async execute(options) {
|
|
12
|
-
const { agentId, prompt, onMessage, abortSignal, requestToolConfirmation, isToolAutoApproved, yoloMode, } = options;
|
|
12
|
+
const { agentId, prompt, onMessage, abortSignal, requestToolConfirmation, isToolAutoApproved, yoloMode, addToAlwaysApproved, } = options;
|
|
13
13
|
// Create a tool confirmation adapter for sub-agent if needed
|
|
14
14
|
const subAgentToolConfirmation = requestToolConfirmation
|
|
15
15
|
? async (toolName, toolArgs) => {
|
|
@@ -25,7 +25,7 @@ export class SubAgentService {
|
|
|
25
25
|
return await requestToolConfirmation(fakeToolCall);
|
|
26
26
|
}
|
|
27
27
|
: undefined;
|
|
28
|
-
const result = await executeSubAgent(agentId, prompt, onMessage, abortSignal, subAgentToolConfirmation, isToolAutoApproved, yoloMode);
|
|
28
|
+
const result = await executeSubAgent(agentId, prompt, onMessage, abortSignal, subAgentToolConfirmation, isToolAutoApproved, yoloMode, addToAlwaysApproved);
|
|
29
29
|
if (!result.success) {
|
|
30
30
|
throw new Error(result.error || 'Sub-agent execution failed');
|
|
31
31
|
}
|