@theia/ai-chat 1.63.0-next.0 → 1.63.0-next.52
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-delegation-tool.d.ts +25 -0
- package/lib/browser/agent-delegation-tool.d.ts.map +1 -0
- package/lib/browser/agent-delegation-tool.js +171 -0
- package/lib/browser/agent-delegation-tool.js.map +1 -0
- package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +15 -1
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/change-set-file-element.d.ts +28 -7
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +86 -18
- 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/delegation-response-content.d.ts +20 -0
- package/lib/browser/delegation-response-content.d.ts.map +1 -0
- package/lib/browser/delegation-response-content.js +51 -0
- package/lib/browser/delegation-response-content.js.map +1 -0
- package/lib/browser/file-chat-variable-contribution.d.ts +15 -1
- package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -1
- package/lib/browser/file-chat-variable-contribution.js +111 -5
- package/lib/browser/file-chat-variable-contribution.js.map +1 -1
- 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/browser/image-context-variable-contribution.d.ts +27 -0
- package/lib/browser/image-context-variable-contribution.d.ts.map +1 -0
- package/lib/browser/image-context-variable-contribution.js +149 -0
- package/lib/browser/image-context-variable-contribution.js.map +1 -0
- package/lib/browser/task-context-service.d.ts +9 -3
- package/lib/browser/task-context-service.d.ts.map +1 -1
- package/lib/browser/task-context-service.js +111 -9
- package/lib/browser/task-context-service.js.map +1 -1
- package/lib/browser/task-context-storage-service.d.ts +1 -0
- package/lib/browser/task-context-storage-service.d.ts.map +1 -1
- package/lib/browser/task-context-storage-service.js +4 -1
- package/lib/browser/task-context-storage-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-agent-service.d.ts +1 -0
- package/lib/common/chat-agent-service.d.ts.map +1 -1
- package/lib/common/chat-agent-service.js +2 -1
- package/lib/common/chat-agent-service.js.map +1 -1
- package/lib/common/chat-agents.d.ts +2 -2
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +25 -6
- 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-request-parser.d.ts.map +1 -1
- package/lib/common/chat-request-parser.js +3 -0
- package/lib/common/chat-request-parser.js.map +1 -1
- package/lib/common/chat-service.d.ts +6 -5
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +9 -11
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/image-context-variable.d.ts +29 -0
- package/lib/common/image-context-variable.d.ts.map +1 -0
- package/lib/common/image-context-variable.js +99 -0
- package/lib/common/image-context-variable.js.map +1 -0
- package/package.json +10 -9
- package/src/browser/agent-delegation-tool.ts +207 -0
- package/src/browser/ai-chat-frontend-module.ts +28 -3
- package/src/browser/change-set-file-element.ts +97 -25
- 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/delegation-response-content.ts +55 -0
- package/src/browser/file-chat-variable-contribution.ts +120 -6
- package/src/browser/frontend-chat-service.ts +3 -6
- package/src/browser/image-context-variable-contribution.ts +153 -0
- package/src/browser/task-context-service.ts +115 -9
- package/src/browser/task-context-storage-service.ts +5 -1
- package/src/common/change-set.ts +197 -0
- package/src/common/chat-agent-service.ts +1 -0
- package/src/common/chat-agents.ts +40 -19
- package/src/common/chat-model.ts +258 -208
- package/src/common/chat-request-parser.ts +3 -0
- package/src/common/chat-service.ts +11 -13
- package/src/common/image-context-variable.ts +116 -0
|
@@ -0,0 +1,207 @@
|
|
|
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 { ToolProvider, ToolRequest } from '@theia/ai-core';
|
|
18
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import {
|
|
20
|
+
ChatAgentService,
|
|
21
|
+
ChatAgentServiceFactory,
|
|
22
|
+
ChatRequest,
|
|
23
|
+
ChatService,
|
|
24
|
+
ChatServiceFactory,
|
|
25
|
+
MutableChatRequestModel,
|
|
26
|
+
MutableChatModel,
|
|
27
|
+
ChatSession,
|
|
28
|
+
} from '../common';
|
|
29
|
+
import { DelegationResponseContent } from './delegation-response-content';
|
|
30
|
+
|
|
31
|
+
export const AGENT_DELEGATION_FUNCTION_ID = 'delegateToAgent';
|
|
32
|
+
|
|
33
|
+
@injectable()
|
|
34
|
+
export class AgentDelegationTool implements ToolProvider {
|
|
35
|
+
static ID = AGENT_DELEGATION_FUNCTION_ID;
|
|
36
|
+
|
|
37
|
+
@inject(ChatAgentServiceFactory)
|
|
38
|
+
protected readonly getChatAgentService: () => ChatAgentService;
|
|
39
|
+
|
|
40
|
+
@inject(ChatServiceFactory)
|
|
41
|
+
protected readonly getChatService: () => ChatService;
|
|
42
|
+
|
|
43
|
+
getTool(): ToolRequest {
|
|
44
|
+
return {
|
|
45
|
+
id: AgentDelegationTool.ID,
|
|
46
|
+
name: AgentDelegationTool.ID,
|
|
47
|
+
description:
|
|
48
|
+
'Delegate a task or question to a specific AI agent. This tool allows you to submit requests to specialized agents based on their capabilities.',
|
|
49
|
+
parameters: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
agentId: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description:
|
|
55
|
+
'The ID of the AI agent to delegate the task to.',
|
|
56
|
+
},
|
|
57
|
+
prompt: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description:
|
|
60
|
+
'The task, question, or prompt to pass to the specified agent.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
required: ['agentId', 'prompt'],
|
|
64
|
+
},
|
|
65
|
+
handler: (arg_string: string, ctx: MutableChatRequestModel) =>
|
|
66
|
+
this.delegateToAgent(arg_string, ctx),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async delegateToAgent(
|
|
71
|
+
arg_string: string,
|
|
72
|
+
ctx: MutableChatRequestModel
|
|
73
|
+
): Promise<string> {
|
|
74
|
+
try {
|
|
75
|
+
const args = JSON.parse(arg_string);
|
|
76
|
+
const { agentId, prompt } = args;
|
|
77
|
+
|
|
78
|
+
if (!agentId || !prompt) {
|
|
79
|
+
const errorMsg = 'Both agentId and prompt parameters are required.';
|
|
80
|
+
console.error(errorMsg, { agentId, prompt });
|
|
81
|
+
return errorMsg;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if the specified agent exists
|
|
85
|
+
const agent = this.getChatAgentService().getAgent(agentId);
|
|
86
|
+
if (!agent) {
|
|
87
|
+
const availableAgents = this.getChatAgentService()
|
|
88
|
+
.getAgents()
|
|
89
|
+
.map(a => a.id);
|
|
90
|
+
const errorMsg = `Agent '${agentId}' not found or not enabled. Available agents: ${availableAgents.join(', ')}`;
|
|
91
|
+
console.error(errorMsg);
|
|
92
|
+
return errorMsg;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let newSession;
|
|
96
|
+
try {
|
|
97
|
+
// FIXME: this creates a new conversation visible in the UI (Panel), which we don't want
|
|
98
|
+
// It is not possible to start a session without specifying a location (default=Panel)
|
|
99
|
+
const chatService = this.getChatService();
|
|
100
|
+
|
|
101
|
+
// Store the current active session to restore it after delegation
|
|
102
|
+
const currentActiveSession = chatService.getActiveSession();
|
|
103
|
+
|
|
104
|
+
newSession = chatService.createSession(
|
|
105
|
+
undefined,
|
|
106
|
+
{ focus: false },
|
|
107
|
+
agent
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Immediately restore the original active session to avoid confusing the user
|
|
111
|
+
if (currentActiveSession) {
|
|
112
|
+
chatService.setActiveSession(currentActiveSession.id, { focus: false });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Setup ChangeSet bubbling from delegated session to parent session
|
|
116
|
+
this.setupChangeSetBubbling(newSession, ctx.session, agent.name);
|
|
117
|
+
} catch (sessionError) {
|
|
118
|
+
const errorMsg = `Failed to create chat session for agent '${agentId}': ${sessionError instanceof Error ? sessionError.message : sessionError}`;
|
|
119
|
+
console.error(errorMsg, sessionError);
|
|
120
|
+
return errorMsg;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Send the request
|
|
124
|
+
const chatRequest: ChatRequest = {
|
|
125
|
+
text: prompt,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let response;
|
|
129
|
+
try {
|
|
130
|
+
const chatService = this.getChatService();
|
|
131
|
+
response = await chatService.sendRequest(
|
|
132
|
+
newSession.id,
|
|
133
|
+
chatRequest
|
|
134
|
+
);
|
|
135
|
+
} catch (sendError) {
|
|
136
|
+
const errorMsg = `Failed to send request to agent '${agentId}': ${sendError instanceof Error ? sendError.message : sendError}`;
|
|
137
|
+
console.error(errorMsg, sendError);
|
|
138
|
+
return errorMsg;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (response) {
|
|
142
|
+
// Add the response content immediately to enable streaming
|
|
143
|
+
// The renderer will handle the streaming updates
|
|
144
|
+
ctx.response.response.addContent(
|
|
145
|
+
new DelegationResponseContent(agent.name, prompt, response)
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Wait for completion to return the final result as tool output
|
|
150
|
+
const result = await response.responseCompleted;
|
|
151
|
+
const stringResult = result.response.asString();
|
|
152
|
+
// Return the raw text to the top-level Agent, as a tool result
|
|
153
|
+
return stringResult;
|
|
154
|
+
} catch (completionError) {
|
|
155
|
+
const errorMsg = `Failed to complete response from agent '${agentId}': ${completionError instanceof Error ? completionError.message : completionError}`;
|
|
156
|
+
console.error(errorMsg, completionError);
|
|
157
|
+
return errorMsg;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const errorMsg = `Delegation to agent '${agentId}' has failed: no response returned.`;
|
|
161
|
+
console.error(errorMsg);
|
|
162
|
+
return errorMsg;
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('Failed to delegate to agent', error);
|
|
166
|
+
return JSON.stringify({
|
|
167
|
+
error: `Failed to parse arguments or delegate to agent: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Sets up monitoring of the ChangeSet in the delegated session and bubbles changes to the parent session.
|
|
174
|
+
* @param delegatedSession The session created for the delegated agent
|
|
175
|
+
* @param parentModel The parent session model that should receive the bubbled changes
|
|
176
|
+
* @param agentName The name of the agent for attribution purposes
|
|
177
|
+
*/
|
|
178
|
+
private setupChangeSetBubbling(
|
|
179
|
+
delegatedSession: ChatSession,
|
|
180
|
+
parentModel: MutableChatModel,
|
|
181
|
+
agentName: string
|
|
182
|
+
): void {
|
|
183
|
+
// Monitor ChangeSet for bubbling
|
|
184
|
+
delegatedSession.model.changeSet.onDidChange(_event => {
|
|
185
|
+
this.bubbleChangeSet(delegatedSession, parentModel, agentName);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Bubbles the ChangeSet from the delegated session to the parent session.
|
|
191
|
+
* @param delegatedSession The session from which to bubble changes
|
|
192
|
+
* @param parentModel The parent session model to receive the bubbled changes
|
|
193
|
+
* @param agentName The name of the agent for attribution purposes
|
|
194
|
+
*/
|
|
195
|
+
private bubbleChangeSet(
|
|
196
|
+
delegatedSession: ChatSession,
|
|
197
|
+
parentModel: MutableChatModel,
|
|
198
|
+
agentName: string
|
|
199
|
+
): void {
|
|
200
|
+
const delegatedElements = delegatedSession.model.changeSet.getElements();
|
|
201
|
+
if (delegatedElements.length > 0) {
|
|
202
|
+
const bubbledTitle = `Changes from ${agentName}`;
|
|
203
|
+
parentModel.changeSet.setTitle(bubbledTitle);
|
|
204
|
+
parentModel.changeSet.addElements(...delegatedElements);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { Agent, AgentService, AIVariableContribution } from '@theia/ai-core/lib/common';
|
|
17
|
+
import { Agent, AgentService, AIVariableContribution, bindToolProvider } from '@theia/ai-core/lib/common';
|
|
18
18
|
import { bindContributionProvider, CommandContribution } from '@theia/core';
|
|
19
19
|
import { FrontendApplicationContribution, LabelProviderContribution, PreferenceContribution } from '@theia/core/lib/browser';
|
|
20
20
|
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
@@ -26,17 +26,21 @@ import {
|
|
|
26
26
|
ChatRequestParserImpl,
|
|
27
27
|
ChatService,
|
|
28
28
|
ToolCallChatResponseContentFactory,
|
|
29
|
-
PinChatAgent
|
|
29
|
+
PinChatAgent,
|
|
30
|
+
ChatServiceFactory,
|
|
31
|
+
ChatAgentServiceFactory
|
|
30
32
|
} from '../common';
|
|
31
33
|
import { ChatAgentsVariableContribution } from '../common/chat-agents-variable-contribution';
|
|
32
34
|
import { CustomChatAgent } from '../common/custom-chat-agent';
|
|
33
35
|
import { DefaultResponseContentFactory, DefaultResponseContentMatcherProvider, ResponseContentMatcherProvider } from '../common/response-content-matcher';
|
|
34
36
|
import { aiChatPreferences } from './ai-chat-preferences';
|
|
37
|
+
import { bindChatToolPreferences, ToolConfirmationManager } from './chat-tool-preferences';
|
|
35
38
|
import { ChangeSetElementArgs, ChangeSetFileElement, ChangeSetFileElementFactory } from './change-set-file-element';
|
|
36
39
|
import { AICustomAgentsFrontendApplicationContribution } from './custom-agent-frontend-application-contribution';
|
|
37
40
|
import { FrontendChatServiceImpl } from './frontend-chat-service';
|
|
38
41
|
import { CustomAgentFactory } from './custom-agent-factory';
|
|
39
42
|
import { ChatToolRequestService } from '../common/chat-tool-request-service';
|
|
43
|
+
import { FrontendChatToolRequestService } from './chat-tool-request-service';
|
|
40
44
|
import { ChangeSetFileService } from './change-set-file-service';
|
|
41
45
|
import { ContextVariableLabelProvider } from './context-variable-label-provider';
|
|
42
46
|
import { ContextFileVariableLabelProvider } from './context-file-variable-label-provider';
|
|
@@ -52,12 +56,15 @@ import { TaskContextVariableLabelProvider } from './task-context-variable-label-
|
|
|
52
56
|
import { TaskContextService, TaskContextStorageService } from './task-context-service';
|
|
53
57
|
import { InMemoryTaskContextStorage } from './task-context-storage-service';
|
|
54
58
|
import { AIChatFrontendContribution } from './ai-chat-frontend-contribution';
|
|
59
|
+
import { ImageContextVariableContribution } from './image-context-variable-contribution';
|
|
60
|
+
import { AgentDelegationTool } from './agent-delegation-tool';
|
|
55
61
|
|
|
56
62
|
export default new ContainerModule(bind => {
|
|
57
63
|
bindContributionProvider(bind, Agent);
|
|
58
64
|
bindContributionProvider(bind, ChatAgent);
|
|
59
65
|
|
|
60
|
-
bind(
|
|
66
|
+
bind(FrontendChatToolRequestService).toSelf().inSingletonScope();
|
|
67
|
+
bind(ChatToolRequestService).toService(FrontendChatToolRequestService);
|
|
61
68
|
|
|
62
69
|
bind(ChatAgentServiceImpl).toSelf().inSingletonScope();
|
|
63
70
|
bind(ChatAgentService).toService(ChatAgentServiceImpl);
|
|
@@ -80,8 +87,19 @@ export default new ContainerModule(bind => {
|
|
|
80
87
|
bind(FrontendChatServiceImpl).toSelf().inSingletonScope();
|
|
81
88
|
bind(ChatService).toService(FrontendChatServiceImpl);
|
|
82
89
|
|
|
90
|
+
bind(ChatServiceFactory).toDynamicValue(ctx => () =>
|
|
91
|
+
ctx.container.get<ChatService>(ChatService)
|
|
92
|
+
);
|
|
93
|
+
bind(ChatAgentServiceFactory).toDynamicValue(ctx => () =>
|
|
94
|
+
ctx.container.get<ChatAgentService>(ChatAgentService)
|
|
95
|
+
);
|
|
96
|
+
|
|
83
97
|
bind(PreferenceContribution).toConstantValue({ schema: aiChatPreferences });
|
|
84
98
|
|
|
99
|
+
// Tool confirmation preferences
|
|
100
|
+
bindChatToolPreferences(bind);
|
|
101
|
+
bind(ToolConfirmationManager).toSelf().inSingletonScope();
|
|
102
|
+
|
|
85
103
|
bind(CustomChatAgent).toSelf();
|
|
86
104
|
bind(CustomAgentFactory).toFactory<CustomChatAgent, [string, string, string, string, string]>(
|
|
87
105
|
ctx => (id: string, name: string, description: string, prompt: string, defaultLLM: string) => {
|
|
@@ -124,14 +142,21 @@ export default new ContainerModule(bind => {
|
|
|
124
142
|
|
|
125
143
|
bind(ChatSessionSummaryAgent).toSelf().inSingletonScope();
|
|
126
144
|
bind(Agent).toService(ChatSessionSummaryAgent);
|
|
145
|
+
|
|
127
146
|
bind(TaskContextVariableContribution).toSelf().inSingletonScope();
|
|
128
147
|
bind(AIVariableContribution).toService(TaskContextVariableContribution);
|
|
129
148
|
bind(TaskContextVariableLabelProvider).toSelf().inSingletonScope();
|
|
130
149
|
bind(LabelProviderContribution).toService(TaskContextVariableLabelProvider);
|
|
131
150
|
|
|
151
|
+
bind(ImageContextVariableContribution).toSelf().inSingletonScope();
|
|
152
|
+
bind(AIVariableContribution).toService(ImageContextVariableContribution);
|
|
153
|
+
bind(LabelProviderContribution).toService(ImageContextVariableContribution);
|
|
154
|
+
|
|
132
155
|
bind(TaskContextService).toSelf().inSingletonScope();
|
|
133
156
|
bind(InMemoryTaskContextStorage).toSelf().inSingletonScope();
|
|
134
157
|
bind(TaskContextStorageService).toService(InMemoryTaskContextStorage);
|
|
135
158
|
bind(AIChatFrontendContribution).toSelf().inSingletonScope();
|
|
136
159
|
bind(CommandContribution).toService(AIChatFrontendContribution);
|
|
160
|
+
|
|
161
|
+
bindToolProvider(AgentDelegationTool, bind);
|
|
137
162
|
});
|
|
@@ -18,7 +18,7 @@ import { DisposableCollection, Emitter, URI } from '@theia/core';
|
|
|
18
18
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
19
19
|
import { Replacement } from '@theia/core/lib/common/content-replacer';
|
|
20
20
|
import { ConfigurableInMemoryResources, ConfigurableMutableReferenceResource } from '@theia/ai-core';
|
|
21
|
-
import { ChangeSetElement
|
|
21
|
+
import { ChangeSetElement } from '../common';
|
|
22
22
|
import { createChangeSetFileUri } from './change-set-file-resource';
|
|
23
23
|
import { ChangeSetFileService } from './change-set-file-service';
|
|
24
24
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
@@ -33,10 +33,10 @@ export const ChangeSetElementArgs = Symbol('ChangeSetElementArgs');
|
|
|
33
33
|
export interface ChangeSetElementArgs extends Partial<ChangeSetElement> {
|
|
34
34
|
/** The URI of the element, expected to be unique within the same change set. */
|
|
35
35
|
uri: URI;
|
|
36
|
-
/** The change set containing this element. */
|
|
37
|
-
changeSet: ChangeSetImpl;
|
|
38
36
|
/** The id of the chat session containing this change set element. */
|
|
39
37
|
chatSessionId: string;
|
|
38
|
+
/** The id of the request with which this change set element is associated. */
|
|
39
|
+
requestId: string;
|
|
40
40
|
/**
|
|
41
41
|
* The state of the file after the changes have been applied.
|
|
42
42
|
* If `undefined`, there is no change.
|
|
@@ -71,38 +71,52 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
71
71
|
@inject(ConfigurableInMemoryResources)
|
|
72
72
|
protected readonly inMemoryResources: ConfigurableInMemoryResources;
|
|
73
73
|
|
|
74
|
+
@inject(ChangeSetFileElementFactory) protected readonly factory: ChangeSetFileElementFactory;
|
|
75
|
+
|
|
74
76
|
protected readonly toDispose = new DisposableCollection();
|
|
75
77
|
protected _state: ChangeSetElementState;
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
private _originalContent: string | undefined;
|
|
80
|
+
protected _initialized = false;
|
|
81
|
+
protected _initializationPromise: Promise<void> | undefined;
|
|
78
82
|
|
|
79
83
|
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
80
84
|
readonly onDidChange = this.onDidChangeEmitter.event;
|
|
81
|
-
protected
|
|
82
|
-
protected
|
|
85
|
+
protected _readOnlyResource: ConfigurableMutableReferenceResource;
|
|
86
|
+
protected _changeResource: ConfigurableMutableReferenceResource;
|
|
83
87
|
|
|
84
88
|
@postConstruct()
|
|
85
89
|
init(): void {
|
|
86
|
-
this.
|
|
87
|
-
this.obtainOriginalContent();
|
|
88
|
-
this.listenForOriginalFileChanges();
|
|
90
|
+
this._initializationPromise = this.initializeAsync();
|
|
89
91
|
this.toDispose.push(this.onDidChangeEmitter);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
protected async
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
|
|
94
|
+
protected async initializeAsync(): Promise<void> {
|
|
95
|
+
await this.obtainOriginalContent();
|
|
96
|
+
this.listenForOriginalFileChanges();
|
|
97
|
+
this._initialized = true;
|
|
98
|
+
}
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Ensures that the element is fully initialized before proceeding.
|
|
102
|
+
* This includes loading the original content from the file system.
|
|
103
|
+
*/
|
|
104
|
+
async ensureInitialized(): Promise<void> {
|
|
105
|
+
await this._initializationPromise;
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Returns true if the element has been fully initialized.
|
|
110
|
+
*/
|
|
111
|
+
get isInitialized(): boolean {
|
|
112
|
+
return this._initialized;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected async obtainOriginalContent(): Promise<void> {
|
|
116
|
+
this._originalContent = await this.changeSetFileService.read(this.uri);
|
|
117
|
+
if (this._readOnlyResource) {
|
|
118
|
+
this.readOnlyResource.update({ contents: this._originalContent ?? '' });
|
|
119
|
+
}
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
protected getInMemoryUri(uri: URI): ConfigurableMutableReferenceResource {
|
|
@@ -112,10 +126,14 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
112
126
|
protected listenForOriginalFileChanges(): void {
|
|
113
127
|
this.toDispose.push(this.fileService.onDidFilesChange(async event => {
|
|
114
128
|
if (!event.contains(this.uri)) { return; }
|
|
129
|
+
if (!this._initialized && this._initializationPromise) {
|
|
130
|
+
// make sure we are initialized
|
|
131
|
+
await this._initializationPromise;
|
|
132
|
+
}
|
|
115
133
|
// If we are applied, the tricky thing becomes the question what to revert to; otherwise, what to apply.
|
|
116
134
|
const newContent = await this.changeSetFileService.read(this.uri).catch(() => '');
|
|
117
135
|
this.readOnlyResource.update({ contents: newContent });
|
|
118
|
-
if (newContent === this.
|
|
136
|
+
if (newContent === this._originalContent) {
|
|
119
137
|
this.state = 'pending';
|
|
120
138
|
} else if (newContent === this.targetState) {
|
|
121
139
|
this.state = 'applied';
|
|
@@ -129,12 +147,41 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
129
147
|
return this.elementProps.uri;
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
protected get readOnlyResource(): ConfigurableMutableReferenceResource {
|
|
151
|
+
if (!this._readOnlyResource) {
|
|
152
|
+
this._readOnlyResource = this.getInMemoryUri(ChangeSetFileElement.toReadOnlyUri(this.uri, this.elementProps.chatSessionId));
|
|
153
|
+
this._readOnlyResource.update({
|
|
154
|
+
autosaveable: false,
|
|
155
|
+
readOnly: true,
|
|
156
|
+
contents: this._originalContent ?? ''
|
|
157
|
+
});
|
|
158
|
+
this.toDispose.push(this._readOnlyResource);
|
|
159
|
+
|
|
160
|
+
// If not yet initialized, update the resource once initialization completes
|
|
161
|
+
if (!this._initialized) {
|
|
162
|
+
this._initializationPromise?.then(() => {
|
|
163
|
+
this._readOnlyResource?.update({ contents: this._originalContent ?? '' });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return this._readOnlyResource;
|
|
168
|
+
}
|
|
169
|
+
|
|
132
170
|
get readOnlyUri(): URI {
|
|
133
|
-
return
|
|
171
|
+
return this.readOnlyResource.uri;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected get changeResource(): ConfigurableMutableReferenceResource {
|
|
175
|
+
if (!this._changeResource) {
|
|
176
|
+
this._changeResource = this.getInMemoryUri(createChangeSetFileUri(this.elementProps.chatSessionId, this.uri));
|
|
177
|
+
this._changeResource.update({ autosaveable: false });
|
|
178
|
+
this.toDispose.push(this._changeResource);
|
|
179
|
+
}
|
|
180
|
+
return this._changeResource;
|
|
134
181
|
}
|
|
135
182
|
|
|
136
183
|
get changedUri(): URI {
|
|
137
|
-
return
|
|
184
|
+
return this.changeResource.uri;
|
|
138
185
|
}
|
|
139
186
|
|
|
140
187
|
get name(): string {
|
|
@@ -172,15 +219,33 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
172
219
|
return this.elementProps.data;
|
|
173
220
|
};
|
|
174
221
|
|
|
222
|
+
get originalContent(): string | undefined {
|
|
223
|
+
if (!this._initialized && this._initializationPromise) {
|
|
224
|
+
console.warn('Accessing originalContent before initialization is complete. Consider using async methods.');
|
|
225
|
+
}
|
|
226
|
+
return this._originalContent;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Gets the original content of the file asynchronously.
|
|
231
|
+
* Ensures initialization is complete before returning the content.
|
|
232
|
+
*/
|
|
233
|
+
async getOriginalContent(): Promise<string | undefined> {
|
|
234
|
+
await this.ensureInitialized();
|
|
235
|
+
return this._originalContent;
|
|
236
|
+
}
|
|
237
|
+
|
|
175
238
|
get targetState(): string {
|
|
176
239
|
return this.elementProps.targetState ?? '';
|
|
177
240
|
}
|
|
178
241
|
|
|
179
242
|
async open(): Promise<void> {
|
|
243
|
+
await this.ensureInitialized();
|
|
180
244
|
this.changeSetFileService.open(this);
|
|
181
245
|
}
|
|
182
246
|
|
|
183
247
|
async openChange(): Promise<void> {
|
|
248
|
+
await this.ensureInitialized();
|
|
184
249
|
this.changeSetFileService.openDiff(
|
|
185
250
|
this.readOnlyUri,
|
|
186
251
|
this.changedUri
|
|
@@ -188,6 +253,7 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
188
253
|
}
|
|
189
254
|
|
|
190
255
|
async apply(contents?: string): Promise<void> {
|
|
256
|
+
await this.ensureInitialized();
|
|
191
257
|
if (!await this.confirm('Apply')) { return; }
|
|
192
258
|
if (!(await this.changeSetFileService.trySave(this.changedUri))) {
|
|
193
259
|
if (this.type === 'delete') {
|
|
@@ -205,13 +271,19 @@ export class ChangeSetFileElement implements ChangeSetElement {
|
|
|
205
271
|
this.state = 'applied';
|
|
206
272
|
}
|
|
207
273
|
|
|
274
|
+
onShow(): void {
|
|
275
|
+
// Ensure we have the latest state when showing
|
|
276
|
+
this.changeResource.update({ contents: this.targetState, onSave: content => this.writeChanges(content) });
|
|
277
|
+
}
|
|
278
|
+
|
|
208
279
|
async revert(): Promise<void> {
|
|
280
|
+
await this.ensureInitialized();
|
|
209
281
|
if (!await this.confirm('Revert')) { return; }
|
|
210
282
|
this.state = 'pending';
|
|
211
283
|
if (this.type === 'add') {
|
|
212
284
|
await this.changeSetFileService.delete(this.uri);
|
|
213
|
-
} else if (this.
|
|
214
|
-
await this.changeSetFileService.write(this.uri, this.
|
|
285
|
+
} else if (this._originalContent) {
|
|
286
|
+
await this.changeSetFileService.write(this.uri, this._originalContent);
|
|
215
287
|
}
|
|
216
288
|
}
|
|
217
289
|
|
|
@@ -42,7 +42,7 @@ export class ChangeSetVariableContribution implements AIVariableContribution, AI
|
|
|
42
42
|
|
|
43
43
|
async resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise<ResolvedAIVariable | undefined> {
|
|
44
44
|
if (!ChatSessionContext.is(context) || request.variable.name !== CHANGE_SET_SUMMARY_VARIABLE.name) { return undefined; }
|
|
45
|
-
if (!context.model.changeSet
|
|
45
|
+
if (!context.model.changeSet.getElements().length) {
|
|
46
46
|
return {
|
|
47
47
|
variable: CHANGE_SET_SUMMARY_VARIABLE,
|
|
48
48
|
value: ''
|