@theia/ai-ide 1.59.0-next.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +48 -0
  2. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +27 -0
  3. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -0
  4. package/lib/browser/ai-configuration/agent-configuration-widget.js +242 -0
  5. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -0
  6. package/lib/browser/ai-configuration/ai-configuration-service.d.ts +13 -0
  7. package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -0
  8. package/lib/browser/ai-configuration/ai-configuration-service.js +44 -0
  9. package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -0
  10. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts +12 -0
  11. package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts.map +1 -0
  12. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +56 -0
  13. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -0
  14. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +20 -0
  15. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -0
  16. package/lib/browser/ai-configuration/ai-configuration-widget.js +89 -0
  17. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -0
  18. package/lib/browser/ai-configuration/language-model-renderer.d.ts +11 -0
  19. package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -0
  20. package/lib/browser/ai-configuration/language-model-renderer.js +125 -0
  21. package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -0
  22. package/lib/browser/ai-configuration/template-settings-renderer.d.ts +12 -0
  23. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -0
  24. package/lib/browser/ai-configuration/template-settings-renderer.js +57 -0
  25. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -0
  26. package/lib/browser/ai-configuration/variable-configuration-widget.d.ts +19 -0
  27. package/lib/browser/ai-configuration/variable-configuration-widget.d.ts.map +1 -0
  28. package/lib/browser/ai-configuration/variable-configuration-widget.js +98 -0
  29. package/lib/browser/ai-configuration/variable-configuration-widget.js.map +1 -0
  30. package/lib/browser/architect-agent.d.ts +13 -0
  31. package/lib/browser/architect-agent.d.ts.map +1 -0
  32. package/lib/browser/architect-agent.js +47 -0
  33. package/lib/browser/architect-agent.js.map +1 -0
  34. package/lib/browser/coder-agent.d.ts +13 -0
  35. package/lib/browser/coder-agent.d.ts.map +1 -0
  36. package/lib/browser/coder-agent.js +48 -0
  37. package/lib/browser/coder-agent.js.map +1 -0
  38. package/lib/browser/content-replacer.d.ts +46 -0
  39. package/lib/browser/content-replacer.d.ts.map +1 -0
  40. package/lib/browser/content-replacer.js +115 -0
  41. package/lib/browser/content-replacer.js.map +1 -0
  42. package/lib/browser/content-replacer.spec.d.ts +2 -0
  43. package/lib/browser/content-replacer.spec.d.ts.map +1 -0
  44. package/lib/browser/content-replacer.spec.js +86 -0
  45. package/lib/browser/content-replacer.spec.js.map +1 -0
  46. package/lib/browser/context-functions.d.ts +10 -0
  47. package/lib/browser/context-functions.d.ts.map +1 -0
  48. package/lib/browser/context-functions.js +82 -0
  49. package/lib/browser/context-functions.js.map +1 -0
  50. package/lib/browser/file-changeset-functions.d.ts +21 -0
  51. package/lib/browser/file-changeset-functions.d.ts.map +1 -0
  52. package/lib/browser/file-changeset-functions.js +189 -0
  53. package/lib/browser/file-changeset-functions.js.map +1 -0
  54. package/lib/browser/frontend-module.d.ts +4 -0
  55. package/lib/browser/frontend-module.d.ts.map +1 -0
  56. package/lib/browser/frontend-module.js +87 -0
  57. package/lib/browser/frontend-module.js.map +1 -0
  58. package/lib/browser/workspace-functions.d.ts +48 -0
  59. package/lib/browser/workspace-functions.d.ts.map +1 -0
  60. package/lib/browser/workspace-functions.js +306 -0
  61. package/lib/browser/workspace-functions.js.map +1 -0
  62. package/lib/browser/workspace-preferences.d.ts +5 -0
  63. package/lib/browser/workspace-preferences.d.ts.map +1 -0
  64. package/lib/browser/workspace-preferences.js +42 -0
  65. package/lib/browser/workspace-preferences.js.map +1 -0
  66. package/lib/common/architect-prompt-template.d.ts +3 -0
  67. package/lib/common/architect-prompt-template.d.ts.map +1 -0
  68. package/lib/common/architect-prompt-template.js +29 -0
  69. package/lib/common/architect-prompt-template.js.map +1 -0
  70. package/lib/common/coder-replace-prompt-template.d.ts +5 -0
  71. package/lib/common/coder-replace-prompt-template.d.ts.map +1 -0
  72. package/lib/common/coder-replace-prompt-template.js +46 -0
  73. package/lib/common/coder-replace-prompt-template.js.map +1 -0
  74. package/lib/common/command-chat-agents.d.ts +37 -0
  75. package/lib/common/command-chat-agents.d.ts.map +1 -0
  76. package/lib/common/command-chat-agents.js +329 -0
  77. package/lib/common/command-chat-agents.js.map +1 -0
  78. package/lib/common/orchestrator-chat-agent.d.ts +24 -0
  79. package/lib/common/orchestrator-chat-agent.d.ts.map +1 -0
  80. package/lib/common/orchestrator-chat-agent.js +164 -0
  81. package/lib/common/orchestrator-chat-agent.js.map +1 -0
  82. package/lib/common/universal-chat-agent.d.ts +15 -0
  83. package/lib/common/universal-chat-agent.d.ts.map +1 -0
  84. package/lib/common/universal-chat-agent.js +107 -0
  85. package/lib/common/universal-chat-agent.js.map +1 -0
  86. package/lib/common/workspace-functions.d.ts +4 -0
  87. package/lib/common/workspace-functions.d.ts.map +1 -0
  88. package/lib/common/workspace-functions.js +22 -0
  89. package/lib/common/workspace-functions.js.map +1 -0
  90. package/lib/package.spec.d.ts +1 -0
  91. package/lib/package.spec.d.ts.map +1 -0
  92. package/lib/package.spec.js +26 -0
  93. package/lib/package.spec.js.map +1 -0
  94. package/package.json +57 -0
  95. package/src/browser/ai-configuration/agent-configuration-widget.tsx +330 -0
  96. package/src/browser/ai-configuration/ai-configuration-service.ts +43 -0
  97. package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +53 -0
  98. package/src/browser/ai-configuration/ai-configuration-widget.tsx +81 -0
  99. package/src/browser/ai-configuration/language-model-renderer.tsx +122 -0
  100. package/src/browser/ai-configuration/template-settings-renderer.tsx +128 -0
  101. package/src/browser/ai-configuration/variable-configuration-widget.tsx +108 -0
  102. package/src/browser/architect-agent.ts +41 -0
  103. package/src/browser/coder-agent.ts +42 -0
  104. package/src/browser/content-replacer.spec.ts +92 -0
  105. package/src/browser/content-replacer.ts +126 -0
  106. package/src/browser/context-functions.ts +78 -0
  107. package/src/browser/file-changeset-functions.ts +185 -0
  108. package/src/browser/frontend-module.ts +102 -0
  109. package/src/browser/style/index.css +127 -0
  110. package/src/browser/workspace-functions.ts +326 -0
  111. package/src/browser/workspace-preferences.ts +43 -0
  112. package/src/common/architect-prompt-template.ts +42 -0
  113. package/src/common/coder-replace-prompt-template.ts +49 -0
  114. package/src/common/command-chat-agents.ts +343 -0
  115. package/src/common/orchestrator-chat-agent.ts +169 -0
  116. package/src/common/universal-chat-agent.ts +102 -0
  117. package/src/common/workspace-functions.ts +18 -0
  118. package/src/package.spec.ts +28 -0
@@ -0,0 +1,92 @@
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 { expect } from 'chai';
18
+ import { ContentReplacer, Replacement } from './content-replacer';
19
+
20
+ describe('ContentReplacer', () => {
21
+ let contentReplacer: ContentReplacer;
22
+
23
+ before(() => {
24
+ contentReplacer = new ContentReplacer();
25
+ });
26
+
27
+ it('should replace content when oldContent matches exactly', () => {
28
+ const originalContent = 'Hello World!';
29
+ const replacements: Replacement[] = [
30
+ { oldContent: 'World', newContent: 'Universe' }
31
+ ];
32
+ const expectedContent = 'Hello Universe!';
33
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
34
+ expect(result.updatedContent).to.equal(expectedContent);
35
+ expect(result.errors).to.be.empty;
36
+ });
37
+
38
+ it('should replace content when oldContent matches after trimming lines', () => {
39
+ const originalContent = 'Line one\n Line two \nLine three';
40
+ const replacements: Replacement[] = [
41
+ { oldContent: 'Line two', newContent: 'Second Line' }
42
+ ];
43
+ const expectedContent = 'Line one\n Second Line \nLine three';
44
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
45
+ expect(result.updatedContent).to.equal(expectedContent);
46
+ expect(result.errors).to.be.empty;
47
+ });
48
+
49
+ it('should return an error when oldContent is not found', () => {
50
+ const originalContent = 'Sample content';
51
+ const replacements: Replacement[] = [
52
+ { oldContent: 'Nonexistent', newContent: 'Replacement' }
53
+ ];
54
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
55
+ expect(result.updatedContent).to.equal(originalContent);
56
+ expect(result.errors).to.include('Content to replace not found: "Nonexistent"');
57
+ });
58
+
59
+ it('should return an error when oldContent has multiple occurrences', () => {
60
+ const originalContent = 'Repeat Repeat Repeat';
61
+ const replacements: Replacement[] = [
62
+ { oldContent: 'Repeat', newContent: 'Once' }
63
+ ];
64
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
65
+ expect(result.updatedContent).to.equal(originalContent);
66
+ expect(result.errors).to.include('Multiple occurrences found for: "Repeat"');
67
+ });
68
+
69
+ it('should prepend newContent when oldContent is an empty string', () => {
70
+ const originalContent = 'Existing content';
71
+ const replacements: Replacement[] = [
72
+ { oldContent: '', newContent: 'Prepended content\n' }
73
+ ];
74
+ const expectedContent = 'Prepended content\nExisting content';
75
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
76
+ expect(result.updatedContent).to.equal(expectedContent);
77
+ expect(result.errors).to.be.empty;
78
+ });
79
+
80
+ it('should handle multiple replacements correctly', () => {
81
+ const originalContent = 'Foo Bar Baz';
82
+ const replacements: Replacement[] = [
83
+ { oldContent: 'Foo', newContent: 'FooModified' },
84
+ { oldContent: 'Bar', newContent: 'BarModified' },
85
+ { oldContent: 'Baz', newContent: 'BazModified' }
86
+ ];
87
+ const expectedContent = 'FooModified BarModified BazModified';
88
+ const result = contentReplacer.applyReplacements(originalContent, replacements);
89
+ expect(result.updatedContent).to.equal(expectedContent);
90
+ expect(result.errors).to.be.empty;
91
+ });
92
+ });
@@ -0,0 +1,126 @@
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 interface Replacement {
18
+ oldContent: string;
19
+ newContent: string;
20
+ }
21
+
22
+ export class ContentReplacer {
23
+ /**
24
+ * Applies a list of replacements to the original content using a multi-step matching strategy.
25
+ * @param originalContent The original file content.
26
+ * @param replacements Array of Replacement objects.
27
+ * @returns An object containing the updated content and any error messages.
28
+ */
29
+ applyReplacements(originalContent: string, replacements: Replacement[]): { updatedContent: string, errors: string[] } {
30
+ let updatedContent = originalContent;
31
+ const errorMessages: string[] = [];
32
+
33
+ replacements.forEach(({ oldContent, newContent }) => {
34
+ // If the old content is empty, prepend the new content to the beginning of the file (e.g. in new file)
35
+ if (oldContent === '') {
36
+ updatedContent = newContent + updatedContent;
37
+ return;
38
+ }
39
+
40
+ let matchIndices = this.findExactMatches(updatedContent, oldContent);
41
+
42
+ if (matchIndices.length === 0) {
43
+ matchIndices = this.findLineTrimmedMatches(updatedContent, oldContent);
44
+ }
45
+
46
+ if (matchIndices.length === 0) {
47
+ errorMessages.push(`Content to replace not found: "${oldContent}"`);
48
+ } else if (matchIndices.length > 1) {
49
+ errorMessages.push(`Multiple occurrences found for: "${oldContent}"`);
50
+ } else {
51
+ updatedContent = this.replaceContentOnce(updatedContent, oldContent, newContent);
52
+ }
53
+ });
54
+
55
+ return { updatedContent, errors: errorMessages };
56
+ }
57
+
58
+ /**
59
+ * Finds all exact matches of a substring within a string.
60
+ * @param content The content to search within.
61
+ * @param search The substring to search for.
62
+ * @returns An array of starting indices where the exact substring is found.
63
+ */
64
+ private findExactMatches(content: string, search: string): number[] {
65
+ const indices: number[] = [];
66
+ let startIndex = 0;
67
+
68
+ while ((startIndex = content.indexOf(search, startIndex)) !== -1) {
69
+ indices.push(startIndex);
70
+ startIndex += search.length;
71
+ }
72
+
73
+ return indices;
74
+ }
75
+
76
+ /**
77
+ * Attempts to find matches by trimming whitespace from lines in the original content and the search string.
78
+ * @param content The original content.
79
+ * @param search The substring to search for, potentially with varying whitespace.
80
+ * @returns An array of starting indices where a trimmed match is found.
81
+ */
82
+ private findLineTrimmedMatches(content: string, search: string): number[] {
83
+ const trimmedSearch = search.trim();
84
+ const lines = content.split('\n');
85
+
86
+ for (let i = 0; i < lines.length; i++) {
87
+ const trimmedLine = lines[i].trim();
88
+ if (trimmedLine === trimmedSearch) {
89
+ // Calculate the starting index of this line in the original content
90
+ const startIndex = this.getLineStartIndex(content, i);
91
+ return [startIndex];
92
+ }
93
+ }
94
+
95
+ return [];
96
+ }
97
+
98
+ /**
99
+ * Calculates the starting index of a specific line number in the content.
100
+ * @param content The original content.
101
+ * @param lineNumber The zero-based line number.
102
+ * @returns The starting index of the specified line.
103
+ */
104
+ private getLineStartIndex(content: string, lineNumber: number): number {
105
+ const lines = content.split('\n');
106
+ let index = 0;
107
+ for (let i = 0; i < lineNumber; i++) {
108
+ index += lines[i].length + 1; // +1 for the newline character
109
+ }
110
+ return index;
111
+ }
112
+
113
+ /**
114
+ * Replaces the first occurrence of oldContent with newContent in the content.
115
+ * @param content The original content.
116
+ * @param oldContent The content to be replaced.
117
+ * @param newContent The content to replace with.
118
+ * @returns The content after replacement.
119
+ */
120
+ private replaceContentOnce(content: string, oldContent: string, newContent: string): string {
121
+ const index = content.indexOf(oldContent);
122
+ if (index === -1) { return content; }
123
+ return content.substring(0, index) + newContent + content.substring(index + oldContent.length);
124
+ }
125
+
126
+ }
@@ -0,0 +1,78 @@
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 { MutableChatRequestModel } from '@theia/ai-chat';
18
+ import { ToolProvider, ToolRequest } from '@theia/ai-core';
19
+ import { injectable } from '@theia/core/shared/inversify';
20
+
21
+ @injectable()
22
+ export class ListChatContext implements ToolProvider {
23
+ static ID = 'context_ListChatContext';
24
+
25
+ getTool(): ToolRequest {
26
+ return {
27
+ id: ListChatContext.ID,
28
+ name: ListChatContext.ID,
29
+ description: 'Returns the list of context elements (such as files) specified by the user manually as part of the chat request.',
30
+ handler: async (_: string, ctx: MutableChatRequestModel): Promise<string> => {
31
+ const result = ctx.context.variables.map(contextElement => ({
32
+ id: contextElement.variable.id + contextElement.arg,
33
+ type: contextElement.variable.name
34
+ }));
35
+ return JSON.stringify(result, undefined, 2);
36
+ }
37
+ };
38
+ }
39
+ }
40
+
41
+ @injectable()
42
+ export class ResolveChatContext implements ToolProvider {
43
+ static ID = 'context_ResolveChatContext';
44
+
45
+ getTool(): ToolRequest {
46
+ return {
47
+ id: ResolveChatContext.ID,
48
+ name: ResolveChatContext.ID,
49
+ description: 'Returns the content of a specific context element (such as files) specified by the user manually as part of the chat request.',
50
+ parameters: {
51
+ type: 'object',
52
+ properties: {
53
+ contextElementId: {
54
+ type: 'string',
55
+ description: 'The id of the context element to resolve.'
56
+ }
57
+ },
58
+ required: ['contextElementId']
59
+ },
60
+ handler: async (args: string, ctx: MutableChatRequestModel): Promise<string> => {
61
+ const { contextElementId } = JSON.parse(args) as { contextElementId: string };
62
+ const variable = ctx.context.variables.find(contextElement => contextElement.variable.id + contextElement.arg === contextElementId);
63
+ if (variable) {
64
+ const result = {
65
+ id: variable.variable.id + variable.arg,
66
+ type: variable.variable.name,
67
+ description: variable.variable.description,
68
+ value: variable.value,
69
+ content: variable.contextValue
70
+ };
71
+ return JSON.stringify(result, undefined, 2);
72
+ }
73
+ return JSON.stringify({ error: 'Context element not found' }, undefined, 2);
74
+ }
75
+ };
76
+ }
77
+ }
78
+
@@ -0,0 +1,185 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 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
+ import { injectable, inject } from '@theia/core/shared/inversify';
17
+ import { ToolProvider, ToolRequest } from '@theia/ai-core';
18
+ import { WorkspaceFunctionScope } from './workspace-functions';
19
+ import { ChangeSetFileElementFactory } from '@theia/ai-chat/lib/browser/change-set-file-element';
20
+ import { ChangeSetImpl, MutableChatRequestModel } from '@theia/ai-chat';
21
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
22
+ import { ContentReplacer, Replacement } from './content-replacer';
23
+
24
+ @injectable()
25
+ export class WriteChangeToFileProvider implements ToolProvider {
26
+ static ID = 'changeSet_writeChangeToFile';
27
+
28
+ @inject(WorkspaceFunctionScope)
29
+ protected readonly workspaceFunctionScope: WorkspaceFunctionScope;
30
+
31
+ @inject(FileService)
32
+ fileService: FileService;
33
+
34
+ @inject(ChangeSetFileElementFactory)
35
+ protected readonly fileChangeFactory: ChangeSetFileElementFactory;
36
+
37
+ getTool(): ToolRequest {
38
+ return {
39
+ id: WriteChangeToFileProvider.ID,
40
+ name: WriteChangeToFileProvider.ID,
41
+ description: `Proposes writing content to a file. If the file exists, it will be overwritten with the provided content.\n
42
+ If the file does not exist, it will be created. This tool will automatically create any directories needed to write the file.\n
43
+ 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
44
+ The proposed changes will be applied when the user accepts. If called again for the same file, previously proposed changes will be overridden.`,
45
+ parameters: {
46
+ type: 'object',
47
+ properties: {
48
+ path: {
49
+ type: 'string',
50
+ description: 'The path of the file to write to.'
51
+ },
52
+ content: {
53
+ type: 'string',
54
+ description: `The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions.\n
55
+ You MUST include ALL parts of the file, even if they haven\'t been modified.`
56
+ }
57
+ },
58
+ required: ['path', 'content']
59
+ },
60
+ handler: async (args: string, ctx: MutableChatRequestModel): Promise<string> => {
61
+ const { path, content } = JSON.parse(args);
62
+ const chatSessionId = ctx.session.id;
63
+ let changeSet = ctx.session.changeSet;
64
+ if (!changeSet) {
65
+ changeSet = new ChangeSetImpl('Changes proposed by Coder');
66
+ ctx.session.setChangeSet(changeSet);
67
+ }
68
+ const uri = await this.workspaceFunctionScope.resolveRelativePath(path);
69
+ let type = 'modify';
70
+ if (content === '') {
71
+ type = 'delete';
72
+ }
73
+ if (!await this.fileService.exists(uri)) {
74
+ type = 'add';
75
+ }
76
+ changeSet.addElements(
77
+ this.fileChangeFactory({
78
+ uri: uri,
79
+ type: type as 'modify' | 'add' | 'delete',
80
+ state: 'pending',
81
+ targetState: content,
82
+ changeSet,
83
+ chatSessionId
84
+ })
85
+ );
86
+ return `Proposed writing to file ${path}. The user will review and potentially apply the changes`;
87
+ }
88
+ };
89
+ }
90
+ }
91
+
92
+ @injectable()
93
+ export class ReplaceContentInFileProvider implements ToolProvider {
94
+ static ID = 'changeSet_replaceContentInFile';
95
+
96
+ @inject(WorkspaceFunctionScope)
97
+ protected readonly workspaceFunctionScope: WorkspaceFunctionScope;
98
+
99
+ @inject(FileService)
100
+ fileService: FileService;
101
+
102
+ @inject(ChangeSetFileElementFactory)
103
+ protected readonly fileChangeFactory: ChangeSetFileElementFactory;
104
+
105
+ private replacer: ContentReplacer;
106
+
107
+ constructor() {
108
+ this.replacer = new ContentReplacer();
109
+ }
110
+
111
+ getTool(): ToolRequest {
112
+ return {
113
+ id: ReplaceContentInFileProvider.ID,
114
+ name: ReplaceContentInFileProvider.ID,
115
+ description: `Request to replace sections of content in an existing file by providing a list of tuples with old content to be matched and replaced.
116
+ Only the first matched instance of each old content in the tuples will be replaced. For deletions, use an empty new content in the tuple.\n
117
+ Make sure you use the same line endings and whitespace as in the original file content. The proposed changes will be applied when the user accepts.\n
118
+ If called again for the same file, it will override previously proposed changes.`,
119
+ parameters: {
120
+ type: 'object',
121
+ properties: {
122
+ path: {
123
+ type: 'string',
124
+ description: 'The path of the file where content will be replaced.'
125
+ },
126
+ replacements: {
127
+ type: 'array',
128
+ items: {
129
+ type: 'object',
130
+ properties: {
131
+ oldContent: {
132
+ type: 'string',
133
+ description: 'The exact content to be replaced. Must match exactly, including whitespace, comments, etc.'
134
+ },
135
+ newContent: {
136
+ type: 'string',
137
+ description: 'The new content to insert in place of matched old content.'
138
+ }
139
+ },
140
+ required: ['oldContent', 'newContent']
141
+ },
142
+ description: 'An array of replacement objects, each containing oldContent and newContent strings.'
143
+ }
144
+ },
145
+ required: ['path', 'replacements']
146
+ },
147
+ handler: async (args: string, ctx: MutableChatRequestModel): Promise<string> => {
148
+ try {
149
+ const { path, replacements } = JSON.parse(args) as { path: string, replacements: Replacement[] };
150
+ const fileUri = await this.workspaceFunctionScope.resolveRelativePath(path);
151
+ const fileContent = (await this.fileService.read(fileUri)).value.toString();
152
+
153
+ const { updatedContent, errors } = this.replacer.applyReplacements(fileContent, replacements);
154
+
155
+ if (errors.length > 0) {
156
+ return `Errors encountered: ${errors.join('; ')}`;
157
+ }
158
+
159
+ if (updatedContent !== fileContent) {
160
+ let changeSet = ctx.session.changeSet;
161
+ if (!changeSet) {
162
+ changeSet = new ChangeSetImpl('Changes proposed by Coder');
163
+ ctx.session.setChangeSet(changeSet);
164
+ }
165
+
166
+ changeSet.addElements(
167
+ this.fileChangeFactory({
168
+ uri: fileUri,
169
+ type: 'modify',
170
+ state: 'pending',
171
+ targetState: updatedContent,
172
+ changeSet,
173
+ chatSessionId: ctx.session.id
174
+ })
175
+ );
176
+ }
177
+ return `Proposed replacements in file ${path}. The user will review and potentially apply the changes.`;
178
+ } catch (error) {
179
+ console.info('Error processing replacements:', error.message);
180
+ return JSON.stringify({ error: error.message });
181
+ }
182
+ }
183
+ };
184
+ }
185
+ }
@@ -0,0 +1,102 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 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 { ContainerModule } from '@theia/core/shared/inversify';
18
+ import { ChatAgent, DefaultChatAgentId, FallbackChatAgentId } from '@theia/ai-chat/lib/common';
19
+ import { Agent, ToolProvider } from '@theia/ai-core/lib/common';
20
+ import { ArchitectAgent } from './architect-agent';
21
+ import { CoderAgent } from './coder-agent';
22
+ import { FileContentFunction, GetWorkspaceDirectoryStructure, GetWorkspaceFileList, WorkspaceFunctionScope } from './workspace-functions';
23
+ import { PreferenceContribution, WidgetFactory, bindViewContribution } from '@theia/core/lib/browser';
24
+ import { WorkspacePreferencesSchema } from './workspace-preferences';
25
+ import {
26
+ ReplaceContentInFileProvider,
27
+ WriteChangeToFileProvider
28
+ } from './file-changeset-functions';
29
+ import { OrchestratorChatAgent, OrchestratorChatAgentId } from '../common/orchestrator-chat-agent';
30
+ import { UniversalChatAgent, UniversalChatAgentId } from '../common/universal-chat-agent';
31
+ import { CommandChatAgent } from '../common/command-chat-agents';
32
+ import { ListChatContext, ResolveChatContext } from './context-functions';
33
+ import { AIAgentConfigurationWidget } from './ai-configuration/agent-configuration-widget';
34
+ import { AIConfigurationSelectionService } from './ai-configuration/ai-configuration-service';
35
+ import { AIAgentConfigurationViewContribution } from './ai-configuration/ai-configuration-view-contribution';
36
+ import { AIConfigurationContainerWidget } from './ai-configuration/ai-configuration-widget';
37
+ import { AIVariableConfigurationWidget } from './ai-configuration/variable-configuration-widget';
38
+
39
+ export default new ContainerModule(bind => {
40
+ bind(PreferenceContribution).toConstantValue({ schema: WorkspacePreferencesSchema });
41
+
42
+ bind(ArchitectAgent).toSelf().inSingletonScope();
43
+ bind(Agent).toService(ArchitectAgent);
44
+ bind(ChatAgent).toService(ArchitectAgent);
45
+
46
+ bind(CoderAgent).toSelf().inSingletonScope();
47
+ bind(Agent).toService(CoderAgent);
48
+ bind(ChatAgent).toService(CoderAgent);
49
+
50
+ bind(OrchestratorChatAgent).toSelf().inSingletonScope();
51
+ bind(Agent).toService(OrchestratorChatAgent);
52
+ bind(ChatAgent).toService(OrchestratorChatAgent);
53
+
54
+ bind(UniversalChatAgent).toSelf().inSingletonScope();
55
+ bind(Agent).toService(UniversalChatAgent);
56
+ bind(ChatAgent).toService(UniversalChatAgent);
57
+
58
+ bind(CommandChatAgent).toSelf().inSingletonScope();
59
+ bind(Agent).toService(CommandChatAgent);
60
+ bind(ChatAgent).toService(CommandChatAgent);
61
+
62
+ bind(DefaultChatAgentId).toConstantValue({ id: OrchestratorChatAgentId });
63
+ bind(FallbackChatAgentId).toConstantValue({ id: UniversalChatAgentId });
64
+
65
+ bind(ToolProvider).to(GetWorkspaceFileList);
66
+ bind(ToolProvider).to(FileContentFunction);
67
+ bind(ToolProvider).to(GetWorkspaceDirectoryStructure);
68
+ bind(WorkspaceFunctionScope).toSelf().inSingletonScope();
69
+
70
+ bind(ToolProvider).to(WriteChangeToFileProvider);
71
+ bind(ToolProvider).to(ReplaceContentInFileProvider);
72
+
73
+ bind(ToolProvider).to(ListChatContext);
74
+ bind(ToolProvider).to(ResolveChatContext);
75
+ bind(AIConfigurationSelectionService).toSelf().inSingletonScope();
76
+ bind(AIConfigurationContainerWidget).toSelf();
77
+ bind(WidgetFactory)
78
+ .toDynamicValue(ctx => ({
79
+ id: AIConfigurationContainerWidget.ID,
80
+ createWidget: () => ctx.container.get(AIConfigurationContainerWidget)
81
+ }))
82
+ .inSingletonScope();
83
+
84
+ bindViewContribution(bind, AIAgentConfigurationViewContribution);
85
+
86
+ bind(AIVariableConfigurationWidget).toSelf();
87
+ bind(WidgetFactory)
88
+ .toDynamicValue(ctx => ({
89
+ id: AIVariableConfigurationWidget.ID,
90
+ createWidget: () => ctx.container.get(AIVariableConfigurationWidget)
91
+ }))
92
+ .inSingletonScope();
93
+
94
+ bind(AIAgentConfigurationWidget).toSelf();
95
+ bind(WidgetFactory)
96
+ .toDynamicValue(ctx => ({
97
+ id: AIAgentConfigurationWidget.ID,
98
+ createWidget: () => ctx.container.get(AIAgentConfigurationWidget)
99
+ }))
100
+ .inSingletonScope();
101
+
102
+ });