@theia/ai-core 1.63.0-next.52 → 1.63.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 (80) hide show
  1. package/lib/browser/agent-completion-notification-service.d.ts +69 -0
  2. package/lib/browser/agent-completion-notification-service.d.ts.map +1 -0
  3. package/lib/browser/agent-completion-notification-service.js +187 -0
  4. package/lib/browser/agent-completion-notification-service.js.map +1 -0
  5. package/lib/browser/agent-preferences.d.ts.map +1 -1
  6. package/lib/browser/agent-preferences.js +11 -0
  7. package/lib/browser/agent-preferences.js.map +1 -1
  8. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
  9. package/lib/browser/ai-core-frontend-module.js +8 -1
  10. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  11. package/lib/browser/ai-core-preferences.d.ts +3 -0
  12. package/lib/browser/ai-core-preferences.d.ts.map +1 -1
  13. package/lib/browser/ai-core-preferences.js +14 -1
  14. package/lib/browser/ai-core-preferences.js.map +1 -1
  15. package/lib/browser/frontend-language-model-registry.d.ts +5 -5
  16. package/lib/browser/frontend-language-model-registry.d.ts.map +1 -1
  17. package/lib/browser/frontend-language-model-registry.js +12 -5
  18. package/lib/browser/frontend-language-model-registry.js.map +1 -1
  19. package/lib/browser/frontend-language-model-registry.spec.d.ts +2 -0
  20. package/lib/browser/frontend-language-model-registry.spec.d.ts.map +1 -0
  21. package/lib/browser/frontend-language-model-registry.spec.js +248 -0
  22. package/lib/browser/frontend-language-model-registry.spec.js.map +1 -0
  23. package/lib/browser/index.d.ts +4 -0
  24. package/lib/browser/index.d.ts.map +1 -1
  25. package/lib/browser/index.js +4 -0
  26. package/lib/browser/index.js.map +1 -1
  27. package/lib/browser/os-notification-service.d.ts +96 -0
  28. package/lib/browser/os-notification-service.d.ts.map +1 -0
  29. package/lib/browser/os-notification-service.js +222 -0
  30. package/lib/browser/os-notification-service.js.map +1 -0
  31. package/lib/browser/window-blink-service.d.ts +41 -0
  32. package/lib/browser/window-blink-service.d.ts.map +1 -0
  33. package/lib/browser/window-blink-service.js +174 -0
  34. package/lib/browser/window-blink-service.js.map +1 -0
  35. package/lib/common/configurable-in-memory-resources.d.ts +2 -0
  36. package/lib/common/configurable-in-memory-resources.d.ts.map +1 -1
  37. package/lib/common/configurable-in-memory-resources.js +7 -0
  38. package/lib/common/configurable-in-memory-resources.js.map +1 -1
  39. package/lib/common/index.d.ts +1 -0
  40. package/lib/common/index.d.ts.map +1 -1
  41. package/lib/common/index.js +1 -0
  42. package/lib/common/index.js.map +1 -1
  43. package/lib/common/language-model-delegate.d.ts +2 -2
  44. package/lib/common/language-model-delegate.d.ts.map +1 -1
  45. package/lib/common/language-model-delegate.js.map +1 -1
  46. package/lib/common/language-model.d.ts +24 -3
  47. package/lib/common/language-model.d.ts.map +1 -1
  48. package/lib/common/language-model.js +4 -0
  49. package/lib/common/language-model.js.map +1 -1
  50. package/lib/common/notification-types.d.ts +7 -0
  51. package/lib/common/notification-types.d.ts.map +1 -0
  52. package/lib/common/notification-types.js +29 -0
  53. package/lib/common/notification-types.js.map +1 -0
  54. package/lib/common/prompt-service.d.ts +14 -4
  55. package/lib/common/prompt-service.d.ts.map +1 -1
  56. package/lib/common/prompt-service.js +78 -9
  57. package/lib/common/prompt-service.js.map +1 -1
  58. package/lib/common/prompt-service.spec.js +3 -0
  59. package/lib/common/prompt-service.spec.js.map +1 -1
  60. package/lib/common/settings-service.d.ts +6 -0
  61. package/lib/common/settings-service.d.ts.map +1 -1
  62. package/lib/common/settings-service.js.map +1 -1
  63. package/package.json +10 -10
  64. package/src/browser/agent-completion-notification-service.ts +242 -0
  65. package/src/browser/agent-preferences.ts +14 -0
  66. package/src/browser/ai-core-frontend-module.ts +11 -1
  67. package/src/browser/ai-core-preferences.ts +20 -0
  68. package/src/browser/frontend-language-model-registry.spec.ts +298 -0
  69. package/src/browser/frontend-language-model-registry.ts +14 -8
  70. package/src/browser/index.ts +4 -0
  71. package/src/browser/os-notification-service.ts +271 -0
  72. package/src/browser/window-blink-service.ts +195 -0
  73. package/src/common/configurable-in-memory-resources.ts +8 -0
  74. package/src/common/index.ts +1 -0
  75. package/src/common/language-model-delegate.ts +5 -2
  76. package/src/common/language-model.ts +12 -3
  77. package/src/common/notification-types.ts +31 -0
  78. package/src/common/prompt-service.spec.ts +4 -1
  79. package/src/common/prompt-service.ts +101 -12
  80. package/src/common/settings-service.ts +6 -0
@@ -1,5 +1,5 @@
1
1
  // *****************************************************************************
2
- // Copyright (C) 2024 EclipseSource GmbH.
2
+ // Copyright (C) 2025 EclipseSource GmbH.
3
3
  //
4
4
  // This program and the accompanying materials are made available under the
5
5
  // terms of the Eclipse Public License v. 2.0 which is available at
@@ -43,6 +43,7 @@ import {
43
43
  LanguageModelResponse,
44
44
  LanguageModelSelector,
45
45
  LanguageModelStreamResponsePart,
46
+ ToolCallResult,
46
47
  } from '../common';
47
48
 
48
49
  @injectable()
@@ -58,7 +59,7 @@ export class LanguageModelDelegateClientImpl
58
59
  this.receiver.send(id, token);
59
60
  }
60
61
 
61
- toolCall(requestId: string, toolId: string, args_string: string): Promise<unknown> {
62
+ toolCall(requestId: string, toolId: string, args_string: string): Promise<ToolCallResult> {
62
63
  return this.receiver.toolCall(requestId, toolId, args_string);
63
64
  }
64
65
 
@@ -236,8 +237,8 @@ export class FrontendLanguageModelRegistryImpl
236
237
  };
237
238
  }
238
239
 
239
- private streams = new Map<string, StreamState>();
240
- private requests = new Map<string, LanguageModelRequest>();
240
+ protected streams = new Map<string, StreamState>();
241
+ protected requests = new Map<string, LanguageModelRequest>();
241
242
 
242
243
  async *getIterable(
243
244
  state: StreamState
@@ -281,16 +282,21 @@ export class FrontendLanguageModelRegistryImpl
281
282
  }
282
283
 
283
284
  // called by backend once tool is invoked
284
- toolCall(id: string, toolId: string, arg_string: string): Promise<unknown> {
285
+ async toolCall(id: string, toolId: string, arg_string: string): Promise<ToolCallResult> {
285
286
  if (!this.requests.has(id)) {
286
- throw new Error('Somehow we got a callback for a non existing request!');
287
+ return { error: true, message: `No request found for ID '${id}'. The request may have been cancelled or completed.` };
287
288
  }
288
289
  const request = this.requests.get(id)!;
289
290
  const tool = request.tools?.find(t => t.id === toolId);
290
291
  if (tool) {
291
- return tool.handler(arg_string);
292
+ try {
293
+ return await tool.handler(arg_string);
294
+ } catch (error) {
295
+ const errorMessage = error instanceof Error ? error.message : String(error);
296
+ return { error: true, message: `Error executing tool '${toolId}': ${errorMessage}` };
297
+ };
292
298
  }
293
- throw new Error(`Could not find a tool for ${toolId}!`);
299
+ return { error: true, message: `Tool '${toolId}' not found in the available tools for this request.` };
294
300
  }
295
301
 
296
302
  // called by backend via the "delegate client" with the error to use for rejection
@@ -14,7 +14,11 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
+ export * from './agent-completion-notification-service';
18
+ export * from './os-notification-service';
19
+ export * from './window-blink-service';
17
20
  export * from './ai-activation-service';
21
+ export * from './ai-command-handler-factory';
18
22
  export * from './ai-core-frontend-application-contribution';
19
23
  export * from './ai-core-frontend-module';
20
24
  export * from './ai-core-preferences';
@@ -0,0 +1,271 @@
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 { injectable } from '@theia/core/shared/inversify';
18
+ import { nls } from '@theia/core/lib/common/nls';
19
+ import { environment } from '@theia/core';
20
+
21
+ /**
22
+ * Configuration options for OS notifications
23
+ */
24
+ export interface OSNotificationOptions {
25
+ /** The notification body text */
26
+ body?: string;
27
+ /** Icon to display with the notification */
28
+ icon?: string;
29
+ /** Whether the notification should be silent */
30
+ silent?: boolean;
31
+ /** Tag to group notifications */
32
+ tag?: string;
33
+ /** Whether the notification requires user interaction to dismiss */
34
+ requireInteraction?: boolean;
35
+ /** Custom data to associate with the notification */
36
+ data?: unknown;
37
+ }
38
+
39
+ /**
40
+ * Result of an OS notification attempt
41
+ */
42
+ export interface OSNotificationResult {
43
+ /** Whether the notification was successfully shown */
44
+ success: boolean;
45
+ /** Error message if the notification failed */
46
+ error?: string;
47
+ /** The created notification instance (if successful) */
48
+ notification?: Notification;
49
+ }
50
+
51
+ /**
52
+ * Service to handle OS-level notifications across different platforms
53
+ * Provides fallback mechanisms for environments where notifications are unavailable
54
+ */
55
+ @injectable()
56
+ export class OSNotificationService {
57
+
58
+ private isElectron: boolean;
59
+
60
+ constructor() {
61
+ this.isElectron = environment.electron.is();
62
+ }
63
+
64
+ /**
65
+ * Show an OS-level notification with the given title and options
66
+ *
67
+ * @param title The notification title
68
+ * @param options Optional notification configuration
69
+ * @returns Promise resolving to the notification result
70
+ */
71
+ async showNotification(title: string, options: OSNotificationOptions = {}): Promise<OSNotificationResult> {
72
+ try {
73
+ if (!this.isNotificationSupported()) {
74
+ return {
75
+ success: false,
76
+ error: 'Notifications are not supported in this environment'
77
+ };
78
+ }
79
+
80
+ const permission = await this.ensurePermission();
81
+ if (permission !== 'granted') {
82
+ return {
83
+ success: false,
84
+ error: `Notification permission ${permission}`
85
+ };
86
+ }
87
+
88
+ const notification = await this.createNotification(title, options);
89
+ return {
90
+ success: true,
91
+ notification
92
+ };
93
+
94
+ } catch (error) {
95
+ console.error('Failed to show OS notification:', error);
96
+ return {
97
+ success: false,
98
+ error: error instanceof Error ? error.message : 'Unknown error occurred'
99
+ };
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Check if notification permission is granted
105
+ *
106
+ * @returns The current notification permission state
107
+ */
108
+ getPermissionStatus(): NotificationPermission {
109
+ if (!this.isNotificationSupported()) {
110
+ return 'denied';
111
+ }
112
+ return Notification.permission;
113
+ }
114
+
115
+ /**
116
+ * Request notification permission from the user
117
+ *
118
+ * @returns Promise resolving to the permission state
119
+ */
120
+ async requestPermission(): Promise<NotificationPermission> {
121
+ if (!this.isNotificationSupported()) {
122
+ return 'denied';
123
+ }
124
+
125
+ if (Notification.permission !== 'default') {
126
+ return Notification.permission;
127
+ }
128
+
129
+ try {
130
+ const permission = await Notification.requestPermission();
131
+ return permission;
132
+ } catch (error) {
133
+ console.error('Failed to request notification permission:', error);
134
+ return 'denied';
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Check if OS notifications are supported in the current environment
140
+ *
141
+ * @returns true if notifications are supported, false otherwise
142
+ */
143
+ isNotificationSupported(): boolean {
144
+ return typeof window !== 'undefined' && 'Notification' in window;
145
+ }
146
+
147
+ /**
148
+ * Show a notification specifically for agent completion
149
+ * This is a convenience method with pre-configured options for agent notifications
150
+ *
151
+ * @param agentName The name of the agent that completed
152
+ * @param taskDescription Optional description of the completed task
153
+ * @returns Promise resolving to the notification result
154
+ */
155
+ async showAgentCompletionNotification(agentName: string, taskDescription?: string): Promise<OSNotificationResult> {
156
+ const title = nls.localize('theia/ai-core/agentCompletionTitle', 'Agent "{0}" Task Completed', agentName);
157
+ const body = taskDescription
158
+ ? nls.localize('theia/ai-core/agentCompletionWithTask',
159
+ 'Agent "{0}" has completed the task: {1}', agentName, taskDescription)
160
+ : nls.localize('theia/ai-core/agentCompletionMessage',
161
+ 'Agent "{0}" has completed its task.', agentName);
162
+
163
+ return this.showNotification(title, {
164
+ body,
165
+ icon: this.getAgentCompletionIcon(),
166
+ tag: `agent-completion-${agentName}`,
167
+ requireInteraction: false,
168
+ data: {
169
+ type: 'agent-completion',
170
+ agentName,
171
+ taskDescription,
172
+ timestamp: Date.now()
173
+ }
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Ensure notification permission is granted
179
+ *
180
+ * @returns Promise resolving to the permission state
181
+ */
182
+ private async ensurePermission(): Promise<NotificationPermission> {
183
+ const currentPermission = this.getPermissionStatus();
184
+
185
+ if (currentPermission === 'granted') {
186
+ return currentPermission;
187
+ }
188
+
189
+ if (currentPermission === 'denied') {
190
+ return currentPermission;
191
+ }
192
+
193
+ return this.requestPermission();
194
+ }
195
+
196
+ /**
197
+ * Create a native notification with the given title and options
198
+ *
199
+ * @param title The notification title
200
+ * @param options The notification options
201
+ * @returns Promise resolving to the created notification
202
+ */
203
+ private async createNotification(title: string, options: OSNotificationOptions): Promise<Notification> {
204
+ return new Promise<Notification>((resolve, reject): void => {
205
+ try {
206
+ const notificationOptions: NotificationOptions = {
207
+ body: options.body,
208
+ icon: options.icon,
209
+ silent: options.silent,
210
+ tag: options.tag,
211
+ requireInteraction: options.requireInteraction,
212
+ data: options.data
213
+ };
214
+
215
+ const notification = new Notification(title, notificationOptions);
216
+
217
+ notification.onshow = () => {
218
+ console.debug('OS notification shown:', title);
219
+ };
220
+
221
+ notification.onerror = error => {
222
+ console.error('OS notification error:', error);
223
+ reject(new Error('Failed to show notification'));
224
+ };
225
+
226
+ notification.onclick = () => {
227
+ console.debug('OS notification clicked:', title);
228
+ this.focusApplicationWindow();
229
+ notification.close();
230
+ };
231
+
232
+ notification.onclose = () => {
233
+ console.debug('OS notification closed:', title);
234
+ };
235
+
236
+ resolve(notification);
237
+
238
+ } catch (error) {
239
+ reject(error);
240
+ }
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Attempt to focus the application window when notification is clicked
246
+ */
247
+ private focusApplicationWindow(): void {
248
+ try {
249
+ if (typeof window !== 'undefined') {
250
+ window.focus();
251
+
252
+ if (this.isElectron && (window as unknown as { electronTheiaCore?: { focusWindow?: () => void } }).electronTheiaCore?.focusWindow) {
253
+ (window as unknown as { electronTheiaCore: { focusWindow: () => void } }).electronTheiaCore.focusWindow();
254
+ }
255
+ }
256
+ } catch (error) {
257
+ console.debug('Could not focus application window:', error);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Get the icon URL for agent completion notifications
263
+ *
264
+ * @returns The icon URL or undefined if not available
265
+ */
266
+ private getAgentCompletionIcon(): string | undefined {
267
+ // This could return a path to an icon file
268
+ // For now, we'll return undefined to use the default system icon
269
+ return undefined;
270
+ }
271
+ }
@@ -0,0 +1,195 @@
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 { injectable } from '@theia/core/shared/inversify';
18
+ import { environment } from '@theia/core';
19
+
20
+ /**
21
+ * Result of a window blink attempt
22
+ */
23
+ export interface WindowBlinkResult {
24
+ /** Whether the window blink was successful */
25
+ success: boolean;
26
+ /** Error message if the blink failed */
27
+ error?: string;
28
+ }
29
+
30
+ /**
31
+ * Service for blinking/flashing the application window to get user attention.
32
+ */
33
+ @injectable()
34
+ export class WindowBlinkService {
35
+
36
+ private isElectron: boolean;
37
+
38
+ constructor() {
39
+ this.isElectron = environment.electron.is();
40
+ }
41
+
42
+ /**
43
+ * Blink/flash the window to get user attention.
44
+ * The implementation varies depending on the platform and environment.
45
+ *
46
+ * @param agentName Optional name of the agent to include in the blink notification
47
+ */
48
+ async blinkWindow(agentName?: string): Promise<WindowBlinkResult> {
49
+ try {
50
+ if (this.isElectron) {
51
+ await this.blinkElectronWindow(agentName);
52
+ } else {
53
+ await this.blinkBrowserWindow(agentName);
54
+ }
55
+ return { success: true };
56
+ } catch (error) {
57
+ console.warn('Failed to blink window:', error);
58
+ try {
59
+ if (document.hidden) {
60
+ this.focusWindow();
61
+ }
62
+ return { success: true };
63
+ } catch (fallbackError) {
64
+ return {
65
+ success: false,
66
+ error: error instanceof Error ? error.message : 'Failed to blink window'
67
+ };
68
+ }
69
+ }
70
+ }
71
+
72
+ private async blinkElectronWindow(agentName?: string): Promise<void> {
73
+ await this.blinkDocumentTitle(agentName);
74
+
75
+ if (document.hidden) {
76
+ try {
77
+ const theiaCoreAPI = (window as unknown as { electronTheiaCore?: { focusWindow?: () => void } }).electronTheiaCore;
78
+ if (theiaCoreAPI?.focusWindow) {
79
+ theiaCoreAPI.focusWindow();
80
+ } else {
81
+ window.focus();
82
+ }
83
+ } catch (error) {
84
+ console.debug('Could not focus hidden window:', error);
85
+ }
86
+ }
87
+ }
88
+
89
+ private async blinkBrowserWindow(agentName?: string): Promise<void> {
90
+ await this.blinkDocumentTitle(agentName);
91
+ this.blinkWithVisibilityAPI();
92
+ if (document.hidden) {
93
+ this.focusWindow();
94
+ }
95
+ }
96
+
97
+ private async blinkDocumentTitle(agentName?: string): Promise<void> {
98
+ const originalTitle = document.title;
99
+ const alertTitle = agentName
100
+ ? `🔔 Theia - Agent "${agentName}" Completed`
101
+ : '🔔 Theia - Agent Completed';
102
+
103
+ let blinkCount = 0;
104
+ const maxBlinks = 6;
105
+
106
+ const blinkInterval = setInterval(() => {
107
+ if (blinkCount >= maxBlinks) {
108
+ clearInterval(blinkInterval);
109
+ document.title = originalTitle;
110
+ return;
111
+ }
112
+
113
+ document.title = blinkCount % 2 === 0 ? alertTitle : originalTitle;
114
+ blinkCount++;
115
+ }, 500);
116
+ }
117
+
118
+ private blinkWithVisibilityAPI(): void {
119
+ // This method provides visual attention-getting behavior without creating notifications
120
+ // as notifications are handled by the OSNotificationService to avoid duplicates
121
+ if (!this.isElectron && typeof document.hidden !== 'undefined') {
122
+ // Focus the window if it's hidden to get user attention
123
+ if (document.hidden) {
124
+ this.focusWindow();
125
+ }
126
+ }
127
+ }
128
+
129
+ private focusWindow(): void {
130
+ try {
131
+ window.focus();
132
+
133
+ // Try to scroll to top to create some visual movement
134
+ if (document.body.scrollTop > 0 || document.documentElement.scrollTop > 0) {
135
+ const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
136
+ window.scrollTo(0, 0);
137
+ setTimeout(() => {
138
+ window.scrollTo(0, currentScroll);
139
+ }, 100);
140
+ }
141
+ } catch (error) {
142
+ console.debug('Could not focus window:', error);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Check if window blinking is supported in the current environment.
148
+ */
149
+ isBlinkSupported(): boolean {
150
+ if (this.isElectron) {
151
+ const theiaCoreAPI = (window as unknown as { electronTheiaCore?: { focusWindow?: () => void } }).electronTheiaCore;
152
+ return !!(theiaCoreAPI?.focusWindow);
153
+ }
154
+
155
+ // In browser, we can always provide some form of attention-getting behavior
156
+ return true;
157
+ }
158
+
159
+ /**
160
+ * Get information about the blinking capabilities.
161
+ */
162
+ getBlinkCapabilities(): {
163
+ supported: boolean;
164
+ method: 'electron' | 'browser' | 'none';
165
+ features: string[];
166
+ } {
167
+ const features: string[] = [];
168
+ let method: 'electron' | 'browser' | 'none' = 'none';
169
+
170
+ if (this.isElectron) {
171
+ method = 'electron';
172
+ const theiaCoreAPI = (window as unknown as { electronTheiaCore?: { focusWindow?: () => void } }).electronTheiaCore;
173
+
174
+ if (theiaCoreAPI?.focusWindow) {
175
+ features.push('electronTheiaCore.focusWindow');
176
+ features.push('document.title blinking');
177
+ features.push('window.focus');
178
+ }
179
+ } else {
180
+ method = 'browser';
181
+ features.push('document.title');
182
+ features.push('window.focus');
183
+
184
+ if (typeof document.hidden !== 'undefined') {
185
+ features.push('Page Visibility API');
186
+ }
187
+ }
188
+
189
+ return {
190
+ supported: features.length > 0,
191
+ method,
192
+ features
193
+ };
194
+ }
195
+ }
@@ -89,6 +89,10 @@ export class ConfigurableMutableResource implements Resource {
89
89
  return !!this.options?.initiallyDirty;
90
90
  }
91
91
 
92
+ get contents(): string | Promise<string> {
93
+ return this.options?.contents ?? '';
94
+ }
95
+
92
96
  readContents(): Promise<string> {
93
97
  return Promise.resolve(this.options?.contents ?? '');
94
98
  }
@@ -153,4 +157,8 @@ export class ConfigurableMutableReferenceResource implements Resource {
153
157
  get autosaveable(): boolean {
154
158
  return this.reference.object.autosaveable;
155
159
  }
160
+
161
+ get contents(): string | Promise<string> {
162
+ return this.reference.object.contents;
163
+ }
156
164
  }
@@ -31,3 +31,4 @@ export * from './language-model-service';
31
31
  export * from './token-usage-service';
32
32
  export * from './ai-variable-resource';
33
33
  export * from './configurable-in-memory-resources';
34
+ export * from './notification-types';
@@ -15,11 +15,14 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { CancellationToken } from '@theia/core';
18
- import { LanguageModelMetaData, LanguageModelParsedResponse, LanguageModelRequest, LanguageModelStreamResponsePart, LanguageModelTextResponse } from './language-model';
18
+ import {
19
+ LanguageModelMetaData, LanguageModelParsedResponse, LanguageModelRequest, LanguageModelStreamResponsePart,
20
+ LanguageModelTextResponse, ToolCallResult
21
+ } from './language-model';
19
22
 
20
23
  export const LanguageModelDelegateClient = Symbol('LanguageModelDelegateClient');
21
24
  export interface LanguageModelDelegateClient {
22
- toolCall(requestId: string, toolId: string, args_string: string): Promise<unknown>;
25
+ toolCall(requestId: string, toolId: string, args_string: string): Promise<ToolCallResult>;
23
26
  send(id: string, token: LanguageModelStreamResponsePart | undefined): void;
24
27
  error(id: string, error: Error): void;
25
28
  }
@@ -55,7 +55,7 @@ export interface ToolResultMessage {
55
55
  tool_use_id: string;
56
56
  name: string;
57
57
  type: 'tool_result';
58
- content?: string;
58
+ content?: ToolCallResult;
59
59
  is_error?: boolean;
60
60
  }
61
61
 
@@ -109,7 +109,7 @@ export interface ToolRequest {
109
109
  name: string;
110
110
  parameters: ToolRequestParameters
111
111
  description?: string;
112
- handler: (arg_string: string, ctx?: unknown) => Promise<unknown>;
112
+ handler: (arg_string: string, ctx?: unknown) => Promise<ToolCallResult>;
113
113
  providerName?: string;
114
114
  }
115
115
 
@@ -245,6 +245,15 @@ export interface ThinkingResponsePart {
245
245
  export const isThinkingResponsePart = (part: unknown): part is ThinkingResponsePart =>
246
246
  !!(part && typeof part === 'object' && 'thought' in part && typeof part.thought === 'string');
247
247
 
248
+ export interface ToolCallTextResult { type: 'text', text: string; };
249
+ export interface ToolCallImageResult extends Base64ImageContent { type: 'image' };
250
+ export interface ToolCallAudioResult { type: 'audio', data: string; mimeType: string };
251
+ export interface ToolCallErrorResult { type: 'error', data: string; };
252
+ export type ToolCallContentResult = ToolCallTextResult | ToolCallImageResult | ToolCallAudioResult | ToolCallErrorResult;
253
+ export interface ToolCallContent {
254
+ content: ToolCallContentResult[];
255
+ }
256
+ export type ToolCallResult = undefined | object | string | ToolCallContent;
248
257
  export interface ToolCall {
249
258
  id?: string;
250
259
  function?: {
@@ -252,7 +261,7 @@ export interface ToolCall {
252
261
  name?: string;
253
262
  },
254
263
  finished?: boolean;
255
- result?: string;
264
+ result?: ToolCallResult;
256
265
  }
257
266
 
258
267
  export interface LanguageModelStreamResponse {
@@ -0,0 +1,31 @@
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
+ export const NOTIFICATION_TYPE_OFF = 'off';
18
+ export const NOTIFICATION_TYPE_OS_NOTIFICATION = 'os-notification';
19
+ export const NOTIFICATION_TYPE_MESSAGE = 'message';
20
+ export const NOTIFICATION_TYPE_BLINK = 'blink';
21
+ export type NotificationType =
22
+ | typeof NOTIFICATION_TYPE_OFF
23
+ | typeof NOTIFICATION_TYPE_OS_NOTIFICATION
24
+ | typeof NOTIFICATION_TYPE_MESSAGE
25
+ | typeof NOTIFICATION_TYPE_BLINK;
26
+ export const NOTIFICATION_TYPES: NotificationType[] = [
27
+ NOTIFICATION_TYPE_OFF,
28
+ NOTIFICATION_TYPE_OS_NOTIFICATION,
29
+ NOTIFICATION_TYPE_MESSAGE,
30
+ NOTIFICATION_TYPE_BLINK,
31
+ ];
@@ -22,7 +22,8 @@ import { PromptService, PromptServiceImpl } from './prompt-service';
22
22
  import { DefaultAIVariableService, AIVariableService } from './variable-service';
23
23
  import { ToolInvocationRegistry } from './tool-invocation-registry';
24
24
  import { ToolRequest } from './language-model';
25
- import { Logger } from '@theia/core';
25
+ import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
26
+ import { ILogger, Logger } from '@theia/core';
26
27
  import * as sinon from 'sinon';
27
28
 
28
29
  describe('PromptService', () => {
@@ -40,6 +41,7 @@ describe('PromptService', () => {
40
41
  resolve: async () => ({ variable: nameVariable, value: 'Jane' })
41
42
  });
42
43
  container.bind<AIVariableService>(AIVariableService).toConstantValue(variableService);
44
+ container.bind<ILogger>(ILogger).toConstantValue(new MockLogger);
43
45
 
44
46
  promptService = container.get<PromptService>(PromptService);
45
47
  promptService.addBuiltInPromptFragment({ id: '1', template: 'Hello, {{name}}!' });
@@ -340,6 +342,7 @@ describe('PromptService', () => {
340
342
  })
341
343
  });
342
344
  container.bind<AIVariableService>(AIVariableService).toConstantValue(variableService);
345
+ container.bind<ILogger>(ILogger).toConstantValue(new MockLogger);
343
346
 
344
347
  const testPromptService = container.get<PromptService>(PromptService);
345
348
  testPromptService.addBuiltInPromptFragment({ id: 'testPrompt', template: 'Template with fragment: {{fragment}}' });