@theia/ai-ide 1.64.0-next.35 → 1.65.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts +5 -2
  2. package/lib/browser/ai-configuration/agent-configuration-widget.d.ts.map +1 -1
  3. package/lib/browser/ai-configuration/agent-configuration-widget.js +15 -1
  4. package/lib/browser/ai-configuration/agent-configuration-widget.js.map +1 -1
  5. package/lib/browser/ai-configuration/ai-configuration-service.d.ts +6 -1
  6. package/lib/browser/ai-configuration/ai-configuration-service.d.ts.map +1 -1
  7. package/lib/browser/ai-configuration/ai-configuration-service.js +10 -1
  8. package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -1
  9. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -0
  10. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
  11. package/lib/browser/ai-configuration/ai-configuration-widget.js +7 -1
  12. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
  13. package/lib/browser/ai-configuration/language-model-renderer.d.ts +4 -2
  14. package/lib/browser/ai-configuration/language-model-renderer.d.ts.map +1 -1
  15. package/lib/browser/ai-configuration/language-model-renderer.js +49 -71
  16. package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -1
  17. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts +41 -0
  18. package/lib/browser/ai-configuration/model-aliases-configuration-widget.d.ts.map +1 -0
  19. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js +225 -0
  20. package/lib/browser/ai-configuration/model-aliases-configuration-widget.js.map +1 -0
  21. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +7 -3
  22. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
  23. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +35 -13
  24. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
  25. package/lib/browser/ai-configuration/template-settings-renderer.d.ts.map +1 -1
  26. package/lib/browser/ai-configuration/template-settings-renderer.js +11 -6
  27. package/lib/browser/ai-configuration/template-settings-renderer.js.map +1 -1
  28. package/lib/browser/ai-ide-activation-service.d.ts +18 -0
  29. package/lib/browser/ai-ide-activation-service.d.ts.map +1 -0
  30. package/lib/browser/ai-ide-activation-service.js +72 -0
  31. package/lib/browser/ai-ide-activation-service.js.map +1 -0
  32. package/lib/browser/ai-ide-preferences.d.ts +4 -0
  33. package/lib/browser/ai-ide-preferences.d.ts.map +1 -0
  34. package/lib/browser/ai-ide-preferences.js +43 -0
  35. package/lib/browser/ai-ide-preferences.js.map +1 -0
  36. package/lib/browser/app-tester-chat-agent.js +1 -1
  37. package/lib/browser/app-tester-chat-agent.js.map +1 -1
  38. package/lib/browser/architect-agent.js +1 -1
  39. package/lib/browser/architect-agent.js.map +1 -1
  40. package/lib/browser/coder-agent.js +1 -1
  41. package/lib/browser/coder-agent.js.map +1 -1
  42. package/lib/browser/context-functions.d.ts.map +1 -1
  43. package/lib/browser/context-functions.js +12 -0
  44. package/lib/browser/context-functions.js.map +1 -1
  45. package/lib/browser/context-functions.spec.d.ts +2 -0
  46. package/lib/browser/context-functions.spec.d.ts.map +1 -0
  47. package/lib/browser/context-functions.spec.js +93 -0
  48. package/lib/browser/context-functions.spec.js.map +1 -0
  49. package/lib/browser/file-changeset-function.spec.d.ts +2 -0
  50. package/lib/browser/file-changeset-function.spec.d.ts.map +1 -0
  51. package/lib/browser/file-changeset-function.spec.js +45 -0
  52. package/lib/browser/file-changeset-function.spec.js.map +1 -0
  53. package/lib/browser/file-changeset-functions.d.ts +13 -3
  54. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  55. package/lib/browser/file-changeset-functions.js +100 -29
  56. package/lib/browser/file-changeset-functions.js.map +1 -1
  57. package/lib/browser/file-changeset-functions.spec.d.ts +2 -0
  58. package/lib/browser/file-changeset-functions.spec.d.ts.map +1 -0
  59. package/lib/browser/file-changeset-functions.spec.js +161 -0
  60. package/lib/browser/file-changeset-functions.spec.js.map +1 -0
  61. package/lib/browser/frontend-module.d.ts.map +1 -1
  62. package/lib/browser/frontend-module.js +20 -0
  63. package/lib/browser/frontend-module.js.map +1 -1
  64. package/lib/browser/ide-chat-welcome-message-provider.js +2 -2
  65. package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
  66. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts +2 -0
  67. package/lib/browser/test/tool-provider-cancellation-test-util.spec.d.ts.map +1 -0
  68. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +52 -0
  69. package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -0
  70. package/lib/browser/workspace-functions.d.ts +3 -3
  71. package/lib/browser/workspace-functions.d.ts.map +1 -1
  72. package/lib/browser/workspace-functions.js +79 -28
  73. package/lib/browser/workspace-functions.js.map +1 -1
  74. package/lib/browser/workspace-functions.spec.d.ts +2 -0
  75. package/lib/browser/workspace-functions.spec.d.ts.map +1 -0
  76. package/lib/browser/workspace-functions.spec.js +161 -0
  77. package/lib/browser/workspace-functions.spec.js.map +1 -0
  78. package/lib/browser/workspace-launch-provider.d.ts +24 -0
  79. package/lib/browser/workspace-launch-provider.d.ts.map +1 -0
  80. package/lib/browser/workspace-launch-provider.js +216 -0
  81. package/lib/browser/workspace-launch-provider.js.map +1 -0
  82. package/lib/browser/workspace-launch-provider.spec.d.ts +2 -0
  83. package/lib/browser/workspace-launch-provider.spec.d.ts.map +1 -0
  84. package/lib/browser/workspace-launch-provider.spec.js +245 -0
  85. package/lib/browser/workspace-launch-provider.spec.js.map +1 -0
  86. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  87. package/lib/browser/workspace-search-provider.js +9 -0
  88. package/lib/browser/workspace-search-provider.js.map +1 -1
  89. package/lib/browser/workspace-search-provider.spec.js +59 -203
  90. package/lib/browser/workspace-search-provider.spec.js.map +1 -1
  91. package/lib/browser/workspace-task-provider.d.ts.map +1 -1
  92. package/lib/browser/workspace-task-provider.js +8 -1
  93. package/lib/browser/workspace-task-provider.js.map +1 -1
  94. package/lib/browser/workspace-task-provider.spec.d.ts +2 -0
  95. package/lib/browser/workspace-task-provider.spec.d.ts.map +1 -0
  96. package/lib/browser/workspace-task-provider.spec.js +109 -0
  97. package/lib/browser/workspace-task-provider.spec.js.map +1 -0
  98. package/lib/common/architect-prompt-template.d.ts.map +1 -1
  99. package/lib/common/architect-prompt-template.js +11 -0
  100. package/lib/common/architect-prompt-template.js.map +1 -1
  101. package/lib/common/command-chat-agents.js +1 -1
  102. package/lib/common/command-chat-agents.js.map +1 -1
  103. package/lib/common/orchestrator-chat-agent.js +1 -1
  104. package/lib/common/orchestrator-chat-agent.js.map +1 -1
  105. package/lib/common/universal-chat-agent.js +1 -1
  106. package/lib/common/universal-chat-agent.js.map +1 -1
  107. package/lib/common/workspace-functions.d.ts +3 -0
  108. package/lib/common/workspace-functions.d.ts.map +1 -1
  109. package/lib/common/workspace-functions.js +4 -1
  110. package/lib/common/workspace-functions.js.map +1 -1
  111. package/package.json +18 -17
  112. package/src/browser/ai-configuration/agent-configuration-widget.tsx +18 -2
  113. package/src/browser/ai-configuration/ai-configuration-service.ts +14 -1
  114. package/src/browser/ai-configuration/ai-configuration-widget.tsx +7 -1
  115. package/src/browser/ai-configuration/language-model-renderer.tsx +87 -59
  116. package/src/browser/ai-configuration/model-aliases-configuration-widget.tsx +279 -0
  117. package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +43 -13
  118. package/src/browser/ai-configuration/template-settings-renderer.tsx +11 -7
  119. package/src/browser/ai-ide-activation-service.ts +65 -0
  120. package/src/browser/ai-ide-preferences.ts +44 -0
  121. package/src/browser/app-tester-chat-agent.ts +1 -1
  122. package/src/browser/architect-agent.ts +1 -1
  123. package/src/browser/coder-agent.ts +1 -1
  124. package/src/browser/context-functions.spec.ts +102 -0
  125. package/src/browser/context-functions.ts +11 -0
  126. package/src/browser/file-changeset-function.spec.ts +52 -0
  127. package/src/browser/file-changeset-functions.spec.ts +212 -0
  128. package/src/browser/file-changeset-functions.ts +102 -25
  129. package/src/browser/frontend-module.ts +29 -1
  130. package/src/browser/ide-chat-welcome-message-provider.tsx +4 -4
  131. package/src/browser/style/index.css +111 -6
  132. package/src/browser/test/tool-provider-cancellation-test-util.spec.ts +60 -0
  133. package/src/browser/workspace-functions.spec.ts +199 -0
  134. package/src/browser/workspace-functions.ts +105 -32
  135. package/src/browser/workspace-launch-provider.spec.ts +320 -0
  136. package/src/browser/workspace-launch-provider.ts +231 -0
  137. package/src/browser/workspace-search-provider.spec.ts +79 -229
  138. package/src/browser/workspace-search-provider.ts +10 -1
  139. package/src/browser/workspace-task-provider.spec.ts +125 -0
  140. package/src/browser/workspace-task-provider.ts +7 -2
  141. package/src/common/architect-prompt-template.ts +11 -0
  142. package/src/common/command-chat-agents.ts +1 -1
  143. package/src/common/orchestrator-chat-agent.ts +1 -1
  144. package/src/common/universal-chat-agent.ts +1 -1
  145. package/src/common/workspace-functions.ts +3 -0
@@ -0,0 +1,65 @@
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
+ import { inject, injectable } from '@theia/core/shared/inversify';
17
+ import { FrontendApplicationContribution, PreferenceService } from '@theia/core/lib/browser';
18
+ import { Emitter, MaybePromise, Event, } from '@theia/core';
19
+ import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
20
+ import { AIActivationService, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser/ai-activation-service';
21
+ import { PREFERENCE_NAME_ENABLE_AI } from './ai-ide-preferences';
22
+
23
+ /**
24
+ * Implements AI Activation Service based on preferences.
25
+ */
26
+ @injectable()
27
+ export class AIIdeActivationServiceImpl implements AIActivationService, FrontendApplicationContribution {
28
+ @inject(ContextKeyService)
29
+ protected readonly contextKeyService: ContextKeyService;
30
+
31
+ @inject(PreferenceService)
32
+ protected preferenceService: PreferenceService;
33
+
34
+ protected isAiEnabledKey: ContextKey<boolean>;
35
+
36
+ protected onDidChangeAIEnabled = new Emitter<boolean>();
37
+ get onDidChangeActiveStatus(): Event<boolean> {
38
+ return this.onDidChangeAIEnabled.event;
39
+ }
40
+
41
+ get isActive(): boolean {
42
+ return this.isAiEnabledKey.get() ?? false;
43
+ }
44
+
45
+ protected updateEnableValue(value: boolean): void {
46
+ if (value !== this.isAiEnabledKey.get()) {
47
+ this.isAiEnabledKey.set(value);
48
+ this.onDidChangeAIEnabled.fire(value);
49
+ }
50
+ }
51
+
52
+ initialize(): MaybePromise<void> {
53
+ this.isAiEnabledKey = this.contextKeyService.createKey(ENABLE_AI_CONTEXT_KEY, false);
54
+ // make sure we don't miss once preferences are ready
55
+ this.preferenceService.ready.then(() => {
56
+ const enableValue = this.preferenceService.get<boolean>(PREFERENCE_NAME_ENABLE_AI, false);
57
+ this.updateEnableValue(enableValue);
58
+ });
59
+ this.preferenceService.onPreferenceChanged(e => {
60
+ if (e.preferenceName === PREFERENCE_NAME_ENABLE_AI) {
61
+ this.updateEnableValue(e.newValue);
62
+ }
63
+ });
64
+ }
65
+ }
@@ -0,0 +1,44 @@
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, ENABLE_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser';
18
+ import { nls } from '@theia/core';
19
+ import { PreferenceSchema } from '@theia/core/lib/browser';
20
+
21
+ // We reuse the context key for the preference name
22
+ export const PREFERENCE_NAME_ENABLE_AI = ENABLE_AI_CONTEXT_KEY;
23
+
24
+ export const aiIdePreferenceSchema: PreferenceSchema = {
25
+ type: 'object',
26
+ properties: {
27
+ [PREFERENCE_NAME_ENABLE_AI]: {
28
+ title: AI_CORE_PREFERENCES_TITLE,
29
+ markdownDescription: nls.localize('theia/ai/ide/enableAI/mdDescription',
30
+ '❗ This setting allows you to access the latest AI capabilities (Beta version).\
31
+ \n\
32
+ Please note that these features are in a beta phase, which means they may \
33
+ undergo changes and will be further improved. It is important to be aware that these features may generate\
34
+ continuous requests to the language models (LLMs) you provide access to. This might incur costs that you\
35
+ need to monitor closely. By enabling this option, you acknowledge these risks.\
36
+ \n\
37
+ **Please note! The settings below in this section will only take effect\n\
38
+ once the main feature setting is enabled. After enabling the feature, you need to configure at least one\
39
+ LLM provider below. Also see [the documentation](https://theia-ide.org/docs/user_ai/)**.'),
40
+ type: 'boolean',
41
+ default: false,
42
+ }
43
+ }
44
+ };
@@ -40,7 +40,7 @@ export class AppTesterChatAgent extends AbstractStreamParsingChatAgent {
40
40
  name = AppTesterChatAgentId;
41
41
  languageModelRequirements: LanguageModelRequirement[] = [{
42
42
  purpose: 'chat',
43
- identifier: 'openai/gpt-4o',
43
+ identifier: 'default/code',
44
44
  }];
45
45
  protected defaultLanguageModelPurpose: string = 'chat';
46
46
  override description = nls.localize('theia/ai/chat/app-tester/description', 'This agent tests your application user interface to verify user-specified test scenarios through the Playwright MCP server. '
@@ -30,7 +30,7 @@ export class ArchitectAgent extends AbstractStreamParsingChatAgent {
30
30
  id = 'Architect';
31
31
  languageModelRequirements: LanguageModelRequirement[] = [{
32
32
  purpose: 'chat',
33
- identifier: 'openai/gpt-4o',
33
+ identifier: 'default/code',
34
34
  }];
35
35
  protected defaultLanguageModelPurpose: string = 'chat';
36
36
 
@@ -30,7 +30,7 @@ export class CoderAgent extends AbstractStreamParsingChatAgent {
30
30
  name = 'Coder';
31
31
  languageModelRequirements: LanguageModelRequirement[] = [{
32
32
  purpose: 'chat',
33
- identifier: 'openai/gpt-4o',
33
+ identifier: 'default/code',
34
34
  }];
35
35
  protected defaultLanguageModelPurpose: string = 'chat';
36
36
 
@@ -0,0 +1,102 @@
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
+ import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
17
+ let disableJSDOM = enableJSDOM();
18
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
19
+ FrontendApplicationConfigProvider.set({});
20
+ import { expect } from 'chai';
21
+ import { ListChatContext, ResolveChatContext, AddFileToChatContext } from './context-functions';
22
+ import { CancellationTokenSource } from '@theia/core';
23
+ import { ChatContextManager, MutableChatModel, MutableChatRequestModel, MutableChatResponseModel } from '@theia/ai-chat';
24
+ import { fail } from 'assert';
25
+ import { ResolvedAIContextVariable } from '@theia/ai-core';
26
+ disableJSDOM();
27
+
28
+ describe('Context Functions Cancellation Tests', () => {
29
+ let cancellationTokenSource: CancellationTokenSource;
30
+ let mockCtx: Partial<MutableChatRequestModel>;
31
+
32
+ before(() => {
33
+ disableJSDOM = enableJSDOM();
34
+ });
35
+ after(() => {
36
+ // Disable JSDOM after all tests
37
+ disableJSDOM();
38
+ });
39
+
40
+ beforeEach(() => {
41
+ cancellationTokenSource = new CancellationTokenSource();
42
+ const context: Partial<ChatContextManager> = {
43
+ addVariables: () => { },
44
+ getVariables: () => mockCtx.context?.variables as ResolvedAIContextVariable[]
45
+ };
46
+ mockCtx = {
47
+ response: {
48
+ cancellationToken: cancellationTokenSource.token
49
+ } as MutableChatResponseModel,
50
+ context: {
51
+ variables: [{
52
+ variable: { id: 'file1', name: 'File' },
53
+ arg: '/path/to/file',
54
+ contextValue: 'file content'
55
+ } as ResolvedAIContextVariable]
56
+ },
57
+ session: {
58
+ context
59
+ } as MutableChatModel
60
+ };
61
+ });
62
+
63
+ afterEach(() => {
64
+ cancellationTokenSource.dispose();
65
+ });
66
+
67
+ it('ListChatContext should respect cancellation token', async () => {
68
+ const listChatContext = new ListChatContext();
69
+ cancellationTokenSource.cancel();
70
+
71
+ const result = await listChatContext.getTool().handler('', mockCtx);
72
+ if (typeof result !== 'string') {
73
+ fail(`Wrong tool call result type: ${result}`);
74
+ }
75
+ const jsonResponse = JSON.parse(result);
76
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
77
+ });
78
+
79
+ it('ResolveChatContext should respect cancellation token', async () => {
80
+ const resolveChatContext = new ResolveChatContext();
81
+ cancellationTokenSource.cancel();
82
+
83
+ const result = await resolveChatContext.getTool().handler('{"contextElementId":"file1/path/to/file"}', mockCtx);
84
+ if (typeof result !== 'string') {
85
+ fail(`Wrong tool call result type: ${result}`);
86
+ }
87
+ const jsonResponse = JSON.parse(result);
88
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
89
+ });
90
+
91
+ it('AddFileToChatContext should respect cancellation token', async () => {
92
+ const addFileToChatContext = new AddFileToChatContext();
93
+ cancellationTokenSource.cancel();
94
+
95
+ const result = await addFileToChatContext.getTool().handler('{"filesToAdd":["/new/path/to/file"]}', mockCtx);
96
+ if (typeof result !== 'string') {
97
+ fail(`Wrong tool call result type: ${result}`);
98
+ }
99
+ const jsonResponse = JSON.parse(result);
100
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
101
+ });
102
+ });
@@ -30,6 +30,9 @@ export class ListChatContext implements ToolProvider {
30
30
  name: ListChatContext.ID,
31
31
  description: 'Returns the list of context elements (such as files) specified by the user manually as part of the chat request.',
32
32
  handler: async (_: string, ctx: MutableChatRequestModel): Promise<string> => {
33
+ if (ctx?.response?.cancellationToken?.isCancellationRequested) {
34
+ return JSON.stringify({ error: 'Operation cancelled by user' });
35
+ }
33
36
  const result = ctx.context.variables.map(contextElement => ({
34
37
  id: contextElement.variable.id + contextElement.arg,
35
38
  type: contextElement.variable.name
@@ -64,6 +67,10 @@ export class ResolveChatContext implements ToolProvider {
64
67
  required: ['contextElementId']
65
68
  },
66
69
  handler: async (args: string, ctx: MutableChatRequestModel): Promise<string> => {
70
+ if (ctx?.response?.cancellationToken?.isCancellationRequested) {
71
+ return JSON.stringify({ error: 'Operation cancelled by user' });
72
+ }
73
+
67
74
  const { contextElementId } = JSON.parse(args) as { contextElementId: string };
68
75
  const variable = ctx.context.variables.find(contextElement => contextElement.variable.id + contextElement.arg === contextElementId);
69
76
  if (variable) {
@@ -101,6 +108,10 @@ export class AddFileToChatContext implements ToolProvider {
101
108
  },
102
109
  description: 'Adds one or more files to the context of the current chat session, and returns the current list of files in the context.',
103
110
  handler: async (arg: string, ctx: MutableChatRequestModel): Promise<string> => {
111
+ if (ctx?.response?.cancellationToken?.isCancellationRequested) {
112
+ return JSON.stringify({ error: 'Operation cancelled by user' });
113
+ }
114
+
104
115
  const { filesToAdd } = JSON.parse(arg) as { filesToAdd: string[] };
105
116
 
106
117
  ctx.session.context.addVariables(...filesToAdd.map(file => ({ arg: file, variable: FILE_VARIABLE })));
@@ -0,0 +1,52 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 Lonti.com Pty Ltd.
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 { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
18
+ import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
19
+ let disableJSDOM = enableJSDOM();
20
+ FrontendApplicationConfigProvider.set({});
21
+
22
+ import { MutableChatRequestModel } from '@theia/ai-chat';
23
+ import { Container } from '@theia/core/shared/inversify';
24
+ import { expect } from 'chai';
25
+ import { DefaultFileChangeSetTitleProvider } from './file-changeset-functions';
26
+
27
+ disableJSDOM();
28
+
29
+ describe('DefaultFileChangeSetTitleProvider', () => {
30
+ let provider: DefaultFileChangeSetTitleProvider;
31
+
32
+ before(() => {
33
+ const container = new Container();
34
+ container.bind(DefaultFileChangeSetTitleProvider).toSelf();
35
+
36
+ provider = container.get(DefaultFileChangeSetTitleProvider);
37
+ disableJSDOM = enableJSDOM();
38
+ });
39
+
40
+ after(() => {
41
+ disableJSDOM();
42
+ });
43
+
44
+ it('should provide the title', () => {
45
+ const ctx = {
46
+ agentId: 'test-agent',
47
+ } as MutableChatRequestModel;
48
+
49
+ const title = provider.getChangeSetTitle(ctx);
50
+ expect(title).to.equal('Changes proposed');
51
+ });
52
+ });
@@ -0,0 +1,212 @@
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 { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
18
+ let disableJSDOM = enableJSDOM();
19
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
20
+ FrontendApplicationConfigProvider.set({});
21
+
22
+ import { expect } from 'chai';
23
+ import { CancellationTokenSource } from '@theia/core';
24
+ import {
25
+ SuggestFileContent,
26
+ WriteFileContent,
27
+ SuggestFileReplacements,
28
+ WriteFileReplacements,
29
+ ClearFileChanges,
30
+ GetProposedFileState,
31
+ ReplaceContentInFileFunctionHelper,
32
+ FileChangeSetTitleProvider,
33
+ DefaultFileChangeSetTitleProvider
34
+ } from './file-changeset-functions';
35
+ import { MutableChatRequestModel, MutableChatResponseModel, ChangeSet, ChangeSetElement, MutableChatModel } from '@theia/ai-chat';
36
+ import { Container } from '@theia/core/shared/inversify';
37
+ import { WorkspaceFunctionScope } from './workspace-functions';
38
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
39
+ import { ChangeSetFileElementFactory, ChangeSetFileElement } from '@theia/ai-chat/lib/browser/change-set-file-element';
40
+ import { URI } from '@theia/core/lib/common/uri';
41
+
42
+ disableJSDOM();
43
+
44
+ describe('File Changeset Functions Cancellation Tests', () => {
45
+ let cancellationTokenSource: CancellationTokenSource;
46
+ let mockCtx: Partial<MutableChatRequestModel>;
47
+ let container: Container;
48
+ before(() => {
49
+ disableJSDOM = enableJSDOM();
50
+ });
51
+ after(() => {
52
+ // Disable JSDOM after all tests
53
+ disableJSDOM();
54
+ });
55
+ beforeEach(() => {
56
+ cancellationTokenSource = new CancellationTokenSource();
57
+
58
+ // Create a mock change set that doesn't do anything
59
+ const mockChangeSet: Partial<ChangeSet> = {
60
+ addElements: (...elements: ChangeSetElement[]) => true,
61
+ setTitle: () => { },
62
+ removeElements: () => true,
63
+ getElementByURI: () => undefined
64
+ };
65
+
66
+ // Setup mock context
67
+ mockCtx = {
68
+ id: 'test-request-id',
69
+ response: {
70
+ cancellationToken: cancellationTokenSource.token
71
+ } as MutableChatResponseModel,
72
+ session: {
73
+ id: 'test-session-id',
74
+ changeSet: mockChangeSet as ChangeSet
75
+ } as MutableChatModel
76
+ };
77
+
78
+ // Create a new container for each test
79
+ container = new Container();
80
+
81
+ // Mock dependencies
82
+ const mockWorkspaceScope = {
83
+ resolveRelativePath: async () => new URI('file:///workspace/test.txt')
84
+ } as unknown as WorkspaceFunctionScope;
85
+
86
+ const mockFileService = {
87
+ exists: async () => true,
88
+ read: async () => ({ value: { toString: () => 'test content' } })
89
+ } as unknown as FileService;
90
+
91
+ const mockFileChangeFactory: ChangeSetFileElementFactory = () => ({
92
+ uri: new URI('file:///workspace/test.txt'),
93
+ type: 'modify',
94
+ state: 'pending',
95
+ targetState: 'new content',
96
+ apply: async () => { },
97
+ } as ChangeSetFileElement);
98
+
99
+ // Register mocks in the container
100
+ container.bind(WorkspaceFunctionScope).toConstantValue(mockWorkspaceScope);
101
+ container.bind(FileService).toConstantValue(mockFileService);
102
+ container.bind(ChangeSetFileElementFactory).toConstantValue(mockFileChangeFactory);
103
+ container.bind(FileChangeSetTitleProvider).to(DefaultFileChangeSetTitleProvider).inSingletonScope();
104
+ container.bind(ReplaceContentInFileFunctionHelper).toSelf();
105
+ container.bind(SuggestFileContent).toSelf();
106
+ container.bind(WriteFileContent).toSelf();
107
+ container.bind(SuggestFileReplacements).toSelf();
108
+ container.bind(WriteFileReplacements).toSelf();
109
+ container.bind(ClearFileChanges).toSelf();
110
+ container.bind(GetProposedFileState).toSelf();
111
+ });
112
+
113
+ afterEach(() => {
114
+ cancellationTokenSource.dispose();
115
+ });
116
+
117
+ it('SuggestFileContent should respect cancellation token', async () => {
118
+ const suggestFileContent = container.get(SuggestFileContent);
119
+ cancellationTokenSource.cancel();
120
+
121
+ const handler = suggestFileContent.getTool().handler;
122
+ const result = await handler(JSON.stringify({ path: 'test.txt', content: 'test content' }), mockCtx as MutableChatRequestModel);
123
+
124
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
125
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
126
+ });
127
+
128
+ it('WriteFileContent should respect cancellation token', async () => {
129
+ const writeFileContent = container.get(WriteFileContent);
130
+ cancellationTokenSource.cancel();
131
+
132
+ const handler = writeFileContent.getTool().handler;
133
+ const result = await handler(JSON.stringify({ path: 'test.txt', content: 'test content' }), mockCtx as MutableChatRequestModel);
134
+
135
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
136
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
137
+ });
138
+
139
+ it('SuggestFileReplacements should respect cancellation token', async () => {
140
+ const suggestFileReplacements = container.get(SuggestFileReplacements);
141
+ cancellationTokenSource.cancel();
142
+
143
+ const handler = suggestFileReplacements.getTool().handler;
144
+ const result = await handler(
145
+ JSON.stringify({
146
+ path: 'test.txt',
147
+ replacements: [{ oldContent: 'old', newContent: 'new' }]
148
+ }),
149
+ mockCtx as MutableChatRequestModel
150
+ );
151
+
152
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
153
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
154
+ });
155
+
156
+ it('WriteFileReplacements should respect cancellation token', async () => {
157
+ const writeFileReplacements = container.get(WriteFileReplacements);
158
+ cancellationTokenSource.cancel();
159
+
160
+ const handler = writeFileReplacements.getTool().handler;
161
+ const result = await handler(
162
+ JSON.stringify({
163
+ path: 'test.txt',
164
+ replacements: [{ oldContent: 'old', newContent: 'new' }]
165
+ }),
166
+ mockCtx as MutableChatRequestModel
167
+ );
168
+
169
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
170
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
171
+ });
172
+
173
+ it('ClearFileChanges should respect cancellation token', async () => {
174
+ const clearFileChanges = container.get(ClearFileChanges);
175
+ cancellationTokenSource.cancel();
176
+
177
+ const handler = clearFileChanges.getTool().handler;
178
+ const result = await handler(JSON.stringify({ path: 'test.txt' }), mockCtx as MutableChatRequestModel);
179
+
180
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
181
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
182
+ });
183
+
184
+ it('GetProposedFileState should respect cancellation token', async () => {
185
+ const getProposedFileState = container.get(GetProposedFileState);
186
+ cancellationTokenSource.cancel();
187
+
188
+ const handler = getProposedFileState.getTool().handler;
189
+ const result = await handler(JSON.stringify({ path: 'test.txt' }), mockCtx as MutableChatRequestModel);
190
+
191
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
192
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
193
+ });
194
+
195
+ it('ReplaceContentInFileFunctionHelper should handle cancellation in common processing', async () => {
196
+ const helper = container.get(ReplaceContentInFileFunctionHelper);
197
+ cancellationTokenSource.cancel();
198
+
199
+ // Test the underlying helper method through the public methods
200
+
201
+ const result = await helper.createChangesetFromToolCall(
202
+ JSON.stringify({
203
+ path: 'test.txt',
204
+ replacements: [{ oldContent: 'old', newContent: 'new' }]
205
+ }),
206
+ mockCtx as MutableChatRequestModel
207
+ );
208
+ const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
209
+ expect(jsonResponse.error).to.equal('Operation cancelled by user');
210
+
211
+ });
212
+ });