@theia/ai-ide 1.72.1 → 1.72.3

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 (35) hide show
  1. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts +1 -0
  2. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +15 -2
  4. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -1
  5. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts +3 -0
  6. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts.map +1 -1
  7. package/lib/browser/ai-configuration/tools-configuration-widget.js +61 -4
  8. package/lib/browser/ai-configuration/tools-configuration-widget.js.map +1 -1
  9. package/lib/browser/ide-chat-welcome-message-provider.d.ts +11 -0
  10. package/lib/browser/ide-chat-welcome-message-provider.d.ts.map +1 -1
  11. package/lib/browser/ide-chat-welcome-message-provider.js +31 -1
  12. package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
  13. package/lib/browser/todo-tool-renderer.d.ts +7 -1
  14. package/lib/browser/todo-tool-renderer.d.ts.map +1 -1
  15. package/lib/browser/todo-tool-renderer.js +25 -1
  16. package/lib/browser/todo-tool-renderer.js.map +1 -1
  17. package/lib/browser/user-interaction-tool-renderer.d.ts +7 -1
  18. package/lib/browser/user-interaction-tool-renderer.d.ts.map +1 -1
  19. package/lib/browser/user-interaction-tool-renderer.js +25 -1
  20. package/lib/browser/user-interaction-tool-renderer.js.map +1 -1
  21. package/lib/browser/workspace-functions.d.ts +27 -3
  22. package/lib/browser/workspace-functions.d.ts.map +1 -1
  23. package/lib/browser/workspace-functions.js +123 -140
  24. package/lib/browser/workspace-functions.js.map +1 -1
  25. package/lib/browser/workspace-functions.spec.js +323 -24
  26. package/lib/browser/workspace-functions.spec.js.map +1 -1
  27. package/package.json +23 -22
  28. package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +15 -1
  29. package/src/browser/ai-configuration/tools-configuration-widget.tsx +106 -17
  30. package/src/browser/ide-chat-welcome-message-provider.tsx +43 -1
  31. package/src/browser/style/index.css +16 -0
  32. package/src/browser/todo-tool-renderer.tsx +30 -3
  33. package/src/browser/user-interaction-tool-renderer.tsx +30 -3
  34. package/src/browser/workspace-functions.spec.ts +385 -25
  35. package/src/browser/workspace-functions.ts +128 -195
@@ -197,31 +197,120 @@ export class AIToolsConfigurationWidget extends AITableConfigurationWidget<ToolI
197
197
 
198
198
  protected override renderHeader(): React.ReactNode {
199
199
  return (
200
- <div className="ai-tools-configuration-header">
201
- <div style={{ fontWeight: 500 }}>
202
- {nls.localize('theia/ai/ide/toolsConfiguration/default/label', 'Default Tool Confirmation Mode:')}
200
+ <>
201
+ <div className="ai-tools-configuration-header">
202
+ <div style={{ fontWeight: 500 }}>
203
+ {nls.localize('theia/ai/ide/toolsConfiguration/default/label', 'Default Tool Confirmation Mode:')}
204
+ </div>
205
+ <select
206
+ className="theia-select"
207
+ value={this.defaultState}
208
+ onChange={this.handleDefaultStateChange}
209
+ >
210
+ {TOOL_OPTIONS.map(opt => (
211
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
212
+ ))}
213
+ </select>
214
+ <button
215
+ className='theia-button secondary ai-tools-reset-button'
216
+ style={{ marginLeft: 'auto' }}
217
+ title={nls.localize('theia/ai/ide/toolsConfiguration/resetAllTooltip', 'Reset all tools to default')}
218
+ onClick={() => this.resetAllToolsToDefault()}
219
+ >
220
+ {nls.localize('theia/ai/ide/toolsConfiguration/resetAll', 'Reset All')}
221
+ </button>
203
222
  </div>
204
- <select
205
- className="theia-select"
206
- value={this.defaultState}
207
- onChange={this.handleDefaultStateChange}
208
- >
209
- {TOOL_OPTIONS.map(opt => (
210
- <option key={opt.value} value={opt.value}>{opt.label}</option>
211
- ))}
212
- </select>
223
+ {this.renderRecommendedDefaultsBanner()}
224
+ </>
225
+ );
226
+ }
227
+
228
+ protected renderRecommendedDefaultsBanner(): React.ReactNode {
229
+ if (this.defaultState !== ToolConfirmationMode.CONFIRM) {
230
+ return undefined;
231
+ }
232
+ return (
233
+ <div className="ai-tools-recommended-defaults-banner">
234
+ <span className="ai-tools-recommended-defaults-text">
235
+ {nls.localize(
236
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/message',
237
+ 'Tool calls currently require approval. Auto-allow all built-in tools at once.' +
238
+ ' Tools that require extra confirmation (e.g. shell execution), MCP tools,' +
239
+ ' and any tools added later will still ask before running.'
240
+ )}
241
+ </span>
213
242
  <button
214
- className='theia-button secondary ai-tools-reset-button'
215
- style={{ marginLeft: 'auto' }}
216
- title={nls.localize('theia/ai/ide/toolsConfiguration/resetAllTooltip', 'Reset all tools to default')}
217
- onClick={() => this.resetAllToolsToDefault()}
243
+ className='theia-button main'
244
+ onClick={() => this.allowCurrentTools()}
218
245
  >
219
- {nls.localize('theia/ai/ide/toolsConfiguration/resetAll', 'Reset All')}
246
+ {nls.localize('theia/ai/ide/toolsConfiguration/recommendedDefaults/apply', 'Allow Default Tools')}
220
247
  </button>
221
248
  </div>
222
249
  );
223
250
  }
224
251
 
252
+ protected async allowCurrentTools(): Promise<void> {
253
+ const dialog = new ConfirmDialog({
254
+ title: nls.localize('theia/ai/ide/toolsConfiguration/recommendedDefaults/dialogTitle', 'Allow Default Tools?'),
255
+ msg: this.buildAllowCurrentToolsMessage(),
256
+ ok: nls.localize('theia/ai/ide/toolsConfiguration/recommendedDefaults/dialogConfirm', 'I understand, allow'),
257
+ cancel: nls.localizeByDefault('Cancel')
258
+ });
259
+ const confirmed = await dialog.open();
260
+ if (!confirmed) {
261
+ return;
262
+ }
263
+ // Leave the global default at CONFIRM so any tool added later still requires confirmation.
264
+ // Skip tools that require extra consent (confirmAlwaysAllow), MCP tools (third-party code
265
+ // that should require informed per-tool consent), and tools the user has explicitly disabled.
266
+ const updates: Array<{ toolId: string; mode: ToolConfirmationMode; toolRequest: ToolRequest }> = [];
267
+ for (const tool of this.toolInvocationRegistry.getAllFunctions()) {
268
+ if (tool.confirmAlwaysAllow) {
269
+ continue;
270
+ }
271
+ if (tool.name.startsWith('mcp_')) {
272
+ continue;
273
+ }
274
+ if (this.toolConfirmationModes[tool.name] === ToolConfirmationMode.DISABLED) {
275
+ continue;
276
+ }
277
+ updates.push({ toolId: tool.name, mode: ToolConfirmationMode.ALWAYS_ALLOW, toolRequest: tool });
278
+ }
279
+ await this.confirmationManager.setConfirmationModes(updates);
280
+ }
281
+
282
+ protected buildAllowCurrentToolsMessage(): HTMLElement {
283
+ const container = document.createElement('div');
284
+ const lines = [
285
+ nls.localize(
286
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/msg/line1',
287
+ 'This sets all currently registered built-in tools to "Always Allow".'
288
+ ),
289
+ nls.localize(
290
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/msg/line2',
291
+ 'Tools that require extra confirmation (such as shell execution) will still ask before running.'
292
+ ),
293
+ nls.localize(
294
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/msg/line3',
295
+ 'MCP tools are third-party and are not included. You can allow them individually in this view.'
296
+ ),
297
+ nls.localize(
298
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/msg/line4',
299
+ 'Tools added later will still default to "Confirm" so you can review them.'
300
+ ),
301
+ nls.localize(
302
+ 'theia/ai/ide/toolsConfiguration/recommendedDefaults/msg/line5',
303
+ 'You can change this later in this view.'
304
+ )
305
+ ];
306
+ for (const line of lines) {
307
+ const p = document.createElement('p');
308
+ p.textContent = line;
309
+ container.appendChild(p);
310
+ }
311
+ return container;
312
+ }
313
+
225
314
  protected getEffectiveState(toolName: string): ToolConfirmationMode {
226
315
  // If there's an explicit setting for this tool, use it
227
316
  const explicitSetting = this.toolConfirmationModes[toolName];
@@ -24,7 +24,9 @@ import { AgentService, FrontendLanguageModelRegistry } from '@theia/ai-core/lib/
24
24
  import { PreferenceService } from '@theia/core/lib/common';
25
25
  import { DEFAULT_CHAT_AGENT_PREF, BYPASS_MODEL_REQUIREMENT_PREF } from '@theia/ai-chat/lib/common/ai-chat-preferences';
26
26
  import { ChatAgentRecommendationService, ChatAgentService } from '@theia/ai-chat/lib/common';
27
- import { OPEN_AI_CONFIG_VIEW } from './ai-configuration/ai-configuration-view-contribution';
27
+ import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
28
+ import { DEFAULT_TOOL_CONFIRMATION_PREFERENCE, TOOL_CONFIRMATION_PREFERENCE, ToolConfirmationMode } from '@theia/ai-chat/lib/common/chat-tool-preferences';
29
+ import { OPEN_AI_CONFIG_VIEW, OPEN_AI_CONFIG_VIEW_TOOLS } from './ai-configuration/ai-configuration-view-contribution';
28
30
  import { AIActivationService } from '@theia/ai-core/lib/browser';
29
31
  import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
30
32
  import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
@@ -83,6 +85,9 @@ export class IdeChatWelcomeMessageProvider implements ChatWelcomeMessageProvider
83
85
  @inject(AgentService)
84
86
  protected agentService: AgentService;
85
87
 
88
+ @inject(ToolConfirmationManager)
89
+ protected readonly toolConfirmationManager: ToolConfirmationManager;
90
+
86
91
  @inject(AIActivationService)
87
92
  protected readonly activationService: AIActivationService;
88
93
 
@@ -120,6 +125,9 @@ export class IdeChatWelcomeMessageProvider implements ChatWelcomeMessageProvider
120
125
  this._modelRequirementBypassed = effectiveValue;
121
126
  this.notifyStateChanged();
122
127
  }
128
+ } else if (e.preferenceName === DEFAULT_TOOL_CONFIRMATION_PREFERENCE || e.preferenceName === TOOL_CONFIRMATION_PREFERENCE) {
129
+ // Re-render so the tool-confirmation explainer is hidden once the user configures confirmation behavior.
130
+ this.notifyStateChanged();
123
131
  }
124
132
  })
125
133
  );
@@ -186,6 +194,19 @@ export class IdeChatWelcomeMessageProvider implements ChatWelcomeMessageProvider
186
194
  return this._defaultAgent;
187
195
  }
188
196
 
197
+ /**
198
+ * Whether to show the tool-confirmation explainer on the welcome screen.
199
+ *
200
+ * Only shown while the user is still in the default state, i.e. every tool call is confirmed and
201
+ * no per-tool overrides exist. Once the user has changed the default mode (e.g. to always allow
202
+ * or to disable tools) or pre-approved individual tools (including via bulk approval), they are
203
+ * already aware of the mechanism, so the explainer is suppressed.
204
+ */
205
+ protected get shouldShowToolConfirmationInfo(): boolean {
206
+ return this.toolConfirmationManager.getDefaultConfirmationMode() === ToolConfirmationMode.CONFIRM
207
+ && Object.keys(this.toolConfirmationManager.getAllConfirmationSettings()).length === 0;
208
+ }
209
+
189
210
  protected setModelRequirementBypassed(bypassed: boolean): void {
190
211
  this.preferenceService.set(BYPASS_MODEL_REQUIREMENT_PREF, bypassed, PreferenceScope.User);
191
212
  }
@@ -227,6 +248,27 @@ Attach context with *#{3}*, *#{4}*, *#{5}*, or click {6}. [Learn more](https://t
227
248
  className="theia-WelcomeMessage-Content"
228
249
  markdownOptions={{ supportHtml: true }}
229
250
  />
251
+ {this.shouldShowToolConfirmationInfo && (
252
+ <div className="theia-alert theia-info-alert theia-WelcomeMessage-Alert">
253
+ <div className="theia-message-header">
254
+ <span className={codicon('info')}></span>
255
+ <span>{nls.localize('theia/ai/ide/toolConfirmationInfo/header', 'Tool confirmation')}</span>
256
+ </div>
257
+ <div className="theia-message-content">
258
+ <LocalizedMarkdown
259
+ localizationKey="theia/ai/ide/toolConfirmationInfo"
260
+ defaultMarkdown={
261
+ 'AI agents may want to use tools to act on your workspace. ' +
262
+ 'By default each tool call needs your confirmation. ' +
263
+ 'You can change this default or pre-approve individual tools in the [Tools configuration view]({0}).'
264
+ }
265
+ args={[`command:${OPEN_AI_CONFIG_VIEW_TOOLS.id}`]}
266
+ markdownRenderer={this.markdownRenderer}
267
+ markdownOptions={{ isTrusted: { enabledCommands: [OPEN_AI_CONFIG_VIEW_TOOLS.id] } }}
268
+ />
269
+ </div>
270
+ </div>
271
+ )}
230
272
  </div>;
231
273
  }
232
274
 
@@ -1251,6 +1251,22 @@
1251
1251
  border-radius: 3px;
1252
1252
  }
1253
1253
 
1254
+ .ai-tools-recommended-defaults-banner {
1255
+ display: flex;
1256
+ align-items: center;
1257
+ gap: calc(var(--theia-ui-padding) * 2);
1258
+ margin-bottom: calc(var(--theia-ui-padding) * 2);
1259
+ padding: calc(var(--theia-ui-padding) * 1.5);
1260
+ background-color: var(--theia-editorInfo-background, var(--theia-editor-background));
1261
+ border: var(--theia-border-width) solid var(--theia-editorInfo-border, var(--theia-widget-border));
1262
+ border-radius: 3px;
1263
+ }
1264
+
1265
+ .ai-tools-recommended-defaults-text {
1266
+ flex: 1;
1267
+ line-height: var(--theia-content-line-height);
1268
+ }
1269
+
1254
1270
  .ai-tools-configuration-default-section {
1255
1271
  margin-bottom: calc(var(--theia-ui-padding) * 2);
1256
1272
  }
@@ -20,11 +20,16 @@ import { ResponseNode } from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
20
20
  import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
21
21
  import { ReactNode } from '@theia/core/shared/react';
22
22
  import * as React from '@theia/core/shared/react';
23
- import { codicon, ContextMenuRenderer, OpenerService } from '@theia/core/lib/browser';
23
+ import { codicon, ContextMenuRenderer, KeybindingRegistry, OpenerService } from '@theia/core/lib/browser';
24
24
  import { nls } from '@theia/core';
25
25
  import { TODO_WRITE_FUNCTION_ID, TodoItem, isValidTodoItem } from '../common/todo-tool';
26
- import { withToolCallConfirmation } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/tool-confirmation';
26
+ import { ToolConfirmationKeybindingHints, withToolCallConfirmation } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/tool-confirmation';
27
+ import {
28
+ APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND,
29
+ DENY_LATEST_TOOL_CONFIRMATION_COMMAND
30
+ } from '@theia/ai-chat-ui/lib/browser/tool-confirmation-keybinding-contribution';
27
31
  import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
32
+ import { PendingToolConfirmationTracker } from '@theia/ai-chat/lib/browser/pending-tool-confirmation-tracker';
28
33
  import { ToolInvocationRegistry } from '@theia/ai-core';
29
34
 
30
35
  interface TodoListComponentProps {
@@ -95,6 +100,12 @@ export class TodoToolRenderer implements ChatResponsePartRenderer<ToolCallChatRe
95
100
  @inject(ToolInvocationRegistry)
96
101
  protected toolInvocationRegistry: ToolInvocationRegistry;
97
102
 
103
+ @inject(PendingToolConfirmationTracker)
104
+ protected pendingToolConfirmationTracker: PendingToolConfirmationTracker;
105
+
106
+ @inject(KeybindingRegistry)
107
+ protected keybindingRegistry: KeybindingRegistry;
108
+
98
109
  canHandle(response: ChatResponseContent): number {
99
110
  if (ToolCallChatResponseContent.is(response) && response.name === TODO_WRITE_FUNCTION_ID) {
100
111
  return 20;
@@ -128,7 +139,9 @@ export class TodoToolRenderer implements ChatResponsePartRenderer<ToolCallChatRe
128
139
  chatId,
129
140
  requestCanceled: parentNode.response.isCanceled,
130
141
  contextMenuRenderer: this.contextMenuRenderer,
131
- openerService: this.openerService
142
+ openerService: this.openerService,
143
+ pendingTracker: this.pendingToolConfirmationTracker,
144
+ keybindingHints: this.getKeybindingHints()
132
145
  }}
133
146
  />
134
147
  );
@@ -156,4 +169,18 @@ export class TodoToolRenderer implements ChatResponsePartRenderer<ToolCallChatRe
156
169
  return undefined;
157
170
  }
158
171
  }
172
+
173
+ protected getKeybindingHints(): ToolConfirmationKeybindingHints {
174
+ const allow = this.formatKeybinding(APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND.id);
175
+ const deny = this.formatKeybinding(DENY_LATEST_TOOL_CONFIRMATION_COMMAND.id);
176
+ return { allow, deny };
177
+ }
178
+
179
+ protected formatKeybinding(commandId: string): string | undefined {
180
+ const bindings = this.keybindingRegistry.getKeybindingsForCommand(commandId);
181
+ if (!bindings.length) {
182
+ return undefined;
183
+ }
184
+ return this.keybindingRegistry.acceleratorFor(bindings[0], '+').join('+');
185
+ }
159
186
  }
@@ -20,11 +20,16 @@ import { ResponseNode } from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
20
20
  import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
21
21
  import { ReactNode } from '@theia/core/shared/react';
22
22
  import * as React from '@theia/core/shared/react';
23
- import { codicon, ContextMenuRenderer, OpenerService } from '@theia/core/lib/browser';
23
+ import { codicon, ContextMenuRenderer, KeybindingRegistry, OpenerService } from '@theia/core/lib/browser';
24
24
  import { nls } from '@theia/core/lib/common/nls';
25
25
  import { useMarkdownRendering } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/markdown-part-renderer';
26
- import { withToolCallConfirmation } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/tool-confirmation';
26
+ import { ToolConfirmationKeybindingHints, withToolCallConfirmation } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/tool-confirmation';
27
+ import {
28
+ APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND,
29
+ DENY_LATEST_TOOL_CONFIRMATION_COMMAND
30
+ } from '@theia/ai-chat-ui/lib/browser/tool-confirmation-keybinding-contribution';
27
31
  import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
32
+ import { PendingToolConfirmationTracker } from '@theia/ai-chat/lib/browser/pending-tool-confirmation-tracker';
28
33
  import { ToolInvocationRegistry } from '@theia/ai-core';
29
34
  import { UserInteractionTool } from './user-interaction-tool';
30
35
  import {
@@ -499,6 +504,12 @@ export class UserInteractionToolRenderer implements ChatResponsePartRenderer<Too
499
504
  @inject(OpenerService)
500
505
  protected openerService: OpenerService;
501
506
 
507
+ @inject(PendingToolConfirmationTracker)
508
+ protected pendingToolConfirmationTracker: PendingToolConfirmationTracker;
509
+
510
+ @inject(KeybindingRegistry)
511
+ protected keybindingRegistry: KeybindingRegistry;
512
+
502
513
  canHandle(response: ChatResponseContent): number {
503
514
  if (ToolCallChatResponseContent.is(response) && response.name === USER_INTERACTION_FUNCTION_ID) {
504
515
  return 20;
@@ -551,9 +562,25 @@ export class UserInteractionToolRenderer implements ChatResponsePartRenderer<Too
551
562
  chatId,
552
563
  requestCanceled: parentNode.response.isCanceled,
553
564
  contextMenuRenderer: this.contextMenuRenderer,
554
- openerService: this.openerService
565
+ openerService: this.openerService,
566
+ pendingTracker: this.pendingToolConfirmationTracker,
567
+ keybindingHints: this.getKeybindingHints()
555
568
  }}
556
569
  />
557
570
  );
558
571
  }
572
+
573
+ protected getKeybindingHints(): ToolConfirmationKeybindingHints {
574
+ const allow = this.formatKeybinding(APPROVE_LATEST_TOOL_CONFIRMATION_COMMAND.id);
575
+ const deny = this.formatKeybinding(DENY_LATEST_TOOL_CONFIRMATION_COMMAND.id);
576
+ return { allow, deny };
577
+ }
578
+
579
+ protected formatKeybinding(commandId: string): string | undefined {
580
+ const bindings = this.keybindingRegistry.getKeybindingsForCommand(commandId);
581
+ if (!bindings.length) {
582
+ return undefined;
583
+ }
584
+ return this.keybindingRegistry.acceleratorFor(bindings[0], '+').join('+');
585
+ }
559
586
  }