@blackbox_ai/blackbox-cli-core 0.0.9 → 0.8.1
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/README.md +11 -183
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/src/blackbox/blackboxOAuth2.js +17 -1
- package/dist/src/blackbox/blackboxOAuth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +15 -3
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/config/blackboxModels.d.ts +3 -2
- package/dist/src/config/blackboxModels.js +262 -33
- package/dist/src/config/blackboxModels.js.map +1 -1
- package/dist/src/config/config.d.ts +65 -0
- package/dist/src/config/config.js +282 -17
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/models.d.ts +1 -1
- package/dist/src/config/models.js +1 -1
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/multiAgentModels.d.ts +63 -0
- package/dist/src/config/multiAgentModels.js +194 -0
- package/dist/src/config/multiAgentModels.js.map +1 -0
- package/dist/src/core/client.js +8 -2
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +1 -0
- package/dist/src/core/contentGenerator.js +57 -7
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/encryptedClientFactory.d.ts +17 -0
- package/dist/src/core/encryptedClientFactory.js +92 -0
- package/dist/src/core/encryptedClientFactory.js.map +1 -0
- package/dist/src/core/encryptedContentGenerator.d.ts +47 -0
- package/dist/src/core/encryptedContentGenerator.js +445 -0
- package/dist/src/core/encryptedContentGenerator.js.map +1 -0
- package/dist/src/core/encryptedGeminiClient.d.ts +59 -0
- package/dist/src/core/encryptedGeminiClient.js +177 -0
- package/dist/src/core/encryptedGeminiClient.js.map +1 -0
- package/dist/src/core/encryptedGeminiClientBridge.d.ts +107 -0
- package/dist/src/core/encryptedGeminiClientBridge.js +808 -0
- package/dist/src/core/encryptedGeminiClientBridge.js.map +1 -0
- package/dist/src/core/encryptedGeminiClientWrapper.d.ts +129 -0
- package/dist/src/core/encryptedGeminiClientWrapper.js +305 -0
- package/dist/src/core/encryptedGeminiClientWrapper.js.map +1 -0
- package/dist/src/core/encryptedTurn.d.ts +40 -0
- package/dist/src/core/encryptedTurn.js +114 -0
- package/dist/src/core/encryptedTurn.js.map +1 -0
- package/dist/src/core/logger.d.ts +21 -0
- package/dist/src/core/logger.js +110 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/openaiContentGenerator/constants.d.ts +2 -0
- package/dist/src/core/openaiContentGenerator/constants.js +2 -0
- package/dist/src/core/openaiContentGenerator/constants.js.map +1 -1
- package/dist/src/core/openaiContentGenerator/converter.d.ts +16 -1
- package/dist/src/core/openaiContentGenerator/converter.js +135 -4
- package/dist/src/core/openaiContentGenerator/converter.js.map +1 -1
- package/dist/src/core/openaiContentGenerator/pipeline.js +6 -2
- package/dist/src/core/openaiContentGenerator/pipeline.js.map +1 -1
- package/dist/src/core/openaiContentGenerator/provider/default.js +10 -1
- package/dist/src/core/openaiContentGenerator/provider/default.js.map +1 -1
- package/dist/src/core/prompts.d.ts +17 -0
- package/dist/src/core/prompts.js +347 -19
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/tokenLimits.d.ts +1 -0
- package/dist/src/core/tokenLimits.js +37 -2
- package/dist/src/core/tokenLimits.js.map +1 -1
- package/dist/src/core/tokenLimits.test.js +36 -1
- package/dist/src/core/tokenLimits.test.js.map +1 -1
- package/dist/src/encrypt/attestation.d.ts +5 -0
- package/dist/src/encrypt/attestation.js +100 -0
- package/dist/src/encrypt/attestation.js.map +1 -0
- package/dist/src/encrypt/client.d.ts +14 -0
- package/dist/src/encrypt/client.js +132 -0
- package/dist/src/encrypt/client.js.map +1 -0
- package/dist/src/encrypt/config.d.ts +22 -0
- package/dist/src/encrypt/config.js +43 -0
- package/dist/src/encrypt/config.js.map +1 -0
- package/dist/src/encrypt/crypto-utils.d.ts +57 -0
- package/dist/src/encrypt/crypto-utils.js +257 -0
- package/dist/src/encrypt/crypto-utils.js.map +1 -0
- package/dist/src/encrypt/history-manager.d.ts +43 -0
- package/dist/src/encrypt/history-manager.js +164 -0
- package/dist/src/encrypt/history-manager.js.map +1 -0
- package/dist/src/encrypt/minimax-template.d.ts +73 -0
- package/dist/src/encrypt/minimax-template.js +276 -0
- package/dist/src/encrypt/minimax-template.js.map +1 -0
- package/dist/src/encrypt/sessions.d.ts +17 -0
- package/dist/src/encrypt/sessions.js +221 -0
- package/dist/src/encrypt/sessions.js.map +1 -0
- package/dist/src/encrypt/streaming-client.d.ts +29 -0
- package/dist/src/encrypt/streaming-client.js +232 -0
- package/dist/src/encrypt/streaming-client.js.map +1 -0
- package/dist/src/encrypt/tool-formatter.d.ts +36 -0
- package/dist/src/encrypt/tool-formatter.js +353 -0
- package/dist/src/encrypt/tool-formatter.js.map +1 -0
- package/dist/src/encrypt/tool-parser.d.ts +93 -0
- package/dist/src/encrypt/tool-parser.js +567 -0
- package/dist/src/encrypt/tool-parser.js.map +1 -0
- package/dist/src/encrypt/types.d.ts +81 -0
- package/dist/src/encrypt/types.js +2 -0
- package/dist/src/encrypt/types.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +3 -3
- package/dist/src/generated/git-commit.js +3 -3
- package/dist/src/ide/ide-client.js +9 -19
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.js +2 -6
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +7 -0
- package/dist/src/mcp/oauth-token-storage.js +24 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/services/EncryptedChatService.d.ts +80 -0
- package/dist/src/services/EncryptedChatService.js +202 -0
- package/dist/src/services/EncryptedChatService.js.map +1 -0
- package/dist/src/services/StatsHistoryService.d.ts +131 -0
- package/dist/src/services/StatsHistoryService.js +427 -0
- package/dist/src/services/StatsHistoryService.js.map +1 -0
- package/dist/src/services/checkpointApiService.d.ts +101 -0
- package/dist/src/services/checkpointApiService.js +215 -0
- package/dist/src/services/checkpointApiService.js.map +1 -0
- package/dist/src/services/environmentSanitization.d.ts +24 -0
- package/dist/src/services/environmentSanitization.js +152 -0
- package/dist/src/services/environmentSanitization.js.map +1 -0
- package/dist/src/telemetry/blackbox-logger/blackbox-logger.d.ts +2 -6
- package/dist/src/telemetry/blackbox-logger/blackbox-logger.js +29 -135
- package/dist/src/telemetry/blackbox-logger/blackbox-logger.js.map +1 -1
- package/dist/src/telemetry/blackbox-logger/blackbox-logger.test.js +1 -1
- package/dist/src/telemetry/blackbox-logger/blackbox-logger.test.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +8 -0
- package/dist/src/telemetry/uiTelemetry.js +17 -0
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/tools/browser-interactive.d.ts +63 -0
- package/dist/src/tools/browser-interactive.js +394 -0
- package/dist/src/tools/browser-interactive.js.map +1 -0
- package/dist/src/tools/browser_use.d.ts +22 -1
- package/dist/src/tools/browser_use.js +403 -23
- package/dist/src/tools/browser_use.js.map +1 -1
- package/dist/src/tools/data-file-constants.d.ts +17 -0
- package/dist/src/tools/data-file-constants.js +30 -0
- package/dist/src/tools/data-file-constants.js.map +1 -0
- package/dist/src/tools/edit.js +44 -7
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/ls.js +40 -6
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +4 -4
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.d.ts +28 -2
- package/dist/src/tools/mcp-client-manager.js +62 -4
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +5 -3
- package/dist/src/tools/mcp-client.js +39 -11
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +3 -1
- package/dist/src/tools/mcp-tool.js +37 -9
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +14 -4
- package/dist/src/tools/memoryTool.js +98 -39
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/read-data-file.d.ts +31 -0
- package/dist/src/tools/read-data-file.js +469 -0
- package/dist/src/tools/read-data-file.js.map +1 -0
- package/dist/src/tools/read-file.js +64 -5
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +40 -6
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +3 -1
- package/dist/src/tools/shell.js +25 -4
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/skill.d.ts +34 -0
- package/dist/src/tools/skill.js +143 -0
- package/dist/src/tools/skill.js.map +1 -0
- package/dist/src/tools/sql_db.d.ts +101 -0
- package/dist/src/tools/sql_db.js +1033 -0
- package/dist/src/tools/sql_db.js.map +1 -0
- package/dist/src/tools/sql_db_configure.d.ts +18 -0
- package/dist/src/tools/sql_db_configure.js +96 -0
- package/dist/src/tools/sql_db_configure.js.map +1 -0
- package/dist/src/tools/taskCompletion.d.ts +29 -0
- package/dist/src/tools/taskCompletion.js +231 -0
- package/dist/src/tools/taskCompletion.js.map +1 -0
- package/dist/src/tools/tool-error.d.ts +3 -1
- package/dist/src/tools/tool-error.js +3 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +8 -0
- package/dist/src/tools/tool-names.js +8 -0
- package/dist/src/tools/tool-names.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +22 -0
- package/dist/src/tools/tool-registry.js +41 -1
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tools.d.ts +18 -2
- package/dist/src/tools/tools.js +3 -0
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +24 -4
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-search.js +160 -2
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/workspace-error-helper.d.ts +9 -0
- package/dist/src/tools/workspace-error-helper.js +43 -0
- package/dist/src/tools/workspace-error-helper.js.map +1 -0
- package/dist/src/tools/workspace-error-helper.test.js +85 -0
- package/dist/src/tools/workspace-error-helper.test.js.map +1 -0
- package/dist/src/tools/write-file.js +42 -7
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/utils/environmentContext.js +3 -1
- package/dist/src/utils/environmentContext.js.map +1 -1
- package/dist/src/utils/environmentContext.test.js +3 -2
- package/dist/src/utils/environmentContext.test.js.map +1 -1
- package/dist/src/utils/fetch.d.ts +3 -1
- package/dist/src/utils/fetch.js +35 -2
- package/dist/src/utils/fetch.js.map +1 -1
- package/dist/src/utils/fileUtils.js +30 -3
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.d.ts +2 -0
- package/dist/src/utils/filesearch/fileSearch.js +38 -7
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/git-worktree-utils.d.ts +56 -0
- package/dist/src/utils/git-worktree-utils.js +176 -0
- package/dist/src/utils/git-worktree-utils.js.map +1 -0
- package/dist/src/utils/imageCompression.d.ts +34 -0
- package/dist/src/utils/imageCompression.js +170 -0
- package/dist/src/utils/imageCompression.js.map +1 -0
- package/dist/src/utils/messageTruncator.d.ts +51 -0
- package/dist/src/utils/messageTruncator.js +346 -0
- package/dist/src/utils/messageTruncator.js.map +1 -0
- package/dist/src/utils/pathReader.js +26 -6
- package/dist/src/utils/pathReader.js.map +1 -1
- package/dist/src/utils/skill.d.ts +65 -0
- package/dist/src/utils/skill.js +241 -0
- package/dist/src/utils/skill.js.map +1 -0
- package/dist/src/utils/textCleaning.d.ts +51 -0
- package/dist/src/utils/textCleaning.js +327 -0
- package/dist/src/utils/textCleaning.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -2
- package/dist/src/tools/mcp-client-manager.test.js +0 -39
- package/dist/src/tools/mcp-client-manager.test.js.map +0 -1
- package/dist/src/tools/mcp-client.test.d.ts +0 -6
- package/dist/src/tools/mcp-client.test.js +0 -454
- package/dist/src/tools/mcp-client.test.js.map +0 -1
- package/dist/src/tools/mcp-tool.test.d.ts +0 -6
- package/dist/src/tools/mcp-tool.test.js +0 -576
- package/dist/src/tools/mcp-tool.test.js.map +0 -1
- package/dist/src/tools/memoryTool.test.d.ts +0 -6
- package/dist/src/tools/memoryTool.test.js +0 -420
- package/dist/src/tools/memoryTool.test.js.map +0 -1
- package/dist/src/tools/tool-registry.test.d.ts +0 -6
- package/dist/src/tools/tool-registry.test.js +0 -332
- package/dist/src/tools/tool-registry.test.js.map +0 -1
- /package/dist/src/tools/{mcp-client-manager.test.d.ts → workspace-error-helper.test.d.ts} +0 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { EncryptedTurn } from './encryptedTurn.js';
|
|
7
|
+
import { getCoreSystemPrompt } from './prompts.js';
|
|
8
|
+
import { ApprovalMode } from '../config/config.js';
|
|
9
|
+
import { getEnvironmentContext } from '../utils/environmentContext.js';
|
|
10
|
+
import { formatThinkContent as formatThinkContentUtil } from '../utils/textCleaning.js';
|
|
11
|
+
import { MessageTruncator } from '../utils/messageTruncator.js';
|
|
12
|
+
/**
|
|
13
|
+
* EncryptedGeminiClientBridge provides a bridge between the encrypted provider
|
|
14
|
+
* and the existing GeminiClient interface, enabling tool execution through
|
|
15
|
+
* the event-based system that the UI layer expects.
|
|
16
|
+
*/
|
|
17
|
+
export class EncryptedGeminiClientBridge {
|
|
18
|
+
encryptedService;
|
|
19
|
+
config;
|
|
20
|
+
tools = [];
|
|
21
|
+
history = [];
|
|
22
|
+
pendingToolResults = new Map();
|
|
23
|
+
messageTruncator;
|
|
24
|
+
constructor(encryptedService, config) {
|
|
25
|
+
this.encryptedService = encryptedService;
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.messageTruncator = new MessageTruncator(config?.getDebugMode() ?? false);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set the available tools for this client
|
|
31
|
+
*/
|
|
32
|
+
setTools(tools) {
|
|
33
|
+
this.tools = tools || [];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Add content to history
|
|
37
|
+
*/
|
|
38
|
+
addHistory(content) {
|
|
39
|
+
this.history.push(content);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get current history
|
|
43
|
+
*/
|
|
44
|
+
getHistory() {
|
|
45
|
+
return [...this.history];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Send a message and get streaming events that match the regular GeminiClient interface
|
|
49
|
+
* This is the key method that bridges the encrypted provider with the event system
|
|
50
|
+
*/
|
|
51
|
+
async *sendMessageStream(request, signal, prompt_id) {
|
|
52
|
+
try {
|
|
53
|
+
// Convert request to text
|
|
54
|
+
const messageText = this.convertRequestToText(request);
|
|
55
|
+
// Get tools from config directly instead of relying on stored tools
|
|
56
|
+
let currentTools = [];
|
|
57
|
+
if (this.config) {
|
|
58
|
+
try {
|
|
59
|
+
const toolRegistry = this.config.getToolRegistry();
|
|
60
|
+
if (toolRegistry) {
|
|
61
|
+
currentTools = toolRegistry.getFunctionDeclarations();
|
|
62
|
+
// Update our stored tools to match
|
|
63
|
+
this.tools = currentTools;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// Silently handle tool registry access errors
|
|
68
|
+
console.error(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Create tools system prompt if we have tools
|
|
72
|
+
const toolsSystemPrompt = EncryptedTurn.createToolsSystemPrompt(currentTools || [], 'minimax');
|
|
73
|
+
// CRITICAL FIX: Add the current user message to history before converting
|
|
74
|
+
// This ensures the user message is included in the message history
|
|
75
|
+
const currentUserMessage = {
|
|
76
|
+
role: 'user',
|
|
77
|
+
parts: [{ text: messageText }]
|
|
78
|
+
};
|
|
79
|
+
// Create a temporary history that includes the current message
|
|
80
|
+
const historyWithCurrentMessage = [...this.history, currentUserMessage];
|
|
81
|
+
// Convert history to message format (including current user message)
|
|
82
|
+
const messageHistory = await this.convertHistoryToMessageFormat(historyWithCurrentMessage, toolsSystemPrompt);
|
|
83
|
+
// Initialize the encrypted service if needed
|
|
84
|
+
if (!this.encryptedService.isReady()) {
|
|
85
|
+
await this.encryptedService.initialize(toolsSystemPrompt);
|
|
86
|
+
}
|
|
87
|
+
// Send message through encrypted service
|
|
88
|
+
const encryptedStream = await this.encryptedService.sendMessage(messageText, messageHistory);
|
|
89
|
+
// Read the entire response first to detect tool calls
|
|
90
|
+
const reader = encryptedStream.getReader();
|
|
91
|
+
const decoder = new TextDecoder();
|
|
92
|
+
let fullResponse = '';
|
|
93
|
+
// BUFFERING LOGIC START
|
|
94
|
+
let streamBuffer = '';
|
|
95
|
+
let inToolCall = false;
|
|
96
|
+
// Safety margin: larger than the longest possible token + potential function name length
|
|
97
|
+
const SAFETY_MARGIN = 60;
|
|
98
|
+
try {
|
|
99
|
+
while (true) {
|
|
100
|
+
if (signal.aborted) {
|
|
101
|
+
yield { type: 'user_cancelled' };
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const { done, value } = await reader.read();
|
|
105
|
+
if (done)
|
|
106
|
+
break;
|
|
107
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
108
|
+
fullResponse += chunk;
|
|
109
|
+
streamBuffer += chunk;
|
|
110
|
+
// Loop to process the buffer as much as possible
|
|
111
|
+
let processing = true;
|
|
112
|
+
while (processing) {
|
|
113
|
+
processing = false; // Default to stopping unless we find a token
|
|
114
|
+
if (inToolCall) {
|
|
115
|
+
// WE ARE INSIDE A TOOL CALL: Hunt for END tokens only
|
|
116
|
+
const endTokens = [
|
|
117
|
+
'</minimax:tool_call>', // MiniMax format - this is the REAL end
|
|
118
|
+
'<|tool▁calls▁end|>', // DeepSeek format
|
|
119
|
+
'</tool_call>', // Qwen format
|
|
120
|
+
'</think>', // Think end (can appear in tool calls)
|
|
121
|
+
// Bare tool call end tokens
|
|
122
|
+
'</read_file>',
|
|
123
|
+
'</read_data_file>',
|
|
124
|
+
'</glob>',
|
|
125
|
+
'</search_files>',
|
|
126
|
+
'</list_files>',
|
|
127
|
+
'</create_file>',
|
|
128
|
+
'</edit_file>',
|
|
129
|
+
'</execute_command>',
|
|
130
|
+
'</browser_action>',
|
|
131
|
+
'</ask_followup_question>',
|
|
132
|
+
'</attempt_completion>',
|
|
133
|
+
'</new_task>',
|
|
134
|
+
'</retrieve_knowledge>',
|
|
135
|
+
'</read_many_files>'
|
|
136
|
+
];
|
|
137
|
+
let endIdx = -1;
|
|
138
|
+
let tokenLen = 0;
|
|
139
|
+
// Find earliest end token
|
|
140
|
+
for (const token of endTokens) {
|
|
141
|
+
const idx = streamBuffer.indexOf(token);
|
|
142
|
+
if (idx !== -1 && (endIdx === -1 || idx < endIdx)) {
|
|
143
|
+
endIdx = idx;
|
|
144
|
+
tokenLen = token.length;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (endIdx !== -1) {
|
|
148
|
+
// Found end token!
|
|
149
|
+
// Discard everything up to the end of this token (hiding the tool call)
|
|
150
|
+
streamBuffer = streamBuffer.substring(endIdx + tokenLen);
|
|
151
|
+
inToolCall = false;
|
|
152
|
+
processing = true; // Loop again to see if there is text after
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// WE ARE IN TEXT MODE: Hunt for START tokens (or rogue END tokens)
|
|
157
|
+
const startTokens = [
|
|
158
|
+
'<minimax:tool_call>', // MiniMax format
|
|
159
|
+
'<invoke name=', // MiniMax invoke start
|
|
160
|
+
'<|tool▁calls▁begin|>', // DeepSeek format
|
|
161
|
+
'<|tool▁call▁begin|>', // DeepSeek format
|
|
162
|
+
'<tool_call>', // Qwen format
|
|
163
|
+
'<think>', // Think tags
|
|
164
|
+
];
|
|
165
|
+
const sepToken = '<|tool▁sep|>'; // Special case: hides preceding word
|
|
166
|
+
// Also look for standalone end tokens to clean up artifacts
|
|
167
|
+
// CRITICAL FIX: Don't treat </invoke> as orphaned - it's part of valid tool calls
|
|
168
|
+
const endTokens = [
|
|
169
|
+
'</minimax:tool_call>', // MiniMax format
|
|
170
|
+
'<|tool▁call▁end|>', // DeepSeek format
|
|
171
|
+
'<|tool▁calls▁end|>', // DeepSeek format
|
|
172
|
+
'</tool_call>', // Qwen format
|
|
173
|
+
'</think>', // Think end
|
|
174
|
+
];
|
|
175
|
+
let matchIdx = -1;
|
|
176
|
+
let matchLen = 0;
|
|
177
|
+
let matchType = null;
|
|
178
|
+
// Helper to find earliest token
|
|
179
|
+
const check = (idx, len, type) => {
|
|
180
|
+
if (idx !== -1 && (matchIdx === -1 || idx < matchIdx)) {
|
|
181
|
+
matchIdx = idx;
|
|
182
|
+
matchLen = len;
|
|
183
|
+
matchType = type;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
// Check start tokens
|
|
187
|
+
for (const token of startTokens) {
|
|
188
|
+
const idx = streamBuffer.indexOf(token);
|
|
189
|
+
if (token === '<think>') {
|
|
190
|
+
check(idx, token.length, 'think');
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
check(idx, token.length, 'start');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// CRITICAL FIX: Also check for bare tool call start tokens (both opening and self-closing)
|
|
197
|
+
const bareToolNames = [
|
|
198
|
+
'read_file',
|
|
199
|
+
'read_data_file',
|
|
200
|
+
'glob',
|
|
201
|
+
'search_files',
|
|
202
|
+
'list_files',
|
|
203
|
+
'create_file',
|
|
204
|
+
'edit_file',
|
|
205
|
+
'execute_command',
|
|
206
|
+
'browser_action',
|
|
207
|
+
'ask_followup_question',
|
|
208
|
+
'attempt_completion',
|
|
209
|
+
'new_task',
|
|
210
|
+
'retrieve_knowledge',
|
|
211
|
+
'read_many_files'
|
|
212
|
+
];
|
|
213
|
+
for (const toolName of bareToolNames) {
|
|
214
|
+
// Check for opening tag format: <tool_name>
|
|
215
|
+
const openingTag = `<${toolName}>`;
|
|
216
|
+
const openingIdx = streamBuffer.indexOf(openingTag);
|
|
217
|
+
if (openingIdx !== -1) {
|
|
218
|
+
check(openingIdx, openingTag.length, 'start');
|
|
219
|
+
}
|
|
220
|
+
// Check for self-closing tag format: <tool_name ... />
|
|
221
|
+
const selfClosingPattern = new RegExp(`<${toolName}\\s[^>]*\\s*/>`, 'g');
|
|
222
|
+
const selfClosingMatch = selfClosingPattern.exec(streamBuffer);
|
|
223
|
+
if (selfClosingMatch && typeof selfClosingMatch.index === 'number') {
|
|
224
|
+
check(selfClosingMatch.index, selfClosingMatch[0].length, 'start');
|
|
225
|
+
}
|
|
226
|
+
// Check for opening tag with attributes: <tool_name ...>
|
|
227
|
+
const attributePattern = new RegExp(`<${toolName}\\s[^>]*>`, 'g');
|
|
228
|
+
const attributeMatch = attributePattern.exec(streamBuffer);
|
|
229
|
+
if (attributeMatch && typeof attributeMatch.index === 'number') {
|
|
230
|
+
check(attributeMatch.index, attributeMatch[0].length, 'start');
|
|
231
|
+
}
|
|
232
|
+
// Check for pipe-delimited format: <|tool_name|>
|
|
233
|
+
const pipeDelimitedTag = `<|${toolName}|>`;
|
|
234
|
+
const pipeIdx = streamBuffer.indexOf(pipeDelimitedTag);
|
|
235
|
+
if (pipeIdx !== -1) {
|
|
236
|
+
check(pipeIdx, pipeDelimitedTag.length, 'start');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Check separator token
|
|
240
|
+
const idxSep = streamBuffer.indexOf(sepToken);
|
|
241
|
+
check(idxSep, sepToken.length, 'sep');
|
|
242
|
+
// Check end tokens
|
|
243
|
+
for (const token of endTokens) {
|
|
244
|
+
const idx = streamBuffer.indexOf(token);
|
|
245
|
+
check(idx, token.length, 'end');
|
|
246
|
+
}
|
|
247
|
+
if (matchIdx !== -1 && matchType !== null) {
|
|
248
|
+
if (matchType === 'think') {
|
|
249
|
+
// Handle think blocks - find the end
|
|
250
|
+
const thinkEnd = streamBuffer.indexOf('</think>', matchIdx);
|
|
251
|
+
if (thinkEnd !== -1) {
|
|
252
|
+
// Complete think block - yield text before, then show think content
|
|
253
|
+
const textToYield = streamBuffer.substring(0, matchIdx);
|
|
254
|
+
if (textToYield && textToYield.length > 0) {
|
|
255
|
+
yield { type: 'content', value: textToYield };
|
|
256
|
+
}
|
|
257
|
+
// Extract and format think content differently from assistant messages
|
|
258
|
+
const thinkContent = streamBuffer.substring(matchIdx + matchLen, thinkEnd);
|
|
259
|
+
if (thinkContent.trim().length > 0) {
|
|
260
|
+
const formattedThinkContent = this.formatThinkContent(thinkContent.trim());
|
|
261
|
+
// Add a single newline after think content to separate it from following content
|
|
262
|
+
const thinkWithNewline = formattedThinkContent + '\n';
|
|
263
|
+
yield { type: 'content', value: thinkWithNewline };
|
|
264
|
+
}
|
|
265
|
+
// Remove the think block and any trailing whitespace/newlines
|
|
266
|
+
let afterThink = streamBuffer.substring(thinkEnd + '</think>'.length);
|
|
267
|
+
// Clean up any excessive newlines that might follow the think block
|
|
268
|
+
afterThink = afterThink.replace(/^\s*\n+/, '\n');
|
|
269
|
+
streamBuffer = afterThink;
|
|
270
|
+
processing = true;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// Incomplete think block - wait for more data
|
|
274
|
+
if (streamBuffer.length > SAFETY_MARGIN) {
|
|
275
|
+
const yieldLen = matchIdx;
|
|
276
|
+
if (yieldLen > 0) {
|
|
277
|
+
const textToYield = streamBuffer.substring(0, yieldLen);
|
|
278
|
+
if (textToYield.length > 0) {
|
|
279
|
+
yield { type: 'content', value: textToYield };
|
|
280
|
+
}
|
|
281
|
+
streamBuffer = streamBuffer.substring(yieldLen);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
processing = false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (matchType === 'sep') {
|
|
288
|
+
// Special case: "FunctionName<sep>Args"
|
|
289
|
+
// We need to remove the "FunctionName" which is likely at the end of the text before matchIdx
|
|
290
|
+
let nameStart = matchIdx;
|
|
291
|
+
// Backtrack to find the start of the function name
|
|
292
|
+
while (nameStart > 0 && /[a-zA-Z0-9_]/.test(streamBuffer[nameStart - 1])) {
|
|
293
|
+
nameStart--;
|
|
294
|
+
}
|
|
295
|
+
// Yield text BEFORE the function name
|
|
296
|
+
const textToYield = streamBuffer.substring(0, nameStart);
|
|
297
|
+
if (textToYield && textToYield.length > 0) {
|
|
298
|
+
yield { type: 'content', value: textToYield };
|
|
299
|
+
}
|
|
300
|
+
// Remove the name + token from buffer and switch to tool mode
|
|
301
|
+
streamBuffer = streamBuffer.substring(matchIdx + matchLen);
|
|
302
|
+
inToolCall = true;
|
|
303
|
+
processing = true;
|
|
304
|
+
}
|
|
305
|
+
else if (matchType === 'end') {
|
|
306
|
+
// Found a standalone end token (hallucination/orphan).
|
|
307
|
+
// Yield text before it, discard token, stay in text mode.
|
|
308
|
+
const textToYield = streamBuffer.substring(0, matchIdx);
|
|
309
|
+
if (textToYield && textToYield.length > 0) {
|
|
310
|
+
yield { type: 'content', value: textToYield };
|
|
311
|
+
}
|
|
312
|
+
streamBuffer = streamBuffer.substring(matchIdx + matchLen);
|
|
313
|
+
processing = true;
|
|
314
|
+
}
|
|
315
|
+
else if (matchType === 'start') {
|
|
316
|
+
// Normal Start Token
|
|
317
|
+
// Yield everything before the token
|
|
318
|
+
const textToYield = streamBuffer.substring(0, matchIdx);
|
|
319
|
+
if (textToYield && textToYield.length > 0) {
|
|
320
|
+
yield { type: 'content', value: textToYield };
|
|
321
|
+
}
|
|
322
|
+
// Remove token, switch to tool mode
|
|
323
|
+
streamBuffer = streamBuffer.substring(matchIdx + matchLen);
|
|
324
|
+
inToolCall = true;
|
|
325
|
+
processing = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// No tokens found in buffer.
|
|
330
|
+
// Keep the last SAFETY_MARGIN chars in buffer to handle split tokens.
|
|
331
|
+
if (streamBuffer.length > SAFETY_MARGIN) {
|
|
332
|
+
const yieldLen = streamBuffer.length - SAFETY_MARGIN;
|
|
333
|
+
const textToYield = streamBuffer.substring(0, yieldLen);
|
|
334
|
+
if (textToYield && textToYield.length > 0) {
|
|
335
|
+
yield { type: 'content', value: textToYield };
|
|
336
|
+
}
|
|
337
|
+
streamBuffer = streamBuffer.substring(yieldLen);
|
|
338
|
+
}
|
|
339
|
+
// Stop processing loop, wait for more data
|
|
340
|
+
processing = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// STREAM FINISHED: Flush remaining buffer
|
|
346
|
+
// Only flush if we are NOT in a tool call.
|
|
347
|
+
if (!inToolCall && streamBuffer.length > 0) {
|
|
348
|
+
yield { type: 'content', value: streamBuffer };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
reader.releaseLock();
|
|
353
|
+
}
|
|
354
|
+
// Add the user message to history after successful send
|
|
355
|
+
this.addHistory(currentUserMessage);
|
|
356
|
+
// Add the model response to history
|
|
357
|
+
const modelResponse = {
|
|
358
|
+
role: 'model',
|
|
359
|
+
parts: [{ text: fullResponse }]
|
|
360
|
+
};
|
|
361
|
+
this.addHistory(modelResponse);
|
|
362
|
+
// CRITICAL FIX: Process tool calls IMMEDIATELY after streaming completes
|
|
363
|
+
// This ensures tool call events are emitted while the generator is still active
|
|
364
|
+
const encryptedTurn = new EncryptedTurn(this, prompt_id);
|
|
365
|
+
// Check if we have tool calls
|
|
366
|
+
if (currentTools && currentTools.length > 0 && this.containsToolCalls(fullResponse)) {
|
|
367
|
+
// Process the response and emit tool call events
|
|
368
|
+
// IMPORTANT: We must yield ALL events from processEncryptedResponse
|
|
369
|
+
for await (const event of encryptedTurn.processEncryptedResponse(fullResponse, true)) {
|
|
370
|
+
// Yield ALL events including tool_call_request and finished
|
|
371
|
+
// Don't skip any events - the UI needs them all
|
|
372
|
+
if (event.type === 'tool_call_request') {
|
|
373
|
+
yield event;
|
|
374
|
+
}
|
|
375
|
+
else if (event.type === 'finished') {
|
|
376
|
+
// Yield the finished event from processEncryptedResponse
|
|
377
|
+
yield event;
|
|
378
|
+
// Don't yield another finished event below
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// Skip content events since we already streamed them
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// No tool calls, just emit finished event
|
|
386
|
+
yield {
|
|
387
|
+
type: 'finished',
|
|
388
|
+
value: 'STOP'
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error('❌ EncryptedGeminiClientBridge.sendMessageStream failed:', error);
|
|
394
|
+
yield {
|
|
395
|
+
type: 'error',
|
|
396
|
+
value: {
|
|
397
|
+
error: {
|
|
398
|
+
message: error instanceof Error ? error.message : 'Unknown error in encrypted provider',
|
|
399
|
+
status: 500
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Convert PartListUnion request to text string
|
|
407
|
+
*/
|
|
408
|
+
convertRequestToText(request) {
|
|
409
|
+
if (typeof request === 'string') {
|
|
410
|
+
return request;
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(request)) {
|
|
413
|
+
return request
|
|
414
|
+
.map(part => {
|
|
415
|
+
if (typeof part === 'string') {
|
|
416
|
+
return part;
|
|
417
|
+
}
|
|
418
|
+
else if (part && typeof part === 'object' && 'text' in part) {
|
|
419
|
+
return part.text;
|
|
420
|
+
}
|
|
421
|
+
else if (part && typeof part === 'object' && 'functionResponse' in part) {
|
|
422
|
+
// Handle function response parts
|
|
423
|
+
const funcResp = part.functionResponse;
|
|
424
|
+
return `Function ${funcResp?.name} returned: ${JSON.stringify(funcResp?.response)}`;
|
|
425
|
+
}
|
|
426
|
+
return '';
|
|
427
|
+
})
|
|
428
|
+
.join('\n');
|
|
429
|
+
}
|
|
430
|
+
return String(request);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Convert Content[] history to message format expected by EncryptedChatService
|
|
434
|
+
* Now uses TOKEN-BASED truncation like the normal Blackbox provider
|
|
435
|
+
*/
|
|
436
|
+
async convertHistoryToMessageFormat(history, toolsSystemPrompt = '') {
|
|
437
|
+
const messages = history.map(content => {
|
|
438
|
+
// CRITICAL FIX: Safely handle undefined or missing parts
|
|
439
|
+
// Ensure parts is always an array before filtering
|
|
440
|
+
let parts = content.parts;
|
|
441
|
+
// Validate that parts exists and is an array
|
|
442
|
+
if (!parts || !Array.isArray(parts)) {
|
|
443
|
+
console.warn('⚠️ [EncryptedGeminiClientBridge] Content has invalid or missing parts:', content);
|
|
444
|
+
parts = [];
|
|
445
|
+
}
|
|
446
|
+
// Extract text from parts
|
|
447
|
+
const textParts = parts.filter((part) => typeof part === 'object' && part !== null && 'text' in part);
|
|
448
|
+
const text = textParts.map(part => part.text).join('\n');
|
|
449
|
+
// Map roles
|
|
450
|
+
const role = content.role === 'model' ? 'assistant' : content.role;
|
|
451
|
+
return { role, content: text };
|
|
452
|
+
});
|
|
453
|
+
// Get the core system prompt like Gemini does
|
|
454
|
+
let coreSystemPrompt = 'You are a helpful AI assistant.';
|
|
455
|
+
let environmentContext = '';
|
|
456
|
+
if (this.config) {
|
|
457
|
+
const userMemory = this.config.getUserMemory();
|
|
458
|
+
coreSystemPrompt = getCoreSystemPrompt(userMemory, {}, this.config.getModel(), this.config.getApprovalMode() === ApprovalMode.YOLO);
|
|
459
|
+
// CRITICAL FIX: Get environment context like regular Gemini does
|
|
460
|
+
const envParts = await getEnvironmentContext(this.config);
|
|
461
|
+
environmentContext = envParts.map(part => typeof part === 'string' ? part : part.text || '').join('\n');
|
|
462
|
+
}
|
|
463
|
+
// Combine core system prompt with environment context (like regular Gemini)
|
|
464
|
+
const systemPromptWithEnvironment = environmentContext.trim()
|
|
465
|
+
? `${coreSystemPrompt}\n\n${environmentContext}`
|
|
466
|
+
: coreSystemPrompt;
|
|
467
|
+
// Then combine with tools system prompt
|
|
468
|
+
const fullSystemPrompt = toolsSystemPrompt
|
|
469
|
+
? `${systemPromptWithEnvironment}\n\n${toolsSystemPrompt}`
|
|
470
|
+
: systemPromptWithEnvironment;
|
|
471
|
+
// Add or update system message
|
|
472
|
+
if (messages.length > 0 && messages[0].role === 'system') {
|
|
473
|
+
// Replace existing system message with our full system prompt
|
|
474
|
+
messages[0].content = fullSystemPrompt;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// Insert system message at the beginning
|
|
478
|
+
const systemMessage = {
|
|
479
|
+
role: 'system',
|
|
480
|
+
content: fullSystemPrompt
|
|
481
|
+
};
|
|
482
|
+
messages.unshift(systemMessage);
|
|
483
|
+
}
|
|
484
|
+
// CRITICAL FIX: Use TOKEN-BASED truncation like normal Blackbox provider
|
|
485
|
+
// This is more accurate and accounts for actual model limits
|
|
486
|
+
const model = this.config?.getModel() || 'blackbox-pro';
|
|
487
|
+
const maxOutputTokens = 8192; // Standard output limit
|
|
488
|
+
// Apply token-based truncation
|
|
489
|
+
const truncatedMessagesRaw = this.messageTruncator.truncateLongMessages(model, messages, maxOutputTokens, null // system prompt already in messages
|
|
490
|
+
);
|
|
491
|
+
// Convert back to the expected type (ensure role is properly typed)
|
|
492
|
+
const truncatedMessages = truncatedMessagesRaw.map(msg => ({
|
|
493
|
+
role: msg.role,
|
|
494
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
495
|
+
}));
|
|
496
|
+
// Log truncation if it occurred
|
|
497
|
+
if (truncatedMessages.length < messages.length) {
|
|
498
|
+
console.log(`📦 [Bridge-Token] Truncated: ${messages.length} → ${truncatedMessages.length} messages`);
|
|
499
|
+
}
|
|
500
|
+
return truncatedMessages;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Check if text contains tool call markup
|
|
504
|
+
*/
|
|
505
|
+
containsToolCalls(text) {
|
|
506
|
+
// Check for wrapped formats
|
|
507
|
+
const hasWrappedFormats = text.includes('<minimax:tool_call>') ||
|
|
508
|
+
text.includes('<invoke name=') ||
|
|
509
|
+
text.includes('<tool_call>') ||
|
|
510
|
+
text.includes('<|tool▁calls▁begin|>') ||
|
|
511
|
+
text.includes('<|tool▁call▁begin|>');
|
|
512
|
+
if (hasWrappedFormats) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
// Check for bare tool call formats
|
|
516
|
+
const bareToolPatterns = [
|
|
517
|
+
/<read_file[\s>]/,
|
|
518
|
+
/<read_data_file[\s>]/,
|
|
519
|
+
/<glob[\s>]/,
|
|
520
|
+
/<search_files[\s>]/,
|
|
521
|
+
/<list_files[\s>]/,
|
|
522
|
+
/<create_file[\s>]/,
|
|
523
|
+
/<edit_file[\s>]/,
|
|
524
|
+
/<execute_command[\s>]/,
|
|
525
|
+
/<browser_action[\s>]/,
|
|
526
|
+
/<ask_followup_question[\s>]/,
|
|
527
|
+
/<attempt_completion[\s>]/,
|
|
528
|
+
/<new_task[\s>]/,
|
|
529
|
+
/<retrieve_knowledge[\s>]/,
|
|
530
|
+
/<read_many_files[\s>]/
|
|
531
|
+
];
|
|
532
|
+
if (bareToolPatterns.some(pattern => pattern.test(text))) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
// Check for pipe-delimited formats
|
|
536
|
+
const pipeDelimitedPatterns = [
|
|
537
|
+
/<\|read_file\|>/,
|
|
538
|
+
/<\|read_data_file\|>/,
|
|
539
|
+
/<\|glob\|>/,
|
|
540
|
+
/<\|search_files\|>/,
|
|
541
|
+
/<\|list_files\|>/,
|
|
542
|
+
/<\|create_file\|>/,
|
|
543
|
+
/<\|edit_file\|>/,
|
|
544
|
+
/<\|execute_command\|>/,
|
|
545
|
+
/<\|browser_action\|>/,
|
|
546
|
+
/<\|ask_followup_question\|>/,
|
|
547
|
+
/<\|attempt_completion\|>/,
|
|
548
|
+
/<\|new_task\|>/,
|
|
549
|
+
/<\|retrieve_knowledge\|>/,
|
|
550
|
+
/<\|read_many_files\|>/
|
|
551
|
+
];
|
|
552
|
+
return pipeDelimitedPatterns.some(pattern => pattern.test(text));
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Get available tools
|
|
556
|
+
*/
|
|
557
|
+
getTools() {
|
|
558
|
+
return this.tools || [];
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Add tool execution result for processing
|
|
562
|
+
*/
|
|
563
|
+
addToolResult(callId, toolName, result) {
|
|
564
|
+
this.pendingToolResults.set(callId, { toolName, result, callId });
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Send tool results back to encrypted service and continue conversation
|
|
568
|
+
*/
|
|
569
|
+
async *sendToolResultsAndContinue(signal, prompt_id) {
|
|
570
|
+
if (this.pendingToolResults.size === 0) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
// Convert tool results to array format
|
|
575
|
+
const toolResults = Array.from(this.pendingToolResults.values());
|
|
576
|
+
// Clear pending results
|
|
577
|
+
this.pendingToolResults.clear();
|
|
578
|
+
// Create a message indicating tool results are available
|
|
579
|
+
const toolResultsMessage = this.formatToolResultsMessage(toolResults);
|
|
580
|
+
// Convert current history to message format
|
|
581
|
+
const messageHistory = await this.convertHistoryToMessageFormat(this.history);
|
|
582
|
+
// Send tool results back to encrypted service
|
|
583
|
+
const encryptedStream = await this.encryptedService.sendMessageWithToolResults(toolResultsMessage, toolResults, messageHistory);
|
|
584
|
+
// Process the response stream
|
|
585
|
+
const reader = encryptedStream.getReader();
|
|
586
|
+
const decoder = new TextDecoder();
|
|
587
|
+
let fullResponse = '';
|
|
588
|
+
// Buffer for streaming logic to hide tool calls
|
|
589
|
+
let streamBuffer = '';
|
|
590
|
+
let inToolCall = false;
|
|
591
|
+
try {
|
|
592
|
+
while (true) {
|
|
593
|
+
if (signal.aborted) {
|
|
594
|
+
yield { type: 'user_cancelled' };
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const { done, value } = await reader.read();
|
|
598
|
+
if (done)
|
|
599
|
+
break;
|
|
600
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
601
|
+
fullResponse += chunk;
|
|
602
|
+
streamBuffer += chunk;
|
|
603
|
+
// Process buffer to yield clean text
|
|
604
|
+
while (true) {
|
|
605
|
+
if (inToolCall) {
|
|
606
|
+
// Look for end tokens
|
|
607
|
+
const endToken1 = '<|tool▁call▁end|>';
|
|
608
|
+
const endToken2 = '<|tool▁calls▁end|>';
|
|
609
|
+
const idx1 = streamBuffer.indexOf(endToken1);
|
|
610
|
+
const idx2 = streamBuffer.indexOf(endToken2);
|
|
611
|
+
let endIdx = -1;
|
|
612
|
+
let tokenLen = 0;
|
|
613
|
+
if (idx1 !== -1 && (idx2 === -1 || idx1 < idx2)) {
|
|
614
|
+
endIdx = idx1;
|
|
615
|
+
tokenLen = endToken1.length;
|
|
616
|
+
}
|
|
617
|
+
else if (idx2 !== -1) {
|
|
618
|
+
endIdx = idx2;
|
|
619
|
+
tokenLen = endToken2.length;
|
|
620
|
+
}
|
|
621
|
+
if (endIdx !== -1) {
|
|
622
|
+
// Found end token - discard everything up to end of token
|
|
623
|
+
streamBuffer = streamBuffer.substring(endIdx + tokenLen);
|
|
624
|
+
inToolCall = false;
|
|
625
|
+
continue; // Check buffer again in normal mode
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// No end token yet. Keep buffer small but safe.
|
|
629
|
+
const maxTokenLen = Math.max(endToken1.length, endToken2.length);
|
|
630
|
+
if (streamBuffer.length > maxTokenLen) {
|
|
631
|
+
streamBuffer = streamBuffer.substring(streamBuffer.length - maxTokenLen);
|
|
632
|
+
}
|
|
633
|
+
break; // Need more data
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Normal mode - check for start tokens
|
|
638
|
+
const startToken1 = '<|tool▁calls▁begin|>';
|
|
639
|
+
const startToken2 = '<|tool▁call▁begin|>';
|
|
640
|
+
const sepToken = '<|tool▁sep|>';
|
|
641
|
+
const idx1 = streamBuffer.indexOf(startToken1);
|
|
642
|
+
const idx2 = streamBuffer.indexOf(startToken2);
|
|
643
|
+
const idxSep = streamBuffer.indexOf(sepToken);
|
|
644
|
+
// Find the earliest start token
|
|
645
|
+
let startIdx = -1;
|
|
646
|
+
let tokenLen = 0;
|
|
647
|
+
let isSep = false;
|
|
648
|
+
// Helper to update best match
|
|
649
|
+
const updateMatch = (idx, len, sep) => {
|
|
650
|
+
if (idx !== -1 && (startIdx === -1 || idx < startIdx)) {
|
|
651
|
+
startIdx = idx;
|
|
652
|
+
tokenLen = len;
|
|
653
|
+
isSep = sep;
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
updateMatch(idx1, startToken1.length, false);
|
|
657
|
+
updateMatch(idx2, startToken2.length, false);
|
|
658
|
+
updateMatch(idxSep, sepToken.length, true);
|
|
659
|
+
if (startIdx !== -1) {
|
|
660
|
+
// Found start token
|
|
661
|
+
if (isSep) {
|
|
662
|
+
// For separator, we need to remove the preceding tool name
|
|
663
|
+
// The tool name is [a-zA-Z0-9_]+ immediately before startIdx
|
|
664
|
+
let nameStart = startIdx;
|
|
665
|
+
while (nameStart > 0 && /[a-zA-Z0-9_]/.test(streamBuffer[nameStart - 1])) {
|
|
666
|
+
nameStart--;
|
|
667
|
+
}
|
|
668
|
+
// Yield everything up to nameStart
|
|
669
|
+
const textToYield = streamBuffer.substring(0, nameStart);
|
|
670
|
+
if (textToYield) {
|
|
671
|
+
yield {
|
|
672
|
+
type: 'content',
|
|
673
|
+
value: textToYield
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
// Remove up to end of token
|
|
677
|
+
streamBuffer = streamBuffer.substring(startIdx + tokenLen);
|
|
678
|
+
inToolCall = true;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
// Normal start token
|
|
683
|
+
const textToYield = streamBuffer.substring(0, startIdx);
|
|
684
|
+
if (textToYield) {
|
|
685
|
+
yield {
|
|
686
|
+
type: 'content',
|
|
687
|
+
value: textToYield
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
streamBuffer = streamBuffer.substring(startIdx + tokenLen);
|
|
691
|
+
inToolCall = true;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
// No start token found.
|
|
697
|
+
// Keep the last 50 chars to be safe (covers max token length + max tool name)
|
|
698
|
+
const keepLen = 50;
|
|
699
|
+
if (streamBuffer.length > keepLen) {
|
|
700
|
+
const yieldLen = streamBuffer.length - keepLen;
|
|
701
|
+
const textToYield = streamBuffer.substring(0, yieldLen);
|
|
702
|
+
yield {
|
|
703
|
+
type: 'content',
|
|
704
|
+
value: textToYield
|
|
705
|
+
};
|
|
706
|
+
streamBuffer = streamBuffer.substring(yieldLen);
|
|
707
|
+
}
|
|
708
|
+
break; // Need more data
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// Flush remaining buffer if not in tool call
|
|
714
|
+
if (!inToolCall && streamBuffer.length > 0) {
|
|
715
|
+
yield {
|
|
716
|
+
type: 'content',
|
|
717
|
+
value: streamBuffer
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
reader.releaseLock();
|
|
723
|
+
}
|
|
724
|
+
// CRITICAL FIX: Process tool calls IMMEDIATELY after streaming completes
|
|
725
|
+
const encryptedTurn = new EncryptedTurn(this, prompt_id);
|
|
726
|
+
if (this.tools && this.tools.length > 0 && this.containsToolCalls(fullResponse)) {
|
|
727
|
+
// Process the response and emit any additional tool call events
|
|
728
|
+
for await (const event of encryptedTurn.processEncryptedResponse(fullResponse, true)) {
|
|
729
|
+
if (event.type === 'tool_call_request') {
|
|
730
|
+
yield event;
|
|
731
|
+
}
|
|
732
|
+
else if (event.type === 'finished') {
|
|
733
|
+
// Yield the finished event from processEncryptedResponse
|
|
734
|
+
yield event;
|
|
735
|
+
// Don't yield another finished event below
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// Skip content events since we already streamed them
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
// No additional tool calls, just emit finished event
|
|
743
|
+
yield {
|
|
744
|
+
type: 'finished',
|
|
745
|
+
value: 'STOP'
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
console.error('❌ EncryptedGeminiClientBridge.sendToolResultsAndContinue failed:', error);
|
|
751
|
+
yield {
|
|
752
|
+
type: 'error',
|
|
753
|
+
value: {
|
|
754
|
+
error: {
|
|
755
|
+
message: error instanceof Error ? error.message : 'Unknown error in tool results processing',
|
|
756
|
+
status: 500
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Format tool results into a readable message
|
|
764
|
+
*/
|
|
765
|
+
formatToolResultsMessage(toolResults) {
|
|
766
|
+
const resultSummaries = toolResults.map(({ toolName, result }) => {
|
|
767
|
+
const resultText = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
768
|
+
return `${toolName}: ${resultText.substring(0, 500)}${resultText.length > 500 ? '...' : ''}`;
|
|
769
|
+
});
|
|
770
|
+
return `Tool execution completed. Results: ${resultSummaries.join('; ')}. Please continue with the user's original request.`;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Check if there are pending tool results
|
|
774
|
+
*/
|
|
775
|
+
hasPendingToolResults() {
|
|
776
|
+
return this.pendingToolResults.size > 0;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Clear pending tool results
|
|
780
|
+
*/
|
|
781
|
+
clearPendingToolResults() {
|
|
782
|
+
this.pendingToolResults.clear();
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Reset all chat history and state
|
|
786
|
+
*/
|
|
787
|
+
resetHistory() {
|
|
788
|
+
this.history = [];
|
|
789
|
+
this.pendingToolResults.clear();
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Check if client is ready
|
|
793
|
+
*/
|
|
794
|
+
isReady() {
|
|
795
|
+
return this.encryptedService.isReady();
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Format think content with special styling to distinguish from assistant messages
|
|
799
|
+
*/
|
|
800
|
+
formatThinkContent(thinkContent) {
|
|
801
|
+
return formatThinkContentUtil(thinkContent, {
|
|
802
|
+
debug: false,
|
|
803
|
+
maxConsecutiveNewlines: 2,
|
|
804
|
+
trimEnds: true
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
//# sourceMappingURL=encryptedGeminiClientBridge.js.map
|