@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.
- package/lib/browser/agent-completion-notification-service.d.ts +69 -0
- package/lib/browser/agent-completion-notification-service.d.ts.map +1 -0
- package/lib/browser/agent-completion-notification-service.js +187 -0
- package/lib/browser/agent-completion-notification-service.js.map +1 -0
- package/lib/browser/agent-preferences.d.ts.map +1 -1
- package/lib/browser/agent-preferences.js +11 -0
- package/lib/browser/agent-preferences.js.map +1 -1
- package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-frontend-module.js +8 -1
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/ai-core-preferences.d.ts +3 -0
- package/lib/browser/ai-core-preferences.d.ts.map +1 -1
- package/lib/browser/ai-core-preferences.js +14 -1
- package/lib/browser/ai-core-preferences.js.map +1 -1
- package/lib/browser/frontend-language-model-registry.d.ts +5 -5
- package/lib/browser/frontend-language-model-registry.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-registry.js +12 -5
- package/lib/browser/frontend-language-model-registry.js.map +1 -1
- package/lib/browser/frontend-language-model-registry.spec.d.ts +2 -0
- package/lib/browser/frontend-language-model-registry.spec.d.ts.map +1 -0
- package/lib/browser/frontend-language-model-registry.spec.js +248 -0
- package/lib/browser/frontend-language-model-registry.spec.js.map +1 -0
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +4 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/os-notification-service.d.ts +96 -0
- package/lib/browser/os-notification-service.d.ts.map +1 -0
- package/lib/browser/os-notification-service.js +222 -0
- package/lib/browser/os-notification-service.js.map +1 -0
- package/lib/browser/window-blink-service.d.ts +41 -0
- package/lib/browser/window-blink-service.d.ts.map +1 -0
- package/lib/browser/window-blink-service.js +174 -0
- package/lib/browser/window-blink-service.js.map +1 -0
- package/lib/common/configurable-in-memory-resources.d.ts +2 -0
- package/lib/common/configurable-in-memory-resources.d.ts.map +1 -1
- package/lib/common/configurable-in-memory-resources.js +7 -0
- package/lib/common/configurable-in-memory-resources.js.map +1 -1
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +1 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/language-model-delegate.d.ts +2 -2
- package/lib/common/language-model-delegate.d.ts.map +1 -1
- package/lib/common/language-model-delegate.js.map +1 -1
- package/lib/common/language-model.d.ts +24 -3
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +4 -0
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/notification-types.d.ts +7 -0
- package/lib/common/notification-types.d.ts.map +1 -0
- package/lib/common/notification-types.js +29 -0
- package/lib/common/notification-types.js.map +1 -0
- package/lib/common/prompt-service.d.ts +14 -4
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js +78 -9
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/common/prompt-service.spec.js +3 -0
- package/lib/common/prompt-service.spec.js.map +1 -1
- package/lib/common/settings-service.d.ts +6 -0
- package/lib/common/settings-service.d.ts.map +1 -1
- package/lib/common/settings-service.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/agent-completion-notification-service.ts +242 -0
- package/src/browser/agent-preferences.ts +14 -0
- package/src/browser/ai-core-frontend-module.ts +11 -1
- package/src/browser/ai-core-preferences.ts +20 -0
- package/src/browser/frontend-language-model-registry.spec.ts +298 -0
- package/src/browser/frontend-language-model-registry.ts +14 -8
- package/src/browser/index.ts +4 -0
- package/src/browser/os-notification-service.ts +271 -0
- package/src/browser/window-blink-service.ts +195 -0
- package/src/common/configurable-in-memory-resources.ts +8 -0
- package/src/common/index.ts +1 -0
- package/src/common/language-model-delegate.ts +5 -2
- package/src/common/language-model.ts +12 -3
- package/src/common/notification-types.ts +31 -0
- package/src/common/prompt-service.spec.ts +4 -1
- package/src/common/prompt-service.ts +101 -12
- package/src/common/settings-service.ts +6 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// *****************************************************************************
|
|
2
|
-
// Copyright (C)
|
|
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<
|
|
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
|
-
|
|
240
|
-
|
|
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<
|
|
285
|
+
async toolCall(id: string, toolId: string, arg_string: string): Promise<ToolCallResult> {
|
|
285
286
|
if (!this.requests.has(id)) {
|
|
286
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/src/browser/index.ts
CHANGED
|
@@ -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
|
}
|
package/src/common/index.ts
CHANGED
|
@@ -15,11 +15,14 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { CancellationToken } from '@theia/core';
|
|
18
|
-
import {
|
|
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<
|
|
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?:
|
|
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<
|
|
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?:
|
|
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 {
|
|
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}}' });
|