@theia/ai-ide 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.
Files changed (102) hide show
  1. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +1 -1
  2. package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -1
  3. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +2 -1
  4. package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
  5. package/lib/browser/ai-configuration/ai-configuration-widget.js +6 -1
  6. package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
  7. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts +0 -1
  8. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.d.ts.map +1 -1
  9. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js +0 -1
  10. package/lib/browser/ai-configuration/prompt-fragments-configuration-widget.js.map +1 -1
  11. package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts +1 -0
  12. package/lib/browser/ai-configuration/token-usage-configuration-widget.d.ts.map +1 -1
  13. package/lib/browser/ai-configuration/token-usage-configuration-widget.js +25 -3
  14. package/lib/browser/ai-configuration/token-usage-configuration-widget.js.map +1 -1
  15. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts +28 -0
  16. package/lib/browser/ai-configuration/tools-configuration-widget.d.ts.map +1 -0
  17. package/lib/browser/ai-configuration/tools-configuration-widget.js +148 -0
  18. package/lib/browser/ai-configuration/tools-configuration-widget.js.map +1 -0
  19. package/lib/browser/app-tester-chat-agent.d.ts +36 -0
  20. package/lib/browser/app-tester-chat-agent.d.ts.map +1 -0
  21. package/lib/browser/app-tester-chat-agent.js +172 -0
  22. package/lib/browser/app-tester-chat-agent.js.map +1 -0
  23. package/lib/browser/architect-agent.d.ts.map +1 -1
  24. package/lib/browser/architect-agent.js +4 -1
  25. package/lib/browser/architect-agent.js.map +1 -1
  26. package/lib/browser/coder-agent.d.ts.map +1 -1
  27. package/lib/browser/coder-agent.js +3 -3
  28. package/lib/browser/coder-agent.js.map +1 -1
  29. package/lib/browser/file-changeset-functions.d.ts +25 -6
  30. package/lib/browser/file-changeset-functions.d.ts.map +1 -1
  31. package/lib/browser/file-changeset-functions.js +248 -106
  32. package/lib/browser/file-changeset-functions.js.map +1 -1
  33. package/lib/browser/frontend-module.d.ts +1 -0
  34. package/lib/browser/frontend-module.d.ts.map +1 -1
  35. package/lib/browser/frontend-module.js +21 -5
  36. package/lib/browser/frontend-module.js.map +1 -1
  37. package/lib/browser/summarize-session-command-contribution.d.ts +6 -1
  38. package/lib/browser/summarize-session-command-contribution.d.ts.map +1 -1
  39. package/lib/browser/summarize-session-command-contribution.js +53 -4
  40. package/lib/browser/summarize-session-command-contribution.js.map +1 -1
  41. package/lib/browser/task-context-file-storage-service.d.ts +4 -2
  42. package/lib/browser/task-context-file-storage-service.d.ts.map +1 -1
  43. package/lib/browser/task-context-file-storage-service.js +19 -9
  44. package/lib/browser/task-context-file-storage-service.js.map +1 -1
  45. package/lib/browser/workspace-functions.d.ts.map +1 -1
  46. package/lib/browser/workspace-functions.js +6 -10
  47. package/lib/browser/workspace-functions.js.map +1 -1
  48. package/lib/browser/workspace-preferences.d.ts +1 -0
  49. package/lib/browser/workspace-preferences.d.ts.map +1 -1
  50. package/lib/browser/workspace-preferences.js +9 -1
  51. package/lib/browser/workspace-preferences.js.map +1 -1
  52. package/lib/browser/workspace-search-provider.d.ts +7 -1
  53. package/lib/browser/workspace-search-provider.d.ts.map +1 -1
  54. package/lib/browser/workspace-search-provider.js +73 -11
  55. package/lib/browser/workspace-search-provider.js.map +1 -1
  56. package/lib/browser/workspace-search-provider.spec.d.ts +2 -0
  57. package/lib/browser/workspace-search-provider.spec.d.ts.map +1 -0
  58. package/lib/browser/workspace-search-provider.spec.js +227 -0
  59. package/lib/browser/workspace-search-provider.spec.js.map +1 -0
  60. package/lib/common/architect-prompt-template.d.ts +1 -0
  61. package/lib/common/architect-prompt-template.d.ts.map +1 -1
  62. package/lib/common/architect-prompt-template.js +166 -5
  63. package/lib/common/architect-prompt-template.js.map +1 -1
  64. package/lib/common/coder-replace-prompt-template.d.ts +4 -5
  65. package/lib/common/coder-replace-prompt-template.d.ts.map +1 -1
  66. package/lib/common/coder-replace-prompt-template.js +95 -67
  67. package/lib/common/coder-replace-prompt-template.js.map +1 -1
  68. package/lib/common/file-changeset-function-ids.d.ts +7 -0
  69. package/lib/common/file-changeset-function-ids.d.ts.map +1 -0
  70. package/lib/common/file-changeset-function-ids.js +25 -0
  71. package/lib/common/file-changeset-function-ids.js.map +1 -0
  72. package/lib/common/summarize-session-commands.d.ts +1 -0
  73. package/lib/common/summarize-session-commands.d.ts.map +1 -1
  74. package/lib/common/summarize-session-commands.js +5 -1
  75. package/lib/common/summarize-session-commands.js.map +1 -1
  76. package/lib/common/workspace-search-provider-util.d.ts +17 -0
  77. package/lib/common/workspace-search-provider-util.d.ts.map +1 -0
  78. package/lib/common/workspace-search-provider-util.js +51 -0
  79. package/lib/common/workspace-search-provider-util.js.map +1 -0
  80. package/package.json +19 -18
  81. package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +1 -1
  82. package/src/browser/ai-configuration/ai-configuration-widget.tsx +6 -1
  83. package/src/browser/ai-configuration/prompt-fragments-configuration-widget.tsx +0 -1
  84. package/src/browser/ai-configuration/token-usage-configuration-widget.tsx +63 -4
  85. package/src/browser/ai-configuration/tools-configuration-widget.tsx +178 -0
  86. package/src/browser/app-tester-chat-agent.ts +178 -0
  87. package/src/browser/architect-agent.ts +5 -2
  88. package/src/browser/coder-agent.ts +5 -5
  89. package/src/browser/file-changeset-functions.ts +236 -89
  90. package/src/browser/frontend-module.ts +33 -10
  91. package/src/browser/style/index.css +84 -11
  92. package/src/browser/summarize-session-command-contribution.ts +58 -6
  93. package/src/browser/task-context-file-storage-service.ts +20 -10
  94. package/src/browser/workspace-functions.ts +7 -11
  95. package/src/browser/workspace-preferences.ts +9 -0
  96. package/src/browser/workspace-search-provider.spec.ts +255 -0
  97. package/src/browser/workspace-search-provider.ts +78 -11
  98. package/src/common/architect-prompt-template.ts +165 -5
  99. package/src/common/coder-replace-prompt-template.ts +101 -65
  100. package/src/common/file-changeset-function-ids.ts +22 -0
  101. package/src/common/summarize-session-commands.ts +5 -0
  102. package/src/common/workspace-search-provider-util.ts +50 -0
@@ -2,6 +2,14 @@
2
2
  padding: var(--theia-ui-padding);
3
3
  }
4
4
 
5
+ .ai-configuration-widget [role="tablist"] {
6
+ overflow: scroll;
7
+ }
8
+
9
+ .ai-configuration-widget [role="tablist"]::-webkit-scrollbar {
10
+ height: 4px;
11
+ }
12
+
5
13
  .theia-ai-settings-container {
6
14
  padding: var(--theia-ui-padding);
7
15
  }
@@ -90,8 +98,15 @@
90
98
  flex-direction: row;
91
99
  }
92
100
 
93
- .configuration-agents-list {
94
- width: 128px;
101
+ #ai-agent-configuration-container-widget
102
+ .ai-agent-configuration-main
103
+ .configuration-agents-list {
104
+ min-width: 160px;
105
+ overflow: hidden;
106
+ white-space: nowrap;
107
+ text-overflow: ellipsis;
108
+ padding-left: 0;
109
+ padding: var(--theia-ui-padding);
95
110
  }
96
111
 
97
112
  .configuration-agent-panel {
@@ -128,9 +143,12 @@
128
143
  }
129
144
 
130
145
  /* Prompt Fragments Configuration Styles */
146
+ .configuration-variables-list,
147
+ .token-usage-configuration-container,
148
+ .ai-tools-configuration-container,
131
149
  .ai-prompt-fragments-configuration {
132
150
  padding: var(--theia-ui-padding);
133
- max-width: 1200px;
151
+ max-width: 600px;
134
152
  margin: 0 auto;
135
153
  }
136
154
 
@@ -422,7 +440,8 @@
422
440
  gap: 8px;
423
441
  }
424
442
 
425
- .prompt-fragment-title h2, h4 {
443
+ .prompt-fragment-title h2,
444
+ h4 {
426
445
  margin-right: 8px;
427
446
  margin: 0;
428
447
  }
@@ -448,10 +467,6 @@
448
467
  }
449
468
 
450
469
  /* MCP Configuration Styles */
451
- .mcp-configuration-container {
452
- padding: 16px;
453
- }
454
-
455
470
  .mcp-configuration-title {
456
471
  margin: 0 0 16px 0;
457
472
  border-bottom: 1px solid var(--theia-panelTitle-activeBorder);
@@ -669,11 +684,69 @@
669
684
  font-style: normal;
670
685
  }
671
686
 
672
- /* Token Usage Configuration Styles */
673
- .token-usage-configuration-container {
674
- padding: 16px;
687
+ /*
688
+ * AI Tools Configuration Widget Styles
689
+ * Only touch styles in this section for the tools configuration widget
690
+ */
691
+ .ai-tools-configuration-default-section {
692
+ margin-bottom: 24px;
693
+ }
694
+
695
+ .ai-tools-configuration-default-label {
696
+ font-weight: bold;
697
+ margin-bottom: 4px;
698
+ }
699
+
700
+ .ai-tools-configuration-default-select {
701
+ font-size: 14px;
702
+ padding: 4px;
703
+ }
704
+
705
+ .ai-tools-configuration-tools-label {
706
+ font-weight: bold;
707
+ margin-bottom: 8px;
708
+ }
709
+
710
+ .ai-tools-configuration-tools-list {
711
+ list-style: none;
712
+ padding: 0;
713
+ margin: 0;
714
+ }
715
+
716
+ .ai-tools-configuration-tool-item {
717
+ display: flex;
718
+ align-items: center;
719
+ margin-bottom: 12px;
720
+ border-radius: 4px;
721
+ padding: 4px;
722
+ transition: background 0.2s, opacity 0.2s;
675
723
  }
676
724
 
725
+ .ai-tools-configuration-tool-item.default {
726
+ opacity: 0.5;
727
+ background: none;
728
+ }
729
+
730
+ .ai-tools-configuration-tool-item.custom {
731
+ opacity: 1;
732
+ background: var(--theia-editor-inactiveSelectionBackground);
733
+ }
734
+
735
+ .ai-tools-configuration-tool-name {
736
+ flex: 1;
737
+ }
738
+
739
+ .ai-tools-configuration-tool-select {
740
+ margin-right: 8px;
741
+ }
742
+
743
+ .ai-tools-configuration-tool-icon {
744
+ font-size: 18px;
745
+ }
746
+
747
+ /* End AI Tools Configuration Widget Styles */
748
+
749
+ /* Token Usage Configuration Styles */
677
750
  .token-usage-configuration-title {
678
751
  margin: 0 0 16px 0;
679
752
  border-bottom: 1px solid var(--theia-widget-border);
@@ -16,12 +16,16 @@
16
16
 
17
17
  import { ChatAgentLocation, ChatService } from '@theia/ai-chat/lib/common';
18
18
  import { CommandContribution, CommandRegistry, CommandService } from '@theia/core';
19
+ import { TaskContextStorageService, TaskContextService } from '@theia/ai-chat/lib/browser/task-context-service';
19
20
  import { injectable, inject } from '@theia/core/shared/inversify';
20
- import { AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER } from '../common/summarize-session-commands';
21
- import { TaskContextService } from '@theia/ai-chat/lib/browser/task-context-service';
21
+ import { AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER, AI_UPDATE_TASK_CONTEXT_COMMAND } from '../common/summarize-session-commands';
22
22
  import { CoderAgent } from './coder-agent';
23
23
  import { TASK_CONTEXT_VARIABLE } from '@theia/ai-chat/lib/browser/task-context-variable';
24
- import { ARCHITECT_TASK_SUMMARY_PROMPT_TEMPLATE_ID } from '../common/architect-prompt-template';
24
+ import { ARCHITECT_TASK_SUMMARY_PROMPT_TEMPLATE_ID, ARCHITECT_TASK_SUMMARY_UPDATE_PROMPT_TEMPLATE_ID } from '../common/architect-prompt-template';
25
+ import { FILE_VARIABLE } from '@theia/ai-core/lib/browser/file-variable-contribution';
26
+ import { AIVariableResolutionRequest } from '@theia/ai-core';
27
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
28
+ import { WorkspaceService } from '@theia/workspace/lib/browser';
25
29
 
26
30
  @injectable()
27
31
  export class SummarizeSessionCommandContribution implements CommandContribution {
@@ -37,7 +41,35 @@ export class SummarizeSessionCommandContribution implements CommandContribution
37
41
  @inject(CoderAgent)
38
42
  protected readonly coderAgent: CoderAgent;
39
43
 
44
+ @inject(TaskContextStorageService)
45
+ protected readonly taskContextStorageService: TaskContextStorageService;
46
+
47
+ @inject(FileService)
48
+ protected readonly fileService: FileService;
49
+
50
+ @inject(WorkspaceService)
51
+ protected readonly wsService: WorkspaceService;
52
+
40
53
  registerCommands(registry: CommandRegistry): void {
54
+ registry.registerCommand(AI_UPDATE_TASK_CONTEXT_COMMAND, {
55
+ execute: async () => {
56
+ const activeSession = this.chatService.getActiveSession();
57
+
58
+ if (!activeSession) {
59
+ return;
60
+ }
61
+
62
+ // Check if there is an existing summary for this session
63
+ if (!this.taskContextService.hasSummary(activeSession)) {
64
+ // If no summary exists, create one first
65
+ await this.taskContextService.summarize(activeSession, ARCHITECT_TASK_SUMMARY_PROMPT_TEMPLATE_ID);
66
+ } else {
67
+ // Update existing summary
68
+ await this.taskContextService.update(activeSession, ARCHITECT_TASK_SUMMARY_UPDATE_PROMPT_TEMPLATE_ID);
69
+ }
70
+ }
71
+ });
72
+
41
73
  registry.registerCommand(AI_SUMMARIZE_SESSION_AS_TASK_FOR_CODER, {
42
74
  execute: async () => {
43
75
  const activeSession = this.chatService.getActiveSession();
@@ -48,9 +80,29 @@ export class SummarizeSessionCommandContribution implements CommandContribution
48
80
 
49
81
  const summaryId = await this.taskContextService.summarize(activeSession, ARCHITECT_TASK_SUMMARY_PROMPT_TEMPLATE_ID);
50
82
 
51
- const newSession = this.chatService.createSession(ChatAgentLocation.Panel, { focus: true }, this.coderAgent);
52
- const summaryVariable = { variable: TASK_CONTEXT_VARIABLE, arg: summaryId };
53
- newSession.model.context.addVariables(summaryVariable);
83
+ // Open the summary in a new editor
84
+ await this.taskContextStorageService.open(summaryId);
85
+
86
+ // Add the summary file to the context of the active Architect session
87
+ const summary = this.taskContextService.getAll().find(s => s.id === summaryId);
88
+ if (summary?.uri) {
89
+ if (await this.fileService.exists(summary?.uri)) {
90
+ const wsRelativePath = await this.wsService.getWorkspaceRelativePath(summary?.uri);
91
+ // Create a file variable for the summary
92
+ const fileVariable: AIVariableResolutionRequest = {
93
+ variable: FILE_VARIABLE,
94
+ arg: wsRelativePath
95
+ };
96
+
97
+ // Add the file to the active session's context
98
+ activeSession.model.context.addVariables(fileVariable);
99
+ }
100
+
101
+ // Create a new session with the coder agent
102
+ const newSession = this.chatService.createSession(ChatAgentLocation.Panel, { focus: true }, this.coderAgent);
103
+ const summaryVariable = { variable: TASK_CONTEXT_VARIABLE, arg: summaryId };
104
+ newSession.model.context.addVariables(summaryVariable);
105
+ }
54
106
  }
55
107
  });
56
108
  }
@@ -17,7 +17,7 @@
17
17
  import { Summary, SummaryMetadata, TaskContextStorageService } from '@theia/ai-chat/lib/browser/task-context-service';
18
18
  import { InMemoryTaskContextStorage } from '@theia/ai-chat/lib/browser/task-context-storage-service';
19
19
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
20
- import { DisposableCollection, EOL, Emitter, Path, URI, unreachable } from '@theia/core';
20
+ import { DisposableCollection, EOL, Emitter, ILogger, Path, URI, unreachable } from '@theia/core';
21
21
  import { PreferenceService, OpenerService, open } from '@theia/core/lib/browser';
22
22
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
23
23
  import { WorkspaceService } from '@theia/workspace/lib/browser';
@@ -33,10 +33,16 @@ export class TaskContextFileStorageService implements TaskContextStorageService
33
33
  @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
34
34
  @inject(FileService) protected readonly fileService: FileService;
35
35
  @inject(OpenerService) protected readonly openerService: OpenerService;
36
+ @inject(ILogger) protected readonly logger: ILogger;
36
37
  protected readonly onDidChangeEmitter = new Emitter<void>();
37
38
  readonly onDidChange = this.onDidChangeEmitter.event;
38
39
 
39
- protected getStorageLocation(): URI | undefined {
40
+ protected sanitizeLabel(label: string): string {
41
+ return label.replace(/^[^\p{L}\p{N}]+/vg, '');
42
+ }
43
+
44
+ protected async getStorageLocation(): Promise<URI | undefined> {
45
+ await this.workspaceService.ready;
40
46
  if (!this.workspaceService.opened) { return; }
41
47
  const values = this.preferenceService.inspect(TASK_CONTEXT_STORAGE_DIRECTORY_PREF);
42
48
  const configuredPath = values?.globalValue === undefined ? values?.defaultValue : values?.globalValue;
@@ -47,9 +53,11 @@ export class TaskContextFileStorageService implements TaskContextStorageService
47
53
 
48
54
  @postConstruct()
49
55
  protected init(): void {
50
- this.watchStorage();
56
+ this.watchStorage().catch(error => this.logger.error(error));
51
57
  this.preferenceService.onPreferenceChanged(e => {
52
- if (e.affects(TASK_CONTEXT_STORAGE_DIRECTORY_PREF)) { this.watchStorage(); }
58
+ if (e.affects(TASK_CONTEXT_STORAGE_DIRECTORY_PREF)) {
59
+ this.watchStorage().catch(error => this.logger.error(error));
60
+ }
53
61
  });
54
62
  }
55
63
 
@@ -57,7 +65,7 @@ export class TaskContextFileStorageService implements TaskContextStorageService
57
65
  protected async watchStorage(): Promise<void> {
58
66
  this.toDisposeOnStorageChange?.dispose();
59
67
  this.toDisposeOnStorageChange = undefined;
60
- const newStorage = this.getStorageLocation();
68
+ const newStorage = await this.getStorageLocation();
61
69
  if (!newStorage) { return; }
62
70
  this.toDisposeOnStorageChange = new DisposableCollection(
63
71
  this.fileService.watch(newStorage, { recursive: true, excludes: [] }),
@@ -109,10 +117,11 @@ export class TaskContextFileStorageService implements TaskContextStorageService
109
117
  const content = await this.fileService.read(uri).then(read => read.value).catch(() => undefined);
110
118
  if (content === undefined) { return; }
111
119
  const { frontmatter, body } = this.maybeReadFrontmatter(content);
120
+ const rawLabel = frontmatter?.label || uri.path.base.slice(0, (-1 * uri.path.ext.length) || uri.path.base.length);
112
121
  const summary = {
113
122
  ...frontmatter,
114
123
  summary: body,
115
- label: frontmatter?.label || uri.path.base.slice(0, (-1 * uri.path.ext.length) || uri.path.base.length),
124
+ label: this.sanitizeLabel(rawLabel),
116
125
  uri,
117
126
  id: frontmatter?.sessionId || uri.path.base
118
127
  };
@@ -124,21 +133,22 @@ export class TaskContextFileStorageService implements TaskContextStorageService
124
133
  }
125
134
 
126
135
  async store(summary: Summary): Promise<void> {
127
- const storageLocation = this.getStorageLocation();
136
+ const label = this.sanitizeLabel(summary.label);
137
+ const storageLocation = await this.getStorageLocation();
128
138
  if (storageLocation) {
129
139
  const frontmatter = {
130
140
  sessionId: summary.sessionId,
131
141
  date: new Date().toISOString(),
132
- label: summary.label,
142
+ label,
133
143
  };
134
- const derivedName = summary.label.trim().replace(/[^\p{L}\p{N}]/vg, '-').replace(/^-+|-+$/g, '');
144
+ const derivedName = label.trim().replace(/[^\p{L}\p{N}]/vg, '-').replace(/^-+|-+$/g, '');
135
145
  const filename = (derivedName.length > 32 ? derivedName.slice(0, derivedName.indexOf('-', 32)) : derivedName) + '.md';
136
146
  const content = yaml.dump(frontmatter).trim() + `${EOL}---${EOL}` + summary.summary;
137
147
  const uri = storageLocation.resolve(filename);
138
148
  summary.uri = uri;
139
149
  await this.fileService.writeFile(uri, BinaryBuffer.fromString(content));
140
150
  }
141
- this.inMemoryStorage.store(summary);
151
+ this.inMemoryStorage.store({ ...summary, label });
142
152
  this.onDidChangeEmitter.fire();
143
153
  }
144
154
 
@@ -157,27 +157,26 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider {
157
157
  @inject(WorkspaceFunctionScope)
158
158
  protected workspaceScope: WorkspaceFunctionScope;
159
159
 
160
- private async getDirectoryStructure(): Promise<string[]> {
160
+ private async getDirectoryStructure(): Promise<Record<string, unknown>> {
161
161
  let workspaceRoot;
162
162
  try {
163
163
  workspaceRoot = await this.workspaceScope.getWorkspaceRoot();
164
164
  } catch (error) {
165
- return [`Error: ${error.message}`];
165
+ return { error: error.message };
166
166
  }
167
167
 
168
168
  return this.buildDirectoryStructure(workspaceRoot);
169
169
  }
170
170
 
171
- private async buildDirectoryStructure(uri: URI, prefix: string = ''): Promise<string[]> {
171
+ private async buildDirectoryStructure(uri: URI): Promise<Record<string, unknown>> {
172
172
  const stat = await this.fileService.resolve(uri);
173
- const result: string[] = [];
173
+ const result: Record<string, unknown> = {};
174
174
 
175
175
  if (stat && stat.isDirectory && stat.children) {
176
176
  for (const child of stat.children) {
177
177
  if (!child.isDirectory || await this.workspaceScope.shouldExclude(child)) { continue; };
178
- const path = `${prefix}${child.resource.path.base}/`;
179
- result.push(path);
180
- result.push(...await this.buildDirectoryStructure(child.resource, `${path}`));
178
+ const dirName = child.resource.path.base;
179
+ result[dirName] = await this.buildDirectoryStructure(child.resource);
181
180
  }
182
181
  }
183
182
 
@@ -323,10 +322,7 @@ export class GetWorkspaceFileList implements ToolProvider {
323
322
  if (await this.workspaceScope.shouldExclude(child)) {
324
323
  continue;
325
324
  };
326
- const relativePath = workspaceRootUri.relative(child.resource);
327
- if (relativePath) {
328
- result.push(relativePath.toString());
329
- }
325
+ result.push(child.resource.path.base);
330
326
  }
331
327
  }
332
328
  }
@@ -19,6 +19,7 @@ import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference
19
19
 
20
20
  export const CONSIDER_GITIGNORE_PREF = 'ai-features.workspaceFunctions.considerGitIgnore';
21
21
  export const USER_EXCLUDE_PATTERN_PREF = 'ai-features.workspaceFunctions.userExcludes';
22
+ export const SEARCH_IN_WORKSPACE_MAX_RESULTS_PREF = 'ai-features.workspaceFunctions.searchMaxResults';
22
23
  export const PROMPT_TEMPLATE_WORKSPACE_DIRECTORIES_PREF = 'ai-features.promptTemplates.WorkspaceTemplateDirectories';
23
24
  export const PROMPT_TEMPLATE_ADDITIONAL_EXTENSIONS_PREF = 'ai-features.promptTemplates.TemplateExtensions';
24
25
  export const PROMPT_TEMPLATE_WORKSPACE_FILES_PREF = 'ai-features.promptTemplates.WorkspaceTemplateFiles';
@@ -46,6 +47,14 @@ export const WorkspacePreferencesSchema: PreferenceSchema = {
46
47
  type: 'string'
47
48
  }
48
49
  },
50
+ [SEARCH_IN_WORKSPACE_MAX_RESULTS_PREF]: {
51
+ type: 'number',
52
+ title: nls.localize('theia/ai/workspace/searchMaxResults/title', 'Maximum Search Results'),
53
+ description: nls.localize('theia/ai/workspace/searchMaxResults/description',
54
+ 'Maximum number of search results returned by the workspace search function.'),
55
+ default: 30,
56
+ minimum: 1
57
+ },
49
58
  [PROMPT_TEMPLATE_WORKSPACE_DIRECTORIES_PREF]: {
50
59
  type: 'array',
51
60
  title: nls.localize('theia/ai/promptTemplates/directories/title', 'Workspace-specific Prompt Template Directories'),
@@ -0,0 +1,255 @@
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 { 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 { expect } from 'chai';
23
+ import { URI } from '@theia/core';
24
+ import { SearchInWorkspaceResult, LinePreview } from '@theia/search-in-workspace/lib/common/search-in-workspace-interface';
25
+ import { optimizeSearchResults } from '../common/workspace-search-provider-util';
26
+
27
+ disableJSDOM();
28
+
29
+ describe('WorkspaceSearchProvider - Token Optimization', () => {
30
+
31
+ before(() => {
32
+ disableJSDOM = enableJSDOM();
33
+ });
34
+
35
+ after(() => {
36
+ disableJSDOM();
37
+ });
38
+
39
+ describe('optimizeSearchResults method', () => {
40
+ it('should preserve all information while optimizing format', () => {
41
+ const workspaceRoot = new URI('file:///workspace');
42
+ const mockResults: SearchInWorkspaceResult[] = [
43
+ {
44
+ root: 'file:///workspace',
45
+ fileUri: 'file:///workspace/src/test.ts',
46
+ matches: [
47
+ {
48
+ line: 1,
49
+ character: 5,
50
+ length: 8,
51
+ lineText: ' const test = "hello"; '
52
+ },
53
+ {
54
+ line: 5,
55
+ character: 10,
56
+ length: 4,
57
+ lineText: '\t\tfunction test() { }\n'
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ root: 'file:///workspace',
63
+ fileUri: 'file:///workspace/lib/utils.js',
64
+ matches: [
65
+ {
66
+ line: 10,
67
+ character: 0,
68
+ length: 6,
69
+ lineText: 'export default function() {}'
70
+ }
71
+ ]
72
+ }
73
+ ];
74
+
75
+ const result = optimizeSearchResults(mockResults, workspaceRoot);
76
+
77
+ expect(result).to.have.length(2);
78
+
79
+ // First file
80
+ expect(result[0]).to.deep.equal({
81
+ file: 'src/test.ts',
82
+ matches: [
83
+ {
84
+ line: 1,
85
+ text: 'const test = "hello";'
86
+ },
87
+ {
88
+ line: 5,
89
+ text: 'function test() { }'
90
+ }
91
+ ]
92
+ });
93
+
94
+ // Second file
95
+ expect(result[1]).to.deep.equal({
96
+ file: 'lib/utils.js',
97
+ matches: [
98
+ {
99
+ line: 10,
100
+ text: 'export default function() {}'
101
+ }
102
+ ]
103
+ });
104
+ });
105
+
106
+ it('should handle LinePreview objects correctly', () => {
107
+ const workspaceRoot = new URI('file:///workspace');
108
+ const linePreview: LinePreview = {
109
+ text: ' preview text with spaces ',
110
+ character: 5
111
+ };
112
+
113
+ const mockResults: SearchInWorkspaceResult[] = [
114
+ {
115
+ root: 'file:///workspace',
116
+ fileUri: 'file:///workspace/preview.ts',
117
+ matches: [
118
+ {
119
+ line: 3,
120
+ character: 5,
121
+ length: 7,
122
+ lineText: linePreview
123
+ }
124
+ ]
125
+ }
126
+ ];
127
+
128
+ const result = optimizeSearchResults(mockResults, workspaceRoot);
129
+
130
+ expect(result[0].matches[0]).to.deep.equal({
131
+ line: 3,
132
+ text: 'preview text with spaces'
133
+ });
134
+ });
135
+
136
+ it('should handle empty LinePreview text gracefully', () => {
137
+ const workspaceRoot = new URI('file:///workspace');
138
+ const linePreview: LinePreview = {
139
+ text: '',
140
+ character: 0
141
+ };
142
+
143
+ const mockResults: SearchInWorkspaceResult[] = [
144
+ {
145
+ root: 'file:///workspace',
146
+ fileUri: 'file:///workspace/empty.ts',
147
+ matches: [
148
+ {
149
+ line: 1,
150
+ character: 0,
151
+ length: 0,
152
+ lineText: linePreview
153
+ }
154
+ ]
155
+ }
156
+ ];
157
+
158
+ const result = optimizeSearchResults(mockResults, workspaceRoot);
159
+
160
+ expect(result[0].matches[0]).to.deep.equal({
161
+ line: 1,
162
+ text: ''
163
+ });
164
+ });
165
+
166
+ it('should preserve semantic whitespace within lines', () => {
167
+ const workspaceRoot = new URI('file:///workspace');
168
+ const mockResults: SearchInWorkspaceResult[] = [
169
+ {
170
+ root: 'file:///workspace',
171
+ fileUri: 'file:///workspace/spaces.ts',
172
+ matches: [
173
+ {
174
+ line: 1,
175
+ character: 0,
176
+ length: 20,
177
+ lineText: ' if (a && b) { '
178
+ }
179
+ ]
180
+ }
181
+ ];
182
+
183
+ const result = optimizeSearchResults(mockResults, workspaceRoot);
184
+
185
+ expect(result[0].matches[0].text).to.equal('if (a && b) {');
186
+ });
187
+
188
+ it('should use absolute URI when relative path cannot be determined', () => {
189
+ const workspaceRoot = new URI('file:///different-workspace');
190
+ const mockResults: SearchInWorkspaceResult[] = [
191
+ {
192
+ root: 'file:///workspace',
193
+ fileUri: 'file:///workspace/outside.ts',
194
+ matches: [
195
+ {
196
+ line: 1,
197
+ character: 0,
198
+ length: 4,
199
+ lineText: 'test'
200
+ }
201
+ ]
202
+ }
203
+ ];
204
+
205
+ const result = optimizeSearchResults(mockResults, workspaceRoot);
206
+
207
+ expect(result[0].file).to.equal('file:///workspace/outside.ts');
208
+ });
209
+ });
210
+
211
+ describe('token efficiency validation', () => {
212
+ it('should produce more compact JSON than original format', () => {
213
+ const workspaceRoot = new URI('file:///workspace');
214
+ const mockResults: SearchInWorkspaceResult[] = [
215
+ {
216
+ root: 'file:///workspace',
217
+ fileUri: 'file:///workspace/src/test.ts',
218
+ matches: [
219
+ {
220
+ line: 1,
221
+ character: 5,
222
+ length: 8,
223
+ lineText: ' const test = "hello"; '
224
+ }
225
+ ]
226
+ }
227
+ ];
228
+
229
+ // Original format (simulated)
230
+ const originalFormat = JSON.stringify([{
231
+ root: 'file:///workspace',
232
+ fileUri: 'file:///workspace/src/test.ts',
233
+ matches: [{
234
+ line: 1,
235
+ character: 5,
236
+ length: 8,
237
+ lineText: ' const test = "hello"; '
238
+ }]
239
+ }]);
240
+
241
+ // Optimized format
242
+ const optimizedResults = optimizeSearchResults(mockResults, workspaceRoot);
243
+ const optimizedFormat = JSON.stringify(optimizedResults);
244
+
245
+ // The optimized format should be significantly shorter
246
+ expect(optimizedFormat.length).to.be.lessThan(originalFormat.length);
247
+
248
+ // But should preserve essential information
249
+ const parsed = JSON.parse(optimizedFormat);
250
+ expect(parsed[0].file).to.equal('src/test.ts');
251
+ expect(parsed[0].matches[0].line).to.equal(1);
252
+ expect(parsed[0].matches[0].text).to.equal('const test = "hello";');
253
+ });
254
+ });
255
+ });