@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.
Files changed (113) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +5 -0
  3. package/out/language-server/agenticChat/agenticChatController.d.ts +9 -2
  4. package/out/language-server/agenticChat/agenticChatController.js +512 -136
  5. package/out/language-server/agenticChat/agenticChatController.js.map +1 -1
  6. package/out/language-server/agenticChat/constants/constants.d.ts +17 -0
  7. package/out/language-server/agenticChat/constants/constants.js +79 -1
  8. package/out/language-server/agenticChat/constants/constants.js.map +1 -1
  9. package/out/language-server/agenticChat/constants/toolConstants.d.ts +24 -0
  10. package/out/language-server/agenticChat/constants/toolConstants.js +35 -0
  11. package/out/language-server/agenticChat/constants/toolConstants.js.map +1 -0
  12. package/out/language-server/agenticChat/context/additionalContextProvider.d.ts +9 -1
  13. package/out/language-server/agenticChat/context/additionalContextProvider.js +92 -34
  14. package/out/language-server/agenticChat/context/additionalContextProvider.js.map +1 -1
  15. package/out/language-server/agenticChat/context/agenticChatTriggerContext.d.ts +11 -2
  16. package/out/language-server/agenticChat/context/agenticChatTriggerContext.js +36 -2
  17. package/out/language-server/agenticChat/context/agenticChatTriggerContext.js.map +1 -1
  18. package/out/language-server/agenticChat/context/contextCommandsProvider.js +1 -1
  19. package/out/language-server/agenticChat/context/contextCommandsProvider.js.map +1 -1
  20. package/out/language-server/agenticChat/context/contextUtils.d.ts +16 -0
  21. package/out/language-server/agenticChat/context/contextUtils.js +29 -0
  22. package/out/language-server/agenticChat/context/contextUtils.js.map +1 -1
  23. package/out/language-server/agenticChat/qAgenticChatServer.d.ts +2 -0
  24. package/out/language-server/agenticChat/qAgenticChatServer.js +18 -2
  25. package/out/language-server/agenticChat/qAgenticChatServer.js.map +1 -1
  26. package/out/language-server/agenticChat/tools/chatDb/chatDb.d.ts +11 -3
  27. package/out/language-server/agenticChat/tools/chatDb/chatDb.js +115 -31
  28. package/out/language-server/agenticChat/tools/chatDb/chatDb.js.map +1 -1
  29. package/out/language-server/agenticChat/tools/chatDb/util.d.ts +15 -1
  30. package/out/language-server/agenticChat/tools/chatDb/util.js +17 -0
  31. package/out/language-server/agenticChat/tools/chatDb/util.js.map +1 -1
  32. package/out/language-server/agenticChat/tools/executeBash.d.ts +0 -11
  33. package/out/language-server/agenticChat/tools/executeBash.js +16 -35
  34. package/out/language-server/agenticChat/tools/executeBash.js.map +1 -1
  35. package/out/language-server/agenticChat/tools/grepSearch.js +3 -1
  36. package/out/language-server/agenticChat/tools/grepSearch.js.map +1 -1
  37. package/out/language-server/agenticChat/tools/mcp/mcpEventHandler.js +208 -170
  38. package/out/language-server/agenticChat/tools/mcp/mcpEventHandler.js.map +1 -1
  39. package/out/language-server/agenticChat/tools/mcp/mcpManager.d.ts +12 -18
  40. package/out/language-server/agenticChat/tools/mcp/mcpManager.js +353 -216
  41. package/out/language-server/agenticChat/tools/mcp/mcpManager.js.map +1 -1
  42. package/out/language-server/agenticChat/tools/mcp/mcpTypes.d.ts +29 -0
  43. package/out/language-server/agenticChat/tools/mcp/mcpTypes.js +60 -1
  44. package/out/language-server/agenticChat/tools/mcp/mcpTypes.js.map +1 -1
  45. package/out/language-server/agenticChat/tools/mcp/mcpUtils.d.ts +27 -4
  46. package/out/language-server/agenticChat/tools/mcp/mcpUtils.js +464 -2
  47. package/out/language-server/agenticChat/tools/mcp/mcpUtils.js.map +1 -1
  48. package/out/language-server/agenticChat/tools/toolServer.js +8 -10
  49. package/out/language-server/agenticChat/tools/toolServer.js.map +1 -1
  50. package/out/language-server/agenticChat/tools/toolShared.js +6 -2
  51. package/out/language-server/agenticChat/tools/toolShared.js.map +1 -1
  52. package/out/language-server/chat/chatController.d.ts +1 -1
  53. package/out/language-server/chat/chatController.js.map +1 -1
  54. package/out/language-server/chat/chatSessionService.d.ts +1 -0
  55. package/out/language-server/chat/chatSessionService.js +5 -2
  56. package/out/language-server/chat/chatSessionService.js.map +1 -1
  57. package/out/language-server/chat/contexts/triggerContext.js +11 -2
  58. package/out/language-server/chat/contexts/triggerContext.js.map +1 -1
  59. package/out/language-server/chat/quickActions.d.ts +7 -1
  60. package/out/language-server/chat/quickActions.js +7 -1
  61. package/out/language-server/chat/quickActions.js.map +1 -1
  62. package/out/language-server/chat/telemetry/chatTelemetryController.d.ts +1 -0
  63. package/out/language-server/chat/telemetry/chatTelemetryController.js +9 -0
  64. package/out/language-server/chat/telemetry/chatTelemetryController.js.map +1 -1
  65. package/out/language-server/configuration/qConfigurationServer.d.ts +2 -0
  66. package/out/language-server/configuration/qConfigurationServer.js.map +1 -1
  67. package/out/language-server/inline-completion/auto-trigger/autoTrigger.d.ts +3 -0
  68. package/out/language-server/inline-completion/auto-trigger/autoTrigger.js +105 -1
  69. package/out/language-server/inline-completion/auto-trigger/autoTrigger.js.map +1 -1
  70. package/out/language-server/inline-completion/auto-trigger/editPredictionAutoTrigger.js +6 -1
  71. package/out/language-server/inline-completion/auto-trigger/editPredictionAutoTrigger.js.map +1 -1
  72. package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.d.ts +1 -0
  73. package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.js +1 -0
  74. package/out/language-server/inline-completion/auto-trigger/editPredictionConfig.js.map +1 -1
  75. package/out/language-server/inline-completion/codeWhispererServer.js +53 -52
  76. package/out/language-server/inline-completion/codeWhispererServer.js.map +1 -1
  77. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.d.ts +4 -1
  78. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js +36 -2
  79. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js.map +1 -1
  80. package/out/language-server/workspaceContext/dependency/dependencyEventBundler.d.ts +11 -5
  81. package/out/language-server/workspaceContext/dependency/dependencyEventBundler.js +37 -10
  82. package/out/language-server/workspaceContext/dependency/dependencyEventBundler.js.map +1 -1
  83. package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.d.ts +1 -1
  84. package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js +8 -3
  85. package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js.map +1 -1
  86. package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js +3 -4
  87. package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js.map +1 -1
  88. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.d.ts +4 -3
  89. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js +5 -9
  90. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js.map +1 -1
  91. package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js +3 -4
  92. package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js.map +1 -1
  93. package/out/language-server/workspaceContext/workspaceContextServer.js +12 -8
  94. package/out/language-server/workspaceContext/workspaceContextServer.js.map +1 -1
  95. package/out/shared/activeUserTracker.d.ts +52 -0
  96. package/out/shared/activeUserTracker.js +164 -0
  97. package/out/shared/activeUserTracker.js.map +1 -0
  98. package/out/shared/amazonQServiceManager/configurationUtils.d.ts +4 -0
  99. package/out/shared/amazonQServiceManager/configurationUtils.js +6 -0
  100. package/out/shared/amazonQServiceManager/configurationUtils.js.map +1 -1
  101. package/out/shared/codeWhispererService.js +3 -1
  102. package/out/shared/codeWhispererService.js.map +1 -1
  103. package/out/shared/streamingClientService.js +12 -1
  104. package/out/shared/streamingClientService.js.map +1 -1
  105. package/out/shared/telemetry/telemetryService.js +1 -2
  106. package/out/shared/telemetry/telemetryService.js.map +1 -1
  107. package/out/shared/telemetry/types.d.ts +7 -1
  108. package/out/shared/telemetry/types.js +1 -0
  109. package/out/shared/telemetry/types.js.map +1 -1
  110. package/out/shared/utils.d.ts +1 -7
  111. package/out/shared/utils.js +39 -36
  112. package/out/shared/utils.js.map +1 -1
  113. 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 === 'executeBash' || toolType === 'executeBash' ? toolUseId : `${toolUseId}_permission`;
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 === 'run-shell-command' ||
156
- params.buttonId === 'reject-shell-command' ||
157
- params.buttonId === 'reject-mcp-tool' ||
158
- params.buttonId === 'allow-tools') {
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 === 'allow-tools' && params.messageId.endsWith('_permission')
164
- ? params.messageId.replace('_permission', '')
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 === 'reject-shell-command' || params.buttonId === 'reject-mcp-tool'
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 === 'undo-changes') {
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 === 'undo-all-changes') {
198
- const toolUseId = params.messageId.replace('_undoall', '');
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 === 'stop-shell-command') {
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 === 'paidtier-upgrade-q-learnmore') {
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 === 'paidtier-upgrade-q') {
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 !== 'undo-changes');
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: 'undo-changes', messageId, tabId });
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
- // Add image context to triggerContext.documentReference for transparency
521
- await this.#additionalContextProvider.appendCustomContextToTriggerContext(triggerContext, params.context, params.tabId);
522
- // Get the initial request input
523
- const initialRequestInput = await this.#prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream, customContext);
524
- // Generate a unique ID for this prompt
525
- const promptId = crypto.randomUUID();
526
- session.setCurrentPromptId(promptId);
527
- // Start the agent loop
528
- const finalResult = await this.#runAgentLoop(initialRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference, additionalContext.filter(item => item.pinned));
529
- // Phase 5: Result Handling - This happens only once
530
- return await this.#handleFinalResult(finalResult, session, params, metric, triggerContext, isNewConversation, chatResultStream);
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 `thinking` indicator never goes away.
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, customContext) {
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, [], this.#getTools(session), additionalContext, session.modelId, this.#origin, customContext);
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, pinnedContext) {
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, pinnedContext);
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
- messages = this.#chatHistoryDb.fixAndGetHistory(tabId, currentMessage, pinnedContextMessages);
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
- // Do not include chatHistory for requests going to Mynah Backend
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, undefined, 2)}`);
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, pinnedContext) {
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
- let truncatedRelevantDocuments = [];
812
- if (request.conversationState.currentMessage.userInputMessage.userInputMessageContext?.editorState
813
- ?.relevantDocuments) {
814
- for (const relevantDoc of request.conversationState.currentMessage.userInputMessage.userInputMessageContext
815
- ?.editorState?.relevantDocuments) {
816
- const docLength = relevantDoc?.text?.length || 0;
817
- if (remainingCharacterBudget > docLength) {
818
- truncatedRelevantDocuments.push(relevantDoc);
819
- remainingCharacterBudget = remainingCharacterBudget - docLength;
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
- truncatedRelevantDocuments;
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 (!['fsWrite', 'fsReplace', 'fsRead', 'listDirectory'].includes(toolUse.name)) {
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 !== 'fsWrite' && toolUse.name !== 'fsReplace') {
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 + '_explanation',
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 'fsRead':
909
- case 'listDirectory':
910
- case 'grepSearch':
911
- case 'fileSearch':
912
- case 'fsWrite':
913
- case 'fsReplace':
914
- case 'executeBash': {
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
- fsRead: { Tool: fsRead_1.FsRead },
917
- listDirectory: { Tool: listDirectory_1.ListDirectory },
918
- fsWrite: { Tool: fsWrite_1.FsWrite },
919
- fsReplace: { Tool: fsReplace_1.FsReplace },
920
- executeBash: { Tool: executeBash_1.ExecuteBash },
921
- grepSearch: { Tool: grepSearch_1.GrepSearch },
922
- fileSearch: { Tool: fileSearch_1.FileSearch },
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
- // const permission = McpManager.instance.getToolPerm('Built-in', toolUse.name)
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
- // const builtInPermission = permission !== 'alwaysAllow'
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
- // const requiresAcceptance = builtInPermission || toolRequiresAcceptance
936
- if (requiresAcceptance || toolUse.name === 'executeBash') {
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, requiresAcceptance, warning, commandCategory);
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 (requiresAcceptance) {
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 === 'fsWrite' || toolUse.name === 'fsReplace') {
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 'fsRead':
1048
- case 'listDirectory':
1049
- case 'fileSearch':
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 'executeBash':
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 'grepSearch':
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 'fsReplace':
1067
- case 'fsWrite':
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 === 'executeBash' || 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 === 'fsWrite' || toolUse.name === 'fsReplace') && toolUse.toolUseId) {
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 === 'executeBash' && toolUse.toolUseId) {
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 === 'fsRead' || toolUse.name === 'listDirectory') {
1477
+ if (toolUse.name === toolConstants_1.FS_READ || toolUse.name === toolConstants_1.LIST_DIRECTORY) {
1250
1478
  return;
1251
1479
  }
1252
- if (toolUse.name === 'fsWrite' || toolUse.name === 'fsReplace') {
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}_undoall`,
1515
+ messageId: `${session.currentUndoAllId}${toolConstants_1.SUFFIX_UNDOALL}`,
1288
1516
  buttons: [
1289
1517
  {
1290
- id: 'undo-all-changes',
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 'fsRead':
1319
- case 'executeBash':
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 'listDirectory':
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 !== 'executeBash') {
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: [{ id: 'stop-shell-command', text: 'Stop', icon: 'stop' }],
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 === 'executeBash') {
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 ? [{ id: 'stop-shell-command', text: 'Stop', icon: 'stop' }] : [],
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 'fsReplace':
1455
- case 'fsWrite':
1456
- case 'fsRead':
1457
- case 'listDirectory':
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 'fileSearch':
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 'executeBash': {
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: 'reject-shell-command',
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 'fsWrite': {
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: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
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 'fsReplace': {
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: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
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 'fsRead':
1627
- case 'listDirectory': {
1628
- buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
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 === 'fsRead') {
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: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
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 === 'executeBash' ? '' : '\n\n') + body : body,
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: 'allow-tools', text: 'Run', icon: 'play', status: 'clear' },
2028
+ { id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Run', icon: 'play', status: 'clear' },
1693
2029
  {
1694
- id: 'reject-mcp-tool',
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: 'undo-changes', text: 'Undo', icon: 'undo' }],
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 === 'fsRead') {
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 === 'fsRead'
2121
+ toolUse.name === toolConstants_1.FS_READ
1786
2122
  ? `${itemCount} file${itemCount > 1 ? 's' : ''} read`
1787
- : toolUse.name === 'fileSearch'
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 !== 'grepSearch') {
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, params, metric, triggerContext, isNewConversation, chatResultStream) {
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(params.tabId, conversationId);
2239
+ this.#telemetryController.setConversationId(tabId, conversationId);
1902
2240
  if (isNewConversation) {
1903
- this.#telemetryController.updateTriggerInfo(params.tabId, {
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(params.tabId, metric.metric);
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(params.tabId, metric.metric, 'Succeeded');
1944
- this.#telemetryController.updateTriggerInfo(params.tabId, {
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 === 'fsWrite' || toolUse?.name === 'fsReplace') {
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 === 'fsRead') {
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 !== 'executeBash' || this.#stoppedToolUses.has(toolUseId))
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 === 'fsWrite' || toolUse.name === 'fsReplace') && typeof toolUse.input === 'string') {
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 + '_explanation';
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
- // make sure to save code reference events
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 Thinking... indicator
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