@aws/lsp-codewhisperer 0.0.64 → 0.0.66

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 (109) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/out/client/token/bearer-token-service.json +1 -1
  3. package/out/language-server/agenticChat/agenticChatController.d.ts +13 -0
  4. package/out/language-server/agenticChat/agenticChatController.js +547 -125
  5. package/out/language-server/agenticChat/agenticChatController.js.map +1 -1
  6. package/out/language-server/agenticChat/constants/constants.d.ts +12 -0
  7. package/out/language-server/agenticChat/constants/constants.js +73 -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 +1 -1
  13. package/out/language-server/agenticChat/context/additionalContextProvider.js +44 -4
  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 +32 -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.js +6 -2
  24. package/out/language-server/agenticChat/qAgenticChatServer.js.map +1 -1
  25. package/out/language-server/agenticChat/tools/chatDb/chatDb.d.ts +11 -3
  26. package/out/language-server/agenticChat/tools/chatDb/chatDb.js +103 -31
  27. package/out/language-server/agenticChat/tools/chatDb/chatDb.js.map +1 -1
  28. package/out/language-server/agenticChat/tools/chatDb/util.d.ts +4 -0
  29. package/out/language-server/agenticChat/tools/chatDb/util.js.map +1 -1
  30. package/out/language-server/agenticChat/tools/executeBash.d.ts +15 -5
  31. package/out/language-server/agenticChat/tools/executeBash.js +97 -22
  32. package/out/language-server/agenticChat/tools/executeBash.js.map +1 -1
  33. package/out/language-server/agenticChat/tools/mcp/mcpManager.js +1 -1
  34. package/out/language-server/agenticChat/tools/mcp/mcpManager.js.map +1 -1
  35. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReview.d.ts +211 -0
  36. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReview.js +630 -0
  37. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReview.js.map +1 -0
  38. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewConstants.d.ts +34 -0
  39. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewConstants.js +200 -0
  40. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewConstants.js.map +1 -0
  41. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewErrors.d.ts +12 -0
  42. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewErrors.js +32 -0
  43. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewErrors.js.map +1 -0
  44. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewSchemas.d.ts +289 -0
  45. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewSchemas.js +140 -0
  46. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewSchemas.js.map +1 -0
  47. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewTypes.d.ts +58 -0
  48. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewTypes.js +3 -0
  49. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewTypes.js.map +1 -0
  50. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewUtils.d.ts +156 -0
  51. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewUtils.js +363 -0
  52. package/out/language-server/agenticChat/tools/qCodeAnalysis/qCodeReviewUtils.js.map +1 -0
  53. package/out/language-server/agenticChat/tools/toolServer.d.ts +1 -0
  54. package/out/language-server/agenticChat/tools/toolServer.js +90 -39
  55. package/out/language-server/agenticChat/tools/toolServer.js.map +1 -1
  56. package/out/language-server/chat/chatSessionService.d.ts +1 -0
  57. package/out/language-server/chat/chatSessionService.js +5 -2
  58. package/out/language-server/chat/chatSessionService.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 +4 -3
  63. package/out/language-server/chat/telemetry/chatTelemetryController.js +22 -3
  64. package/out/language-server/chat/telemetry/chatTelemetryController.js.map +1 -1
  65. package/out/language-server/configuration/qConfigurationServer.d.ts +1 -0
  66. package/out/language-server/configuration/qConfigurationServer.js.map +1 -1
  67. package/out/language-server/inline-completion/auto-trigger/autoTrigger.d.ts +2 -0
  68. package/out/language-server/inline-completion/auto-trigger/autoTrigger.js +69 -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 +72 -43
  76. package/out/language-server/inline-completion/codeWhispererServer.js.map +1 -1
  77. package/out/language-server/workspaceContext/artifactManager.d.ts +4 -1
  78. package/out/language-server/workspaceContext/artifactManager.js +16 -1
  79. package/out/language-server/workspaceContext/artifactManager.js.map +1 -1
  80. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.d.ts +2 -1
  81. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js +9 -6
  82. package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js.map +1 -1
  83. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.d.ts +7 -2
  84. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js +20 -7
  85. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js.map +1 -1
  86. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.d.ts +2 -2
  87. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.js +4 -4
  88. package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.js.map +1 -1
  89. package/out/language-server/workspaceContext/fileUploadJobManager.js +3 -1
  90. package/out/language-server/workspaceContext/fileUploadJobManager.js.map +1 -1
  91. package/out/language-server/workspaceContext/workspaceContextServer.js +32 -19
  92. package/out/language-server/workspaceContext/workspaceContextServer.js.map +1 -1
  93. package/out/language-server/workspaceContext/workspaceFolderManager.d.ts +5 -3
  94. package/out/language-server/workspaceContext/workspaceFolderManager.js +80 -59
  95. package/out/language-server/workspaceContext/workspaceFolderManager.js.map +1 -1
  96. package/out/shared/activeUserTracker.d.ts +52 -0
  97. package/out/shared/activeUserTracker.js +164 -0
  98. package/out/shared/activeUserTracker.js.map +1 -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/telemetry/telemetryService.d.ts +2 -0
  104. package/out/shared/telemetry/telemetryService.js +2 -0
  105. package/out/shared/telemetry/telemetryService.js.map +1 -1
  106. package/out/shared/telemetry/types.d.ts +9 -1
  107. package/out/shared/telemetry/types.js +1 -0
  108. package/out/shared/telemetry/types.js.map +1 -1
  109. package/package.json +8 -6
@@ -7,7 +7,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.AgenticChatController = void 0;
8
8
  const crypto = require("crypto");
9
9
  const path = require("path");
10
+ const os = require("os");
10
11
  const codewhisperer_streaming_1 = require("@amzn/codewhisperer-streaming");
12
+ const toolConstants_1 = require("./constants/toolConstants");
11
13
  const protocol_1 = require("@aws/language-server-runtimes/protocol");
12
14
  const protocol_2 = require("@aws/language-server-runtimes/protocol");
13
15
  const server_interface_1 = require("@aws/language-server-runtimes/server-interface");
@@ -49,6 +51,8 @@ const errors_2 = require("./errors");
49
51
  const vscode_uri_1 = require("vscode-uri");
50
52
  const executeBash_2 = require("./tools/executeBash");
51
53
  const userWrittenCodeTracker_1 = require("../../shared/userWrittenCodeTracker");
54
+ const qCodeReview_1 = require("./tools/qCodeAnalysis/qCodeReview");
55
+ const qCodeReviewConstants_1 = require("./tools/qCodeAnalysis/qCodeReviewConstants");
52
56
  const mcpEventHandler_1 = require("./tools/mcp/mcpEventHandler");
53
57
  const mcpUtils_1 = require("./tools/mcp/mcpUtils");
54
58
  const mcpManager_1 = require("./tools/mcp/mcpManager");
@@ -59,6 +63,7 @@ const modelSelection_1 = require("./constants/modelSelection");
59
63
  const imageVerification_1 = require("../../shared/imageVerification");
60
64
  const path_1 = require("@aws/lsp-core/out/util/path");
61
65
  const agenticChatControllerHelper_1 = require("./utils/agenticChatControllerHelper");
66
+ const activeUserTracker_1 = require("../../shared/activeUserTracker");
62
67
  class AgenticChatController {
63
68
  #features;
64
69
  #chatSessionManagementService;
@@ -78,6 +83,7 @@ class AgenticChatController {
78
83
  #mcpEventHandler;
79
84
  #paidTierMode;
80
85
  #origin;
86
+ #activeUserTracker;
81
87
  // latency metrics
82
88
  #llmRequestStartTime = 0;
83
89
  #toolCallLatencies = [];
@@ -85,6 +91,9 @@ class AgenticChatController {
85
91
  #timeToFirstChunk = -1;
86
92
  #timeBetweenChunks = [];
87
93
  #lastChunkTime = 0;
94
+ // A/B testing allocation
95
+ #abTestingFetchingTimeout;
96
+ #abTestingAllocation;
88
97
  /**
89
98
  * Determines the appropriate message ID for a tool use based on tool type and name
90
99
  * @param toolType The type of tool being used
@@ -94,7 +103,34 @@ class AgenticChatController {
94
103
  #getMessageIdForToolUse(toolType, toolUse) {
95
104
  const toolUseId = toolUse.toolUseId;
96
105
  // Return plain toolUseId for executeBash, add "_permission" suffix for all other tools
97
- return toolUse.name === 'executeBash' || toolType === 'executeBash' ? toolUseId : `${toolUseId}_permission`;
106
+ return toolUse.name === toolConstants_1.EXECUTE_BASH || toolType === toolConstants_1.EXECUTE_BASH
107
+ ? toolUseId
108
+ : `${toolUseId}${toolConstants_1.SUFFIX_PERMISSION}`;
109
+ }
110
+ /**
111
+ * Logs system information that can be helpful for debugging customer issues
112
+ */
113
+ logSystemInformation() {
114
+ const clientInfo = this.#features.lsp.getClientInitializeParams()?.clientInfo;
115
+ const systemInfo = {
116
+ languageServerVersion: this.#features.runtime.serverInfo.version ?? 'unknown',
117
+ clientName: clientInfo?.name ?? 'unknown',
118
+ clientVersion: clientInfo?.version ?? 'unknown',
119
+ OS: os.platform(),
120
+ OSVersion: os.release(),
121
+ ComputeEnv: process.env.COMPUTE_ENV ?? 'unknown',
122
+ extensionVersion: this.#features.lsp.getClientInitializeParams()?.initializationOptions?.aws?.clientInfo?.extension
123
+ ?.version,
124
+ };
125
+ this.#features.logging.info(`System Information: ${JSON.stringify(systemInfo)}`);
126
+ }
127
+ /**
128
+ * Determines the appropriate message ID for a compaction confirmation
129
+ * @param messageId The original messageId
130
+ * @returns The message ID to use
131
+ */
132
+ #getMessageIdForCompact(messageId) {
133
+ return `${messageId}_compact`;
98
134
  }
99
135
  constructor(chatSessionManagementService, features, telemetryService, serviceManager) {
100
136
  this.#features = features;
@@ -113,6 +149,7 @@ class AgenticChatController {
113
149
  this.#contextCommandsProvider = new contextCommandsProvider_1.ContextCommandsProvider(this.#features.logging, this.#features.chat, this.#features.workspace, this.#features.lsp);
114
150
  this.#mcpEventHandler = new mcpEventHandler_1.McpEventHandler(features, telemetryService);
115
151
  this.#origin = (0, utils_2.getOriginFromClientInfo)(this.#features.lsp.getClientInitializeParams()?.clientInfo?.name);
152
+ this.#activeUserTracker = activeUserTracker_1.ActiveUserTracker.getInstance(this.#features);
116
153
  }
117
154
  async onExecuteCommand(params, _token) {
118
155
  this.#log(`onExecuteCommand: ${params.command}`);
@@ -129,16 +166,16 @@ class AgenticChatController {
129
166
  async onButtonClick(params) {
130
167
  this.#log(`onButtonClick event with params: ${JSON.stringify(params)}`);
131
168
  const session = this.#chatSessionManagementService.getSession(params.tabId);
132
- if (params.buttonId === 'run-shell-command' ||
133
- params.buttonId === 'reject-shell-command' ||
134
- params.buttonId === 'reject-mcp-tool' ||
135
- params.buttonId === 'allow-tools') {
169
+ if (params.buttonId === toolConstants_1.BUTTON_RUN_SHELL_COMMAND ||
170
+ params.buttonId === toolConstants_1.BUTTON_REJECT_SHELL_COMMAND ||
171
+ params.buttonId === toolConstants_1.BUTTON_REJECT_MCP_TOOL ||
172
+ params.buttonId === toolConstants_1.BUTTON_ALLOW_TOOLS) {
136
173
  if (!session.data) {
137
174
  return { success: false, failureReason: `could not find chat session for tab: ${params.tabId} ` };
138
175
  }
139
176
  // For 'allow-tools', remove suffix as permission card needs to be seperate from file list card
140
- const messageId = params.buttonId === 'allow-tools' && params.messageId.endsWith('_permission')
141
- ? params.messageId.replace('_permission', '')
177
+ const messageId = params.buttonId === toolConstants_1.BUTTON_ALLOW_TOOLS && params.messageId.endsWith(toolConstants_1.SUFFIX_PERMISSION)
178
+ ? params.messageId.replace(toolConstants_1.SUFFIX_PERMISSION, '')
142
179
  : params.messageId;
143
180
  const handler = session.data.getDeferredToolExecution(messageId);
144
181
  if (!handler?.reject || !handler.resolve) {
@@ -147,7 +184,7 @@ class AgenticChatController {
147
184
  failureReason: `could not find deferred tool execution for message: ${messageId} `,
148
185
  };
149
186
  }
150
- params.buttonId === 'reject-shell-command' || params.buttonId === 'reject-mcp-tool'
187
+ params.buttonId === toolConstants_1.BUTTON_REJECT_SHELL_COMMAND || params.buttonId === toolConstants_1.BUTTON_REJECT_MCP_TOOL
151
188
  ? (() => {
152
189
  handler.reject(new toolShared_1.ToolApprovalException('Command was rejected.', true));
153
190
  this.#stoppedToolUses.add(messageId);
@@ -157,12 +194,12 @@ class AgenticChatController {
157
194
  success: true,
158
195
  };
159
196
  }
160
- else if (params.buttonId === 'undo-changes') {
197
+ else if (params.buttonId === toolConstants_1.BUTTON_UNDO_CHANGES) {
161
198
  const toolUseId = params.messageId;
162
199
  try {
163
200
  await this.#undoFileChange(toolUseId, session.data);
164
201
  this.#updateUndoButtonAfterClick(params.tabId, toolUseId, session.data);
165
- this.#telemetryController.emitInteractWithAgenticChat('RejectDiff', params.tabId, session.data?.pairProgrammingMode, session.data?.getConversationType());
202
+ this.#telemetryController.emitInteractWithAgenticChat('RejectDiff', params.tabId, session.data?.pairProgrammingMode, session.data?.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
166
203
  }
167
204
  catch (err) {
168
205
  return { success: false, failureReason: err.message };
@@ -171,23 +208,23 @@ class AgenticChatController {
171
208
  success: true,
172
209
  };
173
210
  }
174
- else if (params.buttonId === 'undo-all-changes') {
175
- const toolUseId = params.messageId.replace('_undoall', '');
211
+ else if (params.buttonId === toolConstants_1.BUTTON_UNDO_ALL_CHANGES) {
212
+ const toolUseId = params.messageId.replace(toolConstants_1.SUFFIX_UNDOALL, '');
176
213
  await this.#undoAllFileChanges(params.tabId, toolUseId, session.data);
177
214
  return {
178
215
  success: true,
179
216
  };
180
217
  }
181
- else if (params.buttonId === 'stop-shell-command') {
218
+ else if (params.buttonId === toolConstants_1.BUTTON_STOP_SHELL_COMMAND) {
182
219
  this.#stoppedToolUses.add(params.messageId);
183
220
  await this.#renderStoppedShellCommand(params.tabId, params.messageId);
184
221
  return { success: true };
185
222
  }
186
- else if (params.buttonId === 'paidtier-upgrade-q-learnmore') {
223
+ else if (params.buttonId === toolConstants_1.BUTTON_PAIDTIER_UPGRADE_Q_LEARNMORE) {
187
224
  (0, paidTier_1.onPaidTierLearnMore)(this.#features.lsp, this.#features.logging);
188
225
  return { success: true };
189
226
  }
190
- else if (params.buttonId === 'paidtier-upgrade-q') {
227
+ else if (params.buttonId === toolConstants_1.BUTTON_PAIDTIER_UPGRADE_Q) {
191
228
  await this.onManageSubscription(params.tabId);
192
229
  return { success: true };
193
230
  }
@@ -219,7 +256,7 @@ class AgenticChatController {
219
256
  return;
220
257
  }
221
258
  const fileList = cachedToolUse.chatResult?.header?.fileList;
222
- const button = cachedToolUse.chatResult?.header?.buttons?.filter(button => button.id !== 'undo-changes');
259
+ const button = cachedToolUse.chatResult?.header?.buttons?.filter(button => button.id !== toolConstants_1.BUTTON_UNDO_CHANGES);
223
260
  const updatedHeader = {
224
261
  ...cachedToolUse.chatResult?.header,
225
262
  buttons: button,
@@ -267,7 +304,7 @@ class AgenticChatController {
267
304
  return;
268
305
  }
269
306
  for (const messageId of [...toUndo].reverse()) {
270
- await this.onButtonClick({ buttonId: 'undo-changes', messageId, tabId });
307
+ await this.onButtonClick({ buttonId: toolConstants_1.BUTTON_UNDO_CHANGES, messageId, tabId });
271
308
  }
272
309
  }
273
310
  async onOpenFileDialog(params, token) {
@@ -371,6 +408,8 @@ class AgenticChatController {
371
408
  this.#contextCommandsProvider?.dispose();
372
409
  this.#userWrittenCodeTracker?.dispose();
373
410
  this.#mcpEventHandler.dispose();
411
+ this.#activeUserTracker.dispose();
412
+ clearInterval(this.#abTestingFetchingTimeout);
374
413
  }
375
414
  async onListConversations(params) {
376
415
  return this.#tabBarController.onListConversations(params);
@@ -433,11 +472,19 @@ class AgenticChatController {
433
472
  if (!success) {
434
473
  return new server_interface_1.ResponseError(protocol_2.ErrorCodes.InternalError, sessionResult.error);
435
474
  }
475
+ const compactIds = session.getAllDeferredCompactMessageIds();
476
+ await this.#invalidateCompactCommand(params.tabId, compactIds);
436
477
  session.rejectAllDeferredToolExecutions(new toolShared_1.ToolApprovalException('Command ignored: new prompt', false));
437
478
  await this.#invalidateAllShellCommands(params.tabId, session);
438
479
  const metric = new metric_1.Metric({
439
480
  cwsprChatConversationType: 'AgenticChat',
481
+ experimentName: this.#abTestingAllocation?.experimentName,
482
+ userVariation: this.#abTestingAllocation?.userVariation,
440
483
  });
484
+ const isNewActiveUser = this.#activeUserTracker.isNewActiveUser();
485
+ if (isNewActiveUser) {
486
+ this.#telemetryController.emitActiveUser();
487
+ }
441
488
  try {
442
489
  const triggerContext = await this.#getTriggerContext(params, metric);
443
490
  if (triggerContext.programmingLanguage?.languageName) {
@@ -456,13 +503,15 @@ class AgenticChatController {
456
503
  this.#log('cancellation requested');
457
504
  // Abort all operations immediately
458
505
  session.abortRequest();
506
+ const compactIds = session.getAllDeferredCompactMessageIds();
507
+ await this.#invalidateCompactCommand(params.tabId, compactIds);
459
508
  void this.#invalidateAllShellCommands(params.tabId, session);
460
509
  session.rejectAllDeferredToolExecutions(new lsp_core_1.CancellationError('user'));
461
510
  // Then update UI to inform the user
462
511
  await this.#showUndoAllIfRequired(chatResultStream, session);
463
512
  await chatResultStream.updateOngoingProgressResult('Canceled');
464
513
  // Finally, send telemetry/metrics
465
- this.#telemetryController.emitInteractWithAgenticChat('StopChat', params.tabId, session.pairProgrammingMode, session.getConversationType());
514
+ this.#telemetryController.emitInteractWithAgenticChat('StopChat', params.tabId, session.pairProgrammingMode, session.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
466
515
  metric.setDimension('languageServerVersion', this.#features.runtime.serverInfo.version);
467
516
  metric.setDimension('codewhispererCustomizationArn', this.#customizationArn);
468
517
  metric.setDimension('enabled', session.pairProgrammingMode);
@@ -470,8 +519,11 @@ class AgenticChatController {
470
519
  });
471
520
  session.setConversationType('AgenticChat');
472
521
  const additionalContext = await this.#additionalContextProvider.getAdditionalContext(triggerContext, params.tabId, params.context);
473
- // Add active file to context list if it exists
474
- const activeFile = triggerContext.text && triggerContext.relativeFilePath && triggerContext.activeFilePath
522
+ // Add active file to context list if it's not already there
523
+ const activeFile = triggerContext.text &&
524
+ triggerContext.relativeFilePath &&
525
+ triggerContext.activeFilePath &&
526
+ !additionalContext.some(item => item.path === triggerContext.activeFilePath)
475
527
  ? [
476
528
  {
477
529
  name: path.basename(triggerContext.relativeFilePath),
@@ -490,19 +542,31 @@ class AgenticChatController {
490
542
  const customContext = await this.#additionalContextProvider.getImageBlocksFromContext(params.context, params.tabId);
491
543
  // Add image context to triggerContext.documentReference for transparency
492
544
  await this.#additionalContextProvider.appendCustomContextToTriggerContext(triggerContext, params.context, params.tabId);
493
- // Get the initial request input
494
- const initialRequestInput = await this.#prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream, customContext);
495
- // Generate a unique ID for this prompt
496
- const promptId = crypto.randomUUID();
497
- session.setCurrentPromptId(promptId);
498
- // Start the agent loop
499
- const finalResult = await this.#runAgentLoop(initialRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference, additionalContext.filter(item => item.pinned));
500
- // Phase 5: Result Handling - This happens only once
501
- return await this.#handleFinalResult(finalResult, session, params, metric, triggerContext, isNewConversation, chatResultStream);
545
+ let finalResult;
546
+ if (params.prompt.command === quickActions_1.QuickAction.Compact) {
547
+ // Get the compaction request input
548
+ const compactionRequestInput = this.#getCompactionRequestInput(session);
549
+ // Generate a unique ID for this prompt
550
+ const promptId = crypto.randomUUID();
551
+ session.setCurrentPromptId(promptId);
552
+ // Start the compaction call
553
+ finalResult = await this.#runCompaction(compactionRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference);
554
+ }
555
+ else {
556
+ // Get the initial request input
557
+ const initialRequestInput = await this.#prepareRequestInput(params, session, triggerContext, additionalContext, chatResultStream, customContext);
558
+ // Generate a unique ID for this prompt
559
+ const promptId = crypto.randomUUID();
560
+ session.setCurrentPromptId(promptId);
561
+ // Start the agent loop
562
+ finalResult = await this.#runAgentLoop(initialRequestInput, session, metric, chatResultStream, params.tabId, promptId, session.conversationId, token, triggerContext.documentReference, additionalContext.filter(item => item.pinned));
563
+ }
564
+ // Result Handling - This happens only once
565
+ return await this.#handleFinalResult(finalResult, session, params.tabId, metric, triggerContext, isNewConversation, chatResultStream);
502
566
  }
503
567
  catch (err) {
504
568
  // HACK: the chat-client needs to have a partial event with the associated messageId sent before it can accept the final result.
505
- // Without this, the `thinking` indicator never goes away.
569
+ // Without this, the `working` indicator never goes away.
506
570
  // Note: buttons being explicitly empty is required for this hack to work.
507
571
  const errorMessageId = `error-message-id-${(0, uuid_1.v4)()}`;
508
572
  await this.#sendProgressToClient({
@@ -534,9 +598,131 @@ class AgenticChatController {
534
598
  this.#debug('Preparing request input');
535
599
  // Get profileArn from the service manager if available
536
600
  const profileArn = this.#serviceManager?.getActiveProfileArn();
537
- 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);
601
+ 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);
602
+ return requestInput;
603
+ }
604
+ /**
605
+ * Prepares the initial request input for the chat prompt
606
+ */
607
+ #getCompactionRequestInput(session) {
608
+ this.#debug('Preparing compaction request input');
609
+ // Get profileArn from the service manager if available
610
+ const profileArn = this.#serviceManager?.getActiveProfileArn();
611
+ const requestInput = this.#triggerContext.getCompactionChatCommandInput(profileArn, this.#getTools(session), session.modelId, this.#origin);
538
612
  return requestInput;
539
613
  }
614
+ /**
615
+ * Runs the compaction, making requests and processing tool uses until completion
616
+ */
617
+ #shouldCompact(currentRequestCount) {
618
+ // 80% of 570K limit
619
+ if (currentRequestCount > 456_000) {
620
+ this.#debug(`Current request total character count is: ${currentRequestCount}, prompting user to compact`);
621
+ return true;
622
+ }
623
+ else {
624
+ return false;
625
+ }
626
+ }
627
+ /**
628
+ * Runs the compaction to compact history into a single summary
629
+ */
630
+ async #runCompaction(compactionRequestInput, session, metric, chatResultStream, tabId, promptId, conversationIdentifier, token, documentReference) {
631
+ let currentRequestInput = { ...compactionRequestInput };
632
+ let finalResult = null;
633
+ metric.recordStart();
634
+ this.#debug(`Running compaction for conversation id:`, conversationIdentifier || '');
635
+ this.#timeToFirstChunk = -1;
636
+ this.#timeBetweenChunks = [];
637
+ // Check for cancellation
638
+ if (this.#isPromptCanceled(token, session, promptId)) {
639
+ this.#debug('Stopping compaction loop - cancelled by user');
640
+ throw new lsp_core_1.CancellationError('user');
641
+ }
642
+ const currentMessage = currentRequestInput.conversationState?.currentMessage;
643
+ let messages = [];
644
+ let characterCount = 0;
645
+ if (currentMessage) {
646
+ // Get and process the messages from history DB to maintain invariants for service requests
647
+ try {
648
+ const { messages: historyMessages, count: historyCharCount } = this.#chatHistoryDb.fixAndGetHistory(tabId, currentMessage, []);
649
+ messages = historyMessages;
650
+ characterCount = historyCharCount;
651
+ }
652
+ catch (err) {
653
+ if (err instanceof chatDb_1.ToolResultValidationError) {
654
+ this.#features.logging.error(`Tool validation error: ${err.message}`);
655
+ return (finalResult || {
656
+ success: false,
657
+ error: 'Compaction loop failed to produce a final result',
658
+ data: { chatResult: {}, toolUses: {} },
659
+ });
660
+ }
661
+ }
662
+ }
663
+ currentRequestInput.conversationState.history = messages.map(msg => (0, util_1.messageToStreamingMessage)(msg));
664
+ const resultStreamWriter = chatResultStream.getResultStreamWriter();
665
+ if (currentRequestInput.conversationState.history.length == 0) {
666
+ // early terminate
667
+ await resultStreamWriter.write({
668
+ type: 'answer',
669
+ body: 'History is empty, there is nothing to compact.',
670
+ messageId: (0, uuid_1.v4)(),
671
+ });
672
+ return {
673
+ success: true,
674
+ data: {
675
+ chatResult: {},
676
+ toolUses: {},
677
+ },
678
+ };
679
+ }
680
+ else {
681
+ await resultStreamWriter.write({
682
+ type: 'answer',
683
+ body: 'Compacting your chat history, this may take a moment.',
684
+ messageId: (0, uuid_1.v4)(),
685
+ });
686
+ }
687
+ await resultStreamWriter.close();
688
+ // Add loading message before making the request
689
+ const loadingMessageId = `loading-${(0, uuid_1.v4)()}`;
690
+ await chatResultStream.writeResultBlock({ ...constants_1.loadingMessage, messageId: loadingMessageId });
691
+ this.#debug(`Compacting history with ${characterCount} characters`);
692
+ this.#llmRequestStartTime = Date.now();
693
+ // Phase 3: Request Execution
694
+ // Note: these logs are very noisy, but contain information redacted on the backend.
695
+ this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput, undefined, 2)}`);
696
+ const response = await session.getChatResponse(currentRequestInput);
697
+ if (response.$metadata.requestId) {
698
+ metric.mergeWith({
699
+ requestIds: [response.$metadata.requestId],
700
+ });
701
+ }
702
+ this.#features.logging.info(`generateAssistantResponse/SendMessage ResponseMetadata: ${lsp_core_2.loggingUtils.formatObj(response.$metadata)}`);
703
+ await chatResultStream.removeResultBlock(loadingMessageId);
704
+ // Phase 4: Response Processing
705
+ const result = await this.#processAgenticChatResponseWithTimeout(response, metric.mergeWith({
706
+ cwsprChatResponseCode: response.$metadata.httpStatusCode,
707
+ cwsprChatMessageId: response.$metadata.requestId,
708
+ }), chatResultStream, session, documentReference, true);
709
+ const llmLatency = Date.now() - this.#llmRequestStartTime;
710
+ this.#debug(`LLM Response Latency for compaction: ${llmLatency}`);
711
+ 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);
712
+ // replace the history with summary in history DB
713
+ if (result.data?.chatResult.body !== undefined) {
714
+ this.#chatHistoryDb.replaceWithSummary(tabId, 'cwc', conversationIdentifier ?? '', {
715
+ body: result.data?.chatResult.body,
716
+ type: 'prompt',
717
+ shouldDisplayMessage: true,
718
+ timestamp: new Date(),
719
+ });
720
+ }
721
+ else {
722
+ this.#features.logging.warn('No ChatResult body in response, skipping adding to history');
723
+ }
724
+ return result;
725
+ }
540
726
  /**
541
727
  * Runs the agent loop, making requests and processing tool uses until completion
542
728
  */
@@ -545,7 +731,9 @@ class AgenticChatController {
545
731
  let finalResult = null;
546
732
  let iterationCount = 0;
547
733
  let shouldDisplayMessage = true;
734
+ let currentRequestCount = 0;
548
735
  metric.recordStart();
736
+ this.logSystemInformation();
549
737
  while (true) {
550
738
  iterationCount++;
551
739
  this.#debug(`Agent loop iteration ${iterationCount} for conversation id:`, conversationIdentifier || '');
@@ -570,7 +758,11 @@ class AgenticChatController {
570
758
  if (currentMessage) {
571
759
  // Get and process the messages from history DB to maintain invariants for service requests
572
760
  try {
573
- 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}`);
574
766
  }
575
767
  catch (err) {
576
768
  if (err instanceof chatDb_1.ToolResultValidationError) {
@@ -579,7 +771,7 @@ class AgenticChatController {
579
771
  }
580
772
  }
581
773
  }
582
- // Do not include chatHistory for requests going to Mynah Backend
774
+ // Do not include chatHistory for requests going to Mynah Backend
583
775
  currentRequestInput.conversationState.history = currentRequestInput.conversationState?.currentMessage
584
776
  ?.userInputMessage?.userIntent
585
777
  ? []
@@ -590,7 +782,7 @@ class AgenticChatController {
590
782
  this.#llmRequestStartTime = Date.now();
591
783
  // Phase 3: Request Execution
592
784
  // Note: these logs are very noisy, but contain information redacted on the backend.
593
- this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput, undefined, 2)}`);
785
+ this.#debug(`generateAssistantResponse/SendMessage Request: ${JSON.stringify(currentRequestInput, this.#imageReplacer, 2)}`);
594
786
  const response = await session.getChatResponse(currentRequestInput);
595
787
  if (response.$metadata.requestId) {
596
788
  metric.mergeWith({
@@ -683,7 +875,7 @@ class AgenticChatController {
683
875
  if (pendingToolUses.length === 0) {
684
876
  this.recordChunk('agent_loop_done');
685
877
  // No more tool uses, we're done
686
- this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChat', undefined, undefined, 'Succeeded', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode);
878
+ this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChat', undefined, undefined, 'Succeeded', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode, this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
687
879
  finalResult = result;
688
880
  break;
689
881
  }
@@ -704,7 +896,7 @@ class AgenticChatController {
704
896
  metric.setDimension('requestIds', metric.metric.requestIds);
705
897
  const toolNames = this.#toolUseLatencies.map(item => item.toolName);
706
898
  const toolUseIds = this.#toolUseLatencies.map(item => item.toolUseId);
707
- this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChatWithToolUse', toolNames ?? undefined, toolUseIds ?? undefined, 'Succeeded', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode);
899
+ this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChatWithToolUse', toolNames ?? undefined, toolUseIds ?? undefined, 'Succeeded', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode, this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
708
900
  }
709
901
  else {
710
902
  // Send an error card to UI?
@@ -713,7 +905,7 @@ class AgenticChatController {
713
905
  status: codewhisperer_streaming_1.ToolResultStatus.ERROR,
714
906
  content: [{ text: result.error }],
715
907
  }));
716
- this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChatWithToolUse', undefined, undefined, 'Failed', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode);
908
+ this.#telemetryController.emitAgencticLoop_InvokeLLM(response.$metadata.requestId, conversationId, 'AgenticChatWithToolUse', undefined, undefined, 'Failed', this.#features.runtime.serverInfo.version ?? '', session.modelId, llmLatency, this.#toolCallLatencies, this.#timeToFirstChunk, this.#timeBetweenChunks, session.pairProgrammingMode, this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
717
909
  if (result.error.startsWith('ToolUse input is invalid JSON:')) {
718
910
  content =
719
911
  'Your toolUse input is incomplete, try again. If the error happens consistently, break this task down into multiple tool uses with smaller input. Do not apologize.';
@@ -728,6 +920,16 @@ class AgenticChatController {
728
920
  }
729
921
  currentRequestInput = this.#updateRequestInputWithToolResults(currentRequestInput, toolResults, content);
730
922
  }
923
+ if (this.#shouldCompact(currentRequestCount)) {
924
+ const messageId = this.#getMessageIdForCompact((0, uuid_1.v4)());
925
+ const confirmationResult = this.#processCompactConfirmation(messageId);
926
+ const cachedButtonBlockId = await chatResultStream.writeResultBlock(confirmationResult);
927
+ await this.waitForCompactApproval(messageId, chatResultStream, cachedButtonBlockId, session);
928
+ // Get the compaction request input
929
+ const compactionRequestInput = this.#getCompactionRequestInput(session);
930
+ // Start the compaction call
931
+ return await this.#runCompaction(compactionRequestInput, session, metric, chatResultStream, tabId, promptId, session.conversationId, token, documentReference);
932
+ }
731
933
  return (finalResult || {
732
934
  success: false,
733
935
  error: 'Agent loop failed to produce a final result',
@@ -860,36 +1062,36 @@ class AgenticChatController {
860
1062
  // remove progress UI
861
1063
  await chatResultStream.removeResultBlockAndUpdateUI(agenticChatResultStream_1.progressPrefix + toolUse.toolUseId);
862
1064
  // fsRead and listDirectory write to an existing card and could show nothing in the current position
863
- if (!['fsWrite', 'fsReplace', 'fsRead', 'listDirectory'].includes(toolUse.name)) {
1065
+ if (![toolConstants_1.FS_WRITE, toolConstants_1.FS_REPLACE, toolConstants_1.FS_READ, toolConstants_1.LIST_DIRECTORY].includes(toolUse.name)) {
864
1066
  await this.#showUndoAllIfRequired(chatResultStream, session);
865
1067
  }
866
1068
  // fsWrite can take a long time, so we render fsWrite Explanatory upon partial streaming responses.
867
- if (toolUse.name !== 'fsWrite' && toolUse.name !== 'fsReplace') {
1069
+ if (toolUse.name !== toolConstants_1.FS_WRITE && toolUse.name !== toolConstants_1.FS_REPLACE) {
868
1070
  const { explanation } = toolUse.input;
869
1071
  if (explanation) {
870
1072
  await chatResultStream.writeResultBlock({
871
1073
  type: 'directive',
872
- messageId: toolUse.toolUseId + '_explanation',
1074
+ messageId: toolUse.toolUseId + toolConstants_1.SUFFIX_EXPLANATION,
873
1075
  body: explanation,
874
1076
  });
875
1077
  }
876
1078
  }
877
1079
  switch (toolUse.name) {
878
- case 'fsRead':
879
- case 'listDirectory':
880
- case 'grepSearch':
881
- case 'fileSearch':
882
- case 'fsWrite':
883
- case 'fsReplace':
884
- case 'executeBash': {
1080
+ case toolConstants_1.FS_READ:
1081
+ case toolConstants_1.LIST_DIRECTORY:
1082
+ case toolConstants_1.GREP_SEARCH:
1083
+ case toolConstants_1.FILE_SEARCH:
1084
+ case toolConstants_1.FS_WRITE:
1085
+ case toolConstants_1.FS_REPLACE:
1086
+ case toolConstants_1.EXECUTE_BASH: {
885
1087
  const toolMap = {
886
- fsRead: { Tool: fsRead_1.FsRead },
887
- listDirectory: { Tool: listDirectory_1.ListDirectory },
888
- fsWrite: { Tool: fsWrite_1.FsWrite },
889
- fsReplace: { Tool: fsReplace_1.FsReplace },
890
- executeBash: { Tool: executeBash_1.ExecuteBash },
891
- grepSearch: { Tool: grepSearch_1.GrepSearch },
892
- fileSearch: { Tool: fileSearch_1.FileSearch },
1088
+ [toolConstants_1.FS_READ]: { Tool: fsRead_1.FsRead },
1089
+ [toolConstants_1.LIST_DIRECTORY]: { Tool: listDirectory_1.ListDirectory },
1090
+ [toolConstants_1.FS_WRITE]: { Tool: fsWrite_1.FsWrite },
1091
+ [toolConstants_1.FS_REPLACE]: { Tool: fsReplace_1.FsReplace },
1092
+ [toolConstants_1.EXECUTE_BASH]: { Tool: executeBash_1.ExecuteBash },
1093
+ [toolConstants_1.GREP_SEARCH]: { Tool: grepSearch_1.GrepSearch },
1094
+ [toolConstants_1.FILE_SEARCH]: { Tool: fileSearch_1.FileSearch },
893
1095
  };
894
1096
  const { Tool } = toolMap[toolUse.name];
895
1097
  const tool = new Tool(this.#features);
@@ -903,23 +1105,26 @@ class AgenticChatController {
903
1105
  const { requiresAcceptance, warning, commandCategory } = await tool.requiresAcceptance(toolUse.input, approvedPaths);
904
1106
  // Honor built-in permission if available, otherwise use tool's requiresAcceptance
905
1107
  // const requiresAcceptance = builtInPermission || toolRequiresAcceptance
906
- if (requiresAcceptance || toolUse.name === 'executeBash') {
1108
+ if (requiresAcceptance || toolUse.name === toolConstants_1.EXECUTE_BASH) {
907
1109
  // for executeBash, we till send the confirmation message without action buttons
908
1110
  const confirmationResult = this.#processToolConfirmation(toolUse, requiresAcceptance, warning, commandCategory);
909
1111
  cachedButtonBlockId = await chatResultStream.writeResultBlock(confirmationResult);
910
- const isExecuteBash = toolUse.name === 'executeBash';
1112
+ const isExecuteBash = toolUse.name === toolConstants_1.EXECUTE_BASH;
911
1113
  if (isExecuteBash) {
912
- this.#telemetryController.emitInteractWithAgenticChat('GeneratedCommand', tabId, session.pairProgrammingMode, session.getConversationType());
1114
+ this.#telemetryController.emitInteractWithAgenticChat('GeneratedCommand', tabId, session.pairProgrammingMode, session.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
913
1115
  }
914
1116
  if (requiresAcceptance) {
915
1117
  await this.waitForToolApproval(toolUse, chatResultStream, cachedButtonBlockId, session, toolUse.name);
916
1118
  }
917
1119
  if (isExecuteBash) {
918
- this.#telemetryController.emitInteractWithAgenticChat('RunCommand', tabId, session.pairProgrammingMode, session.getConversationType());
1120
+ this.#telemetryController.emitInteractWithAgenticChat('RunCommand', tabId, session.pairProgrammingMode, session.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
919
1121
  }
920
1122
  }
921
1123
  break;
922
1124
  }
1125
+ case qCodeReview_1.QCodeReview.toolName:
1126
+ // no need to write tool message for code review
1127
+ break;
923
1128
  // — DEFAULT ⇒ Only MCP tools, but can also handle generic tool execution messages
924
1129
  default:
925
1130
  // Get original server and tool names from the mapping
@@ -961,7 +1166,7 @@ class AgenticChatController {
961
1166
  }
962
1167
  break;
963
1168
  }
964
- if (toolUse.name === 'fsWrite' || toolUse.name === 'fsReplace') {
1169
+ if (toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) {
965
1170
  const input = toolUse.input;
966
1171
  const document = await this.#triggerContext.getTextDocumentFromPath(input.path, true, true);
967
1172
  session.toolUseLookup.set(toolUse.toolUseId, {
@@ -969,6 +1174,22 @@ class AgenticChatController {
969
1174
  fileChange: { before: document?.getText() },
970
1175
  });
971
1176
  }
1177
+ if (toolUse.name === qCodeReview_1.QCodeReview.toolName) {
1178
+ try {
1179
+ let initialInput = JSON.parse(JSON.stringify(toolUse.input));
1180
+ let ruleArtifacts = await this.#additionalContextProvider.collectWorkspaceRules(tabId);
1181
+ if (ruleArtifacts !== undefined || ruleArtifacts !== null) {
1182
+ this.#features.logging.info(`RuleArtifacts: ${JSON.stringify(ruleArtifacts)}`);
1183
+ let pathsToRulesMap = ruleArtifacts.map(ruleArtifact => ({ path: ruleArtifact.id }));
1184
+ this.#features.logging.info(`PathsToRules: ${JSON.stringify(pathsToRulesMap)}`);
1185
+ initialInput['ruleArtifacts'] = pathsToRulesMap;
1186
+ }
1187
+ toolUse.input = initialInput;
1188
+ }
1189
+ catch (e) {
1190
+ this.#features.logging.warn(`could not parse QCodeReview tool input: ${e}`);
1191
+ }
1192
+ }
972
1193
  // After approval, add the path to the approved paths in the session
973
1194
  const inputPath = toolUse.input?.path || toolUse.input?.cwd;
974
1195
  if (inputPath) {
@@ -995,27 +1216,27 @@ class AgenticChatController {
995
1216
  content: [toolResultContent],
996
1217
  });
997
1218
  switch (toolUse.name) {
998
- case 'fsRead':
999
- case 'listDirectory':
1000
- case 'fileSearch':
1219
+ case toolConstants_1.FS_READ:
1220
+ case toolConstants_1.LIST_DIRECTORY:
1221
+ case toolConstants_1.FILE_SEARCH:
1001
1222
  const initialListDirResult = this.#processReadOrListOrSearch(toolUse, chatResultStream);
1002
1223
  if (initialListDirResult) {
1003
1224
  await chatResultStream.writeResultBlock(initialListDirResult);
1004
1225
  }
1005
1226
  break;
1006
1227
  // no need to write tool result for listDir,fsRead,fileSearch into chat stream
1007
- case 'executeBash':
1228
+ case toolConstants_1.EXECUTE_BASH:
1008
1229
  // no need to write tool result for listDir and fsRead into chat stream
1009
1230
  // executeBash will stream the output instead of waiting until the end
1010
1231
  break;
1011
- case 'grepSearch':
1232
+ case toolConstants_1.GREP_SEARCH:
1012
1233
  const grepSearchResult = this.#processGrepSearchResult(toolUse, result, chatResultStream);
1013
1234
  if (grepSearchResult) {
1014
1235
  await chatResultStream.writeResultBlock(grepSearchResult);
1015
1236
  }
1016
1237
  break;
1017
- case 'fsReplace':
1018
- case 'fsWrite':
1238
+ case toolConstants_1.FS_REPLACE:
1239
+ case toolConstants_1.FS_WRITE:
1019
1240
  const input = toolUse.input;
1020
1241
  // Load from the filesystem instead of workspace.
1021
1242
  // Workspace is likely out of date - when files
@@ -1033,9 +1254,23 @@ class AgenticChatController {
1033
1254
  fileChange: { ...cachedToolUse.fileChange, after: doc?.getText() },
1034
1255
  });
1035
1256
  }
1036
- this.#telemetryController.emitInteractWithAgenticChat('GeneratedDiff', tabId, session.pairProgrammingMode, session.getConversationType());
1257
+ this.#telemetryController.emitInteractWithAgenticChat('GeneratedDiff', tabId, session.pairProgrammingMode, session.getConversationType(), this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
1037
1258
  await chatResultStream.writeResultBlock(chatResult);
1038
1259
  break;
1260
+ case qCodeReview_1.QCodeReview.toolName:
1261
+ // no need to write tool result for code review, this is handled by model via chat
1262
+ // Push result in message so that it is picked by IDE plugin to show in issues panel
1263
+ const qCodeReviewResult = result;
1264
+ if (qCodeReviewResult?.output?.kind === 'json' &&
1265
+ qCodeReviewResult.output.success &&
1266
+ qCodeReviewResult.output.content?.findingsByFile) {
1267
+ await chatResultStream.writeResultBlock({
1268
+ type: 'tool',
1269
+ messageId: toolUse.toolUseId + qCodeReviewConstants_1.FINDINGS_MESSAGE_SUFFIX,
1270
+ body: qCodeReviewResult.output.content.findingsByFile,
1271
+ });
1272
+ }
1273
+ break;
1039
1274
  // — DEFAULT ⇒ MCP tools
1040
1275
  default:
1041
1276
  await this.#handleMcpToolResult(toolUse, result, session, chatResultStream);
@@ -1056,7 +1291,7 @@ class AgenticChatController {
1056
1291
  });
1057
1292
  }
1058
1293
  }
1059
- this.#telemetryController.emitToolUseSuggested(toolUse, session.conversationId ?? '', this.#features.runtime.serverInfo.version ?? '', latency, session.pairProgrammingMode);
1294
+ this.#telemetryController.emitToolUseSuggested(toolUse, session.conversationId ?? '', this.#features.runtime.serverInfo.version ?? '', latency, session.pairProgrammingMode, this.#abTestingAllocation?.experimentName, this.#abTestingAllocation?.userVariation);
1060
1295
  }
1061
1296
  }
1062
1297
  catch (err) {
@@ -1086,12 +1321,12 @@ class AgenticChatController {
1086
1321
  continue;
1087
1322
  }
1088
1323
  // Rethrow error for executeBash or any named tool
1089
- if (toolUse.name === 'executeBash' || toolUse.name) {
1324
+ if (toolUse.name === toolConstants_1.EXECUTE_BASH || toolUse.name) {
1090
1325
  throw err;
1091
1326
  }
1092
1327
  }
1093
1328
  // display fs write failure status in the UX of that file card
1094
- if ((toolUse.name === 'fsWrite' || toolUse.name === 'fsReplace') && toolUse.toolUseId) {
1329
+ if ((toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) && toolUse.toolUseId) {
1095
1330
  const existingCard = chatResultStream.getMessageBlockId(toolUse.toolUseId);
1096
1331
  const fsParam = toolUse.input;
1097
1332
  if (fsParam.path) {
@@ -1125,7 +1360,7 @@ class AgenticChatController {
1125
1360
  }
1126
1361
  }
1127
1362
  }
1128
- else if (toolUse.name === 'executeBash' && toolUse.toolUseId) {
1363
+ else if (toolUse.name === toolConstants_1.EXECUTE_BASH && toolUse.toolUseId) {
1129
1364
  const existingCard = chatResultStream.getMessageBlockId(toolUse.toolUseId);
1130
1365
  const command = toolUse.input.command;
1131
1366
  const completedErrorResult = {
@@ -1183,10 +1418,10 @@ class AgenticChatController {
1183
1418
  * Updates the currentUndoAllId state in the session
1184
1419
  */
1185
1420
  #updateUndoAllState(toolUse, session) {
1186
- if (toolUse.name === 'fsRead' || toolUse.name === 'listDirectory') {
1421
+ if (toolUse.name === toolConstants_1.FS_READ || toolUse.name === toolConstants_1.LIST_DIRECTORY) {
1187
1422
  return;
1188
1423
  }
1189
- if (toolUse.name === 'fsWrite' || toolUse.name === 'fsReplace') {
1424
+ if (toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) {
1190
1425
  if (session.currentUndoAllId === undefined) {
1191
1426
  session.currentUndoAllId = toolUse.toolUseId;
1192
1427
  }
@@ -1221,10 +1456,10 @@ class AgenticChatController {
1221
1456
  }
1222
1457
  await chatResultStream.writeResultBlock({
1223
1458
  type: 'answer',
1224
- messageId: `${session.currentUndoAllId}_undoall`,
1459
+ messageId: `${session.currentUndoAllId}${toolConstants_1.SUFFIX_UNDOALL}`,
1225
1460
  buttons: [
1226
1461
  {
1227
- id: 'undo-all-changes',
1462
+ id: toolConstants_1.BUTTON_UNDO_ALL_CHANGES,
1228
1463
  text: 'Undo all changes',
1229
1464
  icon: 'undo',
1230
1465
  status: 'clear',
@@ -1252,11 +1487,11 @@ class AgenticChatController {
1252
1487
  #validateToolResult(toolUse, result) {
1253
1488
  let maxToolResponseSize;
1254
1489
  switch (toolUse.name) {
1255
- case 'fsRead':
1256
- case 'executeBash':
1490
+ case toolConstants_1.FS_READ:
1491
+ case toolConstants_1.EXECUTE_BASH:
1257
1492
  // fsRead and executeBash already have truncation logic
1258
1493
  return;
1259
- case 'listDirectory':
1494
+ case toolConstants_1.LIST_DIRECTORY:
1260
1495
  maxToolResponseSize = 50_000;
1261
1496
  break;
1262
1497
  default:
@@ -1283,8 +1518,32 @@ class AgenticChatController {
1283
1518
  return undefined;
1284
1519
  }
1285
1520
  }
1521
+ #getToolOverWritableStream(chatResultStream, toolUse) {
1522
+ const toolMsgId = toolUse.toolUseId;
1523
+ return new WritableStream({
1524
+ write: async (chunk) => {
1525
+ if (this.#stoppedToolUses.has(toolMsgId))
1526
+ return;
1527
+ await chatResultStream.removeResultBlockAndUpdateUI(toolMsgId);
1528
+ await chatResultStream.writeResultBlock({
1529
+ type: 'tool',
1530
+ messageId: toolMsgId,
1531
+ body: chunk,
1532
+ });
1533
+ },
1534
+ close: async () => {
1535
+ if (this.#stoppedToolUses.has(toolMsgId))
1536
+ return;
1537
+ await chatResultStream.removeResultBlockAndUpdateUI(toolMsgId);
1538
+ this.#stoppedToolUses.add(toolMsgId);
1539
+ },
1540
+ });
1541
+ }
1286
1542
  #getWritableStream(chatResultStream, toolUse) {
1287
- if (toolUse.name !== 'executeBash') {
1543
+ if (toolUse.name === qCodeReview_1.QCodeReview.toolName) {
1544
+ return this.#getToolOverWritableStream(chatResultStream, toolUse);
1545
+ }
1546
+ if (toolUse.name !== toolConstants_1.EXECUTE_BASH) {
1288
1547
  return;
1289
1548
  }
1290
1549
  const toolMsgId = toolUse.toolUseId;
@@ -1292,7 +1551,7 @@ class AgenticChatController {
1292
1551
  let headerEmitted = false;
1293
1552
  const initialHeader = {
1294
1553
  body: 'shell',
1295
- buttons: [{ id: 'stop-shell-command', text: 'Stop', icon: 'stop' }],
1554
+ buttons: [{ id: toolConstants_1.BUTTON_STOP_SHELL_COMMAND, text: 'Cancel', icon: 'stop' }],
1296
1555
  };
1297
1556
  const completedHeader = {
1298
1557
  body: 'shell',
@@ -1340,7 +1599,7 @@ class AgenticChatController {
1340
1599
  #getUpdateToolConfirmResult(toolUse, isAccept, originalToolName, toolType) {
1341
1600
  const toolName = originalToolName ?? (toolType || toolUse.name);
1342
1601
  // Handle bash commands with special formatting
1343
- if (toolName === 'executeBash') {
1602
+ if (toolName === toolConstants_1.EXECUTE_BASH) {
1344
1603
  return {
1345
1604
  messageId: toolUse.toolUseId,
1346
1605
  type: 'tool',
@@ -1356,7 +1615,7 @@ class AgenticChatController {
1356
1615
  text: 'Rejected',
1357
1616
  },
1358
1617
  }),
1359
- buttons: isAccept ? [{ id: 'stop-shell-command', text: 'Stop', icon: 'stop' }] : [],
1618
+ buttons: isAccept ? [{ id: toolConstants_1.BUTTON_STOP_SHELL_COMMAND, text: 'Cancel', icon: 'stop' }] : [],
1360
1619
  },
1361
1620
  };
1362
1621
  }
@@ -1364,10 +1623,10 @@ class AgenticChatController {
1364
1623
  let header;
1365
1624
  let body;
1366
1625
  switch (toolName) {
1367
- case 'fsReplace':
1368
- case 'fsWrite':
1369
- case 'fsRead':
1370
- case 'listDirectory':
1626
+ case toolConstants_1.FS_REPLACE:
1627
+ case toolConstants_1.FS_WRITE:
1628
+ case toolConstants_1.FS_READ:
1629
+ case toolConstants_1.LIST_DIRECTORY:
1371
1630
  header = {
1372
1631
  body: undefined,
1373
1632
  status: {
@@ -1377,7 +1636,7 @@ class AgenticChatController {
1377
1636
  },
1378
1637
  };
1379
1638
  break;
1380
- case 'fileSearch':
1639
+ case toolConstants_1.FILE_SEARCH:
1381
1640
  const searchPath = toolUse.input.path;
1382
1641
  header = {
1383
1642
  body: 'File Search',
@@ -1444,7 +1703,7 @@ class AgenticChatController {
1444
1703
  status: {
1445
1704
  status: 'error',
1446
1705
  icon: 'stop',
1447
- text: 'Stopped',
1706
+ text: 'Canceled',
1448
1707
  },
1449
1708
  buttons: [],
1450
1709
  },
@@ -1453,6 +1712,97 @@ class AgenticChatController {
1453
1712
  },
1454
1713
  });
1455
1714
  }
1715
+ #processCompactConfirmation(messageId) {
1716
+ const buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
1717
+ const header = {
1718
+ icon: 'warning',
1719
+ iconForegroundStatus: 'warning',
1720
+ body: constants_2.COMPACTION_HEADER_BODY,
1721
+ buttons,
1722
+ };
1723
+ const body = constants_2.COMPACTION_BODY;
1724
+ return {
1725
+ type: 'tool',
1726
+ messageId,
1727
+ header,
1728
+ body,
1729
+ };
1730
+ }
1731
+ /**
1732
+ * Creates a promise that does not resolve until the user accepts or rejects the compaction usage.
1733
+ * @param messageId
1734
+ * @param resultStream
1735
+ * @param promptBlockId id of approval block. This allows us to overwrite the buttons with 'accepted' or 'rejected' text.
1736
+ */
1737
+ async waitForCompactApproval(messageId, resultStream, promptBlockId, session) {
1738
+ const deferred = this.#createDeferred();
1739
+ session.setDeferredToolExecution(messageId, deferred.resolve, deferred.reject);
1740
+ this.#log(`Prompting for compaction approval for messageId: ${messageId}`);
1741
+ await deferred.promise;
1742
+ // Note: we want to overwrite the button block because it already exists in the stream.
1743
+ await resultStream.overwriteResultBlock(this.#getUpdateCompactConfirmResult(messageId), promptBlockId);
1744
+ }
1745
+ /**
1746
+ * Creates an updated ChatResult for compaction confirmation
1747
+ * @param messageId The messageId
1748
+ * @returns ChatResult with appropriate confirmation UI
1749
+ */
1750
+ #getUpdateCompactConfirmResult(messageId) {
1751
+ let header;
1752
+ let body;
1753
+ header = {
1754
+ body: undefined,
1755
+ status: {
1756
+ status: 'success',
1757
+ icon: 'ok',
1758
+ text: 'Allowed',
1759
+ },
1760
+ };
1761
+ return {
1762
+ messageId,
1763
+ type: 'tool',
1764
+ body,
1765
+ header,
1766
+ };
1767
+ }
1768
+ #renderStopShellCommandButton() {
1769
+ const stopKey = this.#getKeyBinding('aws.amazonq.stopCmdExecution');
1770
+ return {
1771
+ id: 'stop-shell-command',
1772
+ text: 'Cancel',
1773
+ icon: 'stop',
1774
+ ...(stopKey ? { description: `Stop: ${stopKey}` } : {}),
1775
+ };
1776
+ }
1777
+ #getKeyBinding(commandId) {
1778
+ // Check for feature flag
1779
+ const shortcut = this.#features.lsp.getClientInitializeParams()?.initializationOptions?.aws?.awsClientCapabilities?.q
1780
+ ?.shortcut;
1781
+ if (!shortcut) {
1782
+ return null;
1783
+ }
1784
+ let defaultKey = '';
1785
+ const OS = os.platform();
1786
+ switch (commandId) {
1787
+ case 'aws.amazonq.runCmdExecution':
1788
+ defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_RUN_SHORTCUT : constants_2.DEFAULT_WINDOW_RUN_SHORTCUT;
1789
+ break;
1790
+ case 'aws.amazonq.rejectCmdExecution':
1791
+ defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_REJECT_SHORTCUT : constants_2.DEFAULT_WINDOW_REJECT_SHORTCUT;
1792
+ break;
1793
+ case 'aws.amazonq.stopCmdExecution':
1794
+ defaultKey = OS === 'darwin' ? constants_2.DEFAULT_MACOS_STOP_SHORTCUT : constants_2.DEFAULT_WINDOW_STOP_SHORTCUT;
1795
+ break;
1796
+ default:
1797
+ this.#log(`#getKeyBinding: ${commandId} shortcut is supported by Q `);
1798
+ break;
1799
+ }
1800
+ if (defaultKey === '') {
1801
+ return null;
1802
+ }
1803
+ //TODO: handle case: user change default keybind, suggestion: read `keybinding.json` provided by VSC
1804
+ return defaultKey;
1805
+ }
1456
1806
  #processToolConfirmation(toolUse, requiresAcceptance, warning, commandCategory, toolType, builtInPermission) {
1457
1807
  const toolName = toolType || toolUse.name;
1458
1808
  let buttons = [];
@@ -1460,13 +1810,13 @@ class AgenticChatController {
1460
1810
  let body;
1461
1811
  // Configure tool-specific UI elements
1462
1812
  switch (toolName) {
1463
- case 'executeBash': {
1813
+ case toolConstants_1.EXECUTE_BASH: {
1464
1814
  const commandString = toolUse.input.command;
1465
1815
  buttons = requiresAcceptance
1466
1816
  ? [
1467
- { id: 'run-shell-command', text: 'Run', icon: 'play' },
1817
+ { id: toolConstants_1.BUTTON_RUN_SHELL_COMMAND, text: 'Run', icon: 'play' },
1468
1818
  {
1469
- id: 'reject-shell-command',
1819
+ id: toolConstants_1.BUTTON_REJECT_SHELL_COMMAND,
1470
1820
  status: 'dimmed-clear',
1471
1821
  text: 'Reject',
1472
1822
  icon: 'cancel',
@@ -1498,12 +1848,12 @@ class AgenticChatController {
1498
1848
  body = '```shell\n' + commandString;
1499
1849
  break;
1500
1850
  }
1501
- case 'fsWrite': {
1851
+ case toolConstants_1.FS_WRITE: {
1502
1852
  const writeFilePath = toolUse.input.path;
1503
1853
  // Validate the path using our synchronous utility
1504
1854
  (0, pathValidation_1.validatePathBasic)(writeFilePath);
1505
1855
  this.#debug(`Processing ${toolUse.name} for path: ${writeFilePath}`);
1506
- buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
1856
+ buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
1507
1857
  header = {
1508
1858
  icon: 'warning',
1509
1859
  iconForegroundStatus: 'warning',
@@ -1517,12 +1867,12 @@ class AgenticChatController {
1517
1867
  : `I need permission to modify files outside of your workspace.\n\`${writeFilePath}\``;
1518
1868
  break;
1519
1869
  }
1520
- case 'fsReplace': {
1870
+ case toolConstants_1.FS_REPLACE: {
1521
1871
  const writeFilePath = toolUse.input.path;
1522
1872
  // For replace, we need to verify the file exists
1523
1873
  (0, pathValidation_1.validatePathExists)(writeFilePath);
1524
1874
  this.#debug(`Processing ${toolUse.name} for path: ${writeFilePath}`);
1525
- buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
1875
+ buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
1526
1876
  header = {
1527
1877
  icon: 'warning',
1528
1878
  iconForegroundStatus: 'warning',
@@ -1536,9 +1886,9 @@ class AgenticChatController {
1536
1886
  : `I need permission to modify files outside of your workspace.\n\`${writeFilePath}\``;
1537
1887
  break;
1538
1888
  }
1539
- case 'fsRead':
1540
- case 'listDirectory': {
1541
- buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
1889
+ case toolConstants_1.FS_READ:
1890
+ case toolConstants_1.LIST_DIRECTORY: {
1891
+ buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
1542
1892
  header = {
1543
1893
  icon: 'tools',
1544
1894
  iconForegroundStatus: 'tools',
@@ -1547,7 +1897,7 @@ class AgenticChatController {
1547
1897
  : '#### Allow read-only tools outside your workspace',
1548
1898
  buttons,
1549
1899
  };
1550
- if (toolName === 'fsRead') {
1900
+ if (toolName === toolConstants_1.FS_READ) {
1551
1901
  const paths = toolUse.input.paths;
1552
1902
  // Validate paths using our synchronous utility
1553
1903
  (0, pathValidation_1.validatePaths)(paths);
@@ -1571,7 +1921,7 @@ class AgenticChatController {
1571
1921
  }
1572
1922
  default: {
1573
1923
  // — DEFAULT ⇒ MCP tools
1574
- buttons = [{ id: 'allow-tools', text: 'Allow', icon: 'ok', status: 'clear' }];
1924
+ buttons = [{ id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Allow', icon: 'ok', status: 'clear' }];
1575
1925
  header = {
1576
1926
  icon: 'tools',
1577
1927
  iconForegroundStatus: 'warning',
@@ -1589,7 +1939,7 @@ class AgenticChatController {
1589
1939
  type: 'tool',
1590
1940
  messageId: this.#getMessageIdForToolUse(toolType, toolUse),
1591
1941
  header,
1592
- body: warning ? (toolName === 'executeBash' ? '' : '\n\n') + body : body,
1942
+ body: warning ? (toolName === toolConstants_1.EXECUTE_BASH ? '' : '\n\n') + body : body,
1593
1943
  };
1594
1944
  }
1595
1945
  else {
@@ -1602,9 +1952,9 @@ class AgenticChatController {
1602
1952
  icon: 'tools',
1603
1953
  body: `${toolName}`,
1604
1954
  buttons: [
1605
- { id: 'allow-tools', text: 'Run', icon: 'play', status: 'clear' },
1955
+ { id: toolConstants_1.BUTTON_ALLOW_TOOLS, text: 'Run', icon: 'play', status: 'clear' },
1606
1956
  {
1607
- id: 'reject-mcp-tool',
1957
+ id: toolConstants_1.BUTTON_REJECT_MCP_TOOL,
1608
1958
  text: 'Reject',
1609
1959
  icon: 'cancel',
1610
1960
  status: 'dimmed-clear',
@@ -1650,7 +2000,7 @@ class AgenticChatController {
1650
2000
  },
1651
2001
  },
1652
2002
  },
1653
- buttons: [{ id: 'undo-changes', text: 'Undo', icon: 'undo' }],
2003
+ buttons: [{ id: toolConstants_1.BUTTON_UNDO_CHANGES, text: 'Undo', icon: 'undo' }],
1654
2004
  },
1655
2005
  };
1656
2006
  }
@@ -1664,7 +2014,7 @@ class AgenticChatController {
1664
2014
  chatResultStream.setMessageIdToUpdateForTool(toolUse.name, messageIdToUpdate);
1665
2015
  }
1666
2016
  let currentPaths = [];
1667
- if (toolUse.name === 'fsRead') {
2017
+ if (toolUse.name === toolConstants_1.FS_READ) {
1668
2018
  currentPaths = toolUse.input?.paths;
1669
2019
  }
1670
2020
  else {
@@ -1695,9 +2045,9 @@ class AgenticChatController {
1695
2045
  }
1696
2046
  else {
1697
2047
  title =
1698
- toolUse.name === 'fsRead'
2048
+ toolUse.name === toolConstants_1.FS_READ
1699
2049
  ? `${itemCount} file${itemCount > 1 ? 's' : ''} read`
1700
- : toolUse.name === 'fileSearch'
2050
+ : toolUse.name === toolConstants_1.FILE_SEARCH
1701
2051
  ? `${itemCount} ${itemCount === 1 ? 'directory' : 'directories'} searched`
1702
2052
  : `${itemCount} ${itemCount === 1 ? 'directory' : 'directories'} listed`;
1703
2053
  }
@@ -1724,7 +2074,7 @@ class AgenticChatController {
1724
2074
  * Process grep search results and format them for display in the chat UI
1725
2075
  */
1726
2076
  #processGrepSearchResult(toolUse, result, chatResultStream) {
1727
- if (toolUse.name !== 'grepSearch') {
2077
+ if (toolUse.name !== toolConstants_1.GREP_SEARCH) {
1728
2078
  return undefined;
1729
2079
  }
1730
2080
  let messageIdToUpdate = toolUse.toolUseId;
@@ -1804,22 +2154,22 @@ class AgenticChatController {
1804
2154
  /**
1805
2155
  * Handles the final result after the agent loop completes
1806
2156
  */
1807
- async #handleFinalResult(result, session, params, metric, triggerContext, isNewConversation, chatResultStream) {
2157
+ async #handleFinalResult(result, session, tabId, metric, triggerContext, isNewConversation, chatResultStream) {
1808
2158
  if (!result.success) {
1809
2159
  throw new errors_2.AgenticChatError(result.error, 'FailedResult');
1810
2160
  }
1811
2161
  const conversationId = session.conversationId;
1812
2162
  this.#debug('Final session conversation id:', conversationId || '');
1813
2163
  if (conversationId) {
1814
- this.#telemetryController.setConversationId(params.tabId, conversationId);
2164
+ this.#telemetryController.setConversationId(tabId, conversationId);
1815
2165
  if (isNewConversation) {
1816
- this.#telemetryController.updateTriggerInfo(params.tabId, {
2166
+ this.#telemetryController.updateTriggerInfo(tabId, {
1817
2167
  startTrigger: {
1818
2168
  hasUserSnippet: metric.metric.cwsprChatHasCodeSnippet ?? false,
1819
2169
  triggerType: triggerContext.triggerType,
1820
2170
  },
1821
2171
  });
1822
- this.#telemetryController.emitStartConversationMetric(params.tabId, metric.metric);
2172
+ this.#telemetryController.emitStartConversationMetric(tabId, metric.metric);
1823
2173
  }
1824
2174
  }
1825
2175
  metric.setDimension('codewhispererCustomizationArn', this.#customizationArn);
@@ -1853,8 +2203,8 @@ class AgenticChatController {
1853
2203
  cwsprChatPinnedPromptContextCount: triggerContext.contextInfo.pinnedContextCount.promptContextCount,
1854
2204
  });
1855
2205
  }
1856
- await this.#telemetryController.emitAddMessageMetric(params.tabId, metric.metric, 'Succeeded');
1857
- this.#telemetryController.updateTriggerInfo(params.tabId, {
2206
+ await this.#telemetryController.emitAddMessageMetric(tabId, metric.metric, 'Succeeded');
2207
+ this.#telemetryController.updateTriggerInfo(tabId, {
1858
2208
  lastMessageTrigger: {
1859
2209
  ...triggerContext,
1860
2210
  messageId: result.data?.chatResult.messageId,
@@ -2104,7 +2454,7 @@ class AgenticChatController {
2104
2454
  const session = this.#chatSessionManagementService.getSession(params.tabId);
2105
2455
  const toolUseId = params.messageId;
2106
2456
  const toolUse = toolUseId ? session.data?.toolUseLookup.get(toolUseId) : undefined;
2107
- if (toolUse?.name === 'fsWrite' || toolUse?.name === 'fsReplace') {
2457
+ if (toolUse?.name === toolConstants_1.FS_WRITE || toolUse?.name === toolConstants_1.FS_REPLACE) {
2108
2458
  const input = toolUse.input;
2109
2459
  this.#features.lsp.workspace.openFileDiff({
2110
2460
  originalFileUri: input.path,
@@ -2113,7 +2463,7 @@ class AgenticChatController {
2113
2463
  fileContent: toolUse.fileChange?.after,
2114
2464
  });
2115
2465
  }
2116
- else if (toolUse?.name === 'fsRead') {
2466
+ else if (toolUse?.name === toolConstants_1.FS_READ) {
2117
2467
  await this.#features.lsp.window.showDocument({ uri: vscode_uri_1.URI.file(params.filePath).toString() });
2118
2468
  }
2119
2469
  else {
@@ -2323,9 +2673,31 @@ class AgenticChatController {
2323
2673
  });
2324
2674
  return triggerContext;
2325
2675
  }
2676
+ async #invalidateCompactCommand(tabId, messageIds) {
2677
+ for (const messageId of messageIds) {
2678
+ await this.#features.chat.sendChatUpdate({
2679
+ tabId,
2680
+ state: { inProgress: false },
2681
+ data: {
2682
+ messages: [
2683
+ {
2684
+ messageId,
2685
+ type: 'tool',
2686
+ body: constants_2.COMPACTION_BODY,
2687
+ header: {
2688
+ body: constants_2.COMPACTION_HEADER_BODY,
2689
+ status: { icon: 'block', text: 'Ignored' },
2690
+ buttons: [],
2691
+ },
2692
+ },
2693
+ ],
2694
+ },
2695
+ });
2696
+ }
2697
+ }
2326
2698
  async #invalidateAllShellCommands(tabId, session) {
2327
2699
  for (const [toolUseId, toolUse] of session.toolUseLookup.entries()) {
2328
- if (toolUse.name !== 'executeBash' || this.#stoppedToolUses.has(toolUseId))
2700
+ if (toolUse.name !== toolConstants_1.EXECUTE_BASH || this.#stoppedToolUses.has(toolUseId))
2329
2701
  continue;
2330
2702
  const params = toolUse.input;
2331
2703
  const command = params.command;
@@ -2550,7 +2922,7 @@ class AgenticChatController {
2550
2922
  return;
2551
2923
  }
2552
2924
  }
2553
- async #processAgenticChatResponseWithTimeout(response, metric, chatResultStream, session, contextList) {
2925
+ async #processAgenticChatResponseWithTimeout(response, metric, chatResultStream, session, contextList, isCompaction) {
2554
2926
  const abortController = new AbortController();
2555
2927
  let timeoutId;
2556
2928
  const timeoutPromise = new Promise((_, reject) => {
@@ -2560,7 +2932,7 @@ class AgenticChatController {
2560
2932
  }, constants_2.RESPONSE_TIMEOUT_MS);
2561
2933
  });
2562
2934
  const streamWriter = chatResultStream.getResultStreamWriter();
2563
- const processResponsePromise = this.#processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortController.signal);
2935
+ const processResponsePromise = this.#processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortController.signal, isCompaction);
2564
2936
  try {
2565
2937
  const result = await Promise.race([processResponsePromise, timeoutPromise]);
2566
2938
  clearTimeout(timeoutId);
@@ -2584,7 +2956,7 @@ class AgenticChatController {
2584
2956
  }
2585
2957
  const toolUses = Object.values(data.toolUses);
2586
2958
  for (const toolUse of toolUses) {
2587
- if ((toolUse.name === 'fsWrite' || toolUse.name === 'fsReplace') && typeof toolUse.input === 'string') {
2959
+ if ((toolUse.name === toolConstants_1.FS_WRITE || toolUse.name === toolConstants_1.FS_REPLACE) && typeof toolUse.input === 'string') {
2588
2960
  const filepath = extractKey(toolUse.input, 'path');
2589
2961
  const msgId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId;
2590
2962
  // render fs write UI as soon as fs write starts
@@ -2613,7 +2985,7 @@ class AgenticChatController {
2613
2985
  }
2614
2986
  // render the tool use explanatory as soon as this is received for fsWrite/fsReplace
2615
2987
  const explanation = extractKey(toolUse.input, 'explanation');
2616
- const messageId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId + '_explanation';
2988
+ const messageId = agenticChatResultStream_1.progressPrefix + toolUse.toolUseId + toolConstants_1.SUFFIX_EXPLANATION;
2617
2989
  if (explanation && !chatResultStream.hasMessage(messageId)) {
2618
2990
  await streamWriter.close();
2619
2991
  await chatResultStream.writeResultBlock({
@@ -2625,12 +2997,12 @@ class AgenticChatController {
2625
2997
  }
2626
2998
  }
2627
2999
  }
2628
- async #processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortSignal) {
3000
+ async #processAgenticChatResponse(response, metric, chatResultStream, streamWriter, session, contextList, abortSignal, isCompaction) {
2629
3001
  const requestId = response.$metadata.requestId;
2630
3002
  const chatEventParser = new agenticChatEventParser_1.AgenticChatEventParser(requestId, metric, this.#features.logging);
2631
3003
  // Display context transparency list once at the beginning of response
2632
3004
  // Use a flag to track if contextList has been sent already to avoid ux flickering
2633
- if (contextList?.filePaths && contextList.filePaths.length > 0 && !session.contextListSent) {
3005
+ if (!isCompaction && contextList?.filePaths && contextList.filePaths.length > 0 && !session.contextListSent) {
2634
3006
  await streamWriter.write({ body: '', contextList });
2635
3007
  session.contextListSent = true;
2636
3008
  }
@@ -2664,7 +3036,7 @@ class AgenticChatController {
2664
3036
  if (chatEvent.assistantResponseEvent && result.data.chatResult.body) {
2665
3037
  this.recordChunk('chunk');
2666
3038
  }
2667
- // make sure to save code reference events
3039
+ // update the UI with response
2668
3040
  if (chatEvent.assistantResponseEvent || chatEvent.codeReferenceEvent) {
2669
3041
  await streamWriter.write(result.data.chatResult);
2670
3042
  }
@@ -2674,9 +3046,17 @@ class AgenticChatController {
2674
3046
  }
2675
3047
  }
2676
3048
  if (isEmptyResponse) {
2677
- // If the response is empty, we need to send an empty answer message to remove the Thinking... indicator
3049
+ // If the response is empty, we need to send an empty answer message to remove the Working... indicator
2678
3050
  await streamWriter.write({ type: 'answer', body: '', messageId: (0, uuid_1.v4)() });
2679
3051
  }
3052
+ if (isCompaction) {
3053
+ // Show a dummy message to the UI for compaction completion
3054
+ await streamWriter.write({
3055
+ type: 'answer',
3056
+ body: 'Conversation history has been compacted successfully!',
3057
+ messageId: (0, uuid_1.v4)(),
3058
+ });
3059
+ }
2680
3060
  await streamWriter.close();
2681
3061
  metric.mergeWith({
2682
3062
  cwsprChatFullResponseLatency: metric.getTimeElapsed(),
@@ -2900,12 +3280,54 @@ class AgenticChatController {
2900
3280
  body: (0, textFormatting_1.toolResultMessage)(toolUse, result),
2901
3281
  });
2902
3282
  }
3283
+ scheduleABTestingFetching(userContext) {
3284
+ if (!userContext) {
3285
+ return;
3286
+ }
3287
+ this.#abTestingFetchingTimeout = setInterval(() => {
3288
+ let codeWhispererServiceToken;
3289
+ try {
3290
+ codeWhispererServiceToken = AmazonQTokenServiceManager_1.AmazonQTokenServiceManager.getInstance().getCodewhispererService();
3291
+ }
3292
+ catch (error) {
3293
+ // getCodewhispererService only returns the cwspr client if the service manager was initialized
3294
+ // i.e. profile was selected otherwise it throws an error
3295
+ // we will not evaluate a/b status until profile is selected and service manager is fully initialized
3296
+ return;
3297
+ }
3298
+ // Clear interval once we have the CodewhispererService
3299
+ clearInterval(this.#abTestingFetchingTimeout);
3300
+ this.#abTestingFetchingTimeout = undefined;
3301
+ codeWhispererServiceToken
3302
+ .listFeatureEvaluations({ userContext })
3303
+ .then(result => {
3304
+ const feature = result.featureEvaluations?.find(feature => feature.feature === 'MaestroWorkspaceContext');
3305
+ if (feature) {
3306
+ this.#abTestingAllocation = {
3307
+ experimentName: feature.feature,
3308
+ userVariation: feature.variation,
3309
+ };
3310
+ }
3311
+ })
3312
+ .catch(error => {
3313
+ this.#features.logging.debug(`Error fetching AB testing result: ${error}`);
3314
+ });
3315
+ }, 5000);
3316
+ }
2903
3317
  #log(...messages) {
2904
3318
  this.#features.logging.log(messages.join(' '));
2905
3319
  }
2906
3320
  #debug(...messages) {
2907
3321
  this.#features.logging.debug(messages.join(' '));
2908
3322
  }
3323
+ // Helper function to sanitize the 'images' field for logging by replacing large binary data (e.g., Uint8Array) with a concise summary.
3324
+ // This prevents logs from being overwhelmed by raw byte arrays and keeps log output readable.
3325
+ #imageReplacer(key, value) {
3326
+ if (key === 'bytes' && value && typeof value.length === 'number') {
3327
+ return `[Uint8Array, length: ${value.length}]`;
3328
+ }
3329
+ return value;
3330
+ }
2909
3331
  }
2910
3332
  exports.AgenticChatController = AgenticChatController;
2911
3333
  //# sourceMappingURL=agenticChatController.js.map