|
15 | 15 | // *****************************************************************************
|
16 | 16 | import { injectable, inject } from '@theia/core/shared/inversify';
|
17 | 17 | import { ToolProvider, ToolRequest } from '@theia/ai-core';
|
18 |
| -import { FileChangeSetService } from './file-changeset-service'; |
| 18 | +import { WorkspaceFunctionScope } from './workspace-functions'; |
| 19 | +import { ChangeSetFileElementFactory } from '@theia/ai-chat/lib/browser/change-set-file-element'; |
| 20 | +import { ChangeSetImpl, ChatRequestModelImpl } from '@theia/ai-chat'; |
| 21 | +import { FileService } from '@theia/filesystem/lib/browser/file-service'; |
19 | 22 |
|
20 | 23 | @injectable()
|
21 |
| -export class InitializeChangeSetProvider implements ToolProvider { |
22 |
| - static ID = 'changeSet_initializeChangeSet'; |
| 24 | +export class WriteChangeToFileProvider implements ToolProvider { |
| 25 | + static ID = 'changeSet_writeChangeToFile'; |
23 | 26 |
|
24 |
| - @inject(FileChangeSetService) |
25 |
| - protected readonly changeSetService: FileChangeSetService; |
| 27 | + @inject(WorkspaceFunctionScope) |
| 28 | + protected readonly workspaceFunctionScope: WorkspaceFunctionScope; |
26 | 29 |
|
27 |
| - getTool(): ToolRequest { |
28 |
| - return { |
29 |
| - id: InitializeChangeSetProvider.ID, |
30 |
| - name: InitializeChangeSetProvider.ID, |
31 |
| - description: 'Creates a new change set with a unique UUID and description.', |
32 |
| - parameters: { |
33 |
| - type: 'object', |
34 |
| - properties: { |
35 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set.' }, |
36 |
| - description: { type: 'string', description: 'High-level description of the change set.' } |
37 |
| - }, |
38 |
| - required: ['uuid', 'description'] |
39 |
| - }, |
40 |
| - handler: async (args: string): Promise<string> => { |
41 |
| - try { |
42 |
| - const { uuid, description } = JSON.parse(args); |
43 |
| - this.changeSetService.initializeChangeSet(uuid, description); |
44 |
| - return `Change set ${uuid} initialized successfully.`; |
45 |
| - } catch (error) { |
46 |
| - return JSON.stringify({ error: error.message }); |
47 |
| - } |
48 |
| - } |
49 |
| - }; |
50 |
| - } |
51 |
| -} |
52 |
| - |
53 |
| -@injectable() |
54 |
| -export class RemoveFileChangeProvider implements ToolProvider { |
55 |
| - static ID = 'changeSet_removeFileChange'; |
56 |
| - |
57 |
| - @inject(FileChangeSetService) |
58 |
| - protected readonly changeSetService: FileChangeSetService; |
59 |
| - |
60 |
| - getTool(): ToolRequest { |
61 |
| - return { |
62 |
| - id: RemoveFileChangeProvider.ID, |
63 |
| - name: RemoveFileChangeProvider.ID, |
64 |
| - description: 'Removes a file and all related changes from the specified change set.', |
65 |
| - parameters: { |
66 |
| - type: 'object', |
67 |
| - properties: { |
68 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set.' }, |
69 |
| - filePath: { type: 'string', description: 'Path to the file.' } |
70 |
| - }, |
71 |
| - required: ['uuid', 'filePath'] |
72 |
| - }, |
73 |
| - handler: async (args: string): Promise<string> => { |
74 |
| - try { |
75 |
| - const { uuid, filePath } = JSON.parse(args); |
76 |
| - this.changeSetService.removeFileChange(uuid, filePath); |
77 |
| - return `File ${filePath} removed from change set ${uuid}.`; |
78 |
| - } catch (error) { |
79 |
| - return JSON.stringify({ error: error.message }); |
80 |
| - } |
81 |
| - } |
82 |
| - }; |
83 |
| - } |
84 |
| -} |
85 |
| - |
86 |
| -@injectable() |
87 |
| -export class ListChangedFilesProvider implements ToolProvider { |
88 |
| - static ID = 'changeSet_listChangedFiles'; |
89 |
| - |
90 |
| - @inject(FileChangeSetService) |
91 |
| - protected readonly changeSetService: FileChangeSetService; |
92 |
| - |
93 |
| - getTool(): ToolRequest { |
94 |
| - return { |
95 |
| - id: ListChangedFilesProvider.ID, |
96 |
| - name: ListChangedFilesProvider.ID, |
97 |
| - description: 'Lists all files included in a specific change set.', |
98 |
| - parameters: { |
99 |
| - type: 'object', |
100 |
| - properties: { |
101 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set.' } |
102 |
| - }, |
103 |
| - required: ['uuid'] |
104 |
| - }, |
105 |
| - handler: async (args: string): Promise<string> => { |
106 |
| - try { |
107 |
| - const { uuid } = JSON.parse(args); |
108 |
| - const files = this.changeSetService.listChangedFiles(uuid); |
109 |
| - return JSON.stringify(files); |
110 |
| - } catch (error) { |
111 |
| - return JSON.stringify({ error: error.message }); |
112 |
| - } |
113 |
| - } |
114 |
| - }; |
115 |
| - } |
116 |
| -} |
117 |
| - |
118 |
| -@injectable() |
119 |
| -export class GetFileChangesProvider implements ToolProvider { |
120 |
| - static ID = 'changeSet_getFileChanges'; |
| 30 | + @inject(FileService) |
| 31 | + fileService: FileService; |
121 | 32 |
|
122 |
| - @inject(FileChangeSetService) |
123 |
| - protected readonly changeSetService: FileChangeSetService; |
| 33 | + @inject(ChangeSetFileElementFactory) |
| 34 | + protected readonly fileChangeFactory: ChangeSetFileElementFactory; |
124 | 35 |
|
125 | 36 | getTool(): ToolRequest {
|
126 | 37 | return {
|
127 |
| - id: GetFileChangesProvider.ID, |
128 |
| - name: GetFileChangesProvider.ID, |
129 |
| - description: 'Fetches the operations of a specific file in a change set.', |
| 38 | + id: WriteChangeToFileProvider.ID, |
| 39 | + name: WriteChangeToFileProvider.ID, |
| 40 | + description: `Proposes writing content to a file. If the file exists, it will be overwritten with the provided content.\n |
| 41 | + If the file does not exist, it will be created. This tool will automatically create any directories needed to write the file.\n |
| 42 | + If the new content is empty, the file will be deleted. To move a file, delete it and re-create it at the new location.\n |
| 43 | + The proposed changes will be applied when the user accepts.`, |
130 | 44 | parameters: {
|
131 | 45 | type: 'object',
|
132 | 46 | properties: {
|
133 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set.' }, |
134 |
| - filePath: { type: 'string', description: 'Path to the file.' } |
| 47 | + path: { |
| 48 | + type: 'string', |
| 49 | + description: 'The path of the file to write to.' |
| 50 | + }, |
| 51 | + content: { |
| 52 | + type: 'string', |
| 53 | + description: `The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions.\n |
| 54 | + You MUST include ALL parts of the file, even if they haven\'t been modified.` |
| 55 | + } |
135 | 56 | },
|
136 |
| - required: ['uuid', 'filePath'] |
| 57 | + required: ['path', 'content'] |
137 | 58 | },
|
138 |
| - handler: async (args: string): Promise<string> => { |
139 |
| - try { |
140 |
| - const { uuid, filePath } = JSON.parse(args); |
141 |
| - const changes = this.changeSetService.getFileChanges(uuid, filePath); |
142 |
| - return JSON.stringify(changes); |
143 |
| - } catch (error) { |
144 |
| - return JSON.stringify({ error: error.message }); |
| 59 | + handler: async (args: string, ctx: ChatRequestModelImpl): Promise<string> => { |
| 60 | + const { path, content } = JSON.parse(args); |
| 61 | + const chatSessionId = ctx.session.id; |
| 62 | + let changeSet = ctx.session.changeSet; |
| 63 | + if (!changeSet) { |
| 64 | + changeSet = new ChangeSetImpl('Changes proposed by Coder'); |
| 65 | + ctx.session.setChangeSet(changeSet); |
145 | 66 | }
|
146 |
| - } |
147 |
| - }; |
148 |
| - } |
149 |
| -} |
150 |
| - |
151 |
| -@injectable() |
152 |
| -export class GetChangeSetProvider implements ToolProvider { |
153 |
| - static ID = 'changeSet_getChangeSet'; |
154 |
| - |
155 |
| - @inject(FileChangeSetService) |
156 |
| - protected readonly changeSetService: FileChangeSetService; |
157 |
| - |
158 |
| - getTool(): ToolRequest { |
159 |
| - return { |
160 |
| - id: GetChangeSetProvider.ID, |
161 |
| - name: GetChangeSetProvider.ID, |
162 |
| - description: 'Fetches the details of a specific change set.', |
163 |
| - parameters: { |
164 |
| - type: 'object', |
165 |
| - properties: { |
166 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set.' } |
167 |
| - }, |
168 |
| - required: ['uuid'] |
169 |
| - }, |
170 |
| - handler: async (args: string): Promise<string> => { |
171 |
| - try { |
172 |
| - const { uuid } = JSON.parse(args); |
173 |
| - const changeSet = this.changeSetService.getChangeSet(uuid); |
174 |
| - return JSON.stringify(changeSet); |
175 |
| - } catch (error) { |
176 |
| - return JSON.stringify({ error: error.message }); |
| 67 | + const uri = await this.workspaceFunctionScope.resolveRelativePath(path); |
| 68 | + let type = 'modify'; |
| 69 | + if (content === '') { |
| 70 | + type = 'delete'; |
177 | 71 | }
|
178 |
| - } |
179 |
| - }; |
180 |
| - } |
181 |
| -} |
182 |
| - |
183 |
| -@injectable() |
184 |
| -export class ApplyChangeSetProvider implements ToolProvider { |
185 |
| - static ID = 'changeSet_applyChangeSet'; |
186 |
| - |
187 |
| - @inject(FileChangeSetService) |
188 |
| - protected readonly changeSetService: FileChangeSetService; |
189 |
| - |
190 |
| - getTool(): ToolRequest { |
191 |
| - return { |
192 |
| - id: ApplyChangeSetProvider.ID, |
193 |
| - name: ApplyChangeSetProvider.ID, |
194 |
| - description: 'Applies the specified change set by UUID, executing all file modifications described within.', |
195 |
| - parameters: { |
196 |
| - type: 'object', |
197 |
| - properties: { |
198 |
| - uuid: { type: 'string', description: 'Unique identifier for the change set to apply.' } |
199 |
| - }, |
200 |
| - required: ['uuid'] |
201 |
| - }, |
202 |
| - handler: async (args: string): Promise<string> => { |
| 72 | + // In case the file does not exist and the content is empty, we consider that the AI wants to add an empty file. |
203 | 73 | try {
|
204 |
| - const { uuid } = JSON.parse(args); |
205 |
| - await this.changeSetService.applyChangeSet(uuid); |
206 |
| - return `Change set ${uuid} applied successfully.`; |
| 74 | + await this.fileService.read(uri); |
207 | 75 | } catch (error) {
|
208 |
| - return JSON.stringify({ error: error.message }); |
| 76 | + type = 'add'; |
209 | 77 | }
|
| 78 | + changeSet.addElement( |
| 79 | + this.fileChangeFactory({ |
| 80 | + uri: uri, |
| 81 | + type: type as 'modify' | 'add' | 'delete', |
| 82 | + state: 'pending', |
| 83 | + targetState: content, |
| 84 | + changeSet, |
| 85 | + chatSessionId |
| 86 | + }) |
| 87 | + ); |
| 88 | + return `Proposed writing to file ${path}. The user will review and potentially apply the changes`; |
210 | 89 | }
|
211 | 90 | };
|
212 | 91 | }
|
|
0 commit comments