@theia/ai-chat-ui 1.71.0-next.8 → 1.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-chat-ui-contribution.js +10 -9
  3. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -0
  5. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/ai-chat-ui-frontend-module.js +3 -0
  7. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  8. package/lib/browser/chat-input-mode-contribution.js +1 -1
  9. package/lib/browser/chat-input-mode-contribution.js.map +1 -1
  10. package/lib/browser/chat-input-widget.d.ts +57 -3
  11. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  12. package/lib/browser/chat-input-widget.js +382 -21
  13. package/lib/browser/chat-input-widget.js.map +1 -1
  14. package/lib/browser/chat-response-part-renderer.d.ts +6 -0
  15. package/lib/browser/chat-response-part-renderer.d.ts.map +1 -1
  16. package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts +2 -0
  17. package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts.map +1 -1
  18. package/lib/browser/chat-response-renderer/delegation-tool-renderer.js +76 -5
  19. package/lib/browser/chat-response-renderer/delegation-tool-renderer.js.map +1 -1
  20. package/lib/browser/chat-response-renderer/index.d.ts +1 -0
  21. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
  22. package/lib/browser/chat-response-renderer/index.js +1 -0
  23. package/lib/browser/chat-response-renderer/index.js.map +1 -1
  24. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +3 -0
  25. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -1
  26. package/lib/browser/chat-response-renderer/question-part-renderer.js +19 -6
  27. package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -1
  28. package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts +21 -0
  29. package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts.map +1 -0
  30. package/lib/browser/chat-response-renderer/tool-call-rendering.js +53 -0
  31. package/lib/browser/chat-response-renderer/tool-call-rendering.js.map +1 -0
  32. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +4 -0
  33. package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
  34. package/lib/browser/chat-response-renderer/tool-confirmation.js +22 -0
  35. package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -1
  36. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +1 -0
  37. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  38. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -18
  39. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  40. package/lib/browser/chat-response-renderer/toolcall-utils.d.ts +1 -0
  41. package/lib/browser/chat-response-renderer/toolcall-utils.d.ts.map +1 -1
  42. package/lib/browser/chat-response-renderer/toolcall-utils.js +3 -0
  43. package/lib/browser/chat-response-renderer/toolcall-utils.js.map +1 -1
  44. package/lib/browser/chat-token-usage-indicator-util.d.ts +35 -0
  45. package/lib/browser/chat-token-usage-indicator-util.d.ts.map +1 -0
  46. package/lib/browser/chat-token-usage-indicator-util.js +138 -0
  47. package/lib/browser/chat-token-usage-indicator-util.js.map +1 -0
  48. package/lib/browser/chat-token-usage-indicator-util.spec.d.ts +2 -0
  49. package/lib/browser/chat-token-usage-indicator-util.spec.d.ts.map +1 -0
  50. package/lib/browser/chat-token-usage-indicator-util.spec.js +226 -0
  51. package/lib/browser/chat-token-usage-indicator-util.spec.js.map +1 -0
  52. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +6 -1
  53. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -1
  54. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +7 -0
  55. package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -1
  56. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  57. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +20 -3
  58. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  59. package/lib/browser/chat-view-preferences.d.ts +18 -0
  60. package/lib/browser/chat-view-preferences.d.ts.map +1 -0
  61. package/lib/browser/chat-view-preferences.js +69 -0
  62. package/lib/browser/chat-view-preferences.js.map +1 -0
  63. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -1
  64. package/lib/browser/chat-view-widget-toolbar-contribution.js +3 -2
  65. package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -1
  66. package/lib/browser/chat-view-widget.d.ts +1 -2
  67. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  68. package/lib/browser/chat-view-widget.js +3 -7
  69. package/lib/browser/chat-view-widget.js.map +1 -1
  70. package/lib/browser/session-settings-dialog.d.ts +2 -5
  71. package/lib/browser/session-settings-dialog.d.ts.map +1 -1
  72. package/lib/browser/session-settings-dialog.js +15 -33
  73. package/lib/browser/session-settings-dialog.js.map +1 -1
  74. package/lib/common/toolcall-utils.d.ts +6 -0
  75. package/lib/common/toolcall-utils.d.ts.map +1 -0
  76. package/lib/common/toolcall-utils.js +40 -0
  77. package/lib/common/toolcall-utils.js.map +1 -0
  78. package/lib/common/toolcall-utils.spec.d.ts +2 -0
  79. package/lib/common/toolcall-utils.spec.d.ts.map +1 -0
  80. package/lib/common/toolcall-utils.spec.js +102 -0
  81. package/lib/common/toolcall-utils.spec.js.map +1 -0
  82. package/package.json +12 -12
  83. package/src/browser/ai-chat-ui-contribution.ts +10 -9
  84. package/src/browser/ai-chat-ui-frontend-module.ts +4 -0
  85. package/src/browser/chat-input-mode-contribution.ts +1 -1
  86. package/src/browser/chat-input-widget.tsx +481 -14
  87. package/src/browser/chat-response-part-renderer.ts +6 -0
  88. package/src/browser/chat-response-renderer/delegation-tool-renderer.tsx +105 -7
  89. package/src/browser/chat-response-renderer/index.ts +1 -0
  90. package/src/browser/chat-response-renderer/question-part-renderer.tsx +24 -8
  91. package/src/browser/chat-response-renderer/tool-call-rendering.tsx +89 -0
  92. package/src/browser/chat-response-renderer/tool-confirmation.tsx +26 -0
  93. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +21 -18
  94. package/src/browser/chat-response-renderer/toolcall-utils.ts +2 -0
  95. package/src/browser/chat-token-usage-indicator-util.spec.ts +262 -0
  96. package/src/browser/chat-token-usage-indicator-util.ts +151 -0
  97. package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +9 -1
  98. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +22 -3
  99. package/src/browser/chat-view-preferences.ts +87 -0
  100. package/src/browser/chat-view-widget-toolbar-contribution.tsx +3 -2
  101. package/src/browser/chat-view-widget.tsx +4 -7
  102. package/src/browser/session-settings-dialog.tsx +15 -97
  103. package/src/browser/style/index.css +164 -54
  104. package/src/browser/style/tool-call-rendering.css +327 -0
  105. package/src/common/toolcall-utils.spec.ts +118 -0
  106. package/src/common/toolcall-utils.ts +36 -0
@@ -16,18 +16,24 @@
16
16
  import {
17
17
  ChangeSet, ChangeSetElement, ChatAgent, ChatChangeEvent, ChatHierarchyBranch,
18
18
  ChatModel, ChatRequestModel, ChatService, ChatSuggestion, EditableChatRequestModel,
19
- ChatRequestParser, ChatMode, ChatSession
19
+ ChatRequestParser, ChatMode, ChatSession, MutableChatModel, ChatSessionSettings
20
20
  } from '@theia/ai-chat';
21
21
  import { ChatAgentService } from '@theia/ai-chat/lib/common/chat-agent-service';
22
22
  import { ParsedChatRequest } from '@theia/ai-chat/lib/common/parsed-chat-request';
23
- import { GenericCapabilitySelections, AIVariableResolutionRequest, ParsedCapability } from '@theia/ai-core';
23
+ import {
24
+ GenericCapabilitySelections, AIVariableResolutionRequest, ParsedCapability,
25
+ FrontendLanguageModelRegistry, ReasoningLevel, ReasoningSettings, ReasoningSupport,
26
+ PREFERENCE_NAME_REASONING, ReasoningPreferenceEntry
27
+ } from '@theia/ai-core';
28
+ import { mergeReasoningSettings } from '@theia/ai-core/lib/browser/frontend-language-model-service';
24
29
  import { ChangeSetDecoratorService } from '@theia/ai-chat/lib/browser/change-set-decorator-service';
25
30
  import { ImageContextVariable } from '@theia/ai-chat/lib/common/image-context-variable';
26
31
  import { AgentCompletionNotificationService, FrontendVariableService, AIActivationService, CompletionNotificationOptions } from '@theia/ai-core/lib/browser';
27
- import { AISettingsService } from '@theia/ai-core/lib/common';
32
+ import { AISettingsService, PromptService } from '@theia/ai-core/lib/common';
28
33
  import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
29
- import { DisposableCollection, Emitter, InMemoryResources, URI, nls, Disposable } from '@theia/core';
30
- import { ContextMenuRenderer, HoverService, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
34
+ import { CommandService, DisposableCollection, Emitter, InMemoryResources, MessageService, URI, nls, Disposable } from '@theia/core';
35
+ import { CommonCommands, ContextMenuRenderer, HoverService, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
36
+ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
31
37
  import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
32
38
  import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
33
39
  import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
@@ -52,6 +58,22 @@ import { CapabilityChip, CapabilityChipsRow } from './chat-capabilities-panel';
52
58
  import { ChatInputFocusService } from './chat-input-focus-service';
53
59
  import { AvailableGenericCapabilities, GenericCapabilitiesService } from './generic-capabilities-service';
54
60
  import { GenericCapabilitiesSection } from './generic-capabilities-section';
61
+ import { PreferenceService } from '@theia/core/lib/common/preferences';
62
+ import {
63
+ CHAT_VIEW_TOKEN_USAGE_ENABLED,
64
+ CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED,
65
+ CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE,
66
+ CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT
67
+ } from './chat-view-preferences';
68
+ import {
69
+ buildBarTooltip,
70
+ CHAT_CONTEXT_WINDOW_SIZE,
71
+ computeSessionTokenUsage,
72
+ decideTokenUsageWarning,
73
+ getLatestTokenUsage,
74
+ getUsageColorClass
75
+ } from './chat-token-usage-indicator-util';
76
+ import { AI_CHAT_NEW_CHAT_WINDOW_COMMAND, ChatCommands } from './chat-view-commands';
55
77
 
56
78
  type Query = (query: string, mode?: string, capabilityOverrides?: Record<string, boolean>, genericCapabilitySelections?: GenericCapabilitySelections) => Promise<void>;
57
79
  type Unpin = () => void;
@@ -155,6 +177,25 @@ export class AIChatInputWidget extends ReactWidget {
155
177
  @inject(AISettingsService)
156
178
  protected readonly aiSettingsService: AISettingsService;
157
179
 
180
+ @inject(PromptService)
181
+ protected readonly promptService: PromptService;
182
+
183
+ @inject(FrontendLanguageModelRegistry)
184
+ protected readonly languageModelRegistry: FrontendLanguageModelRegistry;
185
+
186
+ @inject(PreferenceService) @optional()
187
+ protected readonly preferenceService: PreferenceService | undefined;
188
+
189
+ @inject(MessageService)
190
+ protected readonly messageService: MessageService;
191
+
192
+ @inject(CommandService)
193
+ protected readonly commandService: CommandService;
194
+
195
+ protected tokenUsageEnabled = false;
196
+ /** Sessions we have already notified for the current warning cycle (re-armed when usage drops below the threshold). */
197
+ protected readonly notifiedSessions = new Set<string>();
198
+
158
199
  protected navigationState: ChatInputNavigationState;
159
200
 
160
201
  protected editorRef: SimpleMonacoEditor | undefined = undefined;
@@ -221,6 +262,101 @@ export class AIChatInputWidget extends ReactWidget {
221
262
  }
222
263
  };
223
264
 
265
+ /** Reasoning capability of the model the receiving agent would currently use; undefined hides the selector. */
266
+ protected currentReasoningSupport?: ReasoningSupport;
267
+ /** Id (`provider/model`) of the model that backs {@link currentReasoningSupport}; used to resolve preference defaults. */
268
+ protected currentLanguageModelId?: string;
269
+ /** Saved reasoning selection for the receiving agent (loaded from {@link AISettingsService}); kept in sync with the persisted value. */
270
+ protected savedReasoning?: ReasoningSettings;
271
+
272
+ protected handleReasoningChange = async (level: ReasoningLevel): Promise<void> => {
273
+ const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
274
+ if (!session) {
275
+ return;
276
+ }
277
+ const currentSettings = session.model.settings ?? {};
278
+ const newSettings: ChatSessionSettings = {
279
+ ...currentSettings,
280
+ commonSettings: {
281
+ ...currentSettings.commonSettings,
282
+ reasoning: { level }
283
+ }
284
+ };
285
+ (session.model as MutableChatModel).setSettings(newSettings);
286
+
287
+ // Auto-persist the reasoning selection per-agent so it is restored on the next session
288
+ // and the capabilities indicator does not light up for an unrelated configuration concern.
289
+ if (this.receivingAgent) {
290
+ try {
291
+ await this.aiSettingsService.updateAgentSettings(this.receivingAgent.agentId, {
292
+ reasoning: { level }
293
+ });
294
+ this.savedReasoning = { level };
295
+ } catch (error) {
296
+ console.error('Failed to persist reasoning selection:', error);
297
+ }
298
+ }
299
+
300
+ this.update();
301
+ };
302
+
303
+ /**
304
+ * Resolves the reasoning level to display in the selector. Priority: session override →
305
+ * persisted per-agent selection (from {@link AISettingsService}) →
306
+ * `ai-features.reasoning.defaults` preference entry matching the current model/agent →
307
+ * model's declared default → `'off'`.
308
+ */
309
+ protected getCurrentReasoningLevel(): ReasoningLevel | undefined {
310
+ if (!this.currentReasoningSupport) {
311
+ return undefined;
312
+ }
313
+ const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
314
+ const sessionLevel = session?.model.settings?.commonSettings?.reasoning?.level;
315
+ if (sessionLevel) {
316
+ return sessionLevel;
317
+ }
318
+ if (this.savedReasoning?.level) {
319
+ return this.savedReasoning.level;
320
+ }
321
+ return this.resolvePreferenceReasoningLevel() ?? this.currentReasoningSupport.defaultLevel ?? 'off';
322
+ }
323
+
324
+ protected resolvePreferenceReasoningLevel(): ReasoningLevel | undefined {
325
+ if (!this.preferenceService || !this.currentLanguageModelId) {
326
+ return undefined;
327
+ }
328
+ const entries = this.preferenceService.get<ReasoningPreferenceEntry[]>(PREFERENCE_NAME_REASONING, []);
329
+ const [providerId, modelId] = this.currentLanguageModelId.split('/');
330
+ return mergeReasoningSettings(entries, modelId, providerId, this.receivingAgent?.agentId)?.reasoning?.level;
331
+ }
332
+
333
+ protected async updateReasoningSupport(agentId: string | undefined): Promise<void> {
334
+ let support: ReasoningSupport | undefined;
335
+ let modelId: string | undefined;
336
+ if (agentId) {
337
+ const agent = this.chatAgentService.getAgent(agentId);
338
+ if (agent) {
339
+ for (const requirement of agent.languageModelRequirements ?? []) {
340
+ try {
341
+ const model = await this.languageModelRegistry.selectLanguageModel({ agent: agent.id, ...requirement });
342
+ if (model?.reasoningSupport) {
343
+ support = model.reasoningSupport;
344
+ modelId = model.id;
345
+ break;
346
+ }
347
+ } catch (error) {
348
+ console.warn('Failed to resolve language model for reasoning support:', error);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ if (support !== this.currentReasoningSupport || modelId !== this.currentLanguageModelId) {
354
+ this.currentReasoningSupport = support;
355
+ this.currentLanguageModelId = modelId;
356
+ this.update();
357
+ }
358
+ }
359
+
224
360
  protected handleCapabilityChange = (fragmentId: string, enabled: boolean): void => {
225
361
  const defaultCapability = this.capabilityDefaults.find(c => c.fragmentId === fragmentId);
226
362
  const sessionOverrides = new Map(this.userCapabilityOverrides);
@@ -259,16 +395,21 @@ export class AIChatInputWidget extends ReactWidget {
259
395
  const agentSettings = await this.aiSettingsService.getAgentSettings(agentId);
260
396
  const savedOverrides = agentSettings?.capabilityOverrides;
261
397
  const savedGenericSelections = agentSettings?.genericCapabilitySelections;
398
+ const savedReasoning = agentSettings?.reasoning;
262
399
 
263
400
  // Store saved state for comparison
264
401
  this.savedCapabilityOverrides = savedOverrides ? { ...savedOverrides } : undefined;
265
402
  this.savedGenericCapabilitySelections = savedGenericSelections ? { ...savedGenericSelections } : undefined;
403
+ this.savedReasoning = savedReasoning ? { ...savedReasoning } : undefined;
266
404
 
267
405
  // Initialize from saved settings, or empty if none
268
406
  this.userCapabilityOverrides = savedOverrides
269
407
  ? new Map(Object.entries(savedOverrides))
270
408
  : new Map<string, boolean>();
271
409
  this.genericCapabilitySelections = savedGenericSelections ?? {};
410
+ // Mirror the saved per-agent reasoning into the chat session so the selector reflects it
411
+ // immediately on session/agent switch.
412
+ this.applyReasoningToSession(savedReasoning);
272
413
  }
273
414
 
274
415
  // Update disabled generic capabilities (already used in agent prompt)
@@ -277,6 +418,26 @@ export class AIChatInputWidget extends ReactWidget {
277
418
  this.update();
278
419
  }
279
420
 
421
+ /** Updates the active chat session's `commonSettings.reasoning`; pass `undefined` to clear. */
422
+ protected applyReasoningToSession(reasoning: ReasoningSettings | undefined): void {
423
+ const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
424
+ if (!session) {
425
+ return;
426
+ }
427
+ const currentSettings = session.model.settings ?? {};
428
+ const currentCommon = currentSettings.commonSettings ?? {};
429
+ if ((currentCommon.reasoning?.level ?? undefined) === (reasoning?.level ?? undefined)) {
430
+ return; // no-op when already in sync
431
+ }
432
+ const newCommon: typeof currentCommon = { ...currentCommon };
433
+ if (reasoning) {
434
+ newCommon.reasoning = { ...reasoning };
435
+ } else {
436
+ delete newCommon.reasoning;
437
+ }
438
+ (session.model as MutableChatModel).setSettings({ ...currentSettings, commonSettings: newCommon });
439
+ }
440
+
280
441
  protected async updateAvailableGenericCapabilities(): Promise<void> {
281
442
  if (!this.genericCapabilitiesService) {
282
443
  return;
@@ -313,6 +474,19 @@ export class AIChatInputWidget extends ReactWidget {
313
474
  return new Map(Object.entries(overrides));
314
475
  }
315
476
 
477
+ /**
478
+ * Extracts the mode ID from the last request in the chat model.
479
+ * Used to restore the user's selected mode when switching sessions or on reload.
480
+ */
481
+ protected getLastModeIdFromModel(chatModel: ChatModel): string | undefined {
482
+ const requests = chatModel.getRequests();
483
+ if (requests.length === 0) {
484
+ return undefined;
485
+ }
486
+ const lastRequest = requests[requests.length - 1];
487
+ return lastRequest.request.modeId;
488
+ }
489
+
316
490
  /**
317
491
  * Extracts generic capability selections from the last request in the chat model.
318
492
  * Used to restore user's selections when switching sessions or on reload.
@@ -393,13 +567,19 @@ export class AIChatInputWidget extends ReactWidget {
393
567
 
394
568
  /**
395
569
  * Checks if there are any unsaved changes (capability overrides or generic selections).
570
+ * Reasoning is auto-persisted in {@link handleReasoningChange} and is intentionally excluded.
396
571
  */
397
572
  public hasAnyChangesFromSaved(): boolean {
398
- return (this.hasCapabilityChangesFromSaved() || this.hasGenericCapabilityChangesFromSaved()) && this.receivingAgent !== undefined;
573
+ if (this.receivingAgent === undefined) {
574
+ return false;
575
+ }
576
+ return this.hasCapabilityChangesFromSaved()
577
+ || this.hasGenericCapabilityChangesFromSaved();
399
578
  }
400
579
 
401
580
  /**
402
581
  * Saves current capability selections to settings.
582
+ * Reasoning is auto-persisted via {@link handleReasoningChange} and is not part of this flow.
403
583
  */
404
584
  public async saveCurrentSelectionsToSettings(): Promise<void> {
405
585
  if (!this.receivingAgent) {
@@ -565,7 +745,11 @@ export class AIChatInputWidget extends ReactWidget {
565
745
  // Restore capability overrides and generic selections from the last request in this session (if any)
566
746
  this.userCapabilityOverrides = this.getLastCapabilityOverridesFromModel(chatModel);
567
747
  this.genericCapabilitySelections = this.getLastGenericCapabilitySelectionsFromModel(chatModel);
748
+
568
749
  this.onDisposeForChatModel.push(chatModel.onDidChange(event => {
750
+ if (event.kind === 'responseChanged') {
751
+ this.evaluateTokenUsageWarning(chatModel);
752
+ }
569
753
  if (event.kind === 'addVariable') {
570
754
  // Validate files added via any path (including LLM tool calls)
571
755
  // Get the current variables and validate any new file variables
@@ -589,6 +773,16 @@ export class AIChatInputWidget extends ReactWidget {
589
773
  }
590
774
  }));
591
775
  this._chatModel = chatModel;
776
+ // Evaluate the warning on attach. `notifiedSessions` lives on this widget
777
+ // instance, so the warning fires at most once per (widget lifetime × session):
778
+ // - Within the same widget, switching between sessions that have already been
779
+ // notified does not re-notify.
780
+ // - Closing and reopening the chat view creates a fresh widget with an empty
781
+ // Set, so sessions still above the threshold will be warned about again the
782
+ // first time they are shown after reopen — once per session. Accepted as a
783
+ // rare corner case; promoting the state to the ChatSession would avoid it
784
+ // but isn't worth the coupling today.
785
+ this.evaluateTokenUsageWarning(chatModel);
592
786
  this.scheduleUpdateReceivingAgent();
593
787
  this.update();
594
788
  }
@@ -607,8 +801,8 @@ export class AIChatInputWidget extends ReactWidget {
607
801
  this.id = AIChatInputWidget.ID;
608
802
  this.title.closable = false;
609
803
  this.toDispose.push(this.resources.add(this.getResourceUri(), ''));
610
- this.toDispose.push(this.aiActivationService.onDidChangeActiveStatus(() => {
611
- this.setEnabled(this.aiActivationService.isActive);
804
+ this.toDispose.push(this.aiActivationService.onDidChangeCanRun(() => {
805
+ this.setEnabled(this.aiActivationService.canRun);
612
806
  }));
613
807
  this.toDispose.push(this.chatAgentService.onDefaultAgentChanged(() => {
614
808
  this.scheduleUpdateReceivingAgent();
@@ -620,16 +814,58 @@ export class AIChatInputWidget extends ReactWidget {
620
814
  this.updateReceivingAgentTimeout = undefined;
621
815
  }
622
816
  }));
623
- this.setEnabled(this.aiActivationService.isActive);
817
+ this.setEnabled(this.aiActivationService.canRun);
624
818
  this.historyService.init().then(() => {
625
819
  this.navigationState = new ChatInputNavigationState(this.historyService);
626
820
  });
627
821
  this.initializeContextKeys();
822
+ this.tokenUsageEnabled = this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
823
+ if (this.preferenceService) {
824
+ this.toDispose.push(this.preferenceService.onPreferenceChanged(change => {
825
+ if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_ENABLED) {
826
+ this.tokenUsageEnabled = this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
827
+ this.update();
828
+ } else if (change.preferenceName === PREFERENCE_NAME_REASONING) {
829
+ // Refresh the reasoning selector display when the default preference changes.
830
+ this.update();
831
+ } else if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE) {
832
+ // Threshold changed: clear notified sessions so users are warned again
833
+ // at the new threshold (e.g. after raising it from the warning's
834
+ // "Open Settings" action), and re-evaluate the current session so the
835
+ // warning appears immediately if it's still above the new threshold.
836
+ this.notifiedSessions.clear();
837
+ if (this._chatModel) {
838
+ this.evaluateTokenUsageWarning(this._chatModel);
839
+ }
840
+ // Re-render so the indicator's color bands reflect the new threshold.
841
+ this.update();
842
+ } else if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED && this._chatModel) {
843
+ // If the user just enabled warnings for a session already above threshold,
844
+ // evaluate now so they get an immediate notification instead of waiting for
845
+ // the next response.
846
+ this.evaluateTokenUsageWarning(this._chatModel);
847
+ }
848
+ }));
849
+ }
628
850
  // Listen for prompt fragment changes to refresh capabilities
629
851
  this.toDispose.push(this.capabilitiesService.onDidChangeCapabilities(() => {
630
852
  this.refreshCapabilities();
631
853
  }));
632
854
 
855
+ // Refresh reasoning capability if the language model registry changes (model added/removed/alias re-resolved).
856
+ this.toDispose.push(this.languageModelRegistry.onChange(() => {
857
+ if (this.receivingAgent) {
858
+ this.updateReasoningSupport(this.receivingAgent.agentId);
859
+ }
860
+ }));
861
+
862
+ // When the default mode changes externally (e.g. via AI Configuration),
863
+ // sync the mode selector. Deferred via queueMicrotask so the prompt service's
864
+ // internal state is fully updated before we read agent.modes.
865
+ this.toDispose.push(this.promptService.onSelectedVariantChange(() => {
866
+ queueMicrotask(() => this.syncSelectedModeWithDefault());
867
+ }));
868
+
633
869
  // Listen for generic capabilities changes
634
870
  if (this.genericCapabilitiesService) {
635
871
  this.updateAvailableGenericCapabilities();
@@ -685,6 +921,88 @@ export class AIChatInputWidget extends ReactWidget {
685
921
  this.chatInputLastLineKey.set(isLastVisualOverall);
686
922
  }
687
923
 
924
+ /**
925
+ * Resolve the configured token usage warning threshold as an absolute token count.
926
+ * The preference is stored as a percentage of the context window; this method
927
+ * converts it using the current assumed context window size.
928
+ */
929
+ protected getTokenUsageWarningThreshold(): number {
930
+ const percentage = this.getTokenUsageWarningThresholdPercentage();
931
+ return Math.round((percentage / 100) * CHAT_CONTEXT_WINDOW_SIZE);
932
+ }
933
+
934
+ protected getTokenUsageWarningThresholdPercentage(): number {
935
+ const value = this.preferenceService?.get<number>(
936
+ CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE,
937
+ CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT
938
+ );
939
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 100) {
940
+ return CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT;
941
+ }
942
+ return value;
943
+ }
944
+
945
+ protected isTokenUsageWarningEnabled(): boolean {
946
+ return this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED, false) ?? false;
947
+ }
948
+
949
+ /**
950
+ * Called after a response changes on the currently attached session. Shows a
951
+ * warning the first time the total crosses the threshold, and re-arms when
952
+ * usage drops back below it.
953
+ */
954
+ protected evaluateTokenUsageWarning(chatModel: ChatModel): void {
955
+ // No point doing any work if the feature is off.
956
+ if (!this.isTokenUsageWarningEnabled()) {
957
+ return;
958
+ }
959
+ // `responseChanged` fires on every streaming tick, but providers typically
960
+ // only set `tokenUsage` at completion. Skip in-progress responses so we do
961
+ // the walk + decision once per response rather than per chunk.
962
+ const lastRequest = chatModel.getRequests().at(-1);
963
+ if (lastRequest && !lastRequest.response.isComplete) {
964
+ return;
965
+ }
966
+ const decision = decideTokenUsageWarning({
967
+ totalTokens: computeSessionTokenUsage(chatModel),
968
+ threshold: this.getTokenUsageWarningThreshold(),
969
+ alreadyNotified: this.notifiedSessions.has(chatModel.id)
970
+ });
971
+ if (decision === 'reset') {
972
+ this.notifiedSessions.delete(chatModel.id);
973
+ } else if (decision === 'notify') {
974
+ this.notifiedSessions.add(chatModel.id);
975
+ this.showTokenUsageWarning();
976
+ }
977
+ }
978
+
979
+ protected async showTokenUsageWarning(): Promise<void> {
980
+ const percentage = this.getTokenUsageWarningThresholdPercentage();
981
+ const message = nls.localize(
982
+ 'theia/ai/chat-ui/tokenUsageWarningMessage',
983
+ 'Chat session token usage has reached {0}% of the context window. ' +
984
+ 'Consider summarizing this session or starting a new one to avoid hitting the limit.',
985
+ percentage
986
+ );
987
+ const summarizeAction = nls.localize('theia/ai/chat-ui/tokenUsageWarningSummarizeAction', 'Summarize Current Session');
988
+ const newSessionAction = nls.localize('theia/ai/chat-ui/tokenUsageWarningNewSessionAction', 'Start New Chat');
989
+ const openSettingsAction = nls.localizeByDefault('Open Settings');
990
+ const selected = await this.messageService.warn(message, summarizeAction, newSessionAction, openSettingsAction);
991
+ if (selected === summarizeAction) {
992
+ this.commandService.executeCommand(ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id).catch(error => {
993
+ console.error(`Failed to execute '${ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id}' from token usage warning`, error);
994
+ });
995
+ } else if (selected === newSessionAction) {
996
+ this.commandService.executeCommand(AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id).catch(error => {
997
+ console.error(`Failed to execute '${AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id}' from token usage warning`, error);
998
+ });
999
+ } else if (selected === openSettingsAction) {
1000
+ this.commandService.executeCommand(CommonCommands.OPEN_PREFERENCES.id, CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE).catch(error => {
1001
+ console.error(`Failed to execute '${CommonCommands.OPEN_PREFERENCES.id}' from token usage warning`, error);
1002
+ });
1003
+ }
1004
+ }
1005
+
688
1006
  protected scheduleUpdateReceivingAgent(): void {
689
1007
  if (this.queryInFlight) {
690
1008
  // Don't update capabilities while a query is being sent — the editor is being
@@ -774,7 +1092,11 @@ export class AIChatInputWidget extends ReactWidget {
774
1092
  if (agent && (agentId !== previousAgentId || needsRefresh)) {
775
1093
  const modes = agent.modes ?? [];
776
1094
  const defaultMode = modes.find(m => m.isDefault);
777
- const initialModeId = defaultMode?.id;
1095
+ const hasPreviousRequests = this._chatModel.getRequests().length > 0;
1096
+ const restoredModeId = needsRefresh && hasPreviousRequests
1097
+ ? this.getLastModeIdFromModel(this._chatModel)
1098
+ : undefined;
1099
+ const initialModeId = restoredModeId ?? defaultMode?.id;
778
1100
  this.receivingAgent = {
779
1101
  agentId: agentId,
780
1102
  modes,
@@ -782,14 +1104,39 @@ export class AIChatInputWidget extends ReactWidget {
782
1104
  };
783
1105
  this.chatInputHasModesKey.set(modes.length > 1);
784
1106
  // Only preserve overrides on forced refresh if the session has previous requests
785
- const hasPreviousRequests = this._chatModel.getRequests().length > 0;
786
1107
  const shouldPreserveOverrides = needsRefresh && hasPreviousRequests;
787
1108
  await this.updateCapabilitiesForAgent(agentId, initialModeId, shouldPreserveOverrides);
1109
+ this.updateReasoningSupport(agentId);
788
1110
  } else if (!agent && this.receivingAgent !== undefined) {
789
1111
  this.receivingAgent = undefined;
790
1112
  this.capabilityDefaults = [];
791
1113
  this.userCapabilityOverrides = new Map();
792
1114
  this.chatInputHasModesKey.set(false);
1115
+ this.currentReasoningSupport = undefined;
1116
+ this.update();
1117
+ }
1118
+ }
1119
+
1120
+ /**
1121
+ * Syncs the selected mode in the UI with the agent's current default mode.
1122
+ * Called when the default mode changes externally (e.g. via AI Configuration).
1123
+ */
1124
+ protected syncSelectedModeWithDefault(): void {
1125
+ if (!this.receivingAgent) {
1126
+ return;
1127
+ }
1128
+ const agent = this.chatAgentService.getAgent(this.receivingAgent.agentId);
1129
+ if (!agent?.modes) {
1130
+ return;
1131
+ }
1132
+ const updatedModes = agent.modes;
1133
+ const newDefault = updatedModes.find(m => m.isDefault);
1134
+ if (newDefault && newDefault.id !== this.receivingAgent.currentModeId) {
1135
+ this.receivingAgent = {
1136
+ ...this.receivingAgent,
1137
+ modes: updatedModes,
1138
+ currentModeId: newDefault.id
1139
+ };
793
1140
  this.update();
794
1141
  }
795
1142
  }
@@ -961,6 +1308,11 @@ export class AIChatInputWidget extends ReactWidget {
961
1308
  onModeChange: this.handleModeChange,
962
1309
  keybindingHint: this.getModeKeybindingHint(),
963
1310
  }}
1311
+ reasoningSelectorProps={{
1312
+ reasoningSupport: this.currentReasoningSupport,
1313
+ currentLevel: this.getCurrentReasoningLevel(),
1314
+ onReasoningChange: this.handleReasoningChange,
1315
+ }}
964
1316
  capabilitiesProps={{
965
1317
  capabilities: this.capabilityDefaults,
966
1318
  overrides: this.userCapabilityOverrides,
@@ -979,6 +1331,8 @@ export class AIChatInputWidget extends ReactWidget {
979
1331
  disabledCapabilities: this.disabledGenericCapabilities,
980
1332
  hoverService: this.hoverService,
981
1333
  }}
1334
+ tokenUsageEnabled={this.tokenUsageEnabled}
1335
+ tokenUsageWarningThreshold={this.getTokenUsageWarningThreshold()}
982
1336
  />
983
1337
  );
984
1338
  }
@@ -1292,6 +1646,11 @@ interface ChatInputProperties {
1292
1646
  onModeChange: (mode: string) => void;
1293
1647
  keybindingHint?: string;
1294
1648
  };
1649
+ reasoningSelectorProps: {
1650
+ reasoningSupport?: ReasoningSupport;
1651
+ currentLevel?: ReasoningLevel;
1652
+ onReasoningChange: (level: ReasoningLevel) => void;
1653
+ };
1295
1654
  capabilitiesProps: {
1296
1655
  capabilities: ParsedCapability[];
1297
1656
  overrides: Map<string, boolean>;
@@ -1310,6 +1669,8 @@ interface ChatInputProperties {
1310
1669
  disabledCapabilities: GenericCapabilitySelections;
1311
1670
  hoverService: HoverService;
1312
1671
  };
1672
+ tokenUsageEnabled?: boolean;
1673
+ tokenUsageWarningThreshold: number;
1313
1674
  }
1314
1675
 
1315
1676
  // Utility to check if we have task context in the chat model
@@ -1737,13 +2098,20 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
1737
2098
  // Show mode selector if agent has multiple modes
1738
2099
  const showModeSelector = (props.modeSelectorProps.receivingAgentModes?.length ?? 0) > 1;
1739
2100
 
2101
+ // Token usage computation (cheap pure function walking the model's request list)
2102
+ const totalTokens = props.tokenUsageEnabled ? computeSessionTokenUsage(props.chatModel) : 0;
2103
+ const showTokenUsage = props.tokenUsageEnabled && totalTokens > 0;
2104
+ const tokenColorClass = showTokenUsage ? getUsageColorClass(totalTokens, props.tokenUsageWarningThreshold) : '';
2105
+ const tokenIsWarningOrError = tokenColorClass === 'token-usage-yellow' || tokenColorClass === 'token-usage-red';
2106
+ const tokenTooltip = showTokenUsage ? buildBarTooltip(getLatestTokenUsage(props.chatModel), totalTokens, props.tokenUsageWarningThreshold) : undefined;
2107
+
1740
2108
  return (
1741
2109
  <div className="theia-ChatInput" data-ai-disabled={!props.isEnabled} onDragOver={props.onDragOver} onDrop={props.onDrop} ref={containerRef}>
1742
2110
  {props.showSuggestions !== false && <ChatInputAgentSuggestions suggestions={props.suggestions} opener={props.openerService} />}
1743
2111
  {props.showChangeSet && changeSetUI?.elements &&
1744
2112
  <ChangeSetBox changeSet={changeSetUI} />
1745
2113
  }
1746
- <div className='theia-ChatInput-Editor-Box'>
2114
+ <div className={`theia-ChatInput-Editor-Box${tokenIsWarningOrError ? ` token-usage-border-${tokenColorClass}` : ''}`}>
1747
2115
  {props.showCapabilities !== false && (
1748
2116
  <CapabilitiesBar
1749
2117
  isOpen={props.capabilitiesProps.isOpen}
@@ -1772,6 +2140,11 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
1772
2140
  rightOptions={rightOptions}
1773
2141
  isEnabled={props.isEnabled}
1774
2142
  hoverService={props.hoverService}
2143
+ tokenUsage={showTokenUsage ? {
2144
+ percent: Math.min((totalTokens / CHAT_CONTEXT_WINDOW_SIZE) * 100, 100),
2145
+ colorClass: tokenColorClass,
2146
+ tooltip: tokenTooltip,
2147
+ } : undefined}
1775
2148
  modeSelectorProps={{
1776
2149
  show: showModeSelector,
1777
2150
  modes: props.modeSelectorProps.receivingAgentModes,
@@ -1779,6 +2152,12 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
1779
2152
  onModeChange: props.modeSelectorProps.onModeChange,
1780
2153
  keybindingHint: props.modeSelectorProps.keybindingHint,
1781
2154
  }}
2155
+ reasoningSelectorProps={{
2156
+ show: !!props.reasoningSelectorProps.reasoningSupport,
2157
+ reasoningSupport: props.reasoningSelectorProps.reasoningSupport,
2158
+ currentLevel: props.reasoningSelectorProps.currentLevel,
2159
+ onReasoningChange: props.reasoningSelectorProps.onReasoningChange,
2160
+ }}
1782
2161
  capabilitiesToggle={{
1783
2162
  show: props.showCapabilities !== false,
1784
2163
  isOpen: props.capabilitiesProps.isOpen,
@@ -1797,12 +2176,12 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
1797
2176
  /**
1798
2177
  * Returns an onMouseEnter handler that shows a hover tooltip via HoverService.
1799
2178
  */
1800
- function hoverHandler(hoverService: HoverService, content: string): (e: React.MouseEvent) => void {
2179
+ function hoverHandler(hoverService: HoverService, content: string | MarkdownString, position: 'top' | 'bottom' = 'bottom'): (e: React.MouseEvent) => void {
1801
2180
  return (e: React.MouseEvent) => {
1802
2181
  hoverService.requestHover({
1803
2182
  content,
1804
2183
  target: e.currentTarget as HTMLElement,
1805
- position: 'bottom'
2184
+ position
1806
2185
  });
1807
2186
  };
1808
2187
  }
@@ -1812,6 +2191,11 @@ interface ChatInputOptionsProps {
1812
2191
  rightOptions: Option[];
1813
2192
  isEnabled?: boolean;
1814
2193
  hoverService: HoverService;
2194
+ tokenUsage?: {
2195
+ percent: number;
2196
+ colorClass: string;
2197
+ tooltip?: MarkdownString;
2198
+ };
1815
2199
  modeSelectorProps: {
1816
2200
  show: boolean;
1817
2201
  modes?: ChatMode[];
@@ -1819,6 +2203,12 @@ interface ChatInputOptionsProps {
1819
2203
  onModeChange: (mode: string) => void;
1820
2204
  keybindingHint?: string;
1821
2205
  };
2206
+ reasoningSelectorProps: {
2207
+ show: boolean;
2208
+ reasoningSupport?: ReasoningSupport;
2209
+ currentLevel?: ReasoningLevel;
2210
+ onReasoningChange: (level: ReasoningLevel) => void;
2211
+ };
1822
2212
  capabilitiesToggle: {
1823
2213
  show: boolean;
1824
2214
  isOpen: boolean;
@@ -1834,7 +2224,9 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
1834
2224
  rightOptions,
1835
2225
  isEnabled,
1836
2226
  hoverService,
2227
+ tokenUsage,
1837
2228
  modeSelectorProps,
2229
+ reasoningSelectorProps,
1838
2230
  capabilitiesToggle
1839
2231
  }) => {
1840
2232
  const capabilitiesLabel = nls.localize('theia/ai/chat-ui/toggleCapabilitiesConfig', 'Toggle Capabilities Configuration');
@@ -1847,6 +2239,21 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
1847
2239
  // CSS order property positions them visually (left on left, right on right)
1848
2240
  <div className="theia-ChatInputOptions">
1849
2241
  <div className="theia-ChatInputOptions-right">
2242
+ {tokenUsage && (
2243
+ <span
2244
+ className={`token-usage-badge ${tokenUsage.colorClass}`}
2245
+ {...(tokenUsage.tooltip && { onMouseEnter: hoverHandler(hoverService, tokenUsage.tooltip, 'top') })}
2246
+ >
2247
+ <span
2248
+ className='token-usage-ring'
2249
+ style={{
2250
+ background: `conic-gradient(var(--token-usage-fill) ${tokenUsage.percent}%, var(--token-usage-track) ${tokenUsage.percent}%)`
2251
+ }}
2252
+ >
2253
+ <span className='token-usage-ring-inner' />
2254
+ </span>
2255
+ </span>
2256
+ )}
1850
2257
  {rightOptions.map((option, index) => (
1851
2258
  <span
1852
2259
  key={index}
@@ -1922,6 +2329,15 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
1922
2329
  )}
1923
2330
  </span>
1924
2331
  )}
2332
+ {reasoningSelectorProps.show && reasoningSelectorProps.reasoningSupport && (
2333
+ <ReasoningSelector
2334
+ reasoningSupport={reasoningSelectorProps.reasoningSupport}
2335
+ currentLevel={reasoningSelectorProps.currentLevel}
2336
+ onReasoningChange={reasoningSelectorProps.onReasoningChange}
2337
+ disabled={!isEnabled}
2338
+ hoverService={hoverService}
2339
+ />
2340
+ )}
1925
2341
  </div>
1926
2342
  </div>
1927
2343
  );
@@ -2078,6 +2494,57 @@ const ChatModeSelector: React.FunctionComponent<ChatModeSelectorProps> = React.m
2078
2494
  );
2079
2495
  });
2080
2496
 
2497
+ interface ReasoningSelectorProps {
2498
+ reasoningSupport: ReasoningSupport;
2499
+ currentLevel?: ReasoningLevel;
2500
+ onReasoningChange: (level: ReasoningLevel) => void;
2501
+ disabled?: boolean;
2502
+ hoverService: HoverService;
2503
+ }
2504
+
2505
+ const reasoningLevelLabel = (level: ReasoningLevel): string => {
2506
+ switch (level) {
2507
+ case 'off': return nls.localizeByDefault('Off');
2508
+ case 'minimal': return nls.localize('theia/ai/chat-ui/reasoning/minimal', 'Minimal');
2509
+ case 'low': return nls.localize('theia/ai/chat-ui/reasoning/low', 'Low');
2510
+ case 'medium': return nls.localize('theia/ai/chat-ui/reasoning/medium', 'Medium');
2511
+ case 'high': return nls.localize('theia/ai/chat-ui/reasoning/high', 'High');
2512
+ case 'auto': return nls.localizeByDefault('Auto');
2513
+ }
2514
+ };
2515
+
2516
+ const ReasoningSelector: React.FunctionComponent<ReasoningSelectorProps> = React.memo(({
2517
+ reasoningSupport, currentLevel, onReasoningChange, disabled, hoverService
2518
+ }) => {
2519
+ const options: SelectOption[] = React.useMemo(
2520
+ () => reasoningSupport.supportedLevels.map(level => ({ value: level, label: reasoningLevelLabel(level) })),
2521
+ [reasoningSupport]
2522
+ );
2523
+
2524
+ const handleChange = React.useCallback(
2525
+ (option: SelectOption) => {
2526
+ if (option.value) {
2527
+ onReasoningChange(option.value as ReasoningLevel);
2528
+ }
2529
+ },
2530
+ [onReasoningChange]
2531
+ );
2532
+
2533
+ const title = nls.localizeByDefault('Reasoning');
2534
+ const effectiveLevel = currentLevel ?? reasoningSupport.defaultLevel ?? reasoningSupport.supportedLevels[0] ?? 'off';
2535
+
2536
+ return (
2537
+ <span onMouseEnter={hoverHandler(hoverService, title)}>
2538
+ <SelectComponent
2539
+ className={`theia-ChatInput-ReasoningSelector reasoning-level-${effectiveLevel}${disabled ? ' disabled' : ''}`}
2540
+ options={options}
2541
+ defaultValue={effectiveLevel}
2542
+ onChange={handleChange}
2543
+ />
2544
+ </span>
2545
+ );
2546
+ });
2547
+
2081
2548
  const noPropagation = (handler: () => void) => (e: React.MouseEvent) => {
2082
2549
  handler();
2083
2550
  e.stopPropagation();