@aws/lsp-codewhisperer 0.0.65 → 0.0.67
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/CHANGELOG.md +56 -0
- package/README.md +5 -0
- package/out/language-server/agenticChat/agenticChatController.d.ts +9 -2
- package/out/language-server/agenticChat/agenticChatController.js +512 -136
- package/out/language-server/agenticChat/agenticChatController.js.map +1 -1
- package/out/language-server/agenticChat/constants/constants.d.ts +17 -0
- package/out/language-server/agenticChat/constants/constants.js +79 -1
- package/out/language-server/agenticChat/constants/constants.js.map +1 -1
- package/out/language-server/agenticChat/constants/toolConstants.d.ts +24 -0
- package/out/language-server/agenticChat/constants/toolConstants.js +35 -0
- package/out/language-server/agenticChat/constants/toolConstants.js.map +1 -0
- package/out/language-server/agenticChat/context/additionalContextProvider.d.ts +9 -1
- package/out/language-server/agenticChat/context/additionalContextProvider.js +92 -34
- package/out/language-server/agenticChat/context/additionalContextProvider.js.map +1 -1
- package/out/language-server/agenticChat/context/agenticChatTriggerContext.d.ts +11 -2
- package/out/language-server/agenticChat/context/agenticChatTriggerContext.js +36 -2
- package/out/language-server/agenticChat/context/agenticChatTriggerContext.js.map +1 -1
- package/out/language-server/agenticChat/context/contextCommandsProvider.js +1 -1
- package/out/language-server/agenticChat/context/contextCommandsProvider.js.map +1 -1
- package/out/language-server/agenticChat/context/contextUtils.d.ts +16 -0
- package/out/language-server/agenticChat/context/contextUtils.js +29 -0
- package/out/language-server/agenticChat/context/contextUtils.js.map +1 -1
- package/out/language-server/agenticChat/qAgenticChatServer.d.ts +2 -0
- package/out/language-server/agenticChat/qAgenticChatServer.js +18 -2
- package/out/language-server/agenticChat/qAgenticChatServer.js.map +1 -1
- package/out/language-server/agenticChat/tools/chatDb/chatDb.d.ts +11 -3
- package/out/language-server/agenticChat/tools/chatDb/chatDb.js +115 -31
- package/out/language-server/agenticChat/tools/chatDb/chatDb.js.map +1 -1
- package/out/language-server/agenticChat/tools/chatDb/util.d.ts +15 -1
- package/out/language-server/agenticChat/tools/chatDb/util.js +17 -0
- package/out/language-server/agenticChat/tools/chatDb/util.js.map +1 -1
- package/out/language-server/agenticChat/tools/executeBash.d.ts +0 -11
- package/out/language-server/agenticChat/tools/executeBash.js +16 -35
- package/out/language-server/agenticChat/tools/executeBash.js.map +1 -1
- package/out/language-server/agenticChat/tools/grepSearch.js +3 -1
- package/out/language-server/agenticChat/tools/grepSearch.js.map +1 -1
- package/out/language-server/agenticChat/tools/mcp/mcpEventHandler.js +208 -170
- package/out/language-server/agenticChat/tools/mcp/mcpEventHandler.js.map +1 -1
- package/out/language-server/agenticChat/tools/mcp/mcpManager.d.ts +12 -18
- package/out/language-server/agenticChat/tools/mcp/mcpManager.js +353 -216
- package/out/language-server/agenticChat/tools/mcp/mcpManager.js.map +1 -1
- package/out/language-server/agenticChat/tools/mcp/mcpTypes.d.ts +29 -0
- package/out/language-server/agenticChat/tools/mcp/mcpTypes.js +60 -1
- package/out/language-server/agenticChat/tools/mcp/mcpTypes.js.map +1 -1
- package/out/language-server/agenticChat/tools/mcp/mcpUtils.d.ts +27 -4
- package/out/language-server/agenticChat/tools/mcp/mcpUtils.js +464 -2
- package/out/language-server/agenticChat/tools/mcp/mcpUtils.js.map +1 -1
- package/out/language-server/agenticChat/tools/toolServer.js +8 -10
- package/out/language-server/agenticChat/tools/toolServer.js.map +1 -1
- package/out/language-server/agenticChat/tools/toolShared.js +6 -2
- package/out/language-server/agenticChat/tools/toolShared.js.map +1 -1
- package/out/language-server/chat/chatController.d.ts +1 -1
- package/out/language-server/chat/chatController.js.map +1 -1
- package/out/language-server/chat/chatSessionService.d.ts +1 -0
- package/out/language-server/chat/chatSessionService.js +5 -2
- package/out/language-server/chat/chatSessionService.js.map +1 -1
- package/out/language-server/chat/contexts/triggerContext.js +11 -2
- package/out/language-server/chat/contexts/triggerContext.js.map +1 -1
- package/out/language-server/chat/quickActions.d.ts +7 -1
- package/out/language-server/chat/quickActions.js +7 -1
- package/out/language-server/chat/quickActions.js.map +1 -1
- package/out/language-server/chat/telemetry/chatTelemetryController.d.ts +1 -0
- package/out/language-server/chat/telemetry/chatTelemetryController.js +9 -0
- package/out/language-server/chat/telemetry/chatTelemetryController.js.map +1 -1
- package/out/language-server/configuration/qConfigurationServer.d.ts +2 -0
- package/out/language-server/configuration/qConfigurationServer.js.map +1 -1
- package/out/language-server/inline-completion/auto-trigger/autoTrigger.d.ts +3 -0
- package/out/language-server/inline-completion/auto-trigger/autoTrigger.js +105 -1
- package/out/language-server/inline-completion/auto-trigger/autoTrigger.js.map +1 -1
- package/out/language-server/inline-completion/auto-trigger/editPredictionAutoTrigger.js +6 -1
- package/out/language-server/inline-completion/auto-trigger/editPredictionAutoTrigger.js.map +1 -1
- package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.d.ts +1 -0
- package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.js +1 -0
- package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.js.map +1 -1
- package/out/language-server/inline-completion/codeWhispererServer.js +53 -52
- package/out/language-server/inline-completion/codeWhispererServer.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.d.ts +4 -1
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js +36 -2
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyEventBundler.d.ts +11 -5
- package/out/language-server/workspaceContext/dependency/dependencyEventBundler.js +37 -10
- package/out/language-server/workspaceContext/dependency/dependencyEventBundler.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.d.ts +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js +8 -3
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js +3 -4
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.d.ts +4 -3
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js +5 -9
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js.map +1 -1
- package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js +3 -4
- package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js.map +1 -1
- package/out/language-server/workspaceContext/workspaceContextServer.js +12 -8
- package/out/language-server/workspaceContext/workspaceContextServer.js.map +1 -1
- package/out/shared/activeUserTracker.d.ts +52 -0
- package/out/shared/activeUserTracker.js +164 -0
- package/out/shared/activeUserTracker.js.map +1 -0
- package/out/shared/amazonQServiceManager/configurationUtils.d.ts +4 -0
- package/out/shared/amazonQServiceManager/configurationUtils.js +6 -0
- package/out/shared/amazonQServiceManager/configurationUtils.js.map +1 -1
- package/out/shared/codeWhispererService.js +3 -1
- package/out/shared/codeWhispererService.js.map +1 -1
- package/out/shared/streamingClientService.js +12 -1
- package/out/shared/streamingClientService.js.map +1 -1
- package/out/shared/telemetry/telemetryService.js +1 -2
- package/out/shared/telemetry/telemetryService.js.map +1 -1
- package/out/shared/telemetry/types.d.ts +7 -1
- package/out/shared/telemetry/types.js +1 -0
- package/out/shared/telemetry/types.js.map +1 -1
- package/out/shared/utils.d.ts +1 -7
- package/out/shared/utils.js +39 -36
- package/out/shared/utils.js.map +1 -1
- package/package.json +7 -6
|
@@ -9,6 +9,7 @@ const crypto = require("crypto");
|
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const os = require("os");
|
|
11
11
|
const codewhisperer_streaming_1 = require("@amzn/codewhisperer-streaming");
|
|
12
|
+
const toolConstants_1 = require("./constants/toolConstants");
|
|
12
13
|
const protocol_1 = require("@aws/language-server-runtimes/protocol");
|
|
13
14
|
const protocol_2 = require("@aws/language-server-runtimes/protocol");
|
|
14
15
|
const server_interface_1 = require("@aws/language-server-runtimes/server-interface");
|
|
@@ -62,6 +63,8 @@ const modelSelection_1 = require("./constants/modelSelection");
|
|
|
62
63
|
const imageVerification_1 = require("../../shared/imageVerification");
|
|
63
64
|
const path_1 = require("@aws/lsp-core/out/util/path");
|
|
64
65
|
const agenticChatControllerHelper_1 = require("./utils/agenticChatControllerHelper");
|
|
66
|
+
const activeUserTracker_1 = require("../../shared/activeUserTracker");
|
|
67
|
+
const qAgenticChatServer_1 = require("./qAgenticChatServer");
|
|
65
68
|
class AgenticChatController {
|
|
66
69
|
#features;
|
|
67
70
|
#chatSessionManagementService;
|
|
@@ -81,6 +84,7 @@ class AgenticChatController {
|
|
|
81
84
|
#mcpEventHandler;
|
|
82
85
|
#paidTierMode;
|
|
83
86
|
#origin;
|
|
87
|
+
#activeUserTracker;
|
|
84
88
|
// latency metrics
|
|
85
89
|
#llmRequestStartTime = 0;
|
|
86
90
|
#toolCallLatencies = [];
|
|
@@ -100,7 +104,9 @@ class AgenticChatController {
|
|
|
100
104
|
#getMessageIdForToolUse(toolType, toolUse) {
|
|
101
105
|
const toolUseId = toolUse.toolUseId;
|
|
102
106
|
// Return plain toolUseId for executeBash, add "_permission" suffix for all other tools
|
|
103
|
-
return toolUse.name ===
|
|
107
|
+
return toolUse.name === toolConstants_1.EXECUTE_BASH || toolType === toolConstants_1.EXECUTE_BASH
|
|
108
|
+
? toolUseId
|
|
109
|
+
: `${toolUseId}${toolConstants_1.SUFFIX_PERMISSION}`;
|
|
104
110
|
}
|
|
105
111
|
/**
|
|
106
112
|
* Logs system information that can be helpful for debugging customer issues
|
|
@@ -119,6 +125,14 @@ class AgenticChatController {
|
|
|
119
125
|
};
|
|
120
126
|
this.#features.logging.info(`System Information: ${JSON.stringify(systemInfo)}`);
|
|
121
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Determines the appropriate message ID for a compaction confirmation
|
|
130
|
+
* @param messageId The original messageId
|
|
131
|
+
* @returns The message ID to use
|
|
132
|
+
*/
|
|
133
|
+
#getMessageIdForCompact(messageId) {
|
|
134
|
+
return `${messageId}_compact`;
|
|
135
|
+
}
|
|
122
136
|
constructor(chatSessionManagementService, features, telemetryService, serviceManager) {
|
|
123
137
|
this.#features = features;
|
|
124
138
|
this.#chatSessionManagementService = chatSessionManagementService;
|
|
@@ -136,6 +150,7 @@ class AgenticChatController {
|
|
|
136
150
|
this.#contextCommandsProvider = new contextCommandsProvider_1.ContextCommandsProvider(this.#features.logging, this.#features.chat, this.#features.workspace, this.#features.lsp);
|
|
137
151
|
this.#mcpEventHandler = new mcpEventHandler_1.McpEventHandler(features, telemetryService);
|
|
138
152
|
this.#origin = (0, utils_2.getOriginFromClientInfo)(this.#features.lsp.getClientInitializeParams()?.clientInfo?.name);
|
|
153
|
+
this.#activeUserTracker = activeUserTracker_1.ActiveUserTracker.getInstance(this.#features);
|
|
139
154
|
}
|
|
140
155
|
async onExecuteCommand(params, _token) {
|
|
141
156
|
this.#log(`onExecuteCommand: ${params.command}`);
|
|
@@ -152,16 +167,16 @@ class AgenticChatController {
|
|
|
152
167
|
async onButtonClick(params) {
|
|
153
168
|
this.#log(`onButtonClick event with params: ${JSON.stringify(params)}`);
|
|
154
169
|
const session = this.#chatSessionManagementService.getSession(params.tabId);
|
|
155
|
-
if (params.buttonId ===
|
|
156
|
-
params.buttonId ===
|
|
157
|
-
params.buttonId ===
|
|
158
|
-
params.buttonId ===
|
|
170
|
+
if (params.buttonId === toolConstants_1.BUTTON_RUN_SHELL_COMMAND ||
|
|
171
|
+
params.buttonId === toolConstants_1.BUTTON_REJECT_SHELL_COMMAND ||
|
|
172
|
+
params.buttonId === toolConstants_1.BUTTON_REJECT_MCP_TOOL ||
|
|
173
|
+
params.buttonId === toolConstants_1.BUTTON_ALLOW_TOOLS) {
|
|
159
174
|
if (!session.data) {
|
|
160
175
|
return { success: false, failureReason: `could not find chat session for tab: ${params.tabId} ` };
|
|
161
176
|
}
|
|
162
177
|
// For 'allow-tools', remove suffix as permission card needs to be seperate from file list card
|
|
163
|
-
const messageId = params.buttonId ===
|
|
164
|
-
? params.messageId.replace(
|
|
178
|
+
const messageId = params.buttonId === toolConstants_1.BUTTON_ALLOW_TOOLS && params.messageId.endsWith(toolConstants_1.SUFFIX_PERMISSION)
|
|
179
|
+
? params.messageId.replace(toolConstants_1.SUFFIX_PERMISSION, '')
|
|
165
180
|
: params.messageId;
|
|
166
181
|
const handler = session.data.getDeferredToolExecution(messageId);
|
|
167
182
|
if (!handler?.reject || !handler.resolve) {
|
|
@@ -170,7 +185,7 @@ class AgenticChatController {
|
|
|
170
185
|
failureReason: `could not find deferred tool execution for message: ${messageId} `,
|
|
171
186
|
};
|
|
172
187
|
}
|
|
173
|
-
params.buttonId ===
|
|
188
|
+
params.buttonId === toolConstants_1.BUTTON_REJECT_SHELL_COMMAND || params.buttonId === toolConstants_1.BUTTON_REJECT_MCP_TOOL
|
|
174
189
|
? (() => {
|
|
175
190
|
handler.reject(new toolShared_1.ToolApprovalException('Command was rejected.', true));
|
|
176
191
|
this.#stoppedToolUses.add(messageId);
|
|
@@ -180,7 +195,7 @@ class AgenticChatController {
|
|
|
180
195
|
success: true,
|
|
181
196
|
};
|
|
182
197
|
}
|
|
183
|
-
else if (params.buttonId ===
|
|
198
|
+
else if (params.buttonId === toolConstants_1.BUTTON_UNDO_CHANGES) {
|
|
184
199
|
const toolUseId = params.messageId;
|
|
185
200
|
try {
|
|
186
201
|
await this.#undoFileChange(toolUseId, session.data);
|
|
@@ -194,23 +209,23 @@ class AgenticChatController {
|
|
|
194
209
|
success: true,
|
|
195
210
|
};
|
|
196
211
|
}
|
|
197
|
-
else if (params.buttonId ===
|
|
198
|
-
const toolUseId = params.messageId.replace(
|
|
212
|
+
else if (params.buttonId === toolConstants_1.BUTTON_UNDO_ALL_CHANGES) {
|
|
213
|
+
const toolUseId = params.messageId.replace(toolConstants_1.SUFFIX_UNDOALL, '');
|
|
199
214
|
await this.#undoAllFileChanges(params.tabId, toolUseId, session.data);
|
|
200
215
|
return {
|
|
201
216
|
success: true,
|
|
202
217
|
};
|
|
203
218
|
}
|
|
204
|
-
else if (params.buttonId ===
|
|
219
|
+
else if (params.buttonId === toolConstants_1.BUTTON_STOP_SHELL_COMMAND) {
|
|
205
220
|
this.#stoppedToolUses.add(params.messageId);
|
|
206
221
|
await this.#renderStoppedShellCommand(params.tabId, params.messageId);
|
|
207
222
|
return { success: true };
|
|
208
223
|
}
|
|
209
|
-
else if (params.buttonId ===
|
|
224
|
+
else if (params.buttonId === toolConstants_1.BUTTON_PAIDTIER_UPGRADE_Q_LEARNMORE) {
|
|
210
225
|
(0, paidTier_1.onPaidTierLearnMore)(this.#features.lsp, this.#features.logging);
|
|
211
226
|
return { success: true };
|
|
212
227
|
}
|
|
213
|
-
else if (params.buttonId ===
|
|
228
|
+
else if (params.buttonId === toolConstants_1.BUTTON_PAIDTIER_UPGRADE_Q) {
|
|
214
229
|
await this.onManageSubscription(params.tabId);
|
|
215
230
|
return { success: true };
|
|
216
231
|
}
|
|
@@ -242,7 +257,7 @@ class AgenticChatController {
|
|
|
242
257
|
return;
|
|
243
258
|
}
|
|
244
259
|
const fileList = cachedToolUse.chatResult?.header?.fileList;
|
|
245
|
-
const button = cachedToolUse.chatResult?.header?.buttons?.filter(button => button.id !==
|
|
260
|
+
const button = cachedToolUse.chatResult?.header?.buttons?.filter(button => button.id !== toolConstants_1.BUTTON_UNDO_CHANGES);
|
|
246
261
|
const updatedHeader = {
|
|
247
262
|
...cachedToolUse.chatResult?.header,
|
|
248
263
|
buttons: button,
|
|
@@ -290,7 +305,7 @@ class AgenticChatController {
|
|
|
290
305
|
return;
|
|
291
306
|
}
|
|
292
307
|
for (const messageId of [...toUndo].reverse()) {
|
|
293
|
-
await this.onButtonClick({ buttonId:
|
|
308
|
+
await this.onButtonClick({ buttonId: toolConstants_1.BUTTON_UNDO_CHANGES, messageId, tabId });
|
|
294
309
|
}
|
|
295
310
|
}
|
|
296
311
|
async onOpenFileDialog(params, token) {
|
|
@@ -394,6 +409,7 @@ class AgenticChatController {
|
|
|
394
409
|
this.#contextCommandsProvider?.dispose();
|
|
395
410
|
this.#userWrittenCodeTracker?.dispose();
|
|
396
411
|
this.#mcpEventHandler.dispose();
|
|
412
|
+
this.#activeUserTracker.dispose();
|
|
397
413
|
clearInterval(this.#abTestingFetchingTimeout);
|
|
398
414
|
}
|
|
399
415
|
async onListConversations(params) {
|
|
@@ -457,6 +473,8 @@ class AgenticChatController {
|
|
|
457
473
|
if (!success) {
|
|
458
474
|
return new server_interface_1.ResponseError(protocol_2.ErrorCodes.InternalError, sessionResult.error);
|
|
459
475
|
}
|
|
476
|
+
const compactIds = session.getAllDeferredCompactMessageIds();
|
|
477
|
+
await this.#invalidateCompactCommand(params.tabId, compactIds);
|
|
460
478
|
session.rejectAllDeferredToolExecutions(new toolShared_1.ToolApprovalException('Command ignored: new prompt', false));
|
|
461
479
|
await this.#invalidateAllShellCommands(params.tabId, session);
|
|
462
480
|
const metric = new metric_1.Metric({
|
|
@@ -464,6 +482,10 @@ class AgenticChatController {
|
|
|
464
482
|
experimentName: this.#abTestingAllocation?.experimentName,
|
|
465
483
|
userVariation: this.#abTestingAllocation?.userVariation,
|
|
466
484
|
});
|
|
485
|
+
const isNewActiveUser = this.#activeUserTracker.isNewActiveUser();
|
|
486
|
+
if (isNewActiveUser) {
|
|
487
|
+
this.#telemetryController.emitActiveUser();
|
|
488
|
+
}
|
|
467
489
|
try {
|
|
468
490
|
const triggerContext = await this.#getTriggerContext(params, metric);
|
|
469
491
|
if (triggerContext.programmingLanguage?.languageName) {
|
|
@@ -482,6 +504,8 @@ class AgenticChatController {
|
|
|
482
504
|
this.#log('cancellation requested');
|
|
483
505
|
// Abort all operations immediately
|
|
484
506
|
session.abortRequest();
|
|
507
|
+
const compactIds = session.getAllDeferredCompactMessageIds();
|
|
508
|
+
await this.#invalidateCompactCommand(params.tabId, compactIds);
|
|
485
509
|
void this.#invalidateAllShellCommands(params.tabId, session);
|
|
486
510
|
session.rejectAllDeferredToolExecutions(new lsp_core_1.CancellationError('user'));
|
|
487
511
|
// Then update UI to inform the user
|
|
@@ -517,21 +541,31 @@ class AgenticChatController {
|
|
|
517
541
|
const contextItems = [...additionalContext, ...activeFile];
|
|
518
542
|
triggerContext.documentReference = this.#additionalContextProvider.getFileListFromContext(contextItems);
|
|
519
543
|
const customContext = await this.#additionalContextProvider.getImageBlocksFromContext(params.context, params.tabId);
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
544
|
+
let finalResult;
|
|
545
|
+
if (params.prompt.command === quickActions_1.QuickAction.Compact) {
|
|
546
|
+
// Get the compaction request input
|
|
547
|
+
const compactionRequestInput = this.#getCompactionRequestInput(session);
|
|
548
|
+
// Generate a unique ID for this prompt
|
|
549
|
+
const promptId = crypto.randomUUID();
|
|
550
|
+
session.setCurrentPromptId(promptId);
|
|
551
|
+
// Start the compaction call
|
|
552
|
+
finalResult = await this.#runCompaction(compactionRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
// Get the initial request input
|
|
556
|
+
const initialRequestInput = await this.#prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream, customContext);
|
|
557
|
+
// Generate a unique ID for this prompt
|
|
558
|
+
const promptId = crypto.randomUUID();
|
|
559
|
+
session.setCurrentPromptId(promptId);
|
|
560
|
+
// Start the agent loop
|
|
561
|
+
finalResult = await this.#runAgentLoop(initialRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference, additionalContext);
|
|
562
|
+
}
|
|
563
|
+
// Result Handling - This happens only once
|
|
564
|
+
return await this.#handleFinalResult(finalResult, session, params.tabId, metric, triggerContext, isNewConversation, chatResultStream);
|
|
531
565
|
}
|
|
532
566
|
catch (err) {
|
|
533
567
|
// HACK: the chat-client needs to have a partial event with the associated messageId sent before it can accept the final result.
|
|
534
|
-
// Without this, the `
|
|
568
|
+
// Without this, the `working` indicator never goes away.
|
|
535
569
|
// Note: buttons being explicitly empty is required for this hack to work.
|
|
536
570
|
const errorMessageId = `error-message-id-${(0, uuid_1.v4)()}`;
|
|
537
571
|
await this.#sendProgressToClient({
|
|
@@ -559,21 +593,145 @@ class AgenticChatController {
|
|
|
559
593
|
/**
|
|
560
594
|
* Prepares the initial request input for the chat prompt
|
|
561
595
|
*/
|
|
562
|
-
async #prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream,
|
|
596
|
+
async #prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream, images) {
|
|
563
597
|
this.#debug('Preparing request input');
|
|
564
598
|
// Get profileArn from the service manager if available
|
|
565
599
|
const profileArn = this.#serviceManager?.getActiveProfileArn();
|
|
566
|
-
const requestInput = await this.#triggerContext.getChatParamsFromTrigger(params, triggerContext, codewhisperer_streaming_1.ChatTriggerType.MANUAL, this.#customizationArn, chatResultStream, profileArn,
|
|
600
|
+
const requestInput = await this.#triggerContext.getChatParamsFromTrigger(params, triggerContext, codewhisperer_streaming_1.ChatTriggerType.MANUAL, this.#customizationArn, chatResultStream, profileArn, this.#getTools(session), additionalContext, session.modelId, this.#origin, images);
|
|
601
|
+
return requestInput;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Prepares the initial request input for the chat prompt
|
|
605
|
+
*/
|
|
606
|
+
#getCompactionRequestInput(session) {
|
|
607
|
+
this.#debug('Preparing compaction request input');
|
|
608
|
+
// Get profileArn from the service manager if available
|
|
609
|
+
const profileArn = this.#serviceManager?.getActiveProfileArn();
|
|
610
|
+
const requestInput = this.#triggerContext.getCompactionChatCommandInput(profileArn, this.#getTools(session), session.modelId, this.#origin);
|
|
567
611
|
return requestInput;
|
|
568
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Runs the compaction, making requests and processing tool uses until completion
|
|
615
|
+
*/
|
|
616
|
+
#shouldCompact(currentRequestCount) {
|
|
617
|
+
// 80% of 570K limit
|
|
618
|
+
if ((0, qAgenticChatServer_1.enabledCompaction)(this.#features.lsp.getClientInitializeParams()) && currentRequestCount > 456_000) {
|
|
619
|
+
this.#debug(`Current request total character count is: ${currentRequestCount}, prompting user to compact`);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Runs the compaction to compact history into a single summary
|
|
628
|
+
*/
|
|
629
|
+
async #runCompaction(compactionRequestInput, session, metric, chatResultStream, tabId, promptId, conversationIdentifier, token, documentReference) {
|
|
630
|
+
let currentRequestInput = { ...compactionRequestInput };
|
|
631
|
+
let finalResult = null;
|
|
632
|
+
metric.recordStart();
|
|
633
|
+
this.#debug(`Running compaction for conversation id:`, conversationIdentifier || '');
|
|
634
|
+
this.#timeToFirstChunk = -1;
|
|
635
|
+
this.#timeBetweenChunks = [];
|
|
636
|
+
// Check for cancellation
|
|
637
|
+
if (this.#isPromptCanceled(token, session, promptId)) {
|
|
638
|
+
this.#debug('Stopping compaction loop - cancelled by user');
|
|
639
|
+
throw new lsp_core_1.CancellationError('user');
|
|
640
|
+
}
|
|
641
|
+
const currentMessage = currentRequestInput.conversationState?.currentMessage;
|
|
642
|
+
let messages = [];
|
|
643
|
+
let characterCount = 0;
|
|
644
|
+
if (currentMessage) {
|
|
645
|
+
// Get and process the messages from history DB to maintain invariants for service requests
|
|
646
|
+
try {
|
|
647
|
+
const { messages: historyMessages, count: historyCharCount } = this.#chatHistoryDb.fixAndGetHistory(tabId, currentMessage, []);
|
|
648
|
+
messages = historyMessages;
|
|
649
|
+
characterCount = historyCharCount;
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
if (err instanceof chatDb_1.ToolResultValidationError) {
|
|
653
|
+
this.#features.logging.error(`Tool validation error: ${err.message}`);
|
|
654
|
+
return (finalResult || {
|
|
655
|
+
success: false,
|
|
656
|
+
error: 'Compaction loop failed to produce a final result',
|
|
657
|
+
data: { chatResult: {}, toolUses: {} },
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
currentRequestInput.conversationState.history = messages.map(msg => (0, util_1.messageToStreamingMessage)(msg));
|
|
663
|
+
const resultStreamWriter = chatResultStream.getResultStreamWriter();
|
|
664
|
+
if (currentRequestInput.conversationState.history.length == 0) {
|
|
665
|
+
// early terminate
|
|
666
|
+
await resultStreamWriter.write({
|
|
667
|
+
type: 'answer',
|
|
668
|
+
body: 'History is empty, there is nothing to compact.',
|
|
669
|
+
messageId: (0, uuid_1.v4)(),
|
|
670
|
+
});
|
|
671
|
+
return {
|
|
672
|
+
success: true,
|
|
673
|
+
data: {
|
|
674
|
+
chatResult: {},
|
|
675
|
+
toolUses: {},
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
await resultStreamWriter.write({
|
|
681
|
+
type: 'answer',
|
|
682
|
+
body: 'Compacting your chat history, this may take a moment.',
|
|
683
|
+
messageId: (0, uuid_1.v4)(),
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
await resultStreamWriter.close();
|
|
687
|
+
// Add loading message before making the request
|
|
688
|
+
const loadingMessageId = `loading-${(0, uuid_1.v4)()}`;
|
|
689
|
+
await chatResultStream.writeResultBlock({ ...constants_1.loadingMessage, messageId: loadingMessageId });
|
|
690
|
+
this.#debug(`Compacting history with ${characterCount} characters`);
|
|
691
|
+
this.#llmRequestStartTime = Date.now();
|
|
692
|
+
// Phase 3: Request Execution
|
|
693
|
+
// Note: these logs are very noisy, but contain information redacted on the backend.
|
|
694
|
+
this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput, undefined, 2)}`);
|
|
695
|
+
const response = await session.getChatResponse(currentRequestInput);
|
|
696
|
+
if (response.$metadata.requestId) {
|
|
697
|
+
metric.mergeWith({
|
|
698
|
+
requestIds: [response.$metadata.requestId],
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
this.#features.logging.info(`generateAssistantResponse/SendMessage ResponseMetadata: ${lsp_core_2.loggingUtils.formatObj(response.$metadata)}`);
|
|
702
|
+
await chatResultStream.removeResultBlock(loadingMessageId);
|
|
703
|
+
// Phase 4: Response Processing
|
|
704
|
+
const result = await this.#processAgenticChatResponseWithTimeout(response, metric.mergeWith({
|
|
705
|
+
cwsprChatResponseCode: response.$metadata.httpStatusCode,
|
|
706
|
+
cwsprChatMessageId: response.$metadata.requestId,
|
|
707
|
+
}), chatResultStream, session, documentReference, true);
|
|
708
|
+
const llmLatency = Date.now() - this.#llmRequestStartTime;
|
|
709
|
+
this.#debug(`LLM Response Latency for compaction: ${llmLatency}`);
|
|
710
|
+
this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationIdentifier ?? '', 'AgenticChatWithCompaction', undefined, undefined, 'Succeeded', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, [], this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode);
|
|
711
|
+
// replace the history with summary in history DB
|
|
712
|
+
if (result.data?.chatResult.body !== undefined) {
|
|
713
|
+
this.#chatHistoryDb.replaceWithSummary(tabId, 'cwc', conversationIdentifier ?? '', {
|
|
714
|
+
body: result.data?.chatResult.body,
|
|
715
|
+
type: 'prompt',
|
|
716
|
+
shouldDisplayMessage: true,
|
|
717
|
+
timestamp: new Date(),
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
this.#features.logging.warn('No ChatResult body in response, skipping adding to history');
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
569
725
|
/**
|
|
570
726
|
* Runs the agent loop, making requests and processing tool uses until completion
|
|
571
727
|
*/
|
|
572
|
-
async #runAgentLoop(initialRequestInput, session, metric, chatResultStream, tabId, promptId, conversationIdentifier, token, documentReference,
|
|
728
|
+
async #runAgentLoop(initialRequestInput, session, metric, chatResultStream, tabId, promptId, conversationIdentifier, token, documentReference, additionalContext) {
|
|
573
729
|
let currentRequestInput = { ...initialRequestInput };
|
|
574
730
|
let finalResult = null;
|
|
575
731
|
let iterationCount = 0;
|
|
576
732
|
let shouldDisplayMessage = true;
|
|
733
|
+
let currentRequestCount = 0;
|
|
734
|
+
const pinnedContext = additionalContext?.filter(item => item.pinned);
|
|
577
735
|
metric.recordStart();
|
|
578
736
|
this.logSystemInformation();
|
|
579
737
|
while (true) {
|
|
@@ -587,7 +745,7 @@ class AgenticChatController {
|
|
|
587
745
|
this.#debug('Stopping agent loop - cancelled by user');
|
|
588
746
|
throw new lsp_core_1.CancellationError('user');
|
|
589
747
|
}
|
|
590
|
-
this.truncateRequest(currentRequestInput,
|
|
748
|
+
this.truncateRequest(currentRequestInput, additionalContext);
|
|
591
749
|
const currentMessage = currentRequestInput.conversationState?.currentMessage;
|
|
592
750
|
const conversationId = conversationIdentifier ?? '';
|
|
593
751
|
if (!currentMessage || !conversationId) {
|
|
@@ -600,7 +758,11 @@ class AgenticChatController {
|
|
|
600
758
|
if (currentMessage) {
|
|
601
759
|
// Get and process the messages from history DB to maintain invariants for service requests
|
|
602
760
|
try {
|
|
603
|
-
|
|
761
|
+
const newUserInputCount = this.#chatHistoryDb.calculateNewMessageCharacterCount(currentMessage, pinnedContextMessages);
|
|
762
|
+
const { messages: historyMessages, count: historyCharacterCount } = this.#chatHistoryDb.fixAndGetHistory(tabId, currentMessage, pinnedContextMessages, newUserInputCount);
|
|
763
|
+
messages = historyMessages;
|
|
764
|
+
currentRequestCount = newUserInputCount + historyCharacterCount;
|
|
765
|
+
this.#debug(`Request total character count: ${currentRequestCount}`);
|
|
604
766
|
}
|
|
605
767
|
catch (err) {
|
|
606
768
|
if (err instanceof chatDb_1.ToolResultValidationError) {
|
|
@@ -609,7 +771,7 @@ class AgenticChatController {
|
|
|
609
771
|
}
|
|
610
772
|
}
|
|
611
773
|
}
|
|
612
|
-
//
|
|
774
|
+
// Do not include chatHistory for requests going to Mynah Backend
|
|
613
775
|
currentRequestInput.conversationState.history = currentRequestInput.conversationState?.currentMessage
|
|
614
776
|
?.userInputMessage?.userIntent
|
|
615
777
|
? []
|
|
@@ -620,7 +782,7 @@ class AgenticChatController {
|
|
|
620
782
|
this.#llmRequestStartTime = Date.now();
|
|
621
783
|
// Phase 3: Request Execution
|
|
622
784
|
// Note: these logs are very noisy, but contain information redacted on the backend.
|
|
623
|
-
this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput,
|
|
785
|
+
this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput, this.#imageReplacer, 2)}`);
|
|
624
786
|
const response = await session.getChatResponse(currentRequestInput);
|
|
625
787
|
if (response.$metadata.requestId) {
|
|
626
788
|
metric.mergeWith({
|
|
@@ -645,6 +807,7 @@ class AgenticChatController {
|
|
|
645
807
|
shouldDisplayMessage: shouldDisplayMessage &&
|
|
646
808
|
!currentMessage.userInputMessage?.content?.startsWith('You are Amazon Q'),
|
|
647
809
|
timestamp: new Date(),
|
|
810
|
+
images: currentMessage.userInputMessage?.images,
|
|
648
811
|
});
|
|
649
812
|
}
|
|
650
813
|
}
|
|
@@ -758,6 +921,16 @@ class AgenticChatController {
|
|
|
758
921
|
}
|
|
759
922
|
currentRequestInput = this.#updateRequestInputWithToolResults(currentRequestInput, toolResults, content);
|
|
760
923
|
}
|
|
924
|
+
if (this.#shouldCompact(currentRequestCount)) {
|
|
925
|
+
const messageId = this.#getMessageIdForCompact((0, uuid_1.v4)());
|
|
926
|
+
const confirmationResult = this.#processCompactConfirmation(messageId);
|
|
927
|
+
const cachedButtonBlockId = await chatResultStream.writeResultBlock(confirmationResult);
|
|
928
|
+
await this.waitForCompactApproval(messageId, chatResultStream, cachedButtonBlockId, session);
|
|
929
|
+
// Get the compaction request input
|
|
930
|
+
const compactionRequestInput = this.#getCompactionRequestInput(session);
|
|
931
|
+
// Start the compaction call
|
|
932
|
+
return await this.#runCompaction(compactionRequestInput, session, metric, chatResultStream, tabId, promptId, session.conversationId, token, documentReference);
|
|
933
|
+
}
|
|
761
934
|
return (finalResult || {
|
|
762
935
|
success: false,
|
|
763
936
|
error: 'Agent loop failed to produce a final result',
|
|
@@ -787,7 +960,7 @@ class AgenticChatController {
|
|
|
787
960
|
* Returns the remaining character budget for chat history.
|
|
788
961
|
* @param request
|
|
789
962
|
*/
|
|
790
|
-
truncateRequest(request,
|
|
963
|
+
truncateRequest(request, additionalContext) {
|
|
791
964
|
// TODO: Confirm if this limit applies to SendMessage and rename this constant
|
|
792
965
|
let remainingCharacterBudget = constants_2.GENERATE_ASSISTANT_RESPONSE_INPUT_LIMIT;
|
|
793
966
|
if (!request?.conversationState?.currentMessage?.userInputMessage) {
|
|
@@ -807,20 +980,63 @@ class AgenticChatController {
|
|
|
807
980
|
remainingCharacterBudget = remainingCharacterBudget - message.length;
|
|
808
981
|
}
|
|
809
982
|
}
|
|
810
|
-
// 2. try to fit @context into budget
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
983
|
+
// 2. try to fit @context and images into budget together
|
|
984
|
+
const docs = request.conversationState.currentMessage.userInputMessage.userInputMessageContext?.editorState
|
|
985
|
+
?.relevantDocuments ?? [];
|
|
986
|
+
const images = request.conversationState.currentMessage.userInputMessage.images ?? [];
|
|
987
|
+
// Combine docs and images, preserving the order from additionalContext
|
|
988
|
+
let combined;
|
|
989
|
+
if (additionalContext && additionalContext.length > 0) {
|
|
990
|
+
let docIdx = 0;
|
|
991
|
+
let imageIdx = 0;
|
|
992
|
+
combined = additionalContext
|
|
993
|
+
.map(entry => {
|
|
994
|
+
if (entry.type === 'image') {
|
|
995
|
+
return { type: 'image', value: images[imageIdx++] };
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
return { type: 'doc', value: docs[docIdx++] };
|
|
999
|
+
}
|
|
1000
|
+
})
|
|
1001
|
+
.filter(item => item.value !== undefined);
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
combined = [
|
|
1005
|
+
...docs.map(d => ({ type: 'doc', value: d })),
|
|
1006
|
+
...images.map(i => ({ type: 'image', value: i })),
|
|
1007
|
+
];
|
|
1008
|
+
}
|
|
1009
|
+
const truncatedDocs = [];
|
|
1010
|
+
const truncatedImages = [];
|
|
1011
|
+
for (const item of combined) {
|
|
1012
|
+
let itemLength = 0;
|
|
1013
|
+
if (item.type === 'doc') {
|
|
1014
|
+
itemLength = item.value?.text?.length || 0;
|
|
1015
|
+
if (remainingCharacterBudget >= itemLength) {
|
|
1016
|
+
truncatedDocs.push(item.value);
|
|
1017
|
+
remainingCharacterBudget -= itemLength;
|
|
820
1018
|
}
|
|
821
1019
|
}
|
|
1020
|
+
else if (item.type === 'image') {
|
|
1021
|
+
// Type guard: only call on ImageBlock
|
|
1022
|
+
if (item.value && typeof item.value === 'object' && 'format' in item.value && 'source' in item.value) {
|
|
1023
|
+
itemLength = (0, util_1.estimateCharacterCountFromImageBlock)(item.value);
|
|
1024
|
+
if (remainingCharacterBudget >= itemLength) {
|
|
1025
|
+
truncatedImages.push(item.value);
|
|
1026
|
+
remainingCharacterBudget -= itemLength;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// Assign truncated lists back to request
|
|
1032
|
+
if (request.conversationState.currentMessage.userInputMessage.userInputMessageContext?.editorState
|
|
1033
|
+
?.relevantDocuments) {
|
|
822
1034
|
request.conversationState.currentMessage.userInputMessage.userInputMessageContext.editorState.relevantDocuments =
|
|
823
|
-
|
|
1035
|
+
truncatedDocs;
|
|
1036
|
+
}
|
|
1037
|
+
if (request.conversationState.currentMessage.userInputMessage.images !== undefined &&
|
|
1038
|
+
request.conversationState.currentMessage.userInputMessage.images.length > 0) {
|
|
1039
|
+
request.conversationState.currentMessage.userInputMessage.images = truncatedImages;
|
|
824
1040
|
}
|
|
825
1041
|
// 3. try to fit current file context
|
|
826
1042
|
let truncatedCurrentDocument = undefined;
|
|
@@ -836,6 +1052,7 @@ class AgenticChatController {
|
|
|
836
1052
|
request.conversationState.currentMessage.userInputMessage.userInputMessageContext.editorState.document =
|
|
837
1053
|
truncatedCurrentDocument;
|
|
838
1054
|
}
|
|
1055
|
+
const pinnedContext = additionalContext?.filter(item => item.pinned);
|
|
839
1056
|
// 4. try to fit pinned context into budget
|
|
840
1057
|
if (pinnedContext && pinnedContext.length > 0) {
|
|
841
1058
|
remainingCharacterBudget = this.truncatePinnedContext(remainingCharacterBudget, pinnedContext);
|
|
@@ -890,58 +1107,69 @@ class AgenticChatController {
|
|
|
890
1107
|
// remove progress UI
|
|
891
1108
|
await chatResultStream.removeResultBlockAndUpdateUI(agenticChatResultStream_1.progressPrefix + toolUse.toolUseId);
|
|
892
1109
|
// fsRead and listDirectory write to an existing card and could show nothing in the current position
|
|
893
|
-
if (![
|
|
1110
|
+
if (![toolConstants_1.FS_WRITE, toolConstants_1.FS_REPLACE, toolConstants_1.FS_READ, toolConstants_1.LIST_DIRECTORY].includes(toolUse.name)) {
|
|
894
1111
|
await this.#showUndoAllIfRequired(chatResultStream, session);
|
|
895
1112
|
}
|
|
896
1113
|
// fsWrite can take a long time, so we render fsWrite Explanatory upon partial streaming responses.
|
|
897
|
-
if (toolUse.name !==
|
|
1114
|
+
if (toolUse.name !== toolConstants_1.FS_WRITE && toolUse.name !== toolConstants_1.FS_REPLACE) {
|
|
898
1115
|
const { explanation } = toolUse.input;
|
|
899
1116
|
if (explanation) {
|
|
900
1117
|
await chatResultStream.writeResultBlock({
|
|
901
1118
|
type: 'directive',
|
|
902
|
-
messageId: toolUse.toolUseId +
|
|
1119
|
+
messageId: toolUse.toolUseId + toolConstants_1.SUFFIX_EXPLANATION,
|
|
903
1120
|
body: explanation,
|
|
904
1121
|
});
|
|
905
1122
|
}
|
|
906
1123
|
}
|
|
907
1124
|
switch (toolUse.name) {
|
|
908
|
-
case
|
|
909
|
-
case
|
|
910
|
-
case
|
|
911
|
-
case
|
|
912
|
-
case
|
|
913
|
-
case
|
|
914
|
-
case
|
|
1125
|
+
case toolConstants_1.FS_READ:
|
|
1126
|
+
case toolConstants_1.LIST_DIRECTORY:
|
|
1127
|
+
case toolConstants_1.GREP_SEARCH:
|
|
1128
|
+
case toolConstants_1.FILE_SEARCH:
|
|
1129
|
+
case toolConstants_1.FS_WRITE:
|
|
1130
|
+
case toolConstants_1.FS_REPLACE:
|
|
1131
|
+
case toolConstants_1.EXECUTE_BASH: {
|
|
915
1132
|
const toolMap = {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1133
|
+
[toolConstants_1.FS_READ]: { Tool: fsRead_1.FsRead },
|
|
1134
|
+
[toolConstants_1.LIST_DIRECTORY]: { Tool: listDirectory_1.ListDirectory },
|
|
1135
|
+
[toolConstants_1.FS_WRITE]: { Tool: fsWrite_1.FsWrite },
|
|
1136
|
+
[toolConstants_1.FS_REPLACE]: { Tool: fsReplace_1.FsReplace },
|
|
1137
|
+
[toolConstants_1.EXECUTE_BASH]: { Tool: executeBash_1.ExecuteBash },
|
|
1138
|
+
[toolConstants_1.GREP_SEARCH]: { Tool: grepSearch_1.GrepSearch },
|
|
1139
|
+
[toolConstants_1.FILE_SEARCH]: { Tool: fileSearch_1.FileSearch },
|
|
923
1140
|
};
|
|
924
1141
|
const { Tool } = toolMap[toolUse.name];
|
|
925
1142
|
const tool = new Tool(this.#features);
|
|
926
1143
|
// For MCP tools, get the permission from McpManager
|
|
927
|
-
|
|
1144
|
+
const permission = mcpManager_1.McpManager.instance.getToolPerm('Built-in', toolUse.name);
|
|
928
1145
|
// If permission is 'alwaysAllow', we don't need to ask for acceptance
|
|
929
|
-
|
|
1146
|
+
const builtInPermission = permission !== 'alwaysAllow';
|
|
930
1147
|
// Get the approved paths from the session
|
|
931
1148
|
const approvedPaths = session.approvedPaths;
|
|
932
1149
|
// Pass the approved paths to the tool's requiresAcceptance method
|
|
933
1150
|
const { requiresAcceptance, warning, commandCategory } = await tool.requiresAcceptance(toolUse.input, approvedPaths);
|
|
1151
|
+
const isExecuteBash = toolUse.name === toolConstants_1.EXECUTE_BASH;
|
|
1152
|
+
// check if tool execution's path is out of workspace
|
|
1153
|
+
const isOutOfWorkSpace = warning === constants_2.OUT_OF_WORKSPACE_WARNING_MSG;
|
|
1154
|
+
// check if tool involved secured files
|
|
1155
|
+
const isSecuredFilesInvoled = warning === constants_2.BINARY_FILE_WARNING_MSG || warning === constants_2.CREDENTIAL_FILE_WARNING_MSG;
|
|
934
1156
|
// Honor built-in permission if available, otherwise use tool's requiresAcceptance
|
|
935
|
-
|
|
936
|
-
if
|
|
1157
|
+
let toolRequiresAcceptance = (builtInPermission || isOutOfWorkSpace || isSecuredFilesInvoled) ?? requiresAcceptance;
|
|
1158
|
+
// if the command is read-only and in-workspace --> flip back to no approval needed
|
|
1159
|
+
if (isExecuteBash &&
|
|
1160
|
+
commandCategory === executeBash_2.CommandCategory.ReadOnly &&
|
|
1161
|
+
!isOutOfWorkSpace &&
|
|
1162
|
+
!requiresAcceptance) {
|
|
1163
|
+
toolRequiresAcceptance = false;
|
|
1164
|
+
}
|
|
1165
|
+
if (toolRequiresAcceptance || isExecuteBash) {
|
|
937
1166
|
// for executeBash, we till send the confirmation message without action buttons
|
|
938
|
-
const confirmationResult = this.#processToolConfirmation(toolUse,
|
|
1167
|
+
const confirmationResult = this.#processToolConfirmation(toolUse, toolRequiresAcceptance, warning, commandCategory, toolUse.name, builtInPermission);
|
|
939
1168
|
cachedButtonBlockId = await chatResultStream.writeResultBlock(confirmationResult);
|
|
940
|
-
const isExecuteBash = toolUse.name === 'executeBash';
|
|
941
1169
|
if (isExecuteBash) {
|
|
942
1170
|
this.#telemetryController.emitInteractWithAgenticChat('GeneratedCommand', tabId, session.pairProgrammingMode, session.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
|
|
943
1171
|
}
|
|
944
|
-
if (
|
|
1172
|
+
if (toolRequiresAcceptance) {
|
|
945
1173
|
await this.waitForToolApproval(toolUse, chatResultStream, cachedButtonBlockId, session, toolUse.name);
|
|
946
1174
|
}
|
|
947
1175
|
if (isExecuteBash) {
|
|
@@ -994,7 +1222,7 @@ class AgenticChatController {
|
|
|
994
1222
|
}
|
|
995
1223
|
break;
|
|
996
1224
|
}
|
|
997
|
-
if (toolUse.name ===
|
|
1225
|
+
if (toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) {
|
|
998
1226
|
const input = toolUse.input;
|
|
999
1227
|
const document = await this.#triggerContext.getTextDocumentFromPath(input.path, true, true);
|
|
1000
1228
|
session.toolUseLookup.set(toolUse.toolUseId, {
|
|
@@ -1044,27 +1272,27 @@ class AgenticChatController {
|
|
|
1044
1272
|
content: [toolResultContent],
|
|
1045
1273
|
});
|
|
1046
1274
|
switch (toolUse.name) {
|
|
1047
|
-
case
|
|
1048
|
-
case
|
|
1049
|
-
case
|
|
1275
|
+
case toolConstants_1.FS_READ:
|
|
1276
|
+
case toolConstants_1.LIST_DIRECTORY:
|
|
1277
|
+
case toolConstants_1.FILE_SEARCH:
|
|
1050
1278
|
const initialListDirResult = this.#processReadOrListOrSearch(toolUse, chatResultStream);
|
|
1051
1279
|
if (initialListDirResult) {
|
|
1052
1280
|
await chatResultStream.writeResultBlock(initialListDirResult);
|
|
1053
1281
|
}
|
|
1054
1282
|
break;
|
|
1055
1283
|
// no need to write tool result for listDir,fsRead,fileSearch into chat stream
|
|
1056
|
-
case
|
|
1284
|
+
case toolConstants_1.EXECUTE_BASH:
|
|
1057
1285
|
// no need to write tool result for listDir and fsRead into chat stream
|
|
1058
1286
|
// executeBash will stream the output instead of waiting until the end
|
|
1059
1287
|
break;
|
|
1060
|
-
case
|
|
1288
|
+
case toolConstants_1.GREP_SEARCH:
|
|
1061
1289
|
const grepSearchResult = this.#processGrepSearchResult(toolUse, result, chatResultStream);
|
|
1062
1290
|
if (grepSearchResult) {
|
|
1063
1291
|
await chatResultStream.writeResultBlock(grepSearchResult);
|
|
1064
1292
|
}
|
|
1065
1293
|
break;
|
|
1066
|
-
case
|
|
1067
|
-
case
|
|
1294
|
+
case toolConstants_1.FS_REPLACE:
|
|
1295
|
+
case toolConstants_1.FS_WRITE:
|
|
1068
1296
|
const input = toolUse.input;
|
|
1069
1297
|
// Load from the filesystem instead of workspace.
|
|
1070
1298
|
// Workspace is likely out of date - when files
|
|
@@ -1149,12 +1377,12 @@ class AgenticChatController {
|
|
|
1149
1377
|
continue;
|
|
1150
1378
|
}
|
|
1151
1379
|
// Rethrow error for executeBash or any named tool
|
|
1152
|
-
if (toolUse.name ===
|
|
1380
|
+
if (toolUse.name === toolConstants_1.EXECUTE_BASH || toolUse.name) {
|
|
1153
1381
|
throw err;
|
|
1154
1382
|
}
|
|
1155
1383
|
}
|
|
1156
1384
|
// display fs write failure status in the UX of that file card
|
|
1157
|
-
if ((toolUse.name ===
|
|
1385
|
+
if ((toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) && toolUse.toolUseId) {
|
|
1158
1386
|
const existingCard = chatResultStream.getMessageBlockId(toolUse.toolUseId);
|
|
1159
1387
|
const fsParam = toolUse.input;
|
|
1160
1388
|
if (fsParam.path) {
|
|
@@ -1188,7 +1416,7 @@ class AgenticChatController {
|
|
|
1188
1416
|
}
|
|
1189
1417
|
}
|
|
1190
1418
|
}
|
|
1191
|
-
else if (toolUse.name ===
|
|
1419
|
+
else if (toolUse.name === toolConstants_1.EXECUTE_BASH && toolUse.toolUseId) {
|
|
1192
1420
|
const existingCard = chatResultStream.getMessageBlockId(toolUse.toolUseId);
|
|
1193
1421
|
const command = toolUse.input.command;
|
|
1194
1422
|
const completedErrorResult = {
|
|
@@ -1246,10 +1474,10 @@ class AgenticChatController {
|
|
|
1246
1474
|
* Updates the currentUndoAllId state in the session
|
|
1247
1475
|
*/
|
|
1248
1476
|
#updateUndoAllState(toolUse, session) {
|
|
1249
|
-
if (toolUse.name ===
|
|
1477
|
+
if (toolUse.name === toolConstants_1.FS_READ || toolUse.name === toolConstants_1.LIST_DIRECTORY) {
|
|
1250
1478
|
return;
|
|
1251
1479
|
}
|
|
1252
|
-
if (toolUse.name ===
|
|
1480
|
+
if (toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) {
|
|
1253
1481
|
if (session.currentUndoAllId === undefined) {
|
|
1254
1482
|
session.currentUndoAllId = toolUse.toolUseId;
|
|
1255
1483
|
}
|
|
@@ -1284,10 +1512,10 @@ class AgenticChatController {
|
|
|
1284
1512
|
}
|
|
1285
1513
|
await chatResultStream.writeResultBlock({
|
|
1286
1514
|
type: 'answer',
|
|
1287
|
-
messageId: `${session.currentUndoAllId}
|
|
1515
|
+
messageId: `${session.currentUndoAllId}${toolConstants_1.SUFFIX_UNDOALL}`,
|
|
1288
1516
|
buttons: [
|
|
1289
1517
|
{
|
|
1290
|
-
id:
|
|
1518
|
+
id: toolConstants_1.BUTTON_UNDO_ALL_CHANGES,
|
|
1291
1519
|
text: 'Undo all changes',
|
|
1292
1520
|
icon: 'undo',
|
|
1293
1521
|
status: 'clear',
|
|
@@ -1315,11 +1543,11 @@ class AgenticChatController {
|
|
|
1315
1543
|
#validateToolResult(toolUse, result) {
|
|
1316
1544
|
let maxToolResponseSize;
|
|
1317
1545
|
switch (toolUse.name) {
|
|
1318
|
-
case
|
|
1319
|
-
case
|
|
1546
|
+
case toolConstants_1.FS_READ:
|
|
1547
|
+
case toolConstants_1.EXECUTE_BASH:
|
|
1320
1548
|
// fsRead and executeBash already have truncation logic
|
|
1321
1549
|
return;
|
|
1322
|
-
case
|
|
1550
|
+
case toolConstants_1.LIST_DIRECTORY:
|
|
1323
1551
|
maxToolResponseSize = 50_000;
|
|
1324
1552
|
break;
|
|
1325
1553
|
default:
|
|
@@ -1371,7 +1599,7 @@ class AgenticChatController {
|
|
|
1371
1599
|
if (toolUse.name === qCodeReview_1.QCodeReview.toolName) {
|
|
1372
1600
|
return this.#getToolOverWritableStream(chatResultStream, toolUse);
|
|
1373
1601
|
}
|
|
1374
|
-
if (toolUse.name !==
|
|
1602
|
+
if (toolUse.name !== toolConstants_1.EXECUTE_BASH) {
|
|
1375
1603
|
return;
|
|
1376
1604
|
}
|
|
1377
1605
|
const toolMsgId = toolUse.toolUseId;
|
|
@@ -1379,7 +1607,7 @@ class AgenticChatController {
|
|
|
1379
1607
|
let headerEmitted = false;
|
|
1380
1608
|
const initialHeader = {
|
|
1381
1609
|
body: 'shell',
|
|
1382
|
-
buttons: [
|
|
1610
|
+
buttons: [this.#renderStopShellCommandButton()],
|
|
1383
1611
|
};
|
|
1384
1612
|
const completedHeader = {
|
|
1385
1613
|
body: 'shell',
|
|
@@ -1427,7 +1655,7 @@ class AgenticChatController {
|
|
|
1427
1655
|
#getUpdateToolConfirmResult(toolUse, isAccept, originalToolName, toolType) {
|
|
1428
1656
|
const toolName = originalToolName ?? (toolType || toolUse.name);
|
|
1429
1657
|
// Handle bash commands with special formatting
|
|
1430
|
-
if (toolName ===
|
|
1658
|
+
if (toolName === toolConstants_1.EXECUTE_BASH) {
|
|
1431
1659
|
return {
|
|
1432
1660
|
messageId: toolUse.toolUseId,
|
|
1433
1661
|
type: 'tool',
|
|
@@ -1443,7 +1671,7 @@ class AgenticChatController {
|
|
|
1443
1671
|
text: 'Rejected',
|
|
1444
1672
|
},
|
|
1445
1673
|
}),
|
|
1446
|
-
buttons: isAccept ? [
|
|
1674
|
+
buttons: isAccept ? [this.#renderStopShellCommandButton()] : [],
|
|
1447
1675
|
},
|
|
1448
1676
|
};
|
|
1449
1677
|
}
|
|
@@ -1451,10 +1679,10 @@ class AgenticChatController {
|
|
|
1451
1679
|
let header;
|
|
1452
1680
|
let body;
|
|
1453
1681
|
switch (toolName) {
|
|
1454
|
-
case
|
|
1455
|
-
case
|
|
1456
|
-
case
|
|
1457
|
-
case
|
|
1682
|
+
case toolConstants_1.FS_REPLACE:
|
|
1683
|
+
case toolConstants_1.FS_WRITE:
|
|
1684
|
+
case toolConstants_1.FS_READ:
|
|
1685
|
+
case toolConstants_1.LIST_DIRECTORY:
|
|
1458
1686
|
header = {
|
|
1459
1687
|
body: undefined,
|
|
1460
1688
|
status: {
|
|
@@ -1464,7 +1692,7 @@ class AgenticChatController {
|
|
|
1464
1692
|
},
|
|
1465
1693
|
};
|
|
1466
1694
|
break;
|
|
1467
|
-
case
|
|
1695
|
+
case toolConstants_1.FILE_SEARCH:
|
|
1468
1696
|
const searchPath = toolUse.input.path;
|
|
1469
1697
|
header = {
|
|
1470
1698
|
body: 'File Search',
|
|
@@ -1540,6 +1768,97 @@ class AgenticChatController {
|
|
|
1540
1768
|
},
|
|
1541
1769
|
});
|
|
1542
1770
|
}
|
|
1771
|
+
#processCompactConfirmation(messageId) {
|
|
1772
|
+
const buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
|
|
1773
|
+
const header = {
|
|
1774
|
+
icon: 'warning',
|
|
1775
|
+
iconForegroundStatus: 'warning',
|
|
1776
|
+
body: constants_2.COMPACTION_HEADER_BODY,
|
|
1777
|
+
buttons,
|
|
1778
|
+
};
|
|
1779
|
+
const body = constants_2.COMPACTION_BODY;
|
|
1780
|
+
return {
|
|
1781
|
+
type: 'tool',
|
|
1782
|
+
messageId,
|
|
1783
|
+
header,
|
|
1784
|
+
body,
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Creates a promise that does not resolve until the user accepts or rejects the compaction usage.
|
|
1789
|
+
* @param messageId
|
|
1790
|
+
* @param resultStream
|
|
1791
|
+
* @param promptBlockId id of approval block. This allows us to overwrite the buttons with 'accepted' or 'rejected' text.
|
|
1792
|
+
*/
|
|
1793
|
+
async waitForCompactApproval(messageId, resultStream, promptBlockId, session) {
|
|
1794
|
+
const deferred = this.#createDeferred();
|
|
1795
|
+
session.setDeferredToolExecution(messageId, deferred.resolve, deferred.reject);
|
|
1796
|
+
this.#log(`Prompting for compaction approval for messageId: ${messageId}`);
|
|
1797
|
+
await deferred.promise;
|
|
1798
|
+
// Note: we want to overwrite the button block because it already exists in the stream.
|
|
1799
|
+
await resultStream.overwriteResultBlock(this.#getUpdateCompactConfirmResult(messageId), promptBlockId);
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Creates an updated ChatResult for compaction confirmation
|
|
1803
|
+
* @param messageId The messageId
|
|
1804
|
+
* @returns ChatResult with appropriate confirmation UI
|
|
1805
|
+
*/
|
|
1806
|
+
#getUpdateCompactConfirmResult(messageId) {
|
|
1807
|
+
let header;
|
|
1808
|
+
let body;
|
|
1809
|
+
header = {
|
|
1810
|
+
body: undefined,
|
|
1811
|
+
status: {
|
|
1812
|
+
status: 'success',
|
|
1813
|
+
icon: 'ok',
|
|
1814
|
+
text: 'Allowed',
|
|
1815
|
+
},
|
|
1816
|
+
};
|
|
1817
|
+
return {
|
|
1818
|
+
messageId,
|
|
1819
|
+
type: 'tool',
|
|
1820
|
+
body,
|
|
1821
|
+
header,
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
#renderStopShellCommandButton() {
|
|
1825
|
+
const stopKey = this.#getKeyBinding('aws.amazonq.stopCmdExecution');
|
|
1826
|
+
return {
|
|
1827
|
+
id: toolConstants_1.BUTTON_STOP_SHELL_COMMAND,
|
|
1828
|
+
text: 'Stop',
|
|
1829
|
+
icon: 'stop',
|
|
1830
|
+
...(stopKey ? { description: `Stop: ${stopKey}` } : {}),
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
#getKeyBinding(commandId) {
|
|
1834
|
+
// Check for feature flag
|
|
1835
|
+
const shortcut = this.#features.lsp.getClientInitializeParams()?.initializationOptions?.aws?.awsClientCapabilities?.q
|
|
1836
|
+
?.shortcut;
|
|
1837
|
+
if (!shortcut) {
|
|
1838
|
+
return null;
|
|
1839
|
+
}
|
|
1840
|
+
let defaultKey = '';
|
|
1841
|
+
const OS = os.platform();
|
|
1842
|
+
switch (commandId) {
|
|
1843
|
+
case 'aws.amazonq.runCmdExecution':
|
|
1844
|
+
defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_RUN_SHORTCUT : constants_2.DEFAULT_WINDOW_RUN_SHORTCUT;
|
|
1845
|
+
break;
|
|
1846
|
+
case 'aws.amazonq.rejectCmdExecution':
|
|
1847
|
+
defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_REJECT_SHORTCUT : constants_2.DEFAULT_WINDOW_REJECT_SHORTCUT;
|
|
1848
|
+
break;
|
|
1849
|
+
case 'aws.amazonq.stopCmdExecution':
|
|
1850
|
+
defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_STOP_SHORTCUT : constants_2.DEFAULT_WINDOW_STOP_SHORTCUT;
|
|
1851
|
+
break;
|
|
1852
|
+
default:
|
|
1853
|
+
this.#log(`#getKeyBinding: ${commandId} shortcut is supported by Q `);
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
if (defaultKey === '') {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
//TODO: handle case: user change default keybind, suggestion: read `keybinding.json` provided by VSC
|
|
1860
|
+
return defaultKey;
|
|
1861
|
+
}
|
|
1543
1862
|
#processToolConfirmation(toolUse, requiresAcceptance, warning, commandCategory, toolType, builtInPermission) {
|
|
1544
1863
|
const toolName = toolType || toolUse.name;
|
|
1545
1864
|
let buttons = [];
|
|
@@ -1547,16 +1866,27 @@ class AgenticChatController {
|
|
|
1547
1866
|
let body;
|
|
1548
1867
|
// Configure tool-specific UI elements
|
|
1549
1868
|
switch (toolName) {
|
|
1550
|
-
case
|
|
1869
|
+
case toolConstants_1.EXECUTE_BASH: {
|
|
1551
1870
|
const commandString = toolUse.input.command;
|
|
1871
|
+
// get feature flag
|
|
1872
|
+
const shortcut = this.#features.lsp.getClientInitializeParams()?.initializationOptions?.aws?.awsClientCapabilities?.q
|
|
1873
|
+
?.shortcut;
|
|
1874
|
+
const runKey = this.#getKeyBinding('aws.amazonq.runCmdExecution');
|
|
1875
|
+
const rejectKey = this.#getKeyBinding('aws.amazonq.rejectCmdExecution');
|
|
1552
1876
|
buttons = requiresAcceptance
|
|
1553
1877
|
? [
|
|
1554
|
-
{ id: 'run-shell-command', text: 'Run', icon: 'play' },
|
|
1555
1878
|
{
|
|
1556
|
-
id:
|
|
1879
|
+
id: toolConstants_1.BUTTON_RUN_SHELL_COMMAND,
|
|
1880
|
+
text: 'Run',
|
|
1881
|
+
icon: 'play',
|
|
1882
|
+
...(runKey ? { description: `Run: ${runKey}` } : {}),
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
id: toolConstants_1.BUTTON_REJECT_SHELL_COMMAND,
|
|
1557
1886
|
status: 'dimmed-clear',
|
|
1558
1887
|
text: 'Reject',
|
|
1559
1888
|
icon: 'cancel',
|
|
1889
|
+
...(rejectKey ? { description: `Reject: ${rejectKey}` } : {}),
|
|
1560
1890
|
},
|
|
1561
1891
|
]
|
|
1562
1892
|
: [];
|
|
@@ -1585,12 +1915,12 @@ class AgenticChatController {
|
|
|
1585
1915
|
body = '```shell\n' + commandString;
|
|
1586
1916
|
break;
|
|
1587
1917
|
}
|
|
1588
|
-
case
|
|
1918
|
+
case toolConstants_1.FS_WRITE: {
|
|
1589
1919
|
const writeFilePath = toolUse.input.path;
|
|
1590
1920
|
// Validate the path using our synchronous utility
|
|
1591
1921
|
(0, pathValidation_1.validatePathBasic)(writeFilePath);
|
|
1592
1922
|
this.#debug(`Processing ${toolUse.name} for path: ${writeFilePath}`);
|
|
1593
|
-
buttons = [{ id:
|
|
1923
|
+
buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
|
|
1594
1924
|
header = {
|
|
1595
1925
|
icon: 'warning',
|
|
1596
1926
|
iconForegroundStatus: 'warning',
|
|
@@ -1604,12 +1934,12 @@ class AgenticChatController {
|
|
|
1604
1934
|
: `I need permission to modify files outside of your workspace.\n\`${writeFilePath}\``;
|
|
1605
1935
|
break;
|
|
1606
1936
|
}
|
|
1607
|
-
case
|
|
1937
|
+
case toolConstants_1.FS_REPLACE: {
|
|
1608
1938
|
const writeFilePath = toolUse.input.path;
|
|
1609
1939
|
// For replace, we need to verify the file exists
|
|
1610
1940
|
(0, pathValidation_1.validatePathExists)(writeFilePath);
|
|
1611
1941
|
this.#debug(`Processing ${toolUse.name} for path: ${writeFilePath}`);
|
|
1612
|
-
buttons = [{ id:
|
|
1942
|
+
buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
|
|
1613
1943
|
header = {
|
|
1614
1944
|
icon: 'warning',
|
|
1615
1945
|
iconForegroundStatus: 'warning',
|
|
@@ -1623,9 +1953,9 @@ class AgenticChatController {
|
|
|
1623
1953
|
: `I need permission to modify files outside of your workspace.\n\`${writeFilePath}\``;
|
|
1624
1954
|
break;
|
|
1625
1955
|
}
|
|
1626
|
-
case
|
|
1627
|
-
case
|
|
1628
|
-
buttons = [{ id:
|
|
1956
|
+
case toolConstants_1.FS_READ:
|
|
1957
|
+
case toolConstants_1.LIST_DIRECTORY: {
|
|
1958
|
+
buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
|
|
1629
1959
|
header = {
|
|
1630
1960
|
icon: 'tools',
|
|
1631
1961
|
iconForegroundStatus: 'tools',
|
|
@@ -1634,7 +1964,7 @@ class AgenticChatController {
|
|
|
1634
1964
|
: '#### Allow read-only tools outside your workspace',
|
|
1635
1965
|
buttons,
|
|
1636
1966
|
};
|
|
1637
|
-
if (toolName ===
|
|
1967
|
+
if (toolName === toolConstants_1.FS_READ) {
|
|
1638
1968
|
const paths = toolUse.input.paths;
|
|
1639
1969
|
// Validate paths using our synchronous utility
|
|
1640
1970
|
(0, pathValidation_1.validatePaths)(paths);
|
|
@@ -1645,7 +1975,7 @@ class AgenticChatController {
|
|
|
1645
1975
|
? `I need permission to read files.\n${formattedPaths.join('\n')}`
|
|
1646
1976
|
: `I need permission to read files outside the workspace.\n${formattedPaths.join('\n')}`;
|
|
1647
1977
|
}
|
|
1648
|
-
else {
|
|
1978
|
+
else if (toolName === 'listDirectory') {
|
|
1649
1979
|
const readFilePath = toolUse.input.path;
|
|
1650
1980
|
// Validate the path using our synchronous utility
|
|
1651
1981
|
(0, pathValidation_1.validatePathExists)(readFilePath);
|
|
@@ -1654,11 +1984,17 @@ class AgenticChatController {
|
|
|
1654
1984
|
? `I need permission to list directories.\n\`${readFilePath}\``
|
|
1655
1985
|
: `I need permission to list directories outside the workspace.\n\`${readFilePath}\``;
|
|
1656
1986
|
}
|
|
1987
|
+
else {
|
|
1988
|
+
const readFilePath = toolUse.input.path;
|
|
1989
|
+
body = builtInPermission
|
|
1990
|
+
? `I need permission to search files.\n\`${readFilePath}\``
|
|
1991
|
+
: `I need permission to search files outside the workspace.\n\`${readFilePath}\``;
|
|
1992
|
+
}
|
|
1657
1993
|
break;
|
|
1658
1994
|
}
|
|
1659
1995
|
default: {
|
|
1660
1996
|
// — DEFAULT ⇒ MCP tools
|
|
1661
|
-
buttons = [{ id:
|
|
1997
|
+
buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
|
|
1662
1998
|
header = {
|
|
1663
1999
|
icon: 'tools',
|
|
1664
2000
|
iconForegroundStatus: 'warning',
|
|
@@ -1676,7 +2012,7 @@ class AgenticChatController {
|
|
|
1676
2012
|
type: 'tool',
|
|
1677
2013
|
messageId: this.#getMessageIdForToolUse(toolType, toolUse),
|
|
1678
2014
|
header,
|
|
1679
|
-
body: warning ? (toolName ===
|
|
2015
|
+
body: warning ? (toolName === toolConstants_1.EXECUTE_BASH ? '' : '\n\n') + body : body,
|
|
1680
2016
|
};
|
|
1681
2017
|
}
|
|
1682
2018
|
else {
|
|
@@ -1689,9 +2025,9 @@ class AgenticChatController {
|
|
|
1689
2025
|
icon: 'tools',
|
|
1690
2026
|
body: `${toolName}`,
|
|
1691
2027
|
buttons: [
|
|
1692
|
-
{ id:
|
|
2028
|
+
{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Run', icon: 'play', status: 'clear' },
|
|
1693
2029
|
{
|
|
1694
|
-
id:
|
|
2030
|
+
id: toolConstants_1.BUTTON_REJECT_MCP_TOOL,
|
|
1695
2031
|
text: 'Reject',
|
|
1696
2032
|
icon: 'cancel',
|
|
1697
2033
|
status: 'dimmed-clear',
|
|
@@ -1737,7 +2073,7 @@ class AgenticChatController {
|
|
|
1737
2073
|
},
|
|
1738
2074
|
},
|
|
1739
2075
|
},
|
|
1740
|
-
buttons: [{ id:
|
|
2076
|
+
buttons: [{ id: toolConstants_1.BUTTON_UNDO_CHANGES, text: 'Undo', icon: 'undo' }],
|
|
1741
2077
|
},
|
|
1742
2078
|
};
|
|
1743
2079
|
}
|
|
@@ -1751,7 +2087,7 @@ class AgenticChatController {
|
|
|
1751
2087
|
chatResultStream.setMessageIdToUpdateForTool(toolUse.name, messageIdToUpdate);
|
|
1752
2088
|
}
|
|
1753
2089
|
let currentPaths = [];
|
|
1754
|
-
if (toolUse.name ===
|
|
2090
|
+
if (toolUse.name === toolConstants_1.FS_READ) {
|
|
1755
2091
|
currentPaths = toolUse.input?.paths;
|
|
1756
2092
|
}
|
|
1757
2093
|
else {
|
|
@@ -1782,9 +2118,9 @@ class AgenticChatController {
|
|
|
1782
2118
|
}
|
|
1783
2119
|
else {
|
|
1784
2120
|
title =
|
|
1785
|
-
toolUse.name ===
|
|
2121
|
+
toolUse.name === toolConstants_1.FS_READ
|
|
1786
2122
|
? `${itemCount} file${itemCount > 1 ? 's' : ''} read`
|
|
1787
|
-
: toolUse.name ===
|
|
2123
|
+
: toolUse.name === toolConstants_1.FILE_SEARCH
|
|
1788
2124
|
? `${itemCount} ${itemCount === 1 ? 'directory' : 'directories'} searched`
|
|
1789
2125
|
: `${itemCount} ${itemCount === 1 ? 'directory' : 'directories'} listed`;
|
|
1790
2126
|
}
|
|
@@ -1811,7 +2147,7 @@ class AgenticChatController {
|
|
|
1811
2147
|
* Process grep search results and format them for display in the chat UI
|
|
1812
2148
|
*/
|
|
1813
2149
|
#processGrepSearchResult(toolUse, result, chatResultStream) {
|
|
1814
|
-
if (toolUse.name !==
|
|
2150
|
+
if (toolUse.name !== toolConstants_1.GREP_SEARCH) {
|
|
1815
2151
|
return undefined;
|
|
1816
2152
|
}
|
|
1817
2153
|
let messageIdToUpdate = toolUse.toolUseId;
|
|
@@ -1880,6 +2216,8 @@ class AgenticChatController {
|
|
|
1880
2216
|
cursorState: undefined,
|
|
1881
2217
|
useRelevantDocuments: false,
|
|
1882
2218
|
};
|
|
2219
|
+
// Clear images to avoid passing them again in follow-up toolUse/toolResult loops, as it is may confuse the model
|
|
2220
|
+
updatedRequestInput.conversationState.currentMessage.userInputMessage.images = [];
|
|
1883
2221
|
for (const toolResult of toolResults) {
|
|
1884
2222
|
this.#debug(`ToolResult: ${JSON.stringify(toolResult)}`);
|
|
1885
2223
|
updatedRequestInput.conversationState.currentMessage.userInputMessage.userInputMessageContext.toolResults.push({
|
|
@@ -1891,22 +2229,22 @@ class AgenticChatController {
|
|
|
1891
2229
|
/**
|
|
1892
2230
|
* Handles the final result after the agent loop completes
|
|
1893
2231
|
*/
|
|
1894
|
-
async #handleFinalResult(result, session,
|
|
2232
|
+
async #handleFinalResult(result, session, tabId, metric, triggerContext, isNewConversation, chatResultStream) {
|
|
1895
2233
|
if (!result.success) {
|
|
1896
2234
|
throw new errors_2.AgenticChatError(result.error, 'FailedResult');
|
|
1897
2235
|
}
|
|
1898
2236
|
const conversationId = session.conversationId;
|
|
1899
2237
|
this.#debug('Final session conversation id:', conversationId || '');
|
|
1900
2238
|
if (conversationId) {
|
|
1901
|
-
this.#telemetryController.setConversationId(
|
|
2239
|
+
this.#telemetryController.setConversationId(tabId, conversationId);
|
|
1902
2240
|
if (isNewConversation) {
|
|
1903
|
-
this.#telemetryController.updateTriggerInfo(
|
|
2241
|
+
this.#telemetryController.updateTriggerInfo(tabId, {
|
|
1904
2242
|
startTrigger: {
|
|
1905
2243
|
hasUserSnippet: metric.metric.cwsprChatHasCodeSnippet ?? false,
|
|
1906
2244
|
triggerType: triggerContext.triggerType,
|
|
1907
2245
|
},
|
|
1908
2246
|
});
|
|
1909
|
-
this.#telemetryController.emitStartConversationMetric(
|
|
2247
|
+
this.#telemetryController.emitStartConversationMetric(tabId, metric.metric);
|
|
1910
2248
|
}
|
|
1911
2249
|
}
|
|
1912
2250
|
metric.setDimension('codewhispererCustomizationArn', this.#customizationArn);
|
|
@@ -1940,8 +2278,8 @@ class AgenticChatController {
|
|
|
1940
2278
|
cwsprChatPinnedPromptContextCount: triggerContext.contextInfo.pinnedContextCount.promptContextCount,
|
|
1941
2279
|
});
|
|
1942
2280
|
}
|
|
1943
|
-
await this.#telemetryController.emitAddMessageMetric(
|
|
1944
|
-
this.#telemetryController.updateTriggerInfo(
|
|
2281
|
+
await this.#telemetryController.emitAddMessageMetric(tabId, metric.metric, 'Succeeded');
|
|
2282
|
+
this.#telemetryController.updateTriggerInfo(tabId, {
|
|
1945
2283
|
lastMessageTrigger: {
|
|
1946
2284
|
...triggerContext,
|
|
1947
2285
|
messageId: result.data?.chatResult.messageId,
|
|
@@ -2191,7 +2529,7 @@ class AgenticChatController {
|
|
|
2191
2529
|
const session = this.#chatSessionManagementService.getSession(params.tabId);
|
|
2192
2530
|
const toolUseId = params.messageId;
|
|
2193
2531
|
const toolUse = toolUseId ? session.data?.toolUseLookup.get(toolUseId) : undefined;
|
|
2194
|
-
if (toolUse?.name ===
|
|
2532
|
+
if (toolUse?.name === toolConstants_1.FS_WRITE || toolUse?.name === toolConstants_1.FS_REPLACE) {
|
|
2195
2533
|
const input = toolUse.input;
|
|
2196
2534
|
this.#features.lsp.workspace.openFileDiff({
|
|
2197
2535
|
originalFileUri: input.path,
|
|
@@ -2200,7 +2538,7 @@ class AgenticChatController {
|
|
|
2200
2538
|
fileContent: toolUse.fileChange?.after,
|
|
2201
2539
|
});
|
|
2202
2540
|
}
|
|
2203
|
-
else if (toolUse?.name ===
|
|
2541
|
+
else if (toolUse?.name === toolConstants_1.FS_READ) {
|
|
2204
2542
|
await this.#features.lsp.window.showDocument({ uri: vscode_uri_1.URI.file(params.filePath).toString() });
|
|
2205
2543
|
}
|
|
2206
2544
|
else {
|
|
@@ -2410,9 +2748,31 @@ class AgenticChatController {
|
|
|
2410
2748
|
});
|
|
2411
2749
|
return triggerContext;
|
|
2412
2750
|
}
|
|
2751
|
+
async #invalidateCompactCommand(tabId, messageIds) {
|
|
2752
|
+
for (const messageId of messageIds) {
|
|
2753
|
+
await this.#features.chat.sendChatUpdate({
|
|
2754
|
+
tabId,
|
|
2755
|
+
state: { inProgress: false },
|
|
2756
|
+
data: {
|
|
2757
|
+
messages: [
|
|
2758
|
+
{
|
|
2759
|
+
messageId,
|
|
2760
|
+
type: 'tool',
|
|
2761
|
+
body: constants_2.COMPACTION_BODY,
|
|
2762
|
+
header: {
|
|
2763
|
+
body: constants_2.COMPACTION_HEADER_BODY,
|
|
2764
|
+
status: { icon: 'block', text: 'Ignored' },
|
|
2765
|
+
buttons: [],
|
|
2766
|
+
},
|
|
2767
|
+
},
|
|
2768
|
+
],
|
|
2769
|
+
},
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2413
2773
|
async #invalidateAllShellCommands(tabId, session) {
|
|
2414
2774
|
for (const [toolUseId, toolUse] of session.toolUseLookup.entries()) {
|
|
2415
|
-
if (toolUse.name !==
|
|
2775
|
+
if (toolUse.name !== toolConstants_1.EXECUTE_BASH || this.#stoppedToolUses.has(toolUseId))
|
|
2416
2776
|
continue;
|
|
2417
2777
|
const params = toolUse.input;
|
|
2418
2778
|
const command = params.command;
|
|
@@ -2637,7 +2997,7 @@ class AgenticChatController {
|
|
|
2637
2997
|
return;
|
|
2638
2998
|
}
|
|
2639
2999
|
}
|
|
2640
|
-
async #processAgenticChatResponseWithTimeout(response, metric, chatResultStream, session, contextList) {
|
|
3000
|
+
async #processAgenticChatResponseWithTimeout(response, metric, chatResultStream, session, contextList, isCompaction) {
|
|
2641
3001
|
const abortController = new AbortController();
|
|
2642
3002
|
let timeoutId;
|
|
2643
3003
|
const timeoutPromise = new Promise((_, reject) => {
|
|
@@ -2647,7 +3007,7 @@ class AgenticChatController {
|
|
|
2647
3007
|
}, constants_2.RESPONSE_TIMEOUT_MS);
|
|
2648
3008
|
});
|
|
2649
3009
|
const streamWriter = chatResultStream.getResultStreamWriter();
|
|
2650
|
-
const processResponsePromise = this.#processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortController.signal);
|
|
3010
|
+
const processResponsePromise = this.#processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortController.signal, isCompaction);
|
|
2651
3011
|
try {
|
|
2652
3012
|
const result = await Promise.race([processResponsePromise, timeoutPromise]);
|
|
2653
3013
|
clearTimeout(timeoutId);
|
|
@@ -2671,7 +3031,7 @@ class AgenticChatController {
|
|
|
2671
3031
|
}
|
|
2672
3032
|
const toolUses = Object.values(data.toolUses);
|
|
2673
3033
|
for (const toolUse of toolUses) {
|
|
2674
|
-
if ((toolUse.name ===
|
|
3034
|
+
if ((toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) && typeof toolUse.input === 'string') {
|
|
2675
3035
|
const filepath = extractKey(toolUse.input, 'path');
|
|
2676
3036
|
const msgId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId;
|
|
2677
3037
|
// render fs write UI as soon as fs write starts
|
|
@@ -2700,7 +3060,7 @@ class AgenticChatController {
|
|
|
2700
3060
|
}
|
|
2701
3061
|
// render the tool use explanatory as soon as this is received for fsWrite/fsReplace
|
|
2702
3062
|
const explanation = extractKey(toolUse.input, 'explanation');
|
|
2703
|
-
const messageId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId +
|
|
3063
|
+
const messageId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId + toolConstants_1.SUFFIX_EXPLANATION;
|
|
2704
3064
|
if (explanation && !chatResultStream.hasMessage(messageId)) {
|
|
2705
3065
|
await streamWriter.close();
|
|
2706
3066
|
await chatResultStream.writeResultBlock({
|
|
@@ -2712,12 +3072,12 @@ class AgenticChatController {
|
|
|
2712
3072
|
}
|
|
2713
3073
|
}
|
|
2714
3074
|
}
|
|
2715
|
-
async #processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortSignal) {
|
|
3075
|
+
async #processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortSignal, isCompaction) {
|
|
2716
3076
|
const requestId = response.$metadata.requestId;
|
|
2717
3077
|
const chatEventParser = new agenticChatEventParser_1.AgenticChatEventParser(requestId, metric, this.#features.logging);
|
|
2718
3078
|
// Display context transparency list once at the beginning of response
|
|
2719
3079
|
// Use a flag to track if contextList has been sent already to avoid ux flickering
|
|
2720
|
-
if (contextList?.filePaths && contextList.filePaths.length > 0 && !session.contextListSent) {
|
|
3080
|
+
if (!isCompaction && contextList?.filePaths && contextList.filePaths.length > 0 && !session.contextListSent) {
|
|
2721
3081
|
await streamWriter.write({ body: '', contextList });
|
|
2722
3082
|
session.contextListSent = true;
|
|
2723
3083
|
}
|
|
@@ -2751,7 +3111,7 @@ class AgenticChatController {
|
|
|
2751
3111
|
if (chatEvent.assistantResponseEvent && result.data.chatResult.body) {
|
|
2752
3112
|
this.recordChunk('chunk');
|
|
2753
3113
|
}
|
|
2754
|
-
//
|
|
3114
|
+
// update the UI with response
|
|
2755
3115
|
if (chatEvent.assistantResponseEvent || chatEvent.codeReferenceEvent) {
|
|
2756
3116
|
await streamWriter.write(result.data.chatResult);
|
|
2757
3117
|
}
|
|
@@ -2761,9 +3121,17 @@ class AgenticChatController {
|
|
|
2761
3121
|
}
|
|
2762
3122
|
}
|
|
2763
3123
|
if (isEmptyResponse) {
|
|
2764
|
-
// If the response is empty, we need to send an empty answer message to remove the
|
|
3124
|
+
// If the response is empty, we need to send an empty answer message to remove the Working... indicator
|
|
2765
3125
|
await streamWriter.write({ type: 'answer', body: '', messageId: (0, uuid_1.v4)() });
|
|
2766
3126
|
}
|
|
3127
|
+
if (isCompaction) {
|
|
3128
|
+
// Show a dummy message to the UI for compaction completion
|
|
3129
|
+
await streamWriter.write({
|
|
3130
|
+
type: 'answer',
|
|
3131
|
+
body: 'Conversation history has been compacted successfully!',
|
|
3132
|
+
messageId: (0, uuid_1.v4)(),
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
2767
3135
|
await streamWriter.close();
|
|
2768
3136
|
metric.mergeWith({
|
|
2769
3137
|
cwsprChatFullResponseLatency: metric.getTimeElapsed(),
|
|
@@ -3027,6 +3395,14 @@ class AgenticChatController {
|
|
|
3027
3395
|
#debug(...messages) {
|
|
3028
3396
|
this.#features.logging.debug(messages.join(' '));
|
|
3029
3397
|
}
|
|
3398
|
+
// Helper function to sanitize the 'images' field for logging by replacing large binary data (e.g., Uint8Array) with a concise summary.
|
|
3399
|
+
// This prevents logs from being overwhelmed by raw byte arrays and keeps log output readable.
|
|
3400
|
+
#imageReplacer(key, value) {
|
|
3401
|
+
if (key === 'bytes' && value && typeof value.length === 'number') {
|
|
3402
|
+
return `[Uint8Array, length: ${value.length}]`;
|
|
3403
|
+
}
|
|
3404
|
+
return value;
|
|
3405
|
+
}
|
|
3030
3406
|
}
|
|
3031
3407
|
exports.AgenticChatController = AgenticChatController;
|
|
3032
3408
|
//# sourceMappingURL=agenticChatController.js.map
|