@theia/ai-chat 1.62.1 → 1.63.0-next.24
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/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +7 -1
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/change-set-file-element.d.ts +9 -6
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +27 -14
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/change-set-variable.js +1 -2
- package/lib/browser/change-set-variable.js.map +1 -1
- package/lib/browser/chat-tool-preferences.d.ts +54 -0
- package/lib/browser/chat-tool-preferences.d.ts.map +1 -0
- package/lib/browser/chat-tool-preferences.js +170 -0
- package/lib/browser/chat-tool-preferences.js.map +1 -0
- package/lib/browser/chat-tool-request-service.d.ts +20 -0
- package/lib/browser/chat-tool-request-service.d.ts.map +1 -0
- package/lib/browser/chat-tool-request-service.js +89 -0
- package/lib/browser/chat-tool-request-service.js.map +1 -0
- package/lib/browser/frontend-chat-service.d.ts.map +1 -1
- package/lib/browser/frontend-chat-service.js +2 -6
- package/lib/browser/frontend-chat-service.js.map +1 -1
- package/lib/common/change-set.d.ts +78 -0
- package/lib/common/change-set.d.ts.map +1 -0
- package/lib/common/change-set.js +133 -0
- package/lib/common/change-set.js.map +1 -0
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +4 -1
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-model.d.ts +68 -80
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +224 -136
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-service.d.ts +3 -3
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +5 -8
- package/lib/common/chat-service.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/ai-chat-frontend-module.ts +8 -1
- package/src/browser/change-set-file-element.ts +33 -21
- package/src/browser/change-set-variable.ts +1 -1
- package/src/browser/chat-tool-preferences.ts +178 -0
- package/src/browser/chat-tool-request-service.ts +93 -0
- package/src/browser/frontend-chat-service.ts +3 -6
- package/src/common/change-set.ts +197 -0
- package/src/common/chat-agents.ts +15 -11
- package/src/common/chat-model.ts +249 -207
- package/src/common/chat-service.ts +6 -9
|
@@ -0,0 +1,178 @@
|
|
|
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 { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/browser/ai-core-preferences';
|
|
18
|
+
import { nls } from '@theia/core';
|
|
19
|
+
import { interfaces, injectable, inject } from '@theia/core/shared/inversify';
|
|
20
|
+
import {
|
|
21
|
+
createPreferenceProxy,
|
|
22
|
+
PreferenceProxy,
|
|
23
|
+
PreferenceService,
|
|
24
|
+
PreferenceSchema,
|
|
25
|
+
PreferenceContribution
|
|
26
|
+
} from '@theia/core/lib/browser/preferences';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Enum for tool confirmation modes
|
|
30
|
+
*/
|
|
31
|
+
export enum ToolConfirmationMode {
|
|
32
|
+
YOLO = 'yolo',
|
|
33
|
+
CONFIRM = 'confirm',
|
|
34
|
+
DISABLED = 'disabled'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const TOOL_CONFIRMATION_PREFERENCE = 'ai-features.chat.toolConfirmation';
|
|
38
|
+
|
|
39
|
+
export const chatToolPreferences: PreferenceSchema = {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
[TOOL_CONFIRMATION_PREFERENCE]: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
additionalProperties: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: [ToolConfirmationMode.YOLO, ToolConfirmationMode.CONFIRM, ToolConfirmationMode.DISABLED],
|
|
47
|
+
enumDescriptions: [
|
|
48
|
+
nls.localize('theia/ai/chat/toolConfirmation/yolo/description', 'Execute tools automatically without confirmation'),
|
|
49
|
+
nls.localize('theia/ai/chat/toolConfirmation/confirm/description', 'Ask for confirmation before executing tools'),
|
|
50
|
+
nls.localize('theia/ai/chat/toolConfirmation/disabled/description', 'Disable tool execution')
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
default: {},
|
|
54
|
+
description: nls.localize('theia/ai/chat/toolConfirmation/description',
|
|
55
|
+
'Configure confirmation behavior for different tools. Key is the tool ID, value is the confirmation mode.' +
|
|
56
|
+
'Use "*" as the key to set a global default for all tools.'),
|
|
57
|
+
title: AI_CORE_PREFERENCES_TITLE,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export interface ChatToolConfiguration {
|
|
63
|
+
[TOOL_CONFIRMATION_PREFERENCE]: { [toolId: string]: ToolConfirmationMode };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ChatToolPreferenceContribution = Symbol('ChatToolPreferenceContribution');
|
|
67
|
+
export const ChatToolPreferences = Symbol('ChatToolPreferences');
|
|
68
|
+
export type ChatToolPreferences = PreferenceProxy<ChatToolConfiguration>;
|
|
69
|
+
|
|
70
|
+
export function createChatToolPreferences(preferences: PreferenceService, schema: PreferenceSchema = chatToolPreferences): ChatToolPreferences {
|
|
71
|
+
return createPreferenceProxy(preferences, schema);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function bindChatToolPreferences(bind: interfaces.Bind): void {
|
|
75
|
+
bind(ChatToolPreferences).toDynamicValue((ctx: interfaces.Context) => {
|
|
76
|
+
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
|
|
77
|
+
const contribution = ctx.container.get<PreferenceContribution>(ChatToolPreferenceContribution);
|
|
78
|
+
return createChatToolPreferences(preferences, contribution.schema);
|
|
79
|
+
}).inSingletonScope();
|
|
80
|
+
bind(ChatToolPreferenceContribution).toConstantValue({ schema: chatToolPreferences });
|
|
81
|
+
bind(PreferenceContribution).toService(ChatToolPreferenceContribution);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Utility class to manage tool confirmation settings
|
|
86
|
+
*/
|
|
87
|
+
@injectable()
|
|
88
|
+
export class ToolConfirmationManager {
|
|
89
|
+
@inject(ChatToolPreferences)
|
|
90
|
+
protected readonly preferences: ChatToolPreferences;
|
|
91
|
+
|
|
92
|
+
@inject(PreferenceService)
|
|
93
|
+
protected readonly preferenceService: PreferenceService;
|
|
94
|
+
|
|
95
|
+
// In-memory session overrides (not persisted), per chat
|
|
96
|
+
protected sessionOverrides: Map<string, Map<string, ToolConfirmationMode>> = new Map();
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the confirmation mode for a specific tool, considering session overrides first (per chat)
|
|
100
|
+
*/
|
|
101
|
+
getConfirmationMode(toolId: string, chatId: string): ToolConfirmationMode {
|
|
102
|
+
const chatMap = this.sessionOverrides.get(chatId);
|
|
103
|
+
if (chatMap && chatMap.has(toolId)) {
|
|
104
|
+
return chatMap.get(toolId)!;
|
|
105
|
+
}
|
|
106
|
+
const toolConfirmation = this.preferences[TOOL_CONFIRMATION_PREFERENCE];
|
|
107
|
+
if (toolConfirmation[toolId]) {
|
|
108
|
+
return toolConfirmation[toolId];
|
|
109
|
+
}
|
|
110
|
+
if (toolConfirmation['*']) {
|
|
111
|
+
return toolConfirmation['*'];
|
|
112
|
+
}
|
|
113
|
+
return ToolConfirmationMode.YOLO; // Default to YOLO
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set the confirmation mode for a specific tool (persisted)
|
|
118
|
+
*/
|
|
119
|
+
setConfirmationMode(toolId: string, mode: ToolConfirmationMode): void {
|
|
120
|
+
const current = this.preferences[TOOL_CONFIRMATION_PREFERENCE] || {};
|
|
121
|
+
// Determine the global default (star entry), or fallback to schema default
|
|
122
|
+
let starMode = current['*'];
|
|
123
|
+
if (starMode === undefined) {
|
|
124
|
+
starMode = ToolConfirmationMode.YOLO;
|
|
125
|
+
}
|
|
126
|
+
if (mode === starMode) {
|
|
127
|
+
// Remove the toolId entry if it exists
|
|
128
|
+
if (toolId in current) {
|
|
129
|
+
const { [toolId]: _, ...rest } = current;
|
|
130
|
+
this.preferenceService.updateValue(TOOL_CONFIRMATION_PREFERENCE, rest);
|
|
131
|
+
}
|
|
132
|
+
// else, nothing to update
|
|
133
|
+
} else {
|
|
134
|
+
// Set or update the toolId entry
|
|
135
|
+
const updated = { ...current, [toolId]: mode };
|
|
136
|
+
this.preferenceService.updateValue(TOOL_CONFIRMATION_PREFERENCE, updated);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Set the confirmation mode for a specific tool for this session only (not persisted, per chat)
|
|
142
|
+
*/
|
|
143
|
+
setSessionConfirmationMode(toolId: string, mode: ToolConfirmationMode, chatId: string): void {
|
|
144
|
+
let chatMap = this.sessionOverrides.get(chatId);
|
|
145
|
+
if (!chatMap) {
|
|
146
|
+
chatMap = new Map();
|
|
147
|
+
this.sessionOverrides.set(chatId, chatMap);
|
|
148
|
+
}
|
|
149
|
+
chatMap.set(toolId, mode);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Clear all session overrides for a specific chat, or all if no chatId is given
|
|
154
|
+
*/
|
|
155
|
+
clearSessionOverrides(chatId?: string): void {
|
|
156
|
+
if (chatId) {
|
|
157
|
+
this.sessionOverrides.delete(chatId);
|
|
158
|
+
} else {
|
|
159
|
+
this.sessionOverrides.clear();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all tool confirmation settings
|
|
165
|
+
*/
|
|
166
|
+
getAllConfirmationSettings(): { [toolId: string]: ToolConfirmationMode } {
|
|
167
|
+
return this.preferences[TOOL_CONFIRMATION_PREFERENCE] || {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
resetAllConfirmationModeSettings(): void {
|
|
171
|
+
const current = this.preferences[TOOL_CONFIRMATION_PREFERENCE] || {};
|
|
172
|
+
if ('*' in current) {
|
|
173
|
+
this.preferenceService.updateValue(TOOL_CONFIRMATION_PREFERENCE, { '*': current['*'] });
|
|
174
|
+
} else {
|
|
175
|
+
this.preferenceService.updateValue(TOOL_CONFIRMATION_PREFERENCE, {});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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 { ToolRequest } from '@theia/ai-core';
|
|
18
|
+
import { injectable, inject } from '@theia/core/shared/inversify';
|
|
19
|
+
import { ChatToolRequestService, ChatToolRequest } from '../common/chat-tool-request-service';
|
|
20
|
+
import { MutableChatRequestModel, ToolCallChatResponseContent } from '../common/chat-model';
|
|
21
|
+
import { ToolConfirmationManager, ToolConfirmationMode, ChatToolPreferences } from './chat-tool-preferences';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Frontend-specific implementation of ChatToolRequestService that handles tool confirmation
|
|
25
|
+
*/
|
|
26
|
+
@injectable()
|
|
27
|
+
export class FrontendChatToolRequestService extends ChatToolRequestService {
|
|
28
|
+
|
|
29
|
+
@inject(ToolConfirmationManager)
|
|
30
|
+
protected readonly confirmationManager: ToolConfirmationManager;
|
|
31
|
+
|
|
32
|
+
@inject(ChatToolPreferences)
|
|
33
|
+
protected readonly preferences: ChatToolPreferences;
|
|
34
|
+
|
|
35
|
+
protected override toChatToolRequest(toolRequest: ToolRequest, request: MutableChatRequestModel): ChatToolRequest {
|
|
36
|
+
const confirmationMode = this.confirmationManager.getConfirmationMode(toolRequest.id, request.session.id);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...toolRequest,
|
|
40
|
+
handler: async (arg_string: string) => {
|
|
41
|
+
switch (confirmationMode) {
|
|
42
|
+
case ToolConfirmationMode.DISABLED:
|
|
43
|
+
return { denied: true, message: `Tool ${toolRequest.id} is disabled` };
|
|
44
|
+
|
|
45
|
+
case ToolConfirmationMode.YOLO:
|
|
46
|
+
// Execute immediately without confirmation
|
|
47
|
+
return toolRequest.handler(arg_string, request);
|
|
48
|
+
|
|
49
|
+
case ToolConfirmationMode.CONFIRM:
|
|
50
|
+
default:
|
|
51
|
+
// Create confirmation requirement
|
|
52
|
+
const toolCallContent = this.findToolCallContent(toolRequest, arg_string, request);
|
|
53
|
+
const confirmed = await toolCallContent.confirmed;
|
|
54
|
+
|
|
55
|
+
if (confirmed) {
|
|
56
|
+
return toolRequest.handler(arg_string, request);
|
|
57
|
+
} else {
|
|
58
|
+
// Return an object indicating the user denied the tool execution
|
|
59
|
+
// instead of throwing an error
|
|
60
|
+
return { denied: true, message: `Tool execution denied by user: ${toolRequest.id}` };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Find existing tool call content or create a new one for confirmation tracking
|
|
69
|
+
*
|
|
70
|
+
* Looks for ToolCallChatResponseContent nodes where the name field matches the toolRequest id.
|
|
71
|
+
* Starts from the back of the content array to find the most recent match.
|
|
72
|
+
*/
|
|
73
|
+
protected findToolCallContent(
|
|
74
|
+
toolRequest: ToolRequest,
|
|
75
|
+
arguments_: string,
|
|
76
|
+
request: MutableChatRequestModel
|
|
77
|
+
): ToolCallChatResponseContent {
|
|
78
|
+
// Look for existing tool call content with matching ID
|
|
79
|
+
const response = request.response.response;
|
|
80
|
+
const contentArray = response.content;
|
|
81
|
+
|
|
82
|
+
// Start from the end of the array and find the first match
|
|
83
|
+
for (let i = contentArray.length - 1; i >= 0; i--) {
|
|
84
|
+
const content = contentArray[i];
|
|
85
|
+
if (ToolCallChatResponseContent.is(content) &&
|
|
86
|
+
content.name === toolRequest.id) {
|
|
87
|
+
return content;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
throw new Error(`Tool call content for tool ${toolRequest.id} not found in the response`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
-
import {
|
|
18
|
+
import { ChatAgent, ChatAgentLocation, ChatChangeEvent, ChatServiceImpl, ChatSession, ParsedChatRequest, SessionOptions } from '../common';
|
|
19
19
|
import { PreferenceService } from '@theia/core/lib/browser';
|
|
20
20
|
import { DEFAULT_CHAT_AGENT_PREF, PIN_CHAT_AGENT_PREF } from './ai-chat-preferences';
|
|
21
21
|
import { ChangeSetFileService } from './change-set-file-service';
|
|
@@ -68,11 +68,8 @@ export class FrontendChatServiceImpl extends ChatServiceImpl {
|
|
|
68
68
|
override createSession(location?: ChatAgentLocation, options?: SessionOptions, pinnedAgent?: ChatAgent): ChatSession {
|
|
69
69
|
const session = super.createSession(location, options, pinnedAgent);
|
|
70
70
|
session.model.onDidChange(event => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.changeSetFileService.closeDiffsForSession(session.id);
|
|
74
|
-
} else if (changeSet) {
|
|
75
|
-
this.changeSetFileService.closeDiffsForSession(session.id, changeSet.getElements().map(({ uri }) => uri));
|
|
71
|
+
if (ChatChangeEvent.isChangeSetEvent(event)) {
|
|
72
|
+
this.changeSetFileService.closeDiffsForSession(session.id, session.model.changeSet.getElements().map(({ uri }) => uri));
|
|
76
73
|
}
|
|
77
74
|
});
|
|
78
75
|
return session;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH and others.
|
|
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 { ArrayUtils, Disposable, Emitter, Event, URI } from '@theia/core';
|
|
18
|
+
|
|
19
|
+
export interface ChangeSetElement {
|
|
20
|
+
readonly uri: URI;
|
|
21
|
+
|
|
22
|
+
onDidChange?: Event<void>
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly icon?: string;
|
|
25
|
+
readonly additionalInfo?: string;
|
|
26
|
+
|
|
27
|
+
readonly state?: 'pending' | 'applied' | 'stale';
|
|
28
|
+
readonly type?: 'add' | 'modify' | 'delete';
|
|
29
|
+
readonly data?: { [key: string]: unknown };
|
|
30
|
+
|
|
31
|
+
/** Called when an element is shown in the UI */
|
|
32
|
+
onShow?(): void;
|
|
33
|
+
/** Called when an element is hidden in the UI */
|
|
34
|
+
onHide?(): void;
|
|
35
|
+
open?(): Promise<void>;
|
|
36
|
+
openChange?(): Promise<void>;
|
|
37
|
+
apply?(): Promise<void>;
|
|
38
|
+
revert?(): Promise<void>;
|
|
39
|
+
dispose?(): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ChatUpdateChangeSetEvent {
|
|
43
|
+
kind: 'updateChangeSet';
|
|
44
|
+
elements?: ChangeSetElement[];
|
|
45
|
+
title?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ChangeSetChangeEvent {
|
|
49
|
+
title?: string;
|
|
50
|
+
added?: URI[],
|
|
51
|
+
removed?: URI[],
|
|
52
|
+
modified?: URI[],
|
|
53
|
+
/** Fired when only the state of a given element changes, not its contents */
|
|
54
|
+
state?: URI[],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ChangeSet extends Disposable {
|
|
58
|
+
onDidChange: Event<ChatUpdateChangeSetEvent>;
|
|
59
|
+
readonly title: string;
|
|
60
|
+
setTitle(title: string): void;
|
|
61
|
+
getElements(): ChangeSetElement[];
|
|
62
|
+
/**
|
|
63
|
+
* Find an element by URI.
|
|
64
|
+
* @param uri The URI to look for.
|
|
65
|
+
* @returns The element with the given URI, or undefined if not found.
|
|
66
|
+
*/
|
|
67
|
+
getElementByURI(uri: URI): ChangeSetElement | undefined;
|
|
68
|
+
/** @returns true if addition produces a change; false otherwise. */
|
|
69
|
+
addElements(...elements: ChangeSetElement[]): boolean;
|
|
70
|
+
setElements(...elements: ChangeSetElement[]): void;
|
|
71
|
+
/** @returns true if deletion produces a change; false otherwise. */
|
|
72
|
+
removeElements(...uris: URI[]): boolean;
|
|
73
|
+
dispose(): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class ChangeSetImpl implements ChangeSet {
|
|
77
|
+
/** @param changeSets ordered from tip to root. */
|
|
78
|
+
static combine(changeSets: Iterable<ChangeSetImpl>): Map<string, ChangeSetElement | undefined> {
|
|
79
|
+
const result = new Map<string, ChangeSetElement | undefined>();
|
|
80
|
+
for (const next of changeSets) {
|
|
81
|
+
next._elements.forEach((value, key) => !result.has(key) && result.set(key, value));
|
|
82
|
+
// Break at the first element whose values were set, not just changed through addition and deletion.
|
|
83
|
+
if (next.hasBeenSet) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected readonly _onDidChangeEmitter = new Emitter<ChatUpdateChangeSetEvent>();
|
|
92
|
+
onDidChange: Event<ChatUpdateChangeSetEvent> = this._onDidChangeEmitter.event;
|
|
93
|
+
protected readonly _onDidChangeContentsEmitter = new Emitter<ChangeSetChangeEvent>();
|
|
94
|
+
onDidChangeContents: Event<ChangeSetChangeEvent> = this._onDidChangeContentsEmitter.event;
|
|
95
|
+
|
|
96
|
+
protected hasBeenSet = false;
|
|
97
|
+
protected _elements = new Map<string, ChangeSetElement | undefined>();
|
|
98
|
+
protected _title = 'Suggested Changes';
|
|
99
|
+
get title(): string {
|
|
100
|
+
return this._title;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
constructor(elements: ChangeSetElement[] = []) {
|
|
104
|
+
this.addElements(...elements);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getElements(): ChangeSetElement[] {
|
|
108
|
+
return ArrayUtils.coalesce(Array.from(this._elements.values()));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Will replace any element that is already present, using URI as identity criterion. */
|
|
112
|
+
addElements(...elements: ChangeSetElement[]): boolean {
|
|
113
|
+
const added: URI[] = [];
|
|
114
|
+
const modified: URI[] = [];
|
|
115
|
+
elements.forEach(element => {
|
|
116
|
+
if (this.doAdd(element)) {
|
|
117
|
+
modified.push(element.uri);
|
|
118
|
+
} else {
|
|
119
|
+
added.push(element.uri);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
this.notifyChange({ added, modified });
|
|
123
|
+
return !!(added.length || modified.length);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setTitle(title: string): void {
|
|
127
|
+
this._title = title;
|
|
128
|
+
this.notifyChange({ title });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected doAdd(element: ChangeSetElement): boolean {
|
|
132
|
+
const id = element.uri.toString();
|
|
133
|
+
const existing = this._elements.get(id);
|
|
134
|
+
existing?.dispose?.();
|
|
135
|
+
this._elements.set(id, element);
|
|
136
|
+
element.onDidChange?.(() => this.notifyChange({ state: [element.uri] }));
|
|
137
|
+
return !!existing;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setElements(...elements: ChangeSetElement[]): void {
|
|
141
|
+
this.hasBeenSet = true;
|
|
142
|
+
const added = [];
|
|
143
|
+
const modified = [];
|
|
144
|
+
const removed = [];
|
|
145
|
+
const toHandle = new Set(...this._elements.keys());
|
|
146
|
+
for (const element of elements) {
|
|
147
|
+
toHandle.delete(element.uri.toString());
|
|
148
|
+
if (this.doAdd(element)) {
|
|
149
|
+
added.push(element.uri);
|
|
150
|
+
} else {
|
|
151
|
+
modified.push(element.uri);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const toDelete of toHandle) {
|
|
155
|
+
const uri = new URI(toDelete);
|
|
156
|
+
if (this.doDelete(uri)) {
|
|
157
|
+
removed.push(uri);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.notifyChange({ added, modified, removed });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
removeElements(...uris: URI[]): boolean {
|
|
164
|
+
const removed: URI[] = [];
|
|
165
|
+
for (const uri of uris) {
|
|
166
|
+
if (this.doDelete(uri)) {
|
|
167
|
+
removed.push(uri);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
this.notifyChange({ removed });
|
|
171
|
+
return !!removed.length;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getElementByURI(uri: URI): ChangeSetElement | undefined {
|
|
175
|
+
return this._elements.get(uri.toString());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected doDelete(uri: URI): boolean {
|
|
179
|
+
const id = uri.toString();
|
|
180
|
+
const delendum = this._elements.get(id);
|
|
181
|
+
if (delendum) {
|
|
182
|
+
delendum.dispose?.();
|
|
183
|
+
}
|
|
184
|
+
this._elements.set(id, undefined);
|
|
185
|
+
return !!delendum;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
protected notifyChange(change: ChangeSetChangeEvent): void {
|
|
189
|
+
this._onDidChangeContentsEmitter.fire(change);
|
|
190
|
+
this._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: this.getElements(), title: this.title });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
dispose(): void {
|
|
194
|
+
this._onDidChangeEmitter.dispose();
|
|
195
|
+
this._elements.forEach(element => element?.dispose?.());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
ToolCallChatResponseContentImpl,
|
|
62
62
|
ChatRequestModel,
|
|
63
63
|
ThinkingChatResponseContentImpl,
|
|
64
|
+
ErrorChatResponseContent,
|
|
64
65
|
} from './chat-model';
|
|
65
66
|
import { parseContents } from './parse-contents';
|
|
66
67
|
import { DefaultResponseContentFactory, ResponseContentMatcher, ResponseContentMatcherProvider } from './response-content-matcher';
|
|
@@ -218,6 +219,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
|
|
|
218
219
|
};
|
|
219
220
|
|
|
220
221
|
protected handleError(request: MutableChatRequestModel, error: Error): void {
|
|
222
|
+
console.error('Error handling chat interaction:', error);
|
|
221
223
|
request.response.response.addContent(new ErrorChatResponseContentImpl(error));
|
|
222
224
|
request.response.error(error);
|
|
223
225
|
}
|
|
@@ -258,17 +260,19 @@ export abstract class AbstractChatAgent implements ChatAgent {
|
|
|
258
260
|
text: text,
|
|
259
261
|
});
|
|
260
262
|
if (request.response.isComplete || includeResponseInProgress) {
|
|
261
|
-
const responseMessages: LanguageModelMessage[] = request.response.response.content
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
263
|
+
const responseMessages: LanguageModelMessage[] = request.response.response.content
|
|
264
|
+
.filter(c => !ErrorChatResponseContent.is(c))
|
|
265
|
+
.flatMap(c => {
|
|
266
|
+
if (ChatResponseContent.hasToLanguageModelMessage(c)) {
|
|
267
|
+
return c.toLanguageModelMessage();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
actor: 'ai',
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: c.asString?.() ?? c.asDisplayString?.() ?? '',
|
|
274
|
+
} as TextMessage;
|
|
275
|
+
});
|
|
272
276
|
messages.push(...responseMessages);
|
|
273
277
|
}
|
|
274
278
|
return messages;
|