@theia/ai-core 1.77.0-next.7 → 1.77.0-next.77

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 (172) hide show
  1. package/lib/browser/agents-md-frontend-application-contribution.d.ts +65 -0
  2. package/lib/browser/agents-md-frontend-application-contribution.d.ts.map +1 -0
  3. package/lib/browser/agents-md-frontend-application-contribution.js +269 -0
  4. package/lib/browser/agents-md-frontend-application-contribution.js.map +1 -0
  5. package/lib/browser/agents-md-frontend-application-contribution.spec.d.ts +2 -0
  6. package/lib/browser/agents-md-frontend-application-contribution.spec.d.ts.map +1 -0
  7. package/lib/browser/agents-md-frontend-application-contribution.spec.js +225 -0
  8. package/lib/browser/agents-md-frontend-application-contribution.spec.js.map +1 -0
  9. package/lib/browser/agents-md-migration-service.d.ts +78 -0
  10. package/lib/browser/agents-md-migration-service.d.ts.map +1 -0
  11. package/lib/browser/agents-md-migration-service.js +195 -0
  12. package/lib/browser/agents-md-migration-service.js.map +1 -0
  13. package/lib/browser/agents-md-migration-service.spec.d.ts +2 -0
  14. package/lib/browser/agents-md-migration-service.spec.d.ts.map +1 -0
  15. package/lib/browser/agents-md-migration-service.spec.js +278 -0
  16. package/lib/browser/agents-md-migration-service.spec.js.map +1 -0
  17. package/lib/browser/agents-md-prompt-fragment-contribution.d.ts +13 -0
  18. package/lib/browser/agents-md-prompt-fragment-contribution.d.ts.map +1 -0
  19. package/lib/browser/agents-md-prompt-fragment-contribution.js +76 -0
  20. package/lib/browser/agents-md-prompt-fragment-contribution.js.map +1 -0
  21. package/lib/browser/agents-md-service.d.ts +58 -0
  22. package/lib/browser/agents-md-service.d.ts.map +1 -0
  23. package/lib/browser/agents-md-service.js +191 -0
  24. package/lib/browser/agents-md-service.js.map +1 -0
  25. package/lib/browser/agents-md-service.spec.d.ts +2 -0
  26. package/lib/browser/agents-md-service.spec.d.ts.map +1 -0
  27. package/lib/browser/agents-md-service.spec.js +220 -0
  28. package/lib/browser/agents-md-service.spec.js.map +1 -0
  29. package/lib/browser/agents-md-variable-contribution.d.ts +66 -0
  30. package/lib/browser/agents-md-variable-contribution.d.ts.map +1 -0
  31. package/lib/browser/agents-md-variable-contribution.js +169 -0
  32. package/lib/browser/agents-md-variable-contribution.js.map +1 -0
  33. package/lib/browser/agents-md-variable-contribution.spec.d.ts +2 -0
  34. package/lib/browser/agents-md-variable-contribution.spec.d.ts.map +1 -0
  35. package/lib/browser/agents-md-variable-contribution.spec.js +148 -0
  36. package/lib/browser/agents-md-variable-contribution.spec.js.map +1 -0
  37. package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
  38. package/lib/browser/ai-core-frontend-module.js +17 -0
  39. package/lib/browser/ai-core-frontend-module.js.map +1 -1
  40. package/lib/browser/file-variable-contribution.d.ts +2 -0
  41. package/lib/browser/file-variable-contribution.d.ts.map +1 -1
  42. package/lib/browser/file-variable-contribution.js +8 -23
  43. package/lib/browser/file-variable-contribution.js.map +1 -1
  44. package/lib/browser/file-variable-contribution.spec.d.ts +2 -0
  45. package/lib/browser/file-variable-contribution.spec.d.ts.map +1 -0
  46. package/lib/browser/file-variable-contribution.spec.js +98 -0
  47. package/lib/browser/file-variable-contribution.spec.js.map +1 -0
  48. package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
  49. package/lib/browser/frontend-language-model-service.js +0 -8
  50. package/lib/browser/frontend-language-model-service.js.map +1 -1
  51. package/lib/browser/frontend-prompt-customization-service.d.ts +2 -1
  52. package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
  53. package/lib/browser/frontend-prompt-customization-service.js +10 -0
  54. package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
  55. package/lib/browser/frontend-prompt-customization-service.spec.js +19 -1
  56. package/lib/browser/frontend-prompt-customization-service.spec.js.map +1 -1
  57. package/lib/browser/frontend-variable-service.js +1 -1
  58. package/lib/browser/frontend-variable-service.js.map +1 -1
  59. package/lib/browser/index.d.ts +6 -0
  60. package/lib/browser/index.d.ts.map +1 -1
  61. package/lib/browser/index.js +6 -0
  62. package/lib/browser/index.js.map +1 -1
  63. package/lib/browser/prompt-variable-contribution.js +1 -1
  64. package/lib/browser/prompt-variable-contribution.js.map +1 -1
  65. package/lib/browser/prompt-variable-contribution.spec.js +2 -1
  66. package/lib/browser/prompt-variable-contribution.spec.js.map +1 -1
  67. package/lib/browser/prompttemplate-contribution.js +1 -1
  68. package/lib/browser/prompttemplate-contribution.js.map +1 -1
  69. package/lib/browser/reasoning-labels.d.ts +4 -0
  70. package/lib/browser/reasoning-labels.d.ts.map +1 -0
  71. package/lib/browser/reasoning-labels.js +41 -0
  72. package/lib/browser/reasoning-labels.js.map +1 -0
  73. package/lib/browser/reasoning-labels.spec.d.ts +2 -0
  74. package/lib/browser/reasoning-labels.spec.d.ts.map +1 -0
  75. package/lib/browser/reasoning-labels.spec.js +28 -0
  76. package/lib/browser/reasoning-labels.spec.js.map +1 -0
  77. package/lib/browser/skill-service.spec.js +2 -1
  78. package/lib/browser/skill-service.spec.js.map +1 -1
  79. package/lib/browser/skills-variable-contribution.spec.js +2 -1
  80. package/lib/browser/skills-variable-contribution.spec.js.map +1 -1
  81. package/lib/browser/theia-variable-contribution.js +3 -3
  82. package/lib/browser/theia-variable-contribution.js.map +1 -1
  83. package/lib/browser/workspace-relative-path-resolver.d.ts +20 -0
  84. package/lib/browser/workspace-relative-path-resolver.d.ts.map +1 -0
  85. package/lib/browser/workspace-relative-path-resolver.js +78 -0
  86. package/lib/browser/workspace-relative-path-resolver.js.map +1 -0
  87. package/lib/common/agents-md.d.ts +20 -0
  88. package/lib/common/agents-md.d.ts.map +1 -0
  89. package/lib/common/agents-md.js +38 -0
  90. package/lib/common/agents-md.js.map +1 -0
  91. package/lib/common/ai-core-preferences.d.ts.map +1 -1
  92. package/lib/common/ai-core-preferences.js +6 -3
  93. package/lib/common/ai-core-preferences.js.map +1 -1
  94. package/lib/common/ai-variable-resource.js +3 -3
  95. package/lib/common/ai-variable-resource.js.map +1 -1
  96. package/lib/common/capability-variable-contribution.js +1 -1
  97. package/lib/common/capability-variable-contribution.js.map +1 -1
  98. package/lib/common/index.d.ts +2 -0
  99. package/lib/common/index.d.ts.map +1 -1
  100. package/lib/common/index.js +2 -0
  101. package/lib/common/index.js.map +1 -1
  102. package/lib/common/language-model-service.d.ts.map +1 -1
  103. package/lib/common/language-model-service.js +7 -0
  104. package/lib/common/language-model-service.js.map +1 -1
  105. package/lib/common/language-model-service.spec.js +21 -0
  106. package/lib/common/language-model-service.spec.js.map +1 -1
  107. package/lib/common/language-model.d.ts +10 -3
  108. package/lib/common/language-model.d.ts.map +1 -1
  109. package/lib/common/language-model.js +17 -4
  110. package/lib/common/language-model.js.map +1 -1
  111. package/lib/common/language-model.spec.js +18 -0
  112. package/lib/common/language-model.spec.js.map +1 -1
  113. package/lib/common/prompt-service.d.ts +12 -0
  114. package/lib/common/prompt-service.d.ts.map +1 -1
  115. package/lib/common/prompt-service.js.map +1 -1
  116. package/lib/common/prompt-service.spec.js +2 -1
  117. package/lib/common/prompt-service.spec.js.map +1 -1
  118. package/lib/common/tool-call-execution.d.ts +89 -0
  119. package/lib/common/tool-call-execution.d.ts.map +1 -0
  120. package/lib/common/tool-call-execution.js +67 -0
  121. package/lib/common/tool-call-execution.js.map +1 -0
  122. package/lib/common/tool-call-execution.spec.d.ts +2 -0
  123. package/lib/common/tool-call-execution.spec.d.ts.map +1 -0
  124. package/lib/common/tool-call-execution.spec.js +114 -0
  125. package/lib/common/tool-call-execution.spec.js.map +1 -0
  126. package/lib/common/tool-request-parameters.spec.d.ts +2 -0
  127. package/lib/common/tool-request-parameters.spec.d.ts.map +1 -0
  128. package/lib/common/tool-request-parameters.spec.js +62 -0
  129. package/lib/common/tool-request-parameters.spec.js.map +1 -0
  130. package/lib/common/variable-service.spec.js +2 -1
  131. package/lib/common/variable-service.spec.js.map +1 -1
  132. package/lib/node/ai-core-backend-module.d.ts.map +1 -1
  133. package/lib/node/ai-core-backend-module.js +2 -0
  134. package/lib/node/ai-core-backend-module.js.map +1 -1
  135. package/lib/node/model-snapshot-store.js +2 -2
  136. package/lib/node/model-snapshot-store.js.map +1 -1
  137. package/lib/node/proxy-util.js +2 -1
  138. package/lib/node/proxy-util.js.map +1 -1
  139. package/package.json +9 -9
  140. package/src/browser/agents-md-frontend-application-contribution.spec.ts +244 -0
  141. package/src/browser/agents-md-frontend-application-contribution.ts +291 -0
  142. package/src/browser/agents-md-migration-service.spec.ts +338 -0
  143. package/src/browser/agents-md-migration-service.ts +226 -0
  144. package/src/browser/agents-md-prompt-fragment-contribution.ts +73 -0
  145. package/src/browser/agents-md-service.spec.ts +258 -0
  146. package/src/browser/agents-md-service.ts +212 -0
  147. package/src/browser/agents-md-variable-contribution.spec.ts +180 -0
  148. package/src/browser/agents-md-variable-contribution.ts +182 -0
  149. package/src/browser/ai-core-frontend-module.ts +19 -0
  150. package/src/browser/file-variable-contribution.spec.ts +107 -0
  151. package/src/browser/file-variable-contribution.ts +8 -27
  152. package/src/browser/frontend-language-model-service.ts +1 -10
  153. package/src/browser/frontend-prompt-customization-service.spec.ts +24 -0
  154. package/src/browser/frontend-prompt-customization-service.ts +13 -1
  155. package/src/browser/index.ts +6 -0
  156. package/src/browser/reasoning-labels.spec.ts +28 -0
  157. package/src/browser/reasoning-labels.ts +41 -0
  158. package/src/browser/theia-variable-contribution.ts +3 -3
  159. package/src/browser/workspace-relative-path-resolver.ts +71 -0
  160. package/src/common/agents-md.ts +37 -0
  161. package/src/common/ai-core-preferences.ts +6 -3
  162. package/src/common/ai-variable-resource.ts +1 -1
  163. package/src/common/index.ts +2 -0
  164. package/src/common/language-model-service.spec.ts +23 -1
  165. package/src/common/language-model-service.ts +11 -1
  166. package/src/common/language-model.spec.ts +21 -0
  167. package/src/common/language-model.ts +26 -6
  168. package/src/common/prompt-service.ts +14 -0
  169. package/src/common/tool-call-execution.spec.ts +162 -0
  170. package/src/common/tool-call-execution.ts +161 -0
  171. package/src/common/tool-request-parameters.spec.ts +81 -0
  172. package/src/node/ai-core-backend-module.ts +5 -1
@@ -0,0 +1,107 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics and others.
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 { URI } from '@theia/core';
24
+ import { OpenerService } from '@theia/core/lib/browser';
25
+ import { Container, injectable } from '@theia/core/shared/inversify';
26
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
27
+ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
28
+ import { withWorkspaceServiceDefaults } from '@theia/workspace/lib/browser/test/with-workspace-service-defaults';
29
+ import { FileVariableContribution } from './file-variable-contribution';
30
+ import { WorkspaceRelativePathResolver } from './workspace-relative-path-resolver';
31
+
32
+ disableJSDOM();
33
+
34
+ @injectable()
35
+ class TestFileVariableContribution extends FileVariableContribution {
36
+ resolvePath(path: string): Promise<URI | undefined> {
37
+ return this.makeAbsolute(path);
38
+ }
39
+ }
40
+
41
+ describe('FileVariableContribution path resolution', () => {
42
+
43
+ const ROOTS = ['file:///alice/app', 'file:///bob/app', 'file:///ws/theia'];
44
+ const FILES = ['file:///alice/app/src/a.ts', 'file:///bob/app/src/a.ts', 'file:///bob/app/src/only-bob.ts', 'file:///ws/theia/README.md'];
45
+
46
+ let roots: string[];
47
+ let workspaceService: WorkspaceService;
48
+ let contribution: TestFileVariableContribution;
49
+
50
+ before(() => {
51
+ disableJSDOM = enableJSDOM();
52
+ });
53
+ after(() => {
54
+ disableJSDOM();
55
+ });
56
+
57
+ beforeEach(() => {
58
+ roots = ROOTS;
59
+ const container = new Container();
60
+ const fileService = { exists: async (uri: URI) => FILES.includes(uri.toString()) } as unknown as FileService;
61
+ workspaceService = withWorkspaceServiceDefaults({
62
+ tryGetRoots: () => roots.map(root => ({ resource: new URI(root), isDirectory: true }))
63
+ });
64
+ container.bind(WorkspaceService).toConstantValue(workspaceService);
65
+ container.bind(FileService).toConstantValue(fileService);
66
+ container.bind(OpenerService).toConstantValue({} as OpenerService);
67
+ container.bind(WorkspaceRelativePathResolver).toSelf();
68
+ container.bind(TestFileVariableContribution).toSelf();
69
+ contribution = container.get(TestFileVariableContribution);
70
+ });
71
+
72
+ const resolve = async (path: string): Promise<string | undefined> => (await contribution.resolvePath(path))?.toString();
73
+
74
+ it('resolves the root-prefixed path of a file in every root', async () => {
75
+ for (const file of FILES) {
76
+ expect(await resolve(workspaceService.getRootPrefixedPath(new URI(file)))).to.equal(file);
77
+ }
78
+ });
79
+
80
+ it('resolves a path prefixed with a basename shared by several roots, as saved by earlier versions', async () => {
81
+ expect(await resolve('app/src/only-bob.ts')).to.equal('file:///bob/app/src/only-bob.ts');
82
+ });
83
+
84
+ it('does not resolve a path prefixed with a basename shared by several roots that contain the file', async () => {
85
+ expect(await resolve('app/src/a.ts')).to.be.undefined;
86
+ });
87
+
88
+ it('resolves a path prefixed with a root name that the roots no longer assign', async () => {
89
+ // Saved as `alice/app/...` while `/bob/app` was open, too; now the root is just named `app`.
90
+ roots = ['file:///alice/app', 'file:///ws/theia'];
91
+ expect(await resolve('alice/app/src/a.ts')).to.equal('file:///alice/app/src/a.ts');
92
+ });
93
+
94
+ it('resolves a path without a root name, as saved by earlier versions', async () => {
95
+ expect(await resolve('README.md')).to.equal('file:///ws/theia/README.md');
96
+ });
97
+
98
+ it('does not resolve a relative path that leaves its root', async () => {
99
+ FILES.push('file:///alice/secret.txt');
100
+ try {
101
+ expect(await resolve('../secret.txt')).to.be.undefined;
102
+ expect(await resolve('app/../../secret.txt')).to.be.undefined;
103
+ } finally {
104
+ FILES.pop();
105
+ }
106
+ });
107
+ });
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { nls, Path, URI } from '@theia/core';
17
+ import { nls, URI } from '@theia/core';
18
18
  import { OpenerService, codiconArray, open } from '@theia/core/lib/browser';
19
19
  import { inject, injectable } from '@theia/core/shared/inversify';
20
20
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
@@ -29,6 +29,7 @@ import {
29
29
  ResolvedAIContextVariable,
30
30
  } from '../common/variable-service';
31
31
  import { FrontendVariableService } from './frontend-variable-service';
32
+ import { WorkspaceRelativePathResolver } from './workspace-relative-path-resolver';
32
33
 
33
34
  export namespace FileVariableArgs {
34
35
  export const uri = 'uri';
@@ -55,6 +56,9 @@ export class FileVariableContribution implements AIVariableContribution, AIVaria
55
56
  @inject(OpenerService)
56
57
  protected readonly openerService: OpenerService;
57
58
 
59
+ @inject(WorkspaceRelativePathResolver)
60
+ protected readonly pathResolver: WorkspaceRelativePathResolver;
61
+
58
62
  registerVariables(service: FrontendVariableService): void {
59
63
  service.registerResolver(FILE_VARIABLE, this);
60
64
  service.registerOpener(FILE_VARIABLE, this);
@@ -104,32 +108,9 @@ export class FileVariableContribution implements AIVariableContribution, AIVaria
104
108
  }
105
109
 
106
110
  protected async makeAbsolute(pathStr: string): Promise<URI | undefined> {
107
- const normalizedPath = Path.normalizePathSeparator(pathStr);
108
- const path = new Path(normalizedPath);
109
-
110
- if (!path.isAbsolute) {
111
- const workspaceRoots = this.wsService.tryGetRoots();
112
-
113
- const segments = normalizedPath.split('/');
114
- if (segments.length > 0) {
115
- const potentialRootName = segments[0];
116
- for (const root of workspaceRoots) {
117
- if (root.resource.path.base === potentialRootName) {
118
- const restOfPath = segments.slice(1).join('/');
119
- const uri = restOfPath ? root.resource.resolve(restOfPath) : root.resource;
120
- if (await this.fileService.exists(uri)) {
121
- return uri;
122
- }
123
- }
124
- }
125
- }
126
-
127
- const wsUris = workspaceRoots.map(root => root.resource.resolve(path));
128
- for (const uri of wsUris) {
129
- if (await this.fileService.exists(uri)) {
130
- return uri;
131
- }
132
- }
111
+ const workspaceUri = await this.pathResolver.resolveExistingRelativePath(pathStr);
112
+ if (workspaceUri) {
113
+ return workspaceUri;
133
114
  }
134
115
 
135
116
  const argUri = new URI(pathStr);
@@ -18,7 +18,7 @@ import { nls } from '@theia/core/lib/common/nls';
18
18
  import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import { Prioritizeable } from '@theia/core/lib/common/prioritizeable';
20
20
  import { WorkspaceTrustService } from '@theia/workspace/lib/browser/workspace-trust-service';
21
- import { AiConfigurationService, LanguageModel, LanguageModelResponse, ReasoningSettings, ReasoningSupport, UserRequest } from '../common';
21
+ import { AiConfigurationService, LanguageModel, LanguageModelResponse, ReasoningSettings, UserRequest } from '../common';
22
22
  import { LanguageModelServiceImpl } from '../common/language-model-service';
23
23
  import {
24
24
  PREFERENCE_NAME_REQUEST_SETTINGS,
@@ -76,15 +76,6 @@ export class FrontendLanguageModelServiceImpl extends LanguageModelServiceImpl {
76
76
  languageModelRequest.reasoning = { level: languageModel.reasoningSupport.defaultLevel };
77
77
  }
78
78
  }
79
- // A persisted selection or preference entry can name a level the current model does not accept,
80
- // which the provider would otherwise send as-is; clamp it to the model's declared levels.
81
- if (languageModelRequest.reasoning && languageModel.reasoningSupport) {
82
- const level = ReasoningSupport.clampLevel(languageModel.reasoningSupport, languageModelRequest.reasoning.level);
83
- if (level !== languageModelRequest.reasoning.level) {
84
- languageModelRequest.reasoning = { ...languageModelRequest.reasoning, level };
85
- }
86
- }
87
-
88
79
  return super.sendRequest(languageModel, languageModelRequest);
89
80
  }
90
81
  }
@@ -266,6 +266,10 @@ Template`;
266
266
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
267
267
  return (this as any).provenanceLabel(sourceUri);
268
268
  }
269
+
270
+ public useSourceMaps(active: Map<string, FragmentEntry>, all: Map<string, FragmentEntry>): void {
271
+ Object.assign(this, { activeCustomizations: active, allCustomizations: all });
272
+ }
269
273
  }
270
274
 
271
275
  let service: TestableCustomizationService;
@@ -362,6 +366,26 @@ Template`;
362
366
  expect(idxB).to.be.lessThan(idxC);
363
367
  });
364
368
 
369
+ it('reports every source and marks all equal-priority merged sources active', () => {
370
+ service.testAddTemplate(activeMap, 'project-info', 'Shared instructions',
371
+ 'file:///config/project-info.prompttemplate', allMap, 1, CustomizationSource.CUSTOMIZED);
372
+ service.testAddTemplate(activeMap, 'project-info', 'Root A instructions',
373
+ 'file:///rootA/.prompts/project-info.prompttemplate', allMap, 2, CustomizationSource.FOLDER);
374
+ service.testAddTemplate(activeMap, 'project-info', 'Root B instructions',
375
+ 'file:///rootB/.prompts/project-info.prompttemplate', allMap, 2, CustomizationSource.FOLDER);
376
+ service.testAddTemplate(activeMap, 'other-fragment', 'Unrelated',
377
+ 'file:///config/other.prompttemplate', allMap, 1, CustomizationSource.CUSTOMIZED);
378
+ service.useSourceMaps(activeMap, allMap);
379
+
380
+ expect(service.getPromptFragmentCustomizationSources('project-info').map(source => ({
381
+ path: source.uri.path.toString(), template: source.template, active: source.active
382
+ }))).to.deep.equal([
383
+ { path: '/config/project-info.prompttemplate', template: 'Shared instructions', active: false },
384
+ { path: '/rootA/.prompts/project-info.prompttemplate', template: 'Root A instructions', active: true },
385
+ { path: '/rootB/.prompts/project-info.prompttemplate', template: 'Root B instructions', active: true }
386
+ ]);
387
+ });
388
+
365
389
  it('provenanceLabel extracts grandparent directory name from URI', () => {
366
390
  // For "file:///home/user/my-project/.prompts/foo.prompttemplate"
367
391
  // parent is ".prompts", grandparent is "my-project"
@@ -18,7 +18,8 @@ import { DisposableCollection, URI, Event, Emitter, nls, ILogger } from '@theia/
18
18
  import { OpenerService } from '@theia/core/lib/browser';
19
19
  import { inject, injectable, postConstruct, named } from '@theia/core/shared/inversify';
20
20
  import {
21
- PromptFragmentCustomizationService, CustomAgentDescription, CustomAgentPromptVariant, CustomizedPromptFragment, CommandPromptFragmentMetadata, CustomAgentsLocation
21
+ PromptFragmentCustomizationService, CustomAgentDescription, CustomAgentPromptVariant, CustomizedPromptFragment, CommandPromptFragmentMetadata, CustomAgentsLocation,
22
+ PromptFragmentCustomizationSource
22
23
  } from '../common';
23
24
  import { ConfigurableInMemoryResources } from '../common/configurable-in-memory-resources';
24
25
  import { parseFrontmatter, serializeFrontmatter } from '../common/frontmatter';
@@ -1166,6 +1167,17 @@ export class DefaultPromptFragmentCustomizationService implements PromptFragment
1166
1167
  return this.activeCustomizations.has(id);
1167
1168
  }
1168
1169
 
1170
+ getPromptFragmentCustomizationSources(id: string): PromptFragmentCustomizationSource[] {
1171
+ const activeSources = this.activeCustomizations.get(id)?.sourceUris ?? [];
1172
+ return Array.from(this.allCustomizations.values())
1173
+ .filter(entry => entry.id === id)
1174
+ .map(entry => ({
1175
+ uri: new URI(entry.sourceUri),
1176
+ template: entry.template,
1177
+ active: activeSources.includes(entry.sourceUri)
1178
+ }));
1179
+ }
1180
+
1169
1181
  getActivePromptFragmentCustomization(id: string): CustomizedPromptFragment | undefined {
1170
1182
  const entry = this.activeCustomizations.get(id);
1171
1183
  if (!entry) {
@@ -37,6 +37,11 @@ export * from './theia-variable-contribution';
37
37
  export * from './open-editors-variable-contribution';
38
38
  export * from './skills-variable-contribution';
39
39
  export * from './skill-service';
40
+ export * from './agents-md-service';
41
+ export * from './agents-md-variable-contribution';
42
+ export * from './agents-md-prompt-fragment-contribution';
43
+ export * from './agents-md-migration-service';
44
+ export * from './agents-md-frontend-application-contribution';
40
45
  export * from './skill-prompt-coordinator';
41
46
  export * from './agent-plugin-ui-bridge';
42
47
  export * from './skill-registry-ui-bridge';
@@ -46,3 +51,4 @@ export * from '../common/language-model-service';
46
51
  export * from './generic-capabilities-variable-contribution';
47
52
  export * from './generic-capabilities-prompt-fragment-contribution';
48
53
  export * from './prompt-variable-contribution';
54
+ export * from './workspace-relative-path-resolver';
@@ -0,0 +1,28 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { reasoningLevelDescription, reasoningLevelLabel } from './reasoning-labels';
19
+
20
+ describe('Reasoning selector wording', () => {
21
+ it('labels max and distinguishes provider-specific off behavior from explicit no reasoning', () => {
22
+ expect(reasoningLevelLabel('max')).to.equal('Max');
23
+ expect(reasoningLevelDescription('off')).to.equal(
24
+ 'Omits explicit reasoning effort, allowing provider defaults or raw request settings to apply; on Ollama, disables thinking.');
25
+ expect(reasoningLevelDescription('none')).to.equal('Explicitly requests no reasoning.');
26
+ expect(reasoningLevelDescription('high')).to.equal(undefined);
27
+ });
28
+ });
@@ -0,0 +1,41 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { nls } from '@theia/core/lib/common/nls';
18
+ import { ReasoningLevel } from '../common/language-model';
19
+
20
+ export function reasoningLevelLabel(level: ReasoningLevel): string {
21
+ switch (level) {
22
+ case 'off': return nls.localizeByDefault('Off');
23
+ case 'none': return nls.localizeByDefault('None');
24
+ case 'minimal': return nls.localizeByDefault('Minimal');
25
+ case 'low': return nls.localizeByDefault('Low');
26
+ case 'medium': return nls.localizeByDefault('Medium');
27
+ case 'high': return nls.localizeByDefault('High');
28
+ case 'xhigh': return nls.localizeByDefault('Extra High');
29
+ case 'max': return nls.localizeByDefault('Max');
30
+ case 'auto': return nls.localizeByDefault('Auto');
31
+ }
32
+ }
33
+
34
+ export function reasoningLevelDescription(level: ReasoningLevel): string | undefined {
35
+ switch (level) {
36
+ case 'off': return nls.localize('theia/ai/core/reasoning/offDescription',
37
+ 'Omits explicit reasoning effort, allowing provider defaults or raw request settings to apply; on Ollama, disables thinking.');
38
+ case 'none': return nls.localize('theia/ai/core/reasoning/noneDescription', 'Explicitly requests no reasoning.');
39
+ default: return undefined;
40
+ }
41
+ }
@@ -72,19 +72,19 @@ export class TheiaVariableContribution implements AIVariableContribution, AIVari
72
72
  {
73
73
  name: 'currentRelativeFilePath',
74
74
  description: nls.localize('theia/ai/core/variable-contribution/currentRelativeFilePath',
75
- 'The workspace-relative path of the currently opened file (e.g., my-project/src/index.ts).')
75
+ 'The path of the currently opened file relative to its workspace root, without the root name (e.g., src/index.ts).')
76
76
  },
77
77
  {
78
78
  name: '_f',
79
79
  description: nls.localize('theia/ai/core/variable-contribution/dotRelativePath',
80
- 'Short reference to the workspace-relative path of the currently opened file (\'currentRelativeFilePath\').')
80
+ 'Short reference to the path of the currently opened file relative to its workspace root, without the root name (\'currentRelativeFilePath\').')
81
81
  }
82
82
  ]],
83
83
  ['relativeFileDirname', [
84
84
  {
85
85
  name: 'currentRelativeDirPath',
86
86
  description: nls.localize('theia/ai/core/variable-contribution/currentRelativeDirPath',
87
- 'The workspace-relative path of the directory containing the currently opened file (e.g., my-project/src).')
87
+ 'The path of the directory containing the currently opened file relative to its workspace root, without the root name (e.g., src).')
88
88
  }
89
89
  ]],
90
90
  ['lineNumber', [{}]],
@@ -0,0 +1,71 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 STMicroelectronics and others.
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 { Path, URI } from '@theia/core';
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
20
+ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
21
+
22
+ /**
23
+ * Resolves the relative paths of files in the workspace that AI features, e.g. chat variables, refer to.
24
+ */
25
+ @injectable()
26
+ export class WorkspaceRelativePathResolver {
27
+
28
+ @inject(WorkspaceService)
29
+ protected readonly workspaceService: WorkspaceService;
30
+
31
+ @inject(FileService)
32
+ protected readonly fileService: FileService;
33
+
34
+ /**
35
+ * Resolves a relative path to an existing file or folder in the workspace. Besides the format of
36
+ * {@link WorkspaceService.getRootPrefixedPath}, it accepts the formats of paths saved by earlier versions,
37
+ * e.g. in chat sessions, or before the workspace roots changed: in the format of
38
+ * {@link WorkspaceService.resolveSupraRelativePath}, if only one of the roots contains the file, or relative to any root.
39
+ *
40
+ * @returns `undefined` if the path is absolute, leaves its root or does not resolve to an existing file or folder.
41
+ */
42
+ async resolveExistingRelativePath(relativePath: string): Promise<URI | undefined> {
43
+ const normalizedPath = Path.normalizePathSeparator(relativePath);
44
+ const path = new Path(normalizedPath).normalize();
45
+ const segments = path.toString().split('/').filter(segment => segment.length > 0);
46
+ if (path.isAbsolute || Path.isDrive(segments[0] ?? '') || segments.includes('..')) {
47
+ return undefined;
48
+ }
49
+ const rootPrefixed = this.workspaceService.resolveRootPrefixedPath(normalizedPath);
50
+ if (rootPrefixed && await this.fileService.exists(rootPrefixed)) {
51
+ return rootPrefixed;
52
+ }
53
+ const supraRelative: URI[] = [];
54
+ for (const uri of this.workspaceService.resolveSupraRelativePath(normalizedPath)) {
55
+ if (await this.fileService.exists(uri)) {
56
+ supraRelative.push(uri);
57
+ }
58
+ }
59
+ if (supraRelative.length === 1) {
60
+ return supraRelative[0];
61
+ }
62
+ const roots = this.workspaceService.tryGetRoots();
63
+ for (const root of roots) {
64
+ const uri = root.resource.resolve(segments.join('/'));
65
+ if (await this.fileService.exists(uri)) {
66
+ return uri;
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ }
@@ -0,0 +1,37 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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
+ /**
18
+ * File name of the [AGENTS.md standard](https://agents.md/), the source of project-specific context
19
+ * for AI agents. Only this exact name is recognized: `AGENT.md`, `CLAUDE.md` and the various
20
+ * vendor-specific instruction files are deliberately not treated as fallbacks.
21
+ */
22
+ export const AGENTS_MD_FILE_NAME = 'AGENTS.md';
23
+
24
+ /**
25
+ * Advertises the workspace's `AGENTS.md` files by path so that a tool-calling agent can read them on
26
+ * demand. Reference it from a system prompt with `{{prompt:agents-md}}`.
27
+ */
28
+ export const AGENTS_MD_PROMPT_FRAGMENT_ID = 'agents-md';
29
+
30
+ /**
31
+ * Inlines the full `AGENTS.md` content.
32
+ *
33
+ * @deprecated Kept so that prompts still referencing `{{prompt:project-info}}` keep working, and for
34
+ * consumers without a tool-calling loop, which cannot fetch anything on demand. Prefer
35
+ * {@link AGENTS_MD_PROMPT_FRAGMENT_ID}, which lets the model load the content only when relevant.
36
+ */
37
+ export const PROJECT_INFO_PROMPT_FRAGMENT_ID = 'project-info';
@@ -240,11 +240,14 @@ export const aiCorePreferenceSchema: PreferenceSchema = {
240
240
  + ' `#ai-features.modelSettings.requestSettings#` for the same fields.\n\n'
241
241
  + 'Each entry consists of:\n'
242
242
  + '- `scope`: Defines when the setting applies (`modelId`, `providerId`, `agentId`).\n'
243
- + '- `reasoning.level`: One of `off`, `minimal`, `low`, `medium`, `high`, `auto`.\n\n'
243
+ + '- `reasoning.level`: One of `off`, `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `auto`.\n'
244
+ + 'Supported levels depend on the model. `none` explicitly requests no reasoning; `off` disables thinking'
245
+ + ' on Ollama. For other providers, `off` omits explicit reasoning effort, allowing provider defaults'
246
+ + ' or raw request settings to apply. `auto` uses the provider default.\n\n'
244
247
  + 'Precedence at runtime (highest first): session override via the selector → this preference →'
245
248
  + ' the model\'s declared default. Whichever the selector displays is what gets sent. To override'
246
249
  + ' a provider\'s reasoning field manually via `#ai-features.modelSettings.requestSettings#`, set'
247
- + ' `reasoning.level` to `off` here so the level-based translation is disabled.\n\n'
250
+ + ' `reasoning.level` to `off` here so the level-based translation is disabled (except on Ollama).\n\n'
248
251
  + 'Entries are matched based on scope specificity (agent: 100, model: 10, provider: 1 points).\n\n'
249
252
  + 'Example:\n'
250
253
  + '```json\n'
@@ -272,7 +275,7 @@ export const aiCorePreferenceSchema: PreferenceSchema = {
272
275
  properties: {
273
276
  level: {
274
277
  type: 'string',
275
- enum: ['off', 'minimal', 'low', 'medium', 'high', 'auto'],
278
+ enum: ['off', 'none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'auto'],
276
279
  default: 'auto'
277
280
  }
278
281
  },
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import * as deepEqual from 'fast-deep-equal';
17
+ import deepEqual from 'fast-deep-equal';
18
18
  import { inject, injectable, postConstruct, named } from '@theia/core/shared/inversify';
19
19
  import { Resource, URI, generateUuid, ILogger } from '@theia/core';
20
20
  import { AIVariableContext, AIVariableResolutionRequest } from './variable-service';
@@ -22,6 +22,7 @@ export * from './language-model-delegate';
22
22
  export * from './language-model-util';
23
23
  export * from './language-model';
24
24
  export * from './language-model-alias';
25
+ export * from './tool-call-execution';
25
26
  export * from './model-discovery-status';
26
27
  export * from './model-discovery-util';
27
28
  export * from './prompt-service';
@@ -40,6 +41,7 @@ export * from './ai-variable-resource';
40
41
  export * from './configurable-in-memory-resources';
41
42
  export * from './notification-types';
42
43
  export * from './skill';
44
+ export * from './agents-md';
43
45
  export * from './frontmatter';
44
46
  export * from './capability-utils';
45
47
  export * from './tool-constants';
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { expect } from 'chai';
18
18
  import { LanguageModelServiceImpl } from './language-model-service';
19
- import { LanguageModel, LanguageModelMessage, UserRequest } from './language-model';
19
+ import { LanguageModel, LanguageModelMessage, ReasoningLevel, ReasoningSupport, UserRequest } from './language-model';
20
20
 
21
21
  describe('LanguageModelServiceImpl message filtering', () => {
22
22
 
@@ -33,6 +33,28 @@ describe('LanguageModelServiceImpl message filtering', () => {
33
33
  return { model, captured: () => capturedMessages };
34
34
  }
35
35
 
36
+ for (const [support, requested, expected] of [
37
+ [{ supportedLevels: ['off', 'minimal', 'low', 'auto'] }, 'none', 'off'],
38
+ [{ supportedLevels: ['off', 'low', 'medium', 'high', 'auto'] }, 'max', 'high'],
39
+ [{ supportedLevels: ['none', 'low'] }, 'minimal', 'low'],
40
+ [undefined, 'max', 'max']
41
+ ] satisfies [ReasoningSupport | undefined, ReasoningLevel, ReasoningLevel][]) {
42
+ it(`clamps ${requested} to ${expected} in the common request path with support ${JSON.stringify(support)}`, async () => {
43
+ const service = new LanguageModelServiceImpl();
44
+ const reasoning = { level: requested };
45
+ const model: LanguageModel = {
46
+ id: 'test', name: 'test', vendor: 'test', version: '1', status: { status: 'ready' }, reasoningSupport: support,
47
+ async request(request): Promise<{ text: string }> {
48
+ expect(request.reasoning?.level).to.equal(expected);
49
+ return { text: '' };
50
+ }
51
+ };
52
+ await service.sendRequest(model, { messages: [], sessionId: 'session', requestId: 'req', reasoning });
53
+ expect(reasoning.level).to.equal(requested);
54
+ expect(service.sessions[0].exchanges[0].requests[0].request.reasoning?.level).to.equal(expected);
55
+ });
56
+ }
57
+
36
58
  function baseMessages(): LanguageModelMessage[] {
37
59
  return [
38
60
  { actor: 'user', type: 'text', text: 'hi' },
@@ -15,7 +15,9 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject } from '@theia/core/shared/inversify';
18
- import { isLanguageModelStreamResponse, LanguageModel, LanguageModelRegistry, LanguageModelResponse, LanguageModelStreamResponsePart, UserRequest } from './language-model';
18
+ import {
19
+ isLanguageModelStreamResponse, LanguageModel, LanguageModelRegistry, LanguageModelResponse, LanguageModelStreamResponsePart, ReasoningSupport, UserRequest
20
+ } from './language-model';
19
21
  import { LanguageModelExchangeRequest, LanguageModelSession } from './language-model-interaction-model';
20
22
  import { Emitter } from '@theia/core';
21
23
 
@@ -72,6 +74,14 @@ export class LanguageModelServiceImpl implements LanguageModelService {
72
74
  languageModel: LanguageModel,
73
75
  languageModelRequest: UserRequest
74
76
  ): Promise<LanguageModelResponse> {
77
+ // Clamp persisted selections and direct requests to the model's declared reasoning levels.
78
+ if (languageModelRequest.reasoning && languageModel.reasoningSupport) {
79
+ const level = ReasoningSupport.clampLevel(languageModel.reasoningSupport, languageModelRequest.reasoning.level);
80
+ if (level !== languageModelRequest.reasoning.level) {
81
+ languageModelRequest.reasoning = { ...languageModelRequest.reasoning, level };
82
+ }
83
+ }
84
+
75
85
  // Filter messages based on client settings
76
86
  languageModelRequest.messages = languageModelRequest.messages.filter(message => {
77
87
  if (message.type === 'thinking' && languageModelRequest.clientSettings?.keepThinking === false) {
@@ -367,8 +367,29 @@ describe('ReasoningSupport.clampLevel', () => {
367
367
  expect(ReasoningSupport.clampLevel(oSeries, 'auto')).to.equal('auto');
368
368
  });
369
369
 
370
+ it('passes through native none, xhigh, and max when supported', () => {
371
+ const support: ReasoningSupport = { supportedLevels: ['none', 'low', 'medium', 'high', 'xhigh', 'max', 'auto'] };
372
+ for (const level of support.supportedLevels) {
373
+ expect(ReasoningSupport.clampLevel(support, level)).to.equal(level);
374
+ }
375
+ });
376
+
377
+ it('clamps extended efforts to older models and max to max-only models', () => {
378
+ expect(ReasoningSupport.clampLevel(oSeries, 'xhigh')).to.equal('high');
379
+ expect(ReasoningSupport.clampLevel(oSeries, 'max')).to.equal('high');
380
+ expect(ReasoningSupport.clampLevel({ supportedLevels: ['low', 'medium', 'high', 'max'] }, 'xhigh')).to.equal('max');
381
+ expect(ReasoningSupport.clampLevel({ supportedLevels: ['low', 'medium', 'high', 'xhigh'] }, 'max')).to.equal('xhigh');
382
+ });
383
+
384
+ it('clamps none to the nearest supported effort', () => {
385
+ expect(ReasoningSupport.clampLevel({ supportedLevels: ['low', 'medium', 'high'] }, 'none')).to.equal('low');
386
+ expect(ReasoningSupport.clampLevel(oSeries, 'none')).to.equal('off');
387
+ expect(ReasoningSupport.clampLevel({ supportedLevels: ['off', 'minimal', 'low', 'auto'] }, 'none')).to.equal('off');
388
+ });
389
+
370
390
  it('raises an unsupported minimal to low rather than lowering it to off', () => {
371
391
  expect(ReasoningSupport.clampLevel(oSeries, 'minimal')).to.equal('low');
392
+ expect(ReasoningSupport.clampLevel({ supportedLevels: ['none', 'low'] }, 'minimal')).to.equal('low');
372
393
  });
373
394
 
374
395
  it('lowers an unsupported level when nothing higher is supported', () => {