snow-ai 0.4.16 → 0.4.17
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/bundle/cli.mjs +477445 -0
- package/bundle/sql-wasm.wasm +0 -0
- package/package.json +31 -26
- package/dist/agents/codebaseIndexAgent.d.ts +0 -102
- package/dist/agents/codebaseIndexAgent.js +0 -641
- package/dist/agents/codebaseReviewAgent.d.ts +0 -61
- package/dist/agents/codebaseReviewAgent.js +0 -301
- package/dist/agents/compactAgent.d.ts +0 -55
- package/dist/agents/compactAgent.js +0 -306
- package/dist/agents/promptOptimizeAgent.d.ts +0 -54
- package/dist/agents/promptOptimizeAgent.js +0 -268
- package/dist/agents/reviewAgent.d.ts +0 -50
- package/dist/agents/reviewAgent.js +0 -265
- package/dist/agents/summaryAgent.d.ts +0 -57
- package/dist/agents/summaryAgent.js +0 -260
- package/dist/api/anthropic.d.ts +0 -44
- package/dist/api/anthropic.js +0 -598
- package/dist/api/chat.d.ts +0 -73
- package/dist/api/chat.js +0 -386
- package/dist/api/embedding.d.ts +0 -34
- package/dist/api/embedding.js +0 -80
- package/dist/api/gemini.d.ts +0 -31
- package/dist/api/gemini.js +0 -445
- package/dist/api/models.d.ts +0 -15
- package/dist/api/models.js +0 -139
- package/dist/api/responses.d.ts +0 -38
- package/dist/api/responses.js +0 -515
- package/dist/api/systemPrompt.d.ts +0 -4
- package/dist/api/systemPrompt.js +0 -408
- package/dist/api/types.d.ts +0 -53
- package/dist/api/types.js +0 -4
- package/dist/app.d.ts +0 -8
- package/dist/app.js +0 -112
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -199
- package/dist/hooks/useAgentPicker.d.ts +0 -14
- package/dist/hooks/useAgentPicker.js +0 -119
- package/dist/hooks/useClipboard.d.ts +0 -4
- package/dist/hooks/useClipboard.js +0 -175
- package/dist/hooks/useCommandHandler.d.ts +0 -35
- package/dist/hooks/useCommandHandler.js +0 -346
- package/dist/hooks/useCommandPanel.d.ts +0 -17
- package/dist/hooks/useCommandPanel.js +0 -114
- package/dist/hooks/useConversation.d.ts +0 -49
- package/dist/hooks/useConversation.js +0 -1052
- package/dist/hooks/useFilePicker.d.ts +0 -18
- package/dist/hooks/useFilePicker.js +0 -224
- package/dist/hooks/useGlobalExit.d.ts +0 -5
- package/dist/hooks/useGlobalExit.js +0 -34
- package/dist/hooks/useGlobalNavigation.d.ts +0 -6
- package/dist/hooks/useGlobalNavigation.js +0 -17
- package/dist/hooks/useHistoryNavigation.d.ts +0 -35
- package/dist/hooks/useHistoryNavigation.js +0 -133
- package/dist/hooks/useInputBuffer.d.ts +0 -6
- package/dist/hooks/useInputBuffer.js +0 -45
- package/dist/hooks/useKeyboardInput.d.ts +0 -80
- package/dist/hooks/useKeyboardInput.js +0 -608
- package/dist/hooks/useSessionManagement.d.ts +0 -10
- package/dist/hooks/useSessionManagement.js +0 -43
- package/dist/hooks/useSessionSave.d.ts +0 -8
- package/dist/hooks/useSessionSave.js +0 -63
- package/dist/hooks/useSnapshotState.d.ts +0 -26
- package/dist/hooks/useSnapshotState.js +0 -28
- package/dist/hooks/useStreamingState.d.ts +0 -33
- package/dist/hooks/useStreamingState.js +0 -105
- package/dist/hooks/useTerminalFocus.d.ts +0 -28
- package/dist/hooks/useTerminalFocus.js +0 -87
- package/dist/hooks/useTerminalSize.d.ts +0 -4
- package/dist/hooks/useTerminalSize.js +0 -20
- package/dist/hooks/useTodoPicker.d.ts +0 -16
- package/dist/hooks/useTodoPicker.js +0 -94
- package/dist/hooks/useToolConfirmation.d.ts +0 -19
- package/dist/hooks/useToolConfirmation.js +0 -61
- package/dist/hooks/useVSCodeState.d.ts +0 -8
- package/dist/hooks/useVSCodeState.js +0 -81
- package/dist/i18n/I18nContext.d.ts +0 -14
- package/dist/i18n/I18nContext.js +0 -24
- package/dist/i18n/index.d.ts +0 -3
- package/dist/i18n/index.js +0 -2
- package/dist/i18n/lang/en.d.ts +0 -2
- package/dist/i18n/lang/en.js +0 -502
- package/dist/i18n/lang/es.d.ts +0 -2
- package/dist/i18n/lang/es.js +0 -502
- package/dist/i18n/lang/ja.d.ts +0 -2
- package/dist/i18n/lang/ja.js +0 -502
- package/dist/i18n/lang/ko.d.ts +0 -2
- package/dist/i18n/lang/ko.js +0 -502
- package/dist/i18n/lang/zh-TW.d.ts +0 -2
- package/dist/i18n/lang/zh-TW.js +0 -502
- package/dist/i18n/lang/zh.d.ts +0 -2
- package/dist/i18n/lang/zh.js +0 -502
- package/dist/i18n/translations.d.ts +0 -2
- package/dist/i18n/translations.js +0 -14
- package/dist/i18n/types.d.ts +0 -478
- package/dist/i18n/types.js +0 -1
- package/dist/mcp/aceCodeSearch.d.ts +0 -247
- package/dist/mcp/aceCodeSearch.js +0 -1058
- package/dist/mcp/bash.d.ts +0 -50
- package/dist/mcp/bash.js +0 -153
- package/dist/mcp/codebaseSearch.d.ts +0 -44
- package/dist/mcp/codebaseSearch.js +0 -275
- package/dist/mcp/filesystem.d.ts +0 -392
- package/dist/mcp/filesystem.js +0 -1445
- package/dist/mcp/ideDiagnostics.d.ts +0 -36
- package/dist/mcp/ideDiagnostics.js +0 -90
- package/dist/mcp/notebook.d.ts +0 -10
- package/dist/mcp/notebook.js +0 -367
- package/dist/mcp/subagent.d.ts +0 -37
- package/dist/mcp/subagent.js +0 -113
- package/dist/mcp/todo.d.ts +0 -46
- package/dist/mcp/todo.js +0 -511
- package/dist/mcp/types/aceCodeSearch.types.d.ts +0 -92
- package/dist/mcp/types/aceCodeSearch.types.js +0 -4
- package/dist/mcp/types/bash.types.d.ts +0 -13
- package/dist/mcp/types/bash.types.js +0 -4
- package/dist/mcp/types/filesystem.types.d.ts +0 -210
- package/dist/mcp/types/filesystem.types.js +0 -27
- package/dist/mcp/types/todo.types.d.ts +0 -27
- package/dist/mcp/types/todo.types.js +0 -4
- package/dist/mcp/types/websearch.types.d.ts +0 -30
- package/dist/mcp/types/websearch.types.js +0 -4
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +0 -34
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +0 -146
- package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +0 -14
- package/dist/mcp/utils/aceCodeSearch/language.utils.js +0 -418
- package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +0 -31
- package/dist/mcp/utils/aceCodeSearch/search.utils.js +0 -136
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +0 -20
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +0 -141
- package/dist/mcp/utils/bash/security.utils.d.ts +0 -20
- package/dist/mcp/utils/bash/security.utils.js +0 -34
- package/dist/mcp/utils/filesystem/batch-operations.utils.d.ts +0 -39
- package/dist/mcp/utils/filesystem/batch-operations.utils.js +0 -182
- package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +0 -18
- package/dist/mcp/utils/filesystem/code-analysis.utils.js +0 -165
- package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +0 -16
- package/dist/mcp/utils/filesystem/match-finder.utils.js +0 -85
- package/dist/mcp/utils/filesystem/office-parser.utils.d.ts +0 -43
- package/dist/mcp/utils/filesystem/office-parser.utils.js +0 -163
- package/dist/mcp/utils/filesystem/path-fixer.utils.d.ts +0 -7
- package/dist/mcp/utils/filesystem/path-fixer.utils.js +0 -60
- package/dist/mcp/utils/filesystem/similarity.utils.d.ts +0 -22
- package/dist/mcp/utils/filesystem/similarity.utils.js +0 -75
- package/dist/mcp/utils/todo/date.utils.d.ts +0 -9
- package/dist/mcp/utils/todo/date.utils.js +0 -14
- package/dist/mcp/utils/websearch/browser.utils.d.ts +0 -8
- package/dist/mcp/utils/websearch/browser.utils.js +0 -58
- package/dist/mcp/utils/websearch/text.utils.d.ts +0 -16
- package/dist/mcp/utils/websearch/text.utils.js +0 -39
- package/dist/mcp/websearch.d.ts +0 -88
- package/dist/mcp/websearch.js +0 -375
- package/dist/test/logger-test.d.ts +0 -1
- package/dist/test/logger-test.js +0 -7
- package/dist/types/index.d.ts +0 -15
- package/dist/types/index.js +0 -1
- package/dist/ui/components/AgentPickerPanel.d.ts +0 -10
- package/dist/ui/components/AgentPickerPanel.js +0 -74
- package/dist/ui/components/ChatInput.d.ts +0 -46
- package/dist/ui/components/ChatInput.js +0 -384
- package/dist/ui/components/CommandPanel.d.ts +0 -15
- package/dist/ui/components/CommandPanel.js +0 -80
- package/dist/ui/components/DiffViewer.d.ts +0 -11
- package/dist/ui/components/DiffViewer.js +0 -178
- package/dist/ui/components/FileList.d.ts +0 -15
- package/dist/ui/components/FileList.js +0 -360
- package/dist/ui/components/FileRollbackConfirmation.d.ts +0 -8
- package/dist/ui/components/FileRollbackConfirmation.js +0 -108
- package/dist/ui/components/HelpPanel.d.ts +0 -2
- package/dist/ui/components/HelpPanel.js +0 -67
- package/dist/ui/components/MCPInfoPanel.d.ts +0 -2
- package/dist/ui/components/MCPInfoPanel.js +0 -108
- package/dist/ui/components/MCPInfoScreen.d.ts +0 -7
- package/dist/ui/components/MCPInfoScreen.js +0 -115
- package/dist/ui/components/MarkdownRenderer.d.ts +0 -6
- package/dist/ui/components/MarkdownRenderer.js +0 -70
- package/dist/ui/components/Menu.d.ts +0 -17
- package/dist/ui/components/Menu.js +0 -88
- package/dist/ui/components/MessageList.d.ts +0 -56
- package/dist/ui/components/MessageList.js +0 -97
- package/dist/ui/components/PendingMessages.d.ts +0 -13
- package/dist/ui/components/PendingMessages.js +0 -29
- package/dist/ui/components/PendingToolCalls.d.ts +0 -11
- package/dist/ui/components/PendingToolCalls.js +0 -35
- package/dist/ui/components/ScrollableSelectInput.d.ts +0 -29
- package/dist/ui/components/ScrollableSelectInput.js +0 -157
- package/dist/ui/components/SessionListPanel.d.ts +0 -7
- package/dist/ui/components/SessionListPanel.js +0 -175
- package/dist/ui/components/SessionListScreen.d.ts +0 -7
- package/dist/ui/components/SessionListScreen.js +0 -217
- package/dist/ui/components/SessionListScreenWrapper.d.ts +0 -7
- package/dist/ui/components/SessionListScreenWrapper.js +0 -14
- package/dist/ui/components/ShimmerText.d.ts +0 -9
- package/dist/ui/components/ShimmerText.js +0 -30
- package/dist/ui/components/TodoPickerPanel.d.ts +0 -14
- package/dist/ui/components/TodoPickerPanel.js +0 -119
- package/dist/ui/components/TodoTree.d.ts +0 -15
- package/dist/ui/components/TodoTree.js +0 -60
- package/dist/ui/components/ToolConfirmation.d.ts +0 -21
- package/dist/ui/components/ToolConfirmation.js +0 -204
- package/dist/ui/components/ToolResultPreview.d.ts +0 -13
- package/dist/ui/components/ToolResultPreview.js +0 -337
- package/dist/ui/components/UsagePanel.d.ts +0 -2
- package/dist/ui/components/UsagePanel.js +0 -394
- package/dist/ui/contexts/ThemeContext.d.ts +0 -13
- package/dist/ui/contexts/ThemeContext.js +0 -28
- package/dist/ui/pages/ChatScreen.d.ts +0 -6
- package/dist/ui/pages/ChatScreen.js +0 -1519
- package/dist/ui/pages/CodeBaseConfigScreen.d.ts +0 -8
- package/dist/ui/pages/CodeBaseConfigScreen.js +0 -350
- package/dist/ui/pages/ConfigScreen.d.ts +0 -8
- package/dist/ui/pages/ConfigScreen.js +0 -1101
- package/dist/ui/pages/CustomHeadersScreen.d.ts +0 -6
- package/dist/ui/pages/CustomHeadersScreen.js +0 -502
- package/dist/ui/pages/HeadlessModeScreen.d.ts +0 -7
- package/dist/ui/pages/HeadlessModeScreen.js +0 -381
- package/dist/ui/pages/LanguageSettingsScreen.d.ts +0 -7
- package/dist/ui/pages/LanguageSettingsScreen.js +0 -91
- package/dist/ui/pages/MCPConfigScreen.d.ts +0 -6
- package/dist/ui/pages/MCPConfigScreen.js +0 -55
- package/dist/ui/pages/ProxyConfigScreen.d.ts +0 -8
- package/dist/ui/pages/ProxyConfigScreen.js +0 -149
- package/dist/ui/pages/SensitiveCommandConfigScreen.d.ts +0 -7
- package/dist/ui/pages/SensitiveCommandConfigScreen.js +0 -271
- package/dist/ui/pages/SubAgentConfigScreen.d.ts +0 -9
- package/dist/ui/pages/SubAgentConfigScreen.js +0 -435
- package/dist/ui/pages/SubAgentListScreen.d.ts +0 -9
- package/dist/ui/pages/SubAgentListScreen.js +0 -131
- package/dist/ui/pages/SystemPromptConfigScreen.d.ts +0 -6
- package/dist/ui/pages/SystemPromptConfigScreen.js +0 -326
- package/dist/ui/pages/ThemeSettingsScreen.d.ts +0 -7
- package/dist/ui/pages/ThemeSettingsScreen.js +0 -106
- package/dist/ui/pages/WelcomeScreen.d.ts +0 -7
- package/dist/ui/pages/WelcomeScreen.js +0 -217
- package/dist/ui/themes/index.d.ts +0 -23
- package/dist/ui/themes/index.js +0 -140
- package/dist/utils/apiConfig.d.ts +0 -126
- package/dist/utils/apiConfig.js +0 -423
- package/dist/utils/autoCompress.d.ts +0 -15
- package/dist/utils/autoCompress.js +0 -24
- package/dist/utils/chatExporter.d.ts +0 -9
- package/dist/utils/chatExporter.js +0 -118
- package/dist/utils/checkpointManager.d.ts +0 -74
- package/dist/utils/checkpointManager.js +0 -181
- package/dist/utils/codebaseConfig.d.ts +0 -16
- package/dist/utils/codebaseConfig.js +0 -67
- package/dist/utils/codebaseDatabase.d.ts +0 -102
- package/dist/utils/codebaseDatabase.js +0 -333
- package/dist/utils/codebaseSearchEvents.d.ts +0 -16
- package/dist/utils/codebaseSearchEvents.js +0 -13
- package/dist/utils/commandExecutor.d.ts +0 -13
- package/dist/utils/commandExecutor.js +0 -26
- package/dist/utils/commands/agent.d.ts +0 -2
- package/dist/utils/commands/agent.js +0 -12
- package/dist/utils/commands/clear.d.ts +0 -2
- package/dist/utils/commands/clear.js +0 -12
- package/dist/utils/commands/compact.d.ts +0 -2
- package/dist/utils/commands/compact.js +0 -12
- package/dist/utils/commands/export.d.ts +0 -2
- package/dist/utils/commands/export.js +0 -12
- package/dist/utils/commands/help.d.ts +0 -2
- package/dist/utils/commands/help.js +0 -11
- package/dist/utils/commands/home.d.ts +0 -2
- package/dist/utils/commands/home.js +0 -34
- package/dist/utils/commands/ide.d.ts +0 -2
- package/dist/utils/commands/ide.js +0 -32
- package/dist/utils/commands/init.d.ts +0 -2
- package/dist/utils/commands/init.js +0 -93
- package/dist/utils/commands/mcp.d.ts +0 -2
- package/dist/utils/commands/mcp.js +0 -12
- package/dist/utils/commands/resume.d.ts +0 -2
- package/dist/utils/commands/resume.js +0 -12
- package/dist/utils/commands/review.d.ts +0 -2
- package/dist/utils/commands/review.js +0 -81
- package/dist/utils/commands/role.d.ts +0 -2
- package/dist/utils/commands/role.js +0 -37
- package/dist/utils/commands/todoPicker.d.ts +0 -2
- package/dist/utils/commands/todoPicker.js +0 -12
- package/dist/utils/commands/usage.d.ts +0 -2
- package/dist/utils/commands/usage.js +0 -12
- package/dist/utils/commands/yolo.d.ts +0 -2
- package/dist/utils/commands/yolo.js +0 -12
- package/dist/utils/configManager.d.ts +0 -45
- package/dist/utils/configManager.js +0 -303
- package/dist/utils/contextCompressor.d.ts +0 -16
- package/dist/utils/contextCompressor.js +0 -334
- package/dist/utils/devMode.d.ts +0 -13
- package/dist/utils/devMode.js +0 -54
- package/dist/utils/escapeHandler.d.ts +0 -79
- package/dist/utils/escapeHandler.js +0 -153
- package/dist/utils/fileDialog.d.ts +0 -9
- package/dist/utils/fileDialog.js +0 -74
- package/dist/utils/fileUtils.d.ts +0 -40
- package/dist/utils/fileUtils.js +0 -185
- package/dist/utils/historyManager.d.ts +0 -45
- package/dist/utils/historyManager.js +0 -159
- package/dist/utils/incrementalSnapshot.d.ts +0 -109
- package/dist/utils/incrementalSnapshot.js +0 -383
- package/dist/utils/index.d.ts +0 -11
- package/dist/utils/index.js +0 -18
- package/dist/utils/languageConfig.d.ts +0 -21
- package/dist/utils/languageConfig.js +0 -61
- package/dist/utils/logger.d.ts +0 -37
- package/dist/utils/logger.js +0 -122
- package/dist/utils/mcpToolsManager.d.ts +0 -52
- package/dist/utils/mcpToolsManager.js +0 -878
- package/dist/utils/messageFormatter.d.ts +0 -12
- package/dist/utils/messageFormatter.js +0 -115
- package/dist/utils/notebookManager.d.ts +0 -59
- package/dist/utils/notebookManager.js +0 -213
- package/dist/utils/patch-highlight.d.ts +0 -5
- package/dist/utils/patch-highlight.js +0 -23
- package/dist/utils/processManager.d.ts +0 -27
- package/dist/utils/processManager.js +0 -75
- package/dist/utils/proxyUtils.d.ts +0 -15
- package/dist/utils/proxyUtils.js +0 -50
- package/dist/utils/resourceMonitor.d.ts +0 -65
- package/dist/utils/resourceMonitor.js +0 -175
- package/dist/utils/retryUtils.d.ts +0 -49
- package/dist/utils/retryUtils.js +0 -303
- package/dist/utils/sensitiveCommandManager.d.ts +0 -53
- package/dist/utils/sensitiveCommandManager.js +0 -308
- package/dist/utils/sessionConverter.d.ts +0 -7
- package/dist/utils/sessionConverter.js +0 -306
- package/dist/utils/sessionManager.d.ts +0 -53
- package/dist/utils/sessionManager.js +0 -371
- package/dist/utils/subAgentConfig.d.ts +0 -50
- package/dist/utils/subAgentConfig.js +0 -221
- package/dist/utils/subAgentExecutor.d.ts +0 -40
- package/dist/utils/subAgentExecutor.js +0 -434
- package/dist/utils/terminal.d.ts +0 -5
- package/dist/utils/terminal.js +0 -13
- package/dist/utils/textBuffer.d.ts +0 -99
- package/dist/utils/textBuffer.js +0 -547
- package/dist/utils/textUtils.d.ts +0 -37
- package/dist/utils/textUtils.js +0 -102
- package/dist/utils/themeConfig.d.ts +0 -21
- package/dist/utils/themeConfig.js +0 -61
- package/dist/utils/todoPreprocessor.d.ts +0 -5
- package/dist/utils/todoPreprocessor.js +0 -18
- package/dist/utils/todoScanner.d.ts +0 -8
- package/dist/utils/todoScanner.js +0 -148
- package/dist/utils/toolDisplayConfig.d.ts +0 -16
- package/dist/utils/toolDisplayConfig.js +0 -47
- package/dist/utils/toolExecutor.d.ts +0 -37
- package/dist/utils/toolExecutor.js +0 -224
- package/dist/utils/usageLogger.d.ts +0 -11
- package/dist/utils/usageLogger.js +0 -114
- package/dist/utils/vscodeConnection.d.ts +0 -76
- package/dist/utils/vscodeConnection.js +0 -430
- package/dist/utils/workspaceSnapshot.d.ts +0 -63
- package/dist/utils/workspaceSnapshot.js +0 -300
|
@@ -1,1052 +0,0 @@
|
|
|
1
|
-
import { encoding_for_model } from 'tiktoken';
|
|
2
|
-
import { createStreamingChatCompletion } from '../api/chat.js';
|
|
3
|
-
import { createStreamingResponse } from '../api/responses.js';
|
|
4
|
-
import { createStreamingGeminiCompletion } from '../api/gemini.js';
|
|
5
|
-
import { createStreamingAnthropicCompletion } from '../api/anthropic.js';
|
|
6
|
-
import { getSystemPrompt } from '../api/systemPrompt.js';
|
|
7
|
-
import { collectAllMCPTools, getTodoService } from '../utils/mcpToolsManager.js';
|
|
8
|
-
import { executeToolCalls } from '../utils/toolExecutor.js';
|
|
9
|
-
import { getOpenAiConfig } from '../utils/apiConfig.js';
|
|
10
|
-
import { sessionManager } from '../utils/sessionManager.js';
|
|
11
|
-
import { formatTodoContext } from '../utils/todoPreprocessor.js';
|
|
12
|
-
import { formatToolCallMessage } from '../utils/messageFormatter.js';
|
|
13
|
-
import { resourceMonitor } from '../utils/resourceMonitor.js';
|
|
14
|
-
import { isToolNeedTwoStepDisplay } from '../utils/toolDisplayConfig.js';
|
|
15
|
-
import { shouldAutoCompress, performAutoCompression, } from '../utils/autoCompress.js';
|
|
16
|
-
/**
|
|
17
|
-
* Handle conversation with streaming and tool calls
|
|
18
|
-
* Returns the usage data collected during the conversation
|
|
19
|
-
*/
|
|
20
|
-
export async function handleConversationWithTools(options) {
|
|
21
|
-
const { userContent, imageContents, controller,
|
|
22
|
-
// messages, // No longer used - we load from session instead to get complete history with tool calls
|
|
23
|
-
saveMessage, setMessages, setStreamTokenCount, 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
|
-
};
|
|
28
|
-
// Step 1: Ensure session exists and get existing TODOs
|
|
29
|
-
let currentSession = sessionManager.getCurrentSession();
|
|
30
|
-
if (!currentSession) {
|
|
31
|
-
currentSession = await sessionManager.createNewSession();
|
|
32
|
-
}
|
|
33
|
-
const todoService = getTodoService();
|
|
34
|
-
// Get existing TODO list
|
|
35
|
-
const existingTodoList = await todoService.getTodoList(currentSession.id);
|
|
36
|
-
// Collect all MCP tools
|
|
37
|
-
const mcpTools = await collectAllMCPTools();
|
|
38
|
-
// Build conversation history with TODO context as pinned user message
|
|
39
|
-
let conversationMessages = [
|
|
40
|
-
{ role: 'system', content: getSystemPrompt() },
|
|
41
|
-
];
|
|
42
|
-
// If there are TODOs, add pinned context message at the front
|
|
43
|
-
if (existingTodoList && existingTodoList.todos.length > 0) {
|
|
44
|
-
const todoContext = formatTodoContext(existingTodoList.todos);
|
|
45
|
-
conversationMessages.push({
|
|
46
|
-
role: 'user',
|
|
47
|
-
content: todoContext,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
// Add history messages from session (includes tool_calls and tool results)
|
|
51
|
-
// Load from session to get complete conversation history with tool interactions
|
|
52
|
-
// Filter out internal sub-agent messages (marked with subAgentInternal: true)
|
|
53
|
-
const session = sessionManager.getCurrentSession();
|
|
54
|
-
if (session && session.messages.length > 0) {
|
|
55
|
-
// Use session messages directly (they are already in API format)
|
|
56
|
-
// Filter out sub-agent internal messages before sending to API
|
|
57
|
-
const filteredMessages = session.messages.filter(msg => !msg.subAgentInternal);
|
|
58
|
-
conversationMessages.push(...filteredMessages);
|
|
59
|
-
}
|
|
60
|
-
// Add current user message
|
|
61
|
-
conversationMessages.push({
|
|
62
|
-
role: 'user',
|
|
63
|
-
content: userContent,
|
|
64
|
-
images: imageContents,
|
|
65
|
-
});
|
|
66
|
-
// Save user message (directly save API format message)
|
|
67
|
-
// IMPORTANT: await to ensure message is saved before continuing
|
|
68
|
-
// This prevents loss of user message if conversation is interrupted (ESC)
|
|
69
|
-
try {
|
|
70
|
-
await saveMessage({
|
|
71
|
-
role: 'user',
|
|
72
|
-
content: userContent,
|
|
73
|
-
images: imageContents,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.error('Failed to save user message:', error);
|
|
78
|
-
}
|
|
79
|
-
// Initialize token encoder with proper cleanup tracking
|
|
80
|
-
let encoder;
|
|
81
|
-
let encoderFreed = false;
|
|
82
|
-
const freeEncoder = () => {
|
|
83
|
-
if (!encoderFreed && encoder) {
|
|
84
|
-
try {
|
|
85
|
-
encoder.free();
|
|
86
|
-
encoderFreed = true;
|
|
87
|
-
resourceMonitor.trackEncoderFreed();
|
|
88
|
-
}
|
|
89
|
-
catch (e) {
|
|
90
|
-
console.error('Failed to free encoder:', e);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
try {
|
|
95
|
-
encoder = encoding_for_model('gpt-5');
|
|
96
|
-
resourceMonitor.trackEncoderCreated();
|
|
97
|
-
}
|
|
98
|
-
catch (e) {
|
|
99
|
-
encoder = encoding_for_model('gpt-3.5-turbo');
|
|
100
|
-
resourceMonitor.trackEncoderCreated();
|
|
101
|
-
}
|
|
102
|
-
setStreamTokenCount(0);
|
|
103
|
-
const config = getOpenAiConfig();
|
|
104
|
-
const model = options.useBasicModel
|
|
105
|
-
? config.basicModel || config.advancedModel || 'gpt-5'
|
|
106
|
-
: config.advancedModel || 'gpt-5';
|
|
107
|
-
// Tool calling loop (no limit on rounds)
|
|
108
|
-
let finalAssistantMessage = null;
|
|
109
|
-
// Accumulate usage data across all rounds
|
|
110
|
-
let accumulatedUsage = null;
|
|
111
|
-
// Local set to track approved tools in this conversation (solves async setState issue)
|
|
112
|
-
const sessionApprovedTools = new Set();
|
|
113
|
-
try {
|
|
114
|
-
while (true) {
|
|
115
|
-
if (controller.signal.aborted) {
|
|
116
|
-
freeEncoder();
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
let streamedContent = '';
|
|
120
|
-
let receivedToolCalls;
|
|
121
|
-
let receivedReasoning;
|
|
122
|
-
let receivedThinking; // Accumulate thinking content from all platforms
|
|
123
|
-
let hasStartedReasoning = false; // Track if reasoning has started (for Gemini thinking)
|
|
124
|
-
// Stream AI response - choose API based on config
|
|
125
|
-
let toolCallAccumulator = ''; // Accumulate tool call deltas for token counting
|
|
126
|
-
let reasoningAccumulator = ''; // Accumulate reasoning summary deltas for token counting (Responses API only)
|
|
127
|
-
let chunkCount = 0; // Track number of chunks received (to delay clearing retry status)
|
|
128
|
-
// Get or create session for cache key
|
|
129
|
-
const currentSession = sessionManager.getCurrentSession();
|
|
130
|
-
// Use session ID as cache key to ensure same session requests share cache
|
|
131
|
-
const cacheKey = currentSession?.id;
|
|
132
|
-
// 重试回调函数
|
|
133
|
-
const onRetry = (error, attempt, nextDelay) => {
|
|
134
|
-
if (setRetryStatus) {
|
|
135
|
-
setRetryStatus({
|
|
136
|
-
isRetrying: true,
|
|
137
|
-
attempt,
|
|
138
|
-
nextDelay,
|
|
139
|
-
errorMessage: error.message,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const streamGenerator = config.requestMethod === 'anthropic'
|
|
144
|
-
? createStreamingAnthropicCompletion({
|
|
145
|
-
model,
|
|
146
|
-
messages: conversationMessages,
|
|
147
|
-
temperature: 0,
|
|
148
|
-
max_tokens: config.maxTokens || 4096,
|
|
149
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
150
|
-
sessionId: currentSession?.id,
|
|
151
|
-
// Disable thinking for basicModel (e.g., init command)
|
|
152
|
-
disableThinking: options.useBasicModel,
|
|
153
|
-
}, controller.signal, onRetry)
|
|
154
|
-
: config.requestMethod === 'gemini'
|
|
155
|
-
? createStreamingGeminiCompletion({
|
|
156
|
-
model,
|
|
157
|
-
messages: conversationMessages,
|
|
158
|
-
temperature: 0,
|
|
159
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
160
|
-
}, controller.signal, onRetry)
|
|
161
|
-
: config.requestMethod === 'responses'
|
|
162
|
-
? createStreamingResponse({
|
|
163
|
-
model,
|
|
164
|
-
messages: conversationMessages,
|
|
165
|
-
temperature: 0,
|
|
166
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
167
|
-
tool_choice: 'auto',
|
|
168
|
-
prompt_cache_key: cacheKey, // Use session ID as cache key
|
|
169
|
-
// Don't pass reasoning for basicModel (small models may not support it)
|
|
170
|
-
// Pass null to explicitly disable reasoning in API call
|
|
171
|
-
reasoning: options.useBasicModel ? null : undefined,
|
|
172
|
-
}, controller.signal, onRetry)
|
|
173
|
-
: createStreamingChatCompletion({
|
|
174
|
-
model,
|
|
175
|
-
messages: conversationMessages,
|
|
176
|
-
temperature: 0,
|
|
177
|
-
tools: mcpTools.length > 0 ? mcpTools : undefined,
|
|
178
|
-
}, controller.signal, onRetry);
|
|
179
|
-
for await (const chunk of streamGenerator) {
|
|
180
|
-
if (controller.signal.aborted)
|
|
181
|
-
break;
|
|
182
|
-
// Clear retry status after a delay when first chunk arrives
|
|
183
|
-
// This gives users time to see the retry message (500ms delay)
|
|
184
|
-
chunkCount++;
|
|
185
|
-
if (setRetryStatus && chunkCount === 1) {
|
|
186
|
-
setTimeout(() => {
|
|
187
|
-
setRetryStatus(null);
|
|
188
|
-
}, 500);
|
|
189
|
-
}
|
|
190
|
-
if (chunk.type === 'reasoning_started') {
|
|
191
|
-
// Reasoning started (Responses API only) - set reasoning state
|
|
192
|
-
setIsReasoning?.(true);
|
|
193
|
-
}
|
|
194
|
-
else if (chunk.type === 'reasoning_delta' && chunk.delta) {
|
|
195
|
-
// Handle reasoning delta from Gemini thinking
|
|
196
|
-
// When reasoning_delta is received, set reasoning state if not already set
|
|
197
|
-
if (!hasStartedReasoning) {
|
|
198
|
-
setIsReasoning?.(true);
|
|
199
|
-
hasStartedReasoning = true;
|
|
200
|
-
}
|
|
201
|
-
// Note: reasoning content is NOT sent back to AI, only counted for display
|
|
202
|
-
reasoningAccumulator += chunk.delta;
|
|
203
|
-
try {
|
|
204
|
-
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
205
|
-
setStreamTokenCount(tokens.length);
|
|
206
|
-
}
|
|
207
|
-
catch (e) {
|
|
208
|
-
// Ignore encoding errors
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else if (chunk.type === 'content' && chunk.content) {
|
|
212
|
-
// Accumulate content and update token count
|
|
213
|
-
// When content starts, reasoning is done
|
|
214
|
-
setIsReasoning?.(false);
|
|
215
|
-
streamedContent += chunk.content;
|
|
216
|
-
try {
|
|
217
|
-
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
218
|
-
setStreamTokenCount(tokens.length);
|
|
219
|
-
}
|
|
220
|
-
catch (e) {
|
|
221
|
-
// Ignore encoding errors
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
else if (chunk.type === 'tool_call_delta' && chunk.delta) {
|
|
225
|
-
// Accumulate tool call deltas and update token count in real-time
|
|
226
|
-
// When tool calls start, reasoning is done (OpenAI generally doesn't output text content during tool calls)
|
|
227
|
-
setIsReasoning?.(false);
|
|
228
|
-
toolCallAccumulator += chunk.delta;
|
|
229
|
-
try {
|
|
230
|
-
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
231
|
-
setStreamTokenCount(tokens.length);
|
|
232
|
-
}
|
|
233
|
-
catch (e) {
|
|
234
|
-
// Ignore encoding errors
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
else if (chunk.type === 'tool_calls' && chunk.tool_calls) {
|
|
238
|
-
receivedToolCalls = chunk.tool_calls;
|
|
239
|
-
}
|
|
240
|
-
else if (chunk.type === 'reasoning_data' && chunk.reasoning) {
|
|
241
|
-
// Capture reasoning data from Responses API
|
|
242
|
-
receivedReasoning = chunk.reasoning;
|
|
243
|
-
}
|
|
244
|
-
else if (chunk.type === 'done' && chunk.thinking) {
|
|
245
|
-
// Capture thinking content from Anthropic only (includes signature)
|
|
246
|
-
receivedThinking = chunk.thinking;
|
|
247
|
-
}
|
|
248
|
-
else if (chunk.type === 'usage' && chunk.usage) {
|
|
249
|
-
// Capture usage information both in state and locally
|
|
250
|
-
setContextUsage(chunk.usage);
|
|
251
|
-
// Note: Usage is now saved at API layer (chat.ts, anthropic.ts, etc.)
|
|
252
|
-
// No need to call onUsageUpdate here to avoid duplicate saves
|
|
253
|
-
// Accumulate for final return (UI display purposes)
|
|
254
|
-
if (!accumulatedUsage) {
|
|
255
|
-
accumulatedUsage = {
|
|
256
|
-
prompt_tokens: chunk.usage.prompt_tokens || 0,
|
|
257
|
-
completion_tokens: chunk.usage.completion_tokens || 0,
|
|
258
|
-
total_tokens: chunk.usage.total_tokens || 0,
|
|
259
|
-
cache_creation_input_tokens: chunk.usage.cache_creation_input_tokens,
|
|
260
|
-
cache_read_input_tokens: chunk.usage.cache_read_input_tokens,
|
|
261
|
-
cached_tokens: chunk.usage.cached_tokens,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
// Add to existing usage for UI display
|
|
266
|
-
accumulatedUsage.prompt_tokens += chunk.usage.prompt_tokens || 0;
|
|
267
|
-
accumulatedUsage.completion_tokens +=
|
|
268
|
-
chunk.usage.completion_tokens || 0;
|
|
269
|
-
accumulatedUsage.total_tokens += chunk.usage.total_tokens || 0;
|
|
270
|
-
if (chunk.usage.cache_creation_input_tokens !== undefined) {
|
|
271
|
-
accumulatedUsage.cache_creation_input_tokens =
|
|
272
|
-
(accumulatedUsage.cache_creation_input_tokens || 0) +
|
|
273
|
-
chunk.usage.cache_creation_input_tokens;
|
|
274
|
-
}
|
|
275
|
-
if (chunk.usage.cache_read_input_tokens !== undefined) {
|
|
276
|
-
accumulatedUsage.cache_read_input_tokens =
|
|
277
|
-
(accumulatedUsage.cache_read_input_tokens || 0) +
|
|
278
|
-
chunk.usage.cache_read_input_tokens;
|
|
279
|
-
}
|
|
280
|
-
if (chunk.usage.cached_tokens !== undefined) {
|
|
281
|
-
accumulatedUsage.cached_tokens =
|
|
282
|
-
(accumulatedUsage.cached_tokens || 0) +
|
|
283
|
-
chunk.usage.cached_tokens;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// Reset token count after stream ends
|
|
289
|
-
setStreamTokenCount(0);
|
|
290
|
-
// If aborted during streaming, exit the loop
|
|
291
|
-
// (discontinued message already added by ChatScreen ESC handler)
|
|
292
|
-
if (controller.signal.aborted) {
|
|
293
|
-
freeEncoder();
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
// If there are tool calls, we need to handle them specially
|
|
297
|
-
if (receivedToolCalls && receivedToolCalls.length > 0) {
|
|
298
|
-
// Add assistant message with tool_calls to conversation (OpenAI requires this format)
|
|
299
|
-
const assistantMessage = {
|
|
300
|
-
role: 'assistant',
|
|
301
|
-
content: streamedContent || '',
|
|
302
|
-
tool_calls: receivedToolCalls.map(tc => ({
|
|
303
|
-
id: tc.id,
|
|
304
|
-
type: 'function',
|
|
305
|
-
function: {
|
|
306
|
-
name: tc.function.name,
|
|
307
|
-
arguments: tc.function.arguments,
|
|
308
|
-
},
|
|
309
|
-
})),
|
|
310
|
-
reasoning: receivedReasoning, // Include reasoning data for caching (Responses API)
|
|
311
|
-
thinking: receivedThinking, // Include thinking content (Anthropic/OpenAI)
|
|
312
|
-
};
|
|
313
|
-
conversationMessages.push(assistantMessage);
|
|
314
|
-
// Save assistant message with tool calls
|
|
315
|
-
saveMessage(assistantMessage).catch(error => {
|
|
316
|
-
console.error('Failed to save assistant message:', error);
|
|
317
|
-
});
|
|
318
|
-
// If there's text content before tool calls, display it first
|
|
319
|
-
if (streamedContent && streamedContent.trim()) {
|
|
320
|
-
setMessages(prev => [
|
|
321
|
-
...prev,
|
|
322
|
-
{
|
|
323
|
-
role: 'assistant',
|
|
324
|
-
content: streamedContent.trim(),
|
|
325
|
-
streaming: false,
|
|
326
|
-
},
|
|
327
|
-
]);
|
|
328
|
-
}
|
|
329
|
-
// Display tool calls in UI - 只有耗时工具才显示进行中状态
|
|
330
|
-
// Generate parallel group ID when there are multiple tools
|
|
331
|
-
const parallelGroupId = receivedToolCalls.length > 1
|
|
332
|
-
? `parallel-${Date.now()}-${Math.random()}`
|
|
333
|
-
: undefined;
|
|
334
|
-
for (const toolCall of receivedToolCalls) {
|
|
335
|
-
const toolDisplay = formatToolCallMessage(toolCall);
|
|
336
|
-
let toolArgs;
|
|
337
|
-
try {
|
|
338
|
-
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
339
|
-
}
|
|
340
|
-
catch (e) {
|
|
341
|
-
toolArgs = {};
|
|
342
|
-
}
|
|
343
|
-
// 只有耗时工具才在动态区显示进行中状态
|
|
344
|
-
if (isToolNeedTwoStepDisplay(toolCall.function.name)) {
|
|
345
|
-
setMessages(prev => [
|
|
346
|
-
...prev,
|
|
347
|
-
{
|
|
348
|
-
role: 'assistant',
|
|
349
|
-
content: `⚡ ${toolDisplay.toolName}`,
|
|
350
|
-
streaming: false,
|
|
351
|
-
toolCall: {
|
|
352
|
-
name: toolCall.function.name,
|
|
353
|
-
arguments: toolArgs,
|
|
354
|
-
},
|
|
355
|
-
toolDisplay,
|
|
356
|
-
toolCallId: toolCall.id, // Store tool call ID for later update
|
|
357
|
-
toolPending: true, // Mark as pending execution
|
|
358
|
-
// Mark parallel group for ALL tools (time-consuming or not)
|
|
359
|
-
parallelGroup: parallelGroupId,
|
|
360
|
-
},
|
|
361
|
-
]);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// Filter tools that need confirmation (not in always-approved list OR session-approved list)
|
|
365
|
-
const toolsNeedingConfirmation = [];
|
|
366
|
-
const autoApprovedTools = [];
|
|
367
|
-
for (const toolCall of receivedToolCalls) {
|
|
368
|
-
// Check both global approved list and session-approved list
|
|
369
|
-
const isApproved = isToolAutoApproved(toolCall.function.name) ||
|
|
370
|
-
sessionApprovedTools.has(toolCall.function.name);
|
|
371
|
-
// Check if this is a sensitive command (terminal-execute with sensitive pattern)
|
|
372
|
-
let isSensitiveCommand = false;
|
|
373
|
-
if (toolCall.function.name === 'terminal-execute') {
|
|
374
|
-
try {
|
|
375
|
-
const args = JSON.parse(toolCall.function.arguments);
|
|
376
|
-
const { isSensitiveCommand: checkSensitiveCommand } = await import('../utils/sensitiveCommandManager.js').then(m => ({
|
|
377
|
-
isSensitiveCommand: m.isSensitiveCommand,
|
|
378
|
-
}));
|
|
379
|
-
const sensitiveCheck = checkSensitiveCommand(args.command);
|
|
380
|
-
isSensitiveCommand = sensitiveCheck.isSensitive;
|
|
381
|
-
}
|
|
382
|
-
catch {
|
|
383
|
-
// If parsing fails, treat as normal command
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
// If sensitive command, always require confirmation regardless of approval status
|
|
387
|
-
if (isSensitiveCommand) {
|
|
388
|
-
toolsNeedingConfirmation.push(toolCall);
|
|
389
|
-
}
|
|
390
|
-
else if (isApproved) {
|
|
391
|
-
autoApprovedTools.push(toolCall);
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
toolsNeedingConfirmation.push(toolCall);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
// Request confirmation only once for all tools needing confirmation
|
|
398
|
-
let approvedTools = [...autoApprovedTools];
|
|
399
|
-
// In YOLO mode, auto-approve all tools EXCEPT sensitive commands
|
|
400
|
-
if (yoloMode) {
|
|
401
|
-
// Filter out sensitive commands from auto-approval
|
|
402
|
-
const nonSensitiveTools = [];
|
|
403
|
-
const sensitiveTools = [];
|
|
404
|
-
for (const toolCall of toolsNeedingConfirmation) {
|
|
405
|
-
if (toolCall.function.name === 'terminal-execute') {
|
|
406
|
-
try {
|
|
407
|
-
const args = JSON.parse(toolCall.function.arguments);
|
|
408
|
-
const { isSensitiveCommand: checkSensitiveCommand } = await import('../utils/sensitiveCommandManager.js').then(m => ({
|
|
409
|
-
isSensitiveCommand: m.isSensitiveCommand,
|
|
410
|
-
}));
|
|
411
|
-
const sensitiveCheck = checkSensitiveCommand(args.command);
|
|
412
|
-
if (sensitiveCheck.isSensitive) {
|
|
413
|
-
sensitiveTools.push(toolCall);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
nonSensitiveTools.push(toolCall);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
nonSensitiveTools.push(toolCall);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
nonSensitiveTools.push(toolCall);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
approvedTools.push(...nonSensitiveTools);
|
|
428
|
-
// If there are sensitive tools, still need confirmation even in YOLO mode
|
|
429
|
-
if (sensitiveTools.length > 0) {
|
|
430
|
-
const firstTool = sensitiveTools[0];
|
|
431
|
-
const allTools = sensitiveTools.length > 1 ? sensitiveTools : undefined;
|
|
432
|
-
const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
|
|
433
|
-
if (confirmation === 'reject' ||
|
|
434
|
-
(typeof confirmation === 'object' &&
|
|
435
|
-
confirmation.type === 'reject_with_reply')) {
|
|
436
|
-
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
437
|
-
const rejectMessage = typeof confirmation === 'object'
|
|
438
|
-
? `Tool execution rejected by user: ${confirmation.reason}`
|
|
439
|
-
: 'Error: Tool execution rejected by user';
|
|
440
|
-
// Create UI messages for rejected tools
|
|
441
|
-
const rejectedToolUIMessages = [];
|
|
442
|
-
for (const toolCall of sensitiveTools) {
|
|
443
|
-
const rejectionMessage = {
|
|
444
|
-
role: 'tool',
|
|
445
|
-
tool_call_id: toolCall.id,
|
|
446
|
-
content: rejectMessage,
|
|
447
|
-
};
|
|
448
|
-
conversationMessages.push(rejectionMessage);
|
|
449
|
-
saveMessage(rejectionMessage).catch(error => {
|
|
450
|
-
console.error('Failed to save tool rejection message:', error);
|
|
451
|
-
});
|
|
452
|
-
// Add UI message for each rejected tool
|
|
453
|
-
const toolDisplay = formatToolCallMessage(toolCall);
|
|
454
|
-
const statusIcon = '✗';
|
|
455
|
-
let statusText = '';
|
|
456
|
-
if (typeof confirmation === 'object' && confirmation.reason) {
|
|
457
|
-
statusText = `\n └─ Rejection reason: ${confirmation.reason}`;
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
statusText = `\n └─ ${rejectMessage}`;
|
|
461
|
-
}
|
|
462
|
-
rejectedToolUIMessages.push({
|
|
463
|
-
role: 'assistant',
|
|
464
|
-
content: `${statusIcon} ${toolDisplay.toolName}${statusText}`,
|
|
465
|
-
streaming: false,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
// Add rejected tool messages to UI
|
|
469
|
-
if (rejectedToolUIMessages.length > 0) {
|
|
470
|
-
setMessages(prev => [...prev, ...rejectedToolUIMessages]);
|
|
471
|
-
}
|
|
472
|
-
// If reject_with_reply, continue the conversation instead of ending
|
|
473
|
-
if (typeof confirmation === 'object' &&
|
|
474
|
-
confirmation.type === 'reject_with_reply') {
|
|
475
|
-
// Continue to next iteration - AI will see the rejection message and respond
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
// Original reject behavior - end session
|
|
480
|
-
setMessages(prev => [
|
|
481
|
-
...prev,
|
|
482
|
-
{
|
|
483
|
-
role: 'assistant',
|
|
484
|
-
content: 'Tool call rejected, session ended',
|
|
485
|
-
streaming: false,
|
|
486
|
-
},
|
|
487
|
-
]);
|
|
488
|
-
if (options.setIsStreaming) {
|
|
489
|
-
options.setIsStreaming(false);
|
|
490
|
-
}
|
|
491
|
-
freeEncoder();
|
|
492
|
-
return { usage: accumulatedUsage };
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
// Approved, add sensitive tools to approved list
|
|
496
|
-
approvedTools.push(...sensitiveTools);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
else if (toolsNeedingConfirmation.length > 0) {
|
|
500
|
-
const firstTool = toolsNeedingConfirmation[0];
|
|
501
|
-
const allTools = toolsNeedingConfirmation.length > 1
|
|
502
|
-
? toolsNeedingConfirmation
|
|
503
|
-
: undefined;
|
|
504
|
-
const confirmation = await requestToolConfirmation(firstTool, undefined, allTools);
|
|
505
|
-
if (confirmation === 'reject' ||
|
|
506
|
-
(typeof confirmation === 'object' &&
|
|
507
|
-
confirmation.type === 'reject_with_reply')) {
|
|
508
|
-
setMessages(prev => prev.filter(msg => !msg.toolPending));
|
|
509
|
-
const rejectMessage = typeof confirmation === 'object'
|
|
510
|
-
? `Tool execution rejected by user: ${confirmation.reason}`
|
|
511
|
-
: 'Error: Tool execution rejected by user';
|
|
512
|
-
// Create UI messages for rejected tools
|
|
513
|
-
const rejectedToolUIMessages = [];
|
|
514
|
-
for (const toolCall of toolsNeedingConfirmation) {
|
|
515
|
-
const rejectionMessage = {
|
|
516
|
-
role: 'tool',
|
|
517
|
-
tool_call_id: toolCall.id,
|
|
518
|
-
content: rejectMessage,
|
|
519
|
-
};
|
|
520
|
-
conversationMessages.push(rejectionMessage);
|
|
521
|
-
saveMessage(rejectionMessage).catch(error => {
|
|
522
|
-
console.error('Failed to save tool rejection message:', error);
|
|
523
|
-
});
|
|
524
|
-
// Add UI message for each rejected tool
|
|
525
|
-
const toolDisplay = formatToolCallMessage(toolCall);
|
|
526
|
-
const statusIcon = '✗';
|
|
527
|
-
let statusText = '';
|
|
528
|
-
if (typeof confirmation === 'object' && confirmation.reason) {
|
|
529
|
-
statusText = `\n └─ Rejection reason: ${confirmation.reason}`;
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
statusText = `\n └─ ${rejectMessage}`;
|
|
533
|
-
}
|
|
534
|
-
rejectedToolUIMessages.push({
|
|
535
|
-
role: 'assistant',
|
|
536
|
-
content: `${statusIcon} ${toolDisplay.toolName}${statusText}`,
|
|
537
|
-
streaming: false,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
// Add rejected tool messages to UI
|
|
541
|
-
if (rejectedToolUIMessages.length > 0) {
|
|
542
|
-
setMessages(prev => [...prev, ...rejectedToolUIMessages]);
|
|
543
|
-
}
|
|
544
|
-
// If reject_with_reply, continue the conversation instead of ending
|
|
545
|
-
if (typeof confirmation === 'object' &&
|
|
546
|
-
confirmation.type === 'reject_with_reply') {
|
|
547
|
-
// Continue to next iteration - AI will see the rejection message and respond
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
// Original reject behavior - end session
|
|
552
|
-
setMessages(prev => [
|
|
553
|
-
...prev,
|
|
554
|
-
{
|
|
555
|
-
role: 'assistant',
|
|
556
|
-
content: 'Tool call rejected, session ended',
|
|
557
|
-
streaming: false,
|
|
558
|
-
},
|
|
559
|
-
]);
|
|
560
|
-
if (options.setIsStreaming) {
|
|
561
|
-
options.setIsStreaming(false);
|
|
562
|
-
}
|
|
563
|
-
freeEncoder();
|
|
564
|
-
return { usage: accumulatedUsage };
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// If approved_always, add ALL these tools to both global and session-approved sets
|
|
568
|
-
if (confirmation === 'approve_always') {
|
|
569
|
-
const toolNamesToAdd = toolsNeedingConfirmation.map(t => t.function.name);
|
|
570
|
-
// Add to global state (async, for future sessions)
|
|
571
|
-
addMultipleToAlwaysApproved(toolNamesToAdd);
|
|
572
|
-
// Add to local session set (sync, for this conversation)
|
|
573
|
-
toolNamesToAdd.forEach(name => sessionApprovedTools.add(name));
|
|
574
|
-
}
|
|
575
|
-
// Add all tools to approved list
|
|
576
|
-
approvedTools.push(...toolsNeedingConfirmation);
|
|
577
|
-
}
|
|
578
|
-
// Execute approved tools with sub-agent message callback and terminal output callback
|
|
579
|
-
// Track sub-agent content for token counting
|
|
580
|
-
let subAgentContentAccumulator = '';
|
|
581
|
-
const toolResults = await executeToolCalls(approvedTools, controller.signal, setStreamTokenCount, async (subAgentMessage) => {
|
|
582
|
-
// Handle sub-agent messages - display and save to session
|
|
583
|
-
setMessages(prev => {
|
|
584
|
-
// Handle tool calls from sub-agent
|
|
585
|
-
if (subAgentMessage.message.type === 'tool_calls') {
|
|
586
|
-
const toolCalls = subAgentMessage.message.tool_calls;
|
|
587
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
588
|
-
// Add tool call messages for each tool (only for time-consuming tools)
|
|
589
|
-
const toolMessages = toolCalls
|
|
590
|
-
.filter((toolCall) => isToolNeedTwoStepDisplay(toolCall.function.name))
|
|
591
|
-
.map((toolCall) => {
|
|
592
|
-
const toolDisplay = formatToolCallMessage(toolCall);
|
|
593
|
-
let toolArgs;
|
|
594
|
-
try {
|
|
595
|
-
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
596
|
-
}
|
|
597
|
-
catch (e) {
|
|
598
|
-
toolArgs = {};
|
|
599
|
-
}
|
|
600
|
-
const uiMsg = {
|
|
601
|
-
role: 'subagent',
|
|
602
|
-
content: `\x1b[38;2;184;122;206m⚇⚡ ${toolDisplay.toolName}\x1b[0m`,
|
|
603
|
-
streaming: false,
|
|
604
|
-
toolCall: {
|
|
605
|
-
name: toolCall.function.name,
|
|
606
|
-
arguments: toolArgs,
|
|
607
|
-
},
|
|
608
|
-
// Don't include toolDisplay for sub-agent tools to avoid showing parameters
|
|
609
|
-
toolCallId: toolCall.id,
|
|
610
|
-
toolPending: true,
|
|
611
|
-
subAgent: {
|
|
612
|
-
agentId: subAgentMessage.agentId,
|
|
613
|
-
agentName: subAgentMessage.agentName,
|
|
614
|
-
isComplete: false,
|
|
615
|
-
},
|
|
616
|
-
subAgentInternal: true, // Mark as internal sub-agent message
|
|
617
|
-
};
|
|
618
|
-
return uiMsg;
|
|
619
|
-
});
|
|
620
|
-
// Save all tool calls to session (regardless of display type)
|
|
621
|
-
const sessionMsg = {
|
|
622
|
-
role: 'assistant',
|
|
623
|
-
content: toolCalls
|
|
624
|
-
.map((tc) => {
|
|
625
|
-
const display = formatToolCallMessage(tc);
|
|
626
|
-
return `⚇⚡ ${display.toolName}`;
|
|
627
|
-
})
|
|
628
|
-
.join(', '),
|
|
629
|
-
subAgentInternal: true,
|
|
630
|
-
tool_calls: toolCalls,
|
|
631
|
-
};
|
|
632
|
-
saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool call:', err));
|
|
633
|
-
return [...prev, ...toolMessages];
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
// Handle tool results from sub-agent
|
|
637
|
-
if (subAgentMessage.message.type === 'tool_result') {
|
|
638
|
-
const msg = subAgentMessage.message;
|
|
639
|
-
const isError = msg.content.startsWith('Error:');
|
|
640
|
-
const statusIcon = isError ? '✗' : '✓';
|
|
641
|
-
const statusText = isError ? `\n └─ ${msg.content}` : '';
|
|
642
|
-
// For terminal-execute, try to extract terminal result data
|
|
643
|
-
let terminalResultData;
|
|
644
|
-
if (msg.tool_name === 'terminal-execute' && !isError) {
|
|
645
|
-
try {
|
|
646
|
-
const resultData = JSON.parse(msg.content);
|
|
647
|
-
if (resultData.stdout !== undefined ||
|
|
648
|
-
resultData.stderr !== undefined) {
|
|
649
|
-
terminalResultData = {
|
|
650
|
-
stdout: resultData.stdout,
|
|
651
|
-
stderr: resultData.stderr,
|
|
652
|
-
exitCode: resultData.exitCode,
|
|
653
|
-
command: resultData.command,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
catch (e) {
|
|
658
|
-
// If parsing fails, just show regular result
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
// Create completed tool result message for UI
|
|
662
|
-
const uiMsg = {
|
|
663
|
-
role: 'subagent',
|
|
664
|
-
content: `\x1b[38;2;0;186;255m⚇${statusIcon} ${msg.tool_name}\x1b[0m${statusText}`,
|
|
665
|
-
streaming: false,
|
|
666
|
-
toolResult: !isError ? msg.content : undefined,
|
|
667
|
-
terminalResult: terminalResultData,
|
|
668
|
-
toolCall: terminalResultData
|
|
669
|
-
? {
|
|
670
|
-
name: msg.tool_name,
|
|
671
|
-
arguments: terminalResultData,
|
|
672
|
-
}
|
|
673
|
-
: undefined,
|
|
674
|
-
subAgent: {
|
|
675
|
-
agentId: subAgentMessage.agentId,
|
|
676
|
-
agentName: subAgentMessage.agentName,
|
|
677
|
-
isComplete: false,
|
|
678
|
-
},
|
|
679
|
-
subAgentInternal: true,
|
|
680
|
-
};
|
|
681
|
-
// Save to session as 'tool' role for API compatibility
|
|
682
|
-
const sessionMsg = {
|
|
683
|
-
role: 'tool',
|
|
684
|
-
tool_call_id: msg.tool_call_id,
|
|
685
|
-
content: msg.content,
|
|
686
|
-
subAgentInternal: true,
|
|
687
|
-
};
|
|
688
|
-
saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool result:', err));
|
|
689
|
-
// Add completed tool result message
|
|
690
|
-
return [...prev, uiMsg];
|
|
691
|
-
}
|
|
692
|
-
// Check if we already have a message for this agent
|
|
693
|
-
const existingIndex = prev.findIndex(m => m.role === 'subagent' &&
|
|
694
|
-
m.subAgent?.agentId === subAgentMessage.agentId &&
|
|
695
|
-
!m.subAgent?.isComplete &&
|
|
696
|
-
!m.toolCall);
|
|
697
|
-
// Extract content from the sub-agent message
|
|
698
|
-
let content = '';
|
|
699
|
-
if (subAgentMessage.message.type === 'content') {
|
|
700
|
-
content = subAgentMessage.message.content;
|
|
701
|
-
// Update token count for sub-agent content
|
|
702
|
-
subAgentContentAccumulator += content;
|
|
703
|
-
try {
|
|
704
|
-
const tokens = encoder.encode(subAgentContentAccumulator);
|
|
705
|
-
setStreamTokenCount(tokens.length);
|
|
706
|
-
}
|
|
707
|
-
catch (e) {
|
|
708
|
-
// Ignore encoding errors
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else if (subAgentMessage.message.type === 'done') {
|
|
712
|
-
// Mark as complete and reset token counter
|
|
713
|
-
subAgentContentAccumulator = '';
|
|
714
|
-
setStreamTokenCount(0);
|
|
715
|
-
if (existingIndex !== -1) {
|
|
716
|
-
const updated = [...prev];
|
|
717
|
-
const existing = updated[existingIndex];
|
|
718
|
-
if (existing && existing.subAgent) {
|
|
719
|
-
updated[existingIndex] = {
|
|
720
|
-
...existing,
|
|
721
|
-
subAgent: {
|
|
722
|
-
...existing.subAgent,
|
|
723
|
-
isComplete: true,
|
|
724
|
-
},
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
return updated;
|
|
728
|
-
}
|
|
729
|
-
return prev;
|
|
730
|
-
}
|
|
731
|
-
if (existingIndex !== -1) {
|
|
732
|
-
// Update existing message
|
|
733
|
-
const updated = [...prev];
|
|
734
|
-
const existing = updated[existingIndex];
|
|
735
|
-
if (existing) {
|
|
736
|
-
updated[existingIndex] = {
|
|
737
|
-
...existing,
|
|
738
|
-
content: (existing.content || '') + content,
|
|
739
|
-
streaming: true,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
return updated;
|
|
743
|
-
}
|
|
744
|
-
else if (content) {
|
|
745
|
-
// Add new sub-agent message
|
|
746
|
-
return [
|
|
747
|
-
...prev,
|
|
748
|
-
{
|
|
749
|
-
role: 'subagent',
|
|
750
|
-
content,
|
|
751
|
-
streaming: true,
|
|
752
|
-
subAgent: {
|
|
753
|
-
agentId: subAgentMessage.agentId,
|
|
754
|
-
agentName: subAgentMessage.agentName,
|
|
755
|
-
isComplete: false,
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
];
|
|
759
|
-
}
|
|
760
|
-
return prev;
|
|
761
|
-
});
|
|
762
|
-
}, requestToolConfirmation, isToolAutoApproved, yoloMode, addToAlwaysApproved);
|
|
763
|
-
// Check if aborted during tool execution
|
|
764
|
-
if (controller.signal.aborted) {
|
|
765
|
-
// Need to add tool results for all pending tool calls to complete conversation history
|
|
766
|
-
// This is critical for sub-agents and any tools that were being executed
|
|
767
|
-
if (receivedToolCalls && receivedToolCalls.length > 0) {
|
|
768
|
-
for (const toolCall of receivedToolCalls) {
|
|
769
|
-
const abortedResult = {
|
|
770
|
-
role: 'tool',
|
|
771
|
-
tool_call_id: toolCall.id,
|
|
772
|
-
content: 'Error: Tool execution aborted by user',
|
|
773
|
-
};
|
|
774
|
-
conversationMessages.push(abortedResult);
|
|
775
|
-
saveMessage(abortedResult).catch(error => {
|
|
776
|
-
console.error('Failed to save aborted tool result:', error);
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
freeEncoder();
|
|
781
|
-
break;
|
|
782
|
-
}
|
|
783
|
-
// 在工具执行完成后、发送结果到AI前,检查是否需要压缩
|
|
784
|
-
const config = getOpenAiConfig();
|
|
785
|
-
if (config.enableAutoCompress !== false &&
|
|
786
|
-
options.getCurrentContextPercentage &&
|
|
787
|
-
shouldAutoCompress(options.getCurrentContextPercentage())) {
|
|
788
|
-
try {
|
|
789
|
-
// 显示压缩提示消息
|
|
790
|
-
const compressingMessage = {
|
|
791
|
-
role: 'assistant',
|
|
792
|
-
content: '✵ Auto-compressing context before sending tool results...',
|
|
793
|
-
streaming: false,
|
|
794
|
-
};
|
|
795
|
-
setMessages(prev => [...prev, compressingMessage]);
|
|
796
|
-
const compressionResult = await performAutoCompression();
|
|
797
|
-
if (compressionResult && options.clearSavedMessages) {
|
|
798
|
-
// 更新UI和token使用情况
|
|
799
|
-
options.clearSavedMessages();
|
|
800
|
-
setMessages(compressionResult.uiMessages);
|
|
801
|
-
if (options.setRemountKey) {
|
|
802
|
-
options.setRemountKey(prev => prev + 1);
|
|
803
|
-
}
|
|
804
|
-
options.setContextUsage(compressionResult.usage);
|
|
805
|
-
// 更新累计的usage为压缩后的usage
|
|
806
|
-
accumulatedUsage = compressionResult.usage;
|
|
807
|
-
// 压缩后需要重新构建conversationMessages
|
|
808
|
-
conversationMessages = [];
|
|
809
|
-
const session = sessionManager.getCurrentSession();
|
|
810
|
-
if (session && session.messages.length > 0) {
|
|
811
|
-
conversationMessages.push(...session.messages);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
catch (error) {
|
|
816
|
-
console.error('Auto-compression after tool execution failed:', error);
|
|
817
|
-
// 即使压缩失败也继续处理工具结果
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
// Remove only streaming sub-agent content messages (not tool-related messages)
|
|
821
|
-
// Keep sub-agent tool call and tool result messages for display
|
|
822
|
-
setMessages(prev => prev.filter(m => m.role !== 'subagent' ||
|
|
823
|
-
m.toolCall !== undefined ||
|
|
824
|
-
m.toolResult !== undefined ||
|
|
825
|
-
m.subAgentInternal === true));
|
|
826
|
-
// Update existing tool call messages with results
|
|
827
|
-
// Collect all result messages first, then add them in batch
|
|
828
|
-
const resultMessages = [];
|
|
829
|
-
for (const result of toolResults) {
|
|
830
|
-
const toolCall = receivedToolCalls.find(tc => tc.id === result.tool_call_id);
|
|
831
|
-
if (toolCall) {
|
|
832
|
-
// Special handling for sub-agent tools - show completion message
|
|
833
|
-
// Pass the full JSON result to ToolResultPreview for proper parsing
|
|
834
|
-
if (toolCall.function.name.startsWith('subagent-')) {
|
|
835
|
-
const isError = result.content.startsWith('Error:');
|
|
836
|
-
const statusIcon = isError ? '✗' : '✓';
|
|
837
|
-
const statusText = isError ? `\n └─ ${result.content}` : '';
|
|
838
|
-
// Parse sub-agent result to extract usage information
|
|
839
|
-
let usage = undefined;
|
|
840
|
-
if (!isError) {
|
|
841
|
-
try {
|
|
842
|
-
const subAgentResult = JSON.parse(result.content);
|
|
843
|
-
usage = subAgentResult.usage;
|
|
844
|
-
}
|
|
845
|
-
catch (e) {
|
|
846
|
-
// Ignore parsing errors
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
resultMessages.push({
|
|
850
|
-
role: 'assistant',
|
|
851
|
-
content: `${statusIcon} ${toolCall.function.name}${statusText}`,
|
|
852
|
-
streaming: false,
|
|
853
|
-
// Pass the full result.content for ToolResultPreview to parse
|
|
854
|
-
toolResult: !isError ? result.content : undefined,
|
|
855
|
-
subAgentUsage: usage,
|
|
856
|
-
});
|
|
857
|
-
// Save the tool result to conversation history
|
|
858
|
-
conversationMessages.push(result);
|
|
859
|
-
saveMessage(result).catch(error => {
|
|
860
|
-
console.error('Failed to save tool result:', error);
|
|
861
|
-
});
|
|
862
|
-
continue;
|
|
863
|
-
}
|
|
864
|
-
const isError = result.content.startsWith('Error:');
|
|
865
|
-
const statusIcon = isError ? '✗' : '✓';
|
|
866
|
-
const statusText = isError ? `\n └─ ${result.content}` : '';
|
|
867
|
-
// Check if this is an edit tool with diff data
|
|
868
|
-
let editDiffData;
|
|
869
|
-
if ((toolCall.function.name === 'filesystem-edit' ||
|
|
870
|
-
toolCall.function.name === 'filesystem-edit_search') &&
|
|
871
|
-
!isError) {
|
|
872
|
-
try {
|
|
873
|
-
const resultData = JSON.parse(result.content);
|
|
874
|
-
// Handle single file edit
|
|
875
|
-
if (resultData.oldContent && resultData.newContent) {
|
|
876
|
-
editDiffData = {
|
|
877
|
-
oldContent: resultData.oldContent,
|
|
878
|
-
newContent: resultData.newContent,
|
|
879
|
-
filename: JSON.parse(toolCall.function.arguments).filePath,
|
|
880
|
-
completeOldContent: resultData.completeOldContent,
|
|
881
|
-
completeNewContent: resultData.completeNewContent,
|
|
882
|
-
contextStartLine: resultData.contextStartLine,
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
// Handle batch edit
|
|
886
|
-
else if (resultData.results &&
|
|
887
|
-
Array.isArray(resultData.results)) {
|
|
888
|
-
editDiffData = {
|
|
889
|
-
batchResults: resultData.results,
|
|
890
|
-
isBatch: true,
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
catch (e) {
|
|
895
|
-
// If parsing fails, just show regular result
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
// 处理工具执行结果的显示
|
|
899
|
-
// - 耗时工具(两步显示):完成消息追加到静态区,之前的进行中消息已包含参数
|
|
900
|
-
// - 普通工具(单步显示):完成消息需要包含参数和结果,使用 toolDisplay
|
|
901
|
-
// 获取工具参数的格式化信息
|
|
902
|
-
const toolDisplay = formatToolCallMessage(toolCall);
|
|
903
|
-
const isNonTimeConsuming = !isToolNeedTwoStepDisplay(toolCall.function.name);
|
|
904
|
-
resultMessages.push({
|
|
905
|
-
role: 'assistant',
|
|
906
|
-
content: `${statusIcon} ${toolCall.function.name}${statusText}`,
|
|
907
|
-
streaming: false,
|
|
908
|
-
toolCall: editDiffData
|
|
909
|
-
? {
|
|
910
|
-
name: toolCall.function.name,
|
|
911
|
-
arguments: editDiffData,
|
|
912
|
-
}
|
|
913
|
-
: undefined,
|
|
914
|
-
// 为普通工具添加参数显示(耗时工具在进行中状态已经显示过参数)
|
|
915
|
-
toolDisplay: isNonTimeConsuming ? toolDisplay : undefined,
|
|
916
|
-
// Store tool result for preview rendering
|
|
917
|
-
toolResult: !isError ? result.content : undefined,
|
|
918
|
-
// Mark parallel group for ALL tools (time-consuming or not)
|
|
919
|
-
parallelGroup: parallelGroupId,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
// Add tool result to conversation history and save (skip if already saved above)
|
|
923
|
-
if (toolCall && !toolCall.function.name.startsWith('subagent-')) {
|
|
924
|
-
conversationMessages.push(result);
|
|
925
|
-
saveMessage(result).catch(error => {
|
|
926
|
-
console.error('Failed to save tool result:', error);
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
// Add all result messages in batch to avoid intermediate renders
|
|
931
|
-
if (resultMessages.length > 0) {
|
|
932
|
-
setMessages(prev => [...prev, ...resultMessages]);
|
|
933
|
-
}
|
|
934
|
-
// Check if there are pending user messages to insert
|
|
935
|
-
if (options.getPendingMessages && options.clearPendingMessages) {
|
|
936
|
-
const pendingMessages = options.getPendingMessages();
|
|
937
|
-
if (pendingMessages.length > 0) {
|
|
938
|
-
// 检查 token 占用,如果 >= 80% 先执行自动压缩
|
|
939
|
-
const config = getOpenAiConfig();
|
|
940
|
-
if (config.enableAutoCompress !== false &&
|
|
941
|
-
options.getCurrentContextPercentage &&
|
|
942
|
-
shouldAutoCompress(options.getCurrentContextPercentage())) {
|
|
943
|
-
try {
|
|
944
|
-
// 显示压缩提示消息
|
|
945
|
-
const compressingMessage = {
|
|
946
|
-
role: 'assistant',
|
|
947
|
-
content: '✵ Auto-compressing context before processing pending messages...',
|
|
948
|
-
streaming: false,
|
|
949
|
-
};
|
|
950
|
-
setMessages(prev => [...prev, compressingMessage]);
|
|
951
|
-
const compressionResult = await performAutoCompression();
|
|
952
|
-
if (compressionResult && options.clearSavedMessages) {
|
|
953
|
-
// 更新UI和token使用情况
|
|
954
|
-
options.clearSavedMessages();
|
|
955
|
-
setMessages(compressionResult.uiMessages);
|
|
956
|
-
if (options.setRemountKey) {
|
|
957
|
-
options.setRemountKey(prev => prev + 1);
|
|
958
|
-
}
|
|
959
|
-
options.setContextUsage(compressionResult.usage);
|
|
960
|
-
// 更新累计的usage为压缩后的usage
|
|
961
|
-
accumulatedUsage = compressionResult.usage;
|
|
962
|
-
// 压缩后需要重新构建conversationMessages
|
|
963
|
-
conversationMessages = [];
|
|
964
|
-
const session = sessionManager.getCurrentSession();
|
|
965
|
-
if (session && session.messages.length > 0) {
|
|
966
|
-
conversationMessages.push(...session.messages);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
catch (error) {
|
|
971
|
-
console.error('Auto-compression before pending messages failed:', error);
|
|
972
|
-
// 即使压缩失败也继续处理pending消息
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
// Clear pending messages
|
|
976
|
-
options.clearPendingMessages();
|
|
977
|
-
// Combine multiple pending messages into one
|
|
978
|
-
const combinedMessage = pendingMessages
|
|
979
|
-
.map(m => m.text)
|
|
980
|
-
.join('\n\n');
|
|
981
|
-
// Collect all images from pending messages
|
|
982
|
-
const allPendingImages = pendingMessages
|
|
983
|
-
.flatMap(m => m.images || [])
|
|
984
|
-
.map(img => ({
|
|
985
|
-
type: 'image',
|
|
986
|
-
data: img.data,
|
|
987
|
-
mimeType: img.mimeType,
|
|
988
|
-
}));
|
|
989
|
-
// Add user message to UI
|
|
990
|
-
const userMessage = {
|
|
991
|
-
role: 'user',
|
|
992
|
-
content: combinedMessage,
|
|
993
|
-
images: allPendingImages.length > 0 ? allPendingImages : undefined,
|
|
994
|
-
};
|
|
995
|
-
setMessages(prev => [...prev, userMessage]);
|
|
996
|
-
// Add user message to conversation history (using images field for image data)
|
|
997
|
-
conversationMessages.push({
|
|
998
|
-
role: 'user',
|
|
999
|
-
content: combinedMessage,
|
|
1000
|
-
images: allPendingImages.length > 0 ? allPendingImages : undefined,
|
|
1001
|
-
});
|
|
1002
|
-
// Save user message
|
|
1003
|
-
saveMessage({
|
|
1004
|
-
role: 'user',
|
|
1005
|
-
content: combinedMessage,
|
|
1006
|
-
images: allPendingImages.length > 0 ? allPendingImages : undefined,
|
|
1007
|
-
}).catch(error => {
|
|
1008
|
-
console.error('Failed to save pending user message:', error);
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
// Continue loop to get next response
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
// No tool calls - conversation is complete
|
|
1016
|
-
// Display text content if any
|
|
1017
|
-
if (streamedContent.trim()) {
|
|
1018
|
-
finalAssistantMessage = {
|
|
1019
|
-
role: 'assistant',
|
|
1020
|
-
content: streamedContent.trim(),
|
|
1021
|
-
streaming: false,
|
|
1022
|
-
discontinued: controller.signal.aborted,
|
|
1023
|
-
};
|
|
1024
|
-
setMessages(prev => [...prev, finalAssistantMessage]);
|
|
1025
|
-
// Add to conversation history and save
|
|
1026
|
-
const assistantMessage = {
|
|
1027
|
-
role: 'assistant',
|
|
1028
|
-
content: streamedContent.trim(),
|
|
1029
|
-
reasoning: receivedReasoning, // Include reasoning data for caching (Responses API)
|
|
1030
|
-
thinking: receivedThinking, // Include thinking content (Anthropic/OpenAI)
|
|
1031
|
-
};
|
|
1032
|
-
conversationMessages.push(assistantMessage);
|
|
1033
|
-
saveMessage(assistantMessage).catch(error => {
|
|
1034
|
-
console.error('Failed to save assistant message:', error);
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
// Conversation complete - exit the loop
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
// Free encoder
|
|
1041
|
-
freeEncoder();
|
|
1042
|
-
}
|
|
1043
|
-
catch (error) {
|
|
1044
|
-
throw error;
|
|
1045
|
-
}
|
|
1046
|
-
finally {
|
|
1047
|
-
// ✅ 确保总是释放encoder资源,避免资源泄漏
|
|
1048
|
-
freeEncoder();
|
|
1049
|
-
}
|
|
1050
|
-
// Return the accumulated usage data
|
|
1051
|
-
return { usage: accumulatedUsage };
|
|
1052
|
-
}
|