@theia/ai-chat-ui 1.58.2 → 1.59.0-next.62

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 (51) hide show
  1. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-chat-ui-contribution.js +7 -7
  3. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  5. package/lib/browser/ai-chat-ui-frontend-module.js +7 -4
  6. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  7. package/lib/browser/chat-input-widget.d.ts +21 -4
  8. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  9. package/lib/browser/chat-input-widget.js +177 -37
  10. package/lib/browser/chat-input-widget.js.map +1 -1
  11. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
  12. package/lib/browser/chat-response-renderer/code-part-renderer.js +4 -3
  13. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  14. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -1
  15. package/lib/browser/chat-response-renderer/text-part-renderer.js +3 -1
  16. package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -1
  17. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  18. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -3
  19. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  20. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  21. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +25 -16
  22. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  23. package/lib/browser/chat-view-contribution.d.ts +2 -2
  24. package/lib/browser/chat-view-contribution.d.ts.map +1 -1
  25. package/lib/browser/chat-view-contribution.js +6 -6
  26. package/lib/browser/chat-view-contribution.js.map +1 -1
  27. package/lib/browser/chat-view-language-contribution.d.ts +10 -5
  28. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  29. package/lib/browser/chat-view-language-contribution.js +94 -14
  30. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  31. package/lib/browser/chat-view-widget.d.ts +4 -1
  32. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  33. package/lib/browser/chat-view-widget.js +18 -7
  34. package/lib/browser/chat-view-widget.js.map +1 -1
  35. package/lib/browser/context-variable-picker.d.ts +9 -0
  36. package/lib/browser/context-variable-picker.d.ts.map +1 -0
  37. package/lib/browser/context-variable-picker.js +86 -0
  38. package/lib/browser/context-variable-picker.js.map +1 -0
  39. package/package.json +11 -11
  40. package/src/browser/ai-chat-ui-contribution.ts +8 -8
  41. package/src/browser/ai-chat-ui-frontend-module.ts +8 -4
  42. package/src/browser/chat-input-widget.tsx +255 -49
  43. package/src/browser/chat-response-renderer/code-part-renderer.tsx +4 -3
  44. package/src/browser/chat-response-renderer/text-part-renderer.tsx +4 -1
  45. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +9 -3
  46. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +26 -16
  47. package/src/browser/chat-view-contribution.ts +6 -6
  48. package/src/browser/chat-view-language-contribution.ts +103 -19
  49. package/src/browser/chat-view-widget.tsx +22 -8
  50. package/src/browser/context-variable-picker.ts +85 -0
  51. package/src/browser/style/index.css +95 -9
@@ -47,6 +47,7 @@ import {
47
47
  postConstruct
48
48
  } from '@theia/core/shared/inversify';
49
49
  import * as React from '@theia/core/shared/react';
50
+ import { nls } from '@theia/core/lib/common/nls';
50
51
 
51
52
  import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
52
53
  import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
@@ -151,43 +152,51 @@ export class ChatViewTreeWidget extends TreeWidget {
151
152
  return <div className={'theia-ResponseNode'}>
152
153
  <div className='theia-ResponseNode-Content' key={'disabled-message'}>
153
154
  <div className="disable-message">
154
- <span className="section-header"> 🚀 Experimental AI Feature Available!</span>
155
+ <span className="section-header">{
156
+ nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiFeatureHeader', '🚀 AI Features Available (Alpha Version)!')}
157
+ </span>
155
158
  <div className="section-title">
156
- <p><code>Currently, all AI Features are disabled!</code></p>
159
+ <p><code>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/featuresDisabled', 'Currently, all AI Features are disabled!')}</code></p>
157
160
  </div>
158
161
  <div className="section-title">
159
- <p>How to Enable Experimental AI Features:</p>
162
+ <p>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/howToEnable', 'How to Enable the AI Features:')}</p>
160
163
  </div>
161
164
  <div className="section-content">
162
- <p>To enable the experimental AI features, please go to &nbsp;
163
- {this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
165
+ <p>To enable the AI features, please go to &nbsp;
166
+ {this.renderLinkButton(nls.localize('theia/ai/chat-ui/chat-view-tree-widget/settingsMenu', 'the settings menu'), CommonCommands.OPEN_PREFERENCES.id)}
164
167
  &nbsp;and locate the <strong>AI Features</strong> section.</p>
165
168
  <ol>
166
- <li>Toggle the switch for <strong>'Ai-features: Enable'</strong>.</li>
169
+ <li>Toggle the switch for <strong>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiFeaturesEnable', 'Ai-features: Enable')}</strong>.</li>
167
170
  <li>Provide at least one LLM provider (e.g. OpenAI), also see <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a>
168
171
  for more information.</li>
169
172
  </ol>
170
- <p>This will activate the new AI capabilities in the app. Please remember, these features are still in development, so they may change or be unstable. 🚧</p>
173
+ <p>This will activate the AI capabilities in the app. Please remember, these features are <strong>in an alpha state</strong>,
174
+ so they may change and we are working on improving them 🚧.<br></br>
175
+ Please support us by <a href="https://github.com/eclipse-theia/theia">providing feedback
176
+ </a>!</p>
171
177
  </div>
172
178
 
173
179
  <div className="section-title">
174
180
  <p>Currently Supported Views and Features:</p>
175
181
  </div>
176
182
  <div className="section-content">
177
- <p>Once the experimental AI features are enabled, you can access the following views and features:</p>
183
+ <p>Once the AI features are enabled, you can access the following views and features:</p>
178
184
  <ul>
179
185
  <li>Code Completion</li>
180
186
  <li>Terminal Assistance (via CTRL+I in a terminal)</li>
181
187
  <li>This Chat View (features the following agents):
182
188
  <ul>
183
189
  <li>Universal Chat Agent</li>
184
- <li>Workspace Chat Agent</li>
190
+ <li>Coder Chat Agent</li>
191
+ <li>Architect Chat Agent</li>
185
192
  <li>Command Chat Agent</li>
186
193
  <li>Orchestrator Chat Agent</li>
187
194
  </ul>
188
195
  </li>
189
- <li>{this.renderLinkButton('AI History View', 'aiHistory:open')}</li>
190
- <li>{this.renderLinkButton('AI Configuration View', 'aiConfiguration:open')}</li>
196
+ <li>{this.renderLinkButton(nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiHistoryView', 'AI History View'), 'aiHistory:open')}</li>
197
+ <li>{this.renderLinkButton(
198
+ nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiConfigurationView', 'AI Configuration View'), 'aiConfiguration:open')}
199
+ </li>
191
200
  </ul>
192
201
  <p>See <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a> for more information.</p>
193
202
  </div>
@@ -307,8 +316,9 @@ export class ChatViewTreeWidget extends TreeWidget {
307
316
  }}>
308
317
  {this.getAgentLabel(node)}
309
318
  </h3>
310
- {inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>Generating</span>}
311
- {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>Waiting for input</span>}
319
+ {inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>{nls.localizeByDefault('Generating')}</span>}
320
+ {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>{
321
+ nls.localize('theia/ai/chat-ui/chat-view-tree-widget/waitingForInput', 'Waiting for input')}</span>}
312
322
  <div className='theia-ChatNodeToolbar'>
313
323
  {!inProgress &&
314
324
  toolbarContributions.length > 0 &&
@@ -338,9 +348,9 @@ export class ChatViewTreeWidget extends TreeWidget {
338
348
  private getAgentLabel(node: RequestNode | ResponseNode): string {
339
349
  if (isRequestNode(node)) {
340
350
  // TODO find user name
341
- return 'You';
351
+ return nls.localize('theia/ai/chat-ui/chat-view-tree-widget/you', 'You');
342
352
  }
343
- return this.getAgent(node)?.name ?? 'AI';
353
+ return this.getAgent(node)?.name ?? nls.localize('theia/ai/chat-ui/chat-view-tree-widget/ai', 'AI');
344
354
  }
345
355
 
346
356
  private getAgent(node: RequestNode | ResponseNode): ChatAgent | undefined {
@@ -420,7 +430,7 @@ export class ChatViewTreeWidget extends TreeWidget {
420
430
  [-1, undefined])[1];
421
431
  if (!renderer) {
422
432
  console.error('No renderer found for content', content);
423
- return <div>Error: No renderer found</div>;
433
+ return <div>{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/noRenderer', 'Error: No renderer found')}</div>;
424
434
  }
425
435
  return renderer.render(content, node);
426
436
  }
@@ -69,7 +69,7 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
69
69
  const parent = extractRequestOrResponseNodes(args).find(arg => arg.parent)?.parent;
70
70
  const text = parent?.children
71
71
  .filter(isRequestOrResponseNode)
72
- .map(child => this.getText(child))
72
+ .map(child => this.getCopyText(child))
73
73
  .join('\n\n---\n\n');
74
74
  if (text) {
75
75
  this.clipboardService.writeText(text);
@@ -93,19 +93,19 @@ export class ChatViewMenuContribution implements MenuContribution, CommandContri
93
93
  }
94
94
 
95
95
  protected copyMessage(args: (RequestNode | ResponseNode)[]): void {
96
- const text = this.getTextAndJoin(args);
96
+ const text = this.getCopyTextAndJoin(args);
97
97
  this.clipboardService.writeText(text);
98
98
  }
99
99
 
100
- protected getTextAndJoin(args: (RequestNode | ResponseNode)[] | undefined): string {
101
- return args !== undefined ? args.map(arg => this.getText(arg)).join() : '';
100
+ protected getCopyTextAndJoin(args: (RequestNode | ResponseNode)[] | undefined): string {
101
+ return args !== undefined ? args.map(arg => this.getCopyText(arg)).join() : '';
102
102
  }
103
103
 
104
- protected getText(arg: RequestNode | ResponseNode): string {
104
+ protected getCopyText(arg: RequestNode | ResponseNode): string {
105
105
  if (isRequestNode(arg)) {
106
106
  return arg.request.request.text;
107
107
  } else if (isResponseNode(arg)) {
108
- return arg.response.response.asString();
108
+ return arg.response.response.asDisplayString();
109
109
  }
110
110
  return '';
111
111
  }
@@ -13,18 +13,24 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { inject, injectable, named } from '@theia/core/shared/inversify';
17
- import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
16
+ import { ChatAgentService } from '@theia/ai-chat';
17
+ import { AIContextVariable, AIVariableService } from '@theia/ai-core/lib/common';
18
+ import { PromptText } from '@theia/ai-core/lib/common/prompt-text';
19
+ import { ToolInvocationRegistry } from '@theia/ai-core/lib/common/tool-invocation-registry';
20
+ import { MaybePromise, nls } from '@theia/core';
21
+ import { ApplicationShell, FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
22
+ import { inject, injectable } from '@theia/core/shared/inversify';
18
23
  import * as monaco from '@theia/monaco-editor-core';
19
- import { ContributionProvider, MaybePromise } from '@theia/core';
20
24
  import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
21
- import { ChatAgentService } from '@theia/ai-chat';
22
- import { AIVariableService } from '@theia/ai-core/lib/common';
23
- import { ToolProvider } from '@theia/ai-core/lib/common/tool-invocation-registry';
25
+ import { ChatViewWidget } from './chat-view-widget';
24
26
 
25
27
  export const CHAT_VIEW_LANGUAGE_ID = 'theia-ai-chat-view-language';
26
28
  export const CHAT_VIEW_LANGUAGE_EXTENSION = 'aichatviewlanguage';
27
29
 
30
+ const VARIABLE_RESOLUTION_CONTEXT = { context: 'chat-input-autocomplete' };
31
+ const VARIABLE_ARGUMENT_PICKER_COMMAND = 'trigger-variable-argument-picker';
32
+ const VARIABLE_ADD_CONTEXT_COMMAND = 'add-context-variable';
33
+
28
34
  @injectable()
29
35
  export class ChatViewLanguageContribution implements FrontendApplicationContribution {
30
36
 
@@ -34,25 +40,34 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
34
40
  @inject(AIVariableService)
35
41
  protected readonly variableService: AIVariableService;
36
42
 
37
- @inject(ContributionProvider)
38
- @named(ToolProvider)
39
- private providers: ContributionProvider<ToolProvider>;
43
+ @inject(ToolInvocationRegistry)
44
+ protected readonly toolInvocationRegistry: ToolInvocationRegistry;
45
+
46
+ @inject(ApplicationShell)
47
+ protected readonly shell: ApplicationShell;
40
48
 
41
49
  onStart(_app: FrontendApplication): MaybePromise<void> {
42
50
  monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
43
51
 
44
52
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
45
- triggerCharacters: ['@'],
53
+ triggerCharacters: [PromptText.AGENT_CHAR],
46
54
  provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideAgentCompletions(model, position),
47
55
  });
48
56
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
49
- triggerCharacters: ['#'],
57
+ triggerCharacters: [PromptText.VARIABLE_CHAR],
50
58
  provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableCompletions(model, position),
51
59
  });
52
60
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
53
- triggerCharacters: ['~'],
61
+ triggerCharacters: [PromptText.VARIABLE_CHAR, PromptText.VARIABLE_SEPARATOR_CHAR],
62
+ provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableWithArgCompletions(model, position),
63
+ });
64
+ monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
65
+ triggerCharacters: [PromptText.FUNCTION_CHAR],
54
66
  provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideToolCompletions(model, position),
55
67
  });
68
+
69
+ monaco.editor.registerCommand(VARIABLE_ARGUMENT_PICKER_COMMAND, this.triggerVariableArgumentPicker.bind(this));
70
+ monaco.editor.registerCommand(VARIABLE_ADD_CONTEXT_COMMAND, (_, ...args) => args.length > 1 ? this.addContextVariable(args[0], args[1]) : undefined);
56
71
  }
57
72
 
58
73
  getCompletionRange(model: monaco.editor.ITextModel, position: monaco.Position, triggerCharacter: string): monaco.Range | undefined {
@@ -65,7 +80,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
65
80
  return undefined;
66
81
  }
67
82
 
68
- // Calculate the range from the position of the '@' character
83
+ // Calculate the range from the position of the trigger character
69
84
  const wordInfo = model.getWordUntilPosition(position);
70
85
  return new monaco.Range(
71
86
  position.lineNumber,
@@ -83,7 +98,8 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
83
98
  kind: monaco.languages.CompletionItemKind,
84
99
  getId: (item: T) => string,
85
100
  getName: (item: T) => string,
86
- getDescription: (item: T) => string
101
+ getDescription: (item: T) => string,
102
+ command?: monaco.languages.Command
87
103
  ): ProviderResult<monaco.languages.CompletionList> {
88
104
  const completionRange = this.getCompletionRange(model, position, triggerChar);
89
105
  if (completionRange === undefined) {
@@ -95,6 +111,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
95
111
  label: getName(item),
96
112
  range: completionRange,
97
113
  detail: getDescription(item),
114
+ command
98
115
  }));
99
116
  return { suggestions };
100
117
  }
@@ -103,7 +120,7 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
103
120
  return this.getSuggestions(
104
121
  model,
105
122
  position,
106
- '@',
123
+ PromptText.AGENT_CHAR,
107
124
  this.agentService.getAgents(),
108
125
  monaco.languages.CompletionItemKind.Value,
109
126
  agent => agent.id,
@@ -116,25 +133,92 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
116
133
  return this.getSuggestions(
117
134
  model,
118
135
  position,
119
- '#',
136
+ PromptText.VARIABLE_CHAR,
120
137
  this.variableService.getVariables(),
121
138
  monaco.languages.CompletionItemKind.Variable,
122
139
  variable => variable.name,
123
140
  variable => variable.name,
124
- variable => variable.description
141
+ variable => variable.description,
142
+ {
143
+ title: nls.localize('theia/ai/chat-ui/selectVariableArguments', 'Select variable arguments'),
144
+ id: VARIABLE_ARGUMENT_PICKER_COMMAND,
145
+ }
125
146
  );
126
147
  }
127
148
 
149
+ async provideVariableWithArgCompletions(model: monaco.editor.ITextModel, position: monaco.Position): Promise<monaco.languages.CompletionList> {
150
+ const variables = this.variableService.getVariables();
151
+ const suggestions: monaco.languages.CompletionItem[] = [];
152
+ for (const variable of variables) {
153
+ const provider = await this.variableService.getArgumentCompletionProvider(variable.name);
154
+ if (provider) {
155
+ const items = await provider(model, position);
156
+ if (items) {
157
+ suggestions.push(...items.map(item => ({
158
+ ...item,
159
+ // trigger command to check if we should add a context variable
160
+ command: {
161
+ title: nls.localize('theia/ai/chat-ui/addContextVariable', 'Add context variable'),
162
+ id: VARIABLE_ADD_CONTEXT_COMMAND,
163
+ arguments: [variable.name, item.insertText]
164
+ }
165
+ })));
166
+ }
167
+ }
168
+ }
169
+ return { suggestions };
170
+ }
171
+
128
172
  provideToolCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
129
173
  return this.getSuggestions(
130
174
  model,
131
175
  position,
132
- '~',
133
- this.providers.getContributions().map(provider => provider.getTool()),
176
+ PromptText.FUNCTION_CHAR,
177
+ this.toolInvocationRegistry.getAllFunctions(),
134
178
  monaco.languages.CompletionItemKind.Function,
135
179
  tool => tool.id,
136
180
  tool => tool.name,
137
181
  tool => tool.description ?? ''
138
182
  );
139
183
  }
184
+
185
+ protected async triggerVariableArgumentPicker(): Promise<void> {
186
+ const inputEditor = monaco.editor.getEditors().find(editor => editor.hasTextFocus());
187
+ if (!inputEditor) {
188
+ return;
189
+ }
190
+ const model = inputEditor.getModel();
191
+ const position = inputEditor.getPosition();
192
+ if (!model || !position) {
193
+ return;
194
+ }
195
+ const variableName = model.getWordAtPosition(position)?.word;
196
+ if (!variableName) {
197
+ return;
198
+ }
199
+ const provider = await this.variableService.getArgumentPicker(variableName, VARIABLE_RESOLUTION_CONTEXT);
200
+ if (!provider) {
201
+ return;
202
+ }
203
+ const arg = await provider(VARIABLE_RESOLUTION_CONTEXT);
204
+ if (!arg) {
205
+ return;
206
+ }
207
+ inputEditor.executeEdits('variable-argument-picker', [{
208
+ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
209
+ text: PromptText.VARIABLE_SEPARATOR_CHAR + arg
210
+ }]);
211
+ await this.addContextVariable(variableName, arg);
212
+ }
213
+
214
+ protected async addContextVariable(variableName: string, arg: string | undefined): Promise<void> {
215
+ const variable = this.variableService.getVariable(variableName);
216
+ if (!variable || !AIContextVariable.is(variable)) {
217
+ return;
218
+ }
219
+ const widget = this.shell.getWidgetById(ChatViewWidget.ID);
220
+ if (widget instanceof ChatViewWidget) {
221
+ widget.addContext({ variable, arg });
222
+ }
223
+ }
140
224
  }
@@ -21,6 +21,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
21
21
  import { AIChatInputWidget } from './chat-input-widget';
22
22
  import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
23
23
  import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
24
+ import { AIVariableResolutionRequest } from '@theia/ai-core';
24
25
 
25
26
  export namespace ChatViewWidget {
26
27
  export interface State {
@@ -32,7 +33,7 @@ export namespace ChatViewWidget {
32
33
  export class ChatViewWidget extends BaseWidget implements ExtractableWidget, StatefulWidget {
33
34
 
34
35
  public static ID = 'chat-view-widget';
35
- static LABEL = `✨ ${nls.localizeByDefault('Chat')} [Experimental]`;
36
+ static LABEL = `${nls.localizeByDefault('Chat')}`;
36
37
 
37
38
  @inject(ChatService)
38
39
  protected chatService: ChatService;
@@ -91,8 +92,10 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
91
92
  this.chatSession = this.chatService.createSession();
92
93
 
93
94
  this.inputWidget.onQuery = this.onQuery.bind(this);
95
+ this.inputWidget.onUnpin = this.onUnpin.bind(this);
94
96
  this.inputWidget.onCancel = this.onCancel.bind(this);
95
97
  this.inputWidget.chatModel = this.chatSession.model;
98
+ this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
96
99
  this.inputWidget.onDeleteChangeSet = this.onDeleteChangeSet.bind(this);
97
100
  this.inputWidget.onDeleteChangeSetElement = this.onDeleteChangeSetElement.bind(this);
98
101
  this.treeWidget.trackChatModel(this.chatSession.model);
@@ -117,6 +120,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
117
120
  this.chatSession = session;
118
121
  this.treeWidget.trackChatModel(this.chatSession.model);
119
122
  this.inputWidget.chatModel = this.chatSession.model;
123
+ this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
120
124
  if (event.focus) {
121
125
  this.show();
122
126
  }
@@ -157,18 +161,19 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
157
161
  return this.onStateChangedEmitter.event;
158
162
  }
159
163
 
160
- protected async onQuery(query: string): Promise<void> {
164
+ protected async onQuery(query: string, contextVariableRequests?: AIVariableResolutionRequest[]): Promise<void> {
161
165
  if (query.length === 0) { return; }
162
166
 
163
- const chatRequest: ChatRequest = {
164
- text: query
165
- };
166
-
167
- const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
167
+ const chatRequest: ChatRequest = { text: query };
168
+ const context = { variableRequests: contextVariableRequests ?? [] };
169
+ const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest, context);
168
170
  requestProgress?.responseCompleted.then(responseModel => {
169
171
  if (responseModel.isError) {
170
- this.messageService.error(responseModel.errorObject?.message ?? 'An error occurred during chat service invocation.');
172
+ this.messageService.error(responseModel.errorObject?.message ??
173
+ nls.localize('theia/ai/chat-ui/errorChatInvocation', 'An error occurred during chat service invocation.'));
171
174
  }
175
+ }).finally(() => {
176
+ this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
172
177
  });
173
178
  if (!requestProgress) {
174
179
  this.messageService.error(`Was not able to send request "${chatRequest.text}" to session ${this.chatSession.id}`);
@@ -177,6 +182,11 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
177
182
  // Tree Widget currently tracks the ChatModel itself. Therefore no notification necessary.
178
183
  }
179
184
 
185
+ protected onUnpin(): void {
186
+ this.chatSession.pinnedAgent = undefined;
187
+ this.inputWidget.pinnedAgent = this.chatSession.pinnedAgent;
188
+ }
189
+
180
190
  protected onCancel(requestModel: ChatRequestModel): void {
181
191
  this.chatService.cancelRequest(requestModel.session.id, requestModel.id);
182
192
  }
@@ -204,4 +214,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
204
214
  get isExtractable(): boolean {
205
215
  return this.secondaryWindow === undefined;
206
216
  }
217
+
218
+ addContext(variable: AIVariableResolutionRequest): void {
219
+ this.inputWidget.addContext(variable);
220
+ }
207
221
  }
@@ -0,0 +1,85 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { AIContextVariable, AIVariableResolutionRequest, AIVariableService, PromptText } from '@theia/ai-core';
18
+ import { QuickInputService } from '@theia/core';
19
+ import { inject, injectable } from '@theia/core/shared/inversify';
20
+
21
+ const QUERY_CONTEXT = { type: 'context-variable-picker' };
22
+
23
+ @injectable()
24
+ export class ContextVariablePicker {
25
+
26
+ @inject(AIVariableService)
27
+ protected readonly variableService: AIVariableService;
28
+
29
+ @inject(QuickInputService)
30
+ protected readonly quickInputService: QuickInputService;
31
+
32
+ async pickContextVariable(): Promise<AIVariableResolutionRequest | undefined> {
33
+ const variables = this.variableService.getContextVariables();
34
+ const selection = await this.quickInputService.showQuickPick(
35
+ variables.map(v => ({
36
+ id: v.id,
37
+ label: v.label ?? v.name,
38
+ variable: v,
39
+ iconClasses: v.iconClasses,
40
+ })),
41
+ { placeholder: 'Select a context variable to be attached to the message', }
42
+ );
43
+ if (!selection) {
44
+ return undefined;
45
+ }
46
+
47
+ const variable = selection.variable;
48
+ if (!variable.args || variable.args.length === 0) {
49
+ return { variable };
50
+ }
51
+
52
+ const argumentPicker = await this.variableService.getArgumentPicker(variable.name, QUERY_CONTEXT);
53
+ if (!argumentPicker) {
54
+ return this.useGenericArgumentPicker(variable);
55
+ }
56
+ const arg = await argumentPicker(QUERY_CONTEXT);
57
+ if (!arg) {
58
+ return undefined;
59
+ }
60
+
61
+ return { variable, arg };
62
+ }
63
+
64
+ protected async useGenericArgumentPicker(variable: AIContextVariable): Promise<AIVariableResolutionRequest | undefined> {
65
+ const args: string[] = [];
66
+ for (const argument of variable.args ?? []) {
67
+ const placeHolder = argument.description;
68
+ let input: string | undefined;
69
+ if (argument.enum) {
70
+ const picked = await this.quickInputService.pick(
71
+ argument.enum.map(enumItem => ({ label: enumItem })),
72
+ { placeHolder, canPickMany: false }
73
+ );
74
+ input = picked?.label;
75
+ } else {
76
+ input = await this.quickInputService.input({ placeHolder });
77
+ }
78
+ if (!input && !argument.isOptional) {
79
+ return;
80
+ }
81
+ args.push(input ?? '');
82
+ }
83
+ return { variable, arg: args.join(PromptText.VARIABLE_SEPARATOR_CHAR) };
84
+ }
85
+ }