@theia/ai-core 1.77.0-next.7 → 1.77.0-next.71
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.
- package/lib/browser/agents-md-frontend-application-contribution.d.ts +65 -0
- package/lib/browser/agents-md-frontend-application-contribution.d.ts.map +1 -0
- package/lib/browser/agents-md-frontend-application-contribution.js +269 -0
- package/lib/browser/agents-md-frontend-application-contribution.js.map +1 -0
- package/lib/browser/agents-md-frontend-application-contribution.spec.d.ts +2 -0
- package/lib/browser/agents-md-frontend-application-contribution.spec.d.ts.map +1 -0
- package/lib/browser/agents-md-frontend-application-contribution.spec.js +225 -0
- package/lib/browser/agents-md-frontend-application-contribution.spec.js.map +1 -0
- package/lib/browser/agents-md-migration-service.d.ts +78 -0
- package/lib/browser/agents-md-migration-service.d.ts.map +1 -0
- package/lib/browser/agents-md-migration-service.js +195 -0
- package/lib/browser/agents-md-migration-service.js.map +1 -0
- package/lib/browser/agents-md-migration-service.spec.d.ts +2 -0
- package/lib/browser/agents-md-migration-service.spec.d.ts.map +1 -0
- package/lib/browser/agents-md-migration-service.spec.js +278 -0
- package/lib/browser/agents-md-migration-service.spec.js.map +1 -0
- package/lib/browser/agents-md-prompt-fragment-contribution.d.ts +13 -0
- package/lib/browser/agents-md-prompt-fragment-contribution.d.ts.map +1 -0
- package/lib/browser/agents-md-prompt-fragment-contribution.js +76 -0
- package/lib/browser/agents-md-prompt-fragment-contribution.js.map +1 -0
- package/lib/browser/agents-md-service.d.ts +58 -0
- package/lib/browser/agents-md-service.d.ts.map +1 -0
- package/lib/browser/agents-md-service.js +191 -0
- package/lib/browser/agents-md-service.js.map +1 -0
- package/lib/browser/agents-md-service.spec.d.ts +2 -0
- package/lib/browser/agents-md-service.spec.d.ts.map +1 -0
- package/lib/browser/agents-md-service.spec.js +220 -0
- package/lib/browser/agents-md-service.spec.js.map +1 -0
- package/lib/browser/agents-md-variable-contribution.d.ts +66 -0
- package/lib/browser/agents-md-variable-contribution.d.ts.map +1 -0
- package/lib/browser/agents-md-variable-contribution.js +169 -0
- package/lib/browser/agents-md-variable-contribution.js.map +1 -0
- package/lib/browser/agents-md-variable-contribution.spec.d.ts +2 -0
- package/lib/browser/agents-md-variable-contribution.spec.d.ts.map +1 -0
- package/lib/browser/agents-md-variable-contribution.spec.js +148 -0
- package/lib/browser/agents-md-variable-contribution.spec.js.map +1 -0
- package/lib/browser/ai-core-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-frontend-module.js +17 -0
- package/lib/browser/ai-core-frontend-module.js.map +1 -1
- package/lib/browser/file-variable-contribution.d.ts +2 -0
- package/lib/browser/file-variable-contribution.d.ts.map +1 -1
- package/lib/browser/file-variable-contribution.js +8 -23
- package/lib/browser/file-variable-contribution.js.map +1 -1
- package/lib/browser/file-variable-contribution.spec.d.ts +2 -0
- package/lib/browser/file-variable-contribution.spec.d.ts.map +1 -0
- package/lib/browser/file-variable-contribution.spec.js +98 -0
- package/lib/browser/file-variable-contribution.spec.js.map +1 -0
- package/lib/browser/frontend-language-model-service.d.ts.map +1 -1
- package/lib/browser/frontend-language-model-service.js +0 -8
- package/lib/browser/frontend-language-model-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts +2 -1
- package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.js +10 -0
- package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.spec.js +19 -1
- package/lib/browser/frontend-prompt-customization-service.spec.js.map +1 -1
- package/lib/browser/frontend-variable-service.js +1 -1
- package/lib/browser/frontend-variable-service.js.map +1 -1
- package/lib/browser/index.d.ts +6 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +6 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/prompt-variable-contribution.js +1 -1
- package/lib/browser/prompt-variable-contribution.js.map +1 -1
- package/lib/browser/prompt-variable-contribution.spec.js +2 -1
- package/lib/browser/prompt-variable-contribution.spec.js.map +1 -1
- package/lib/browser/prompttemplate-contribution.js +1 -1
- package/lib/browser/prompttemplate-contribution.js.map +1 -1
- package/lib/browser/reasoning-labels.d.ts +4 -0
- package/lib/browser/reasoning-labels.d.ts.map +1 -0
- package/lib/browser/reasoning-labels.js +41 -0
- package/lib/browser/reasoning-labels.js.map +1 -0
- package/lib/browser/reasoning-labels.spec.d.ts +2 -0
- package/lib/browser/reasoning-labels.spec.d.ts.map +1 -0
- package/lib/browser/reasoning-labels.spec.js +28 -0
- package/lib/browser/reasoning-labels.spec.js.map +1 -0
- package/lib/browser/skill-service.spec.js +2 -1
- package/lib/browser/skill-service.spec.js.map +1 -1
- package/lib/browser/skills-variable-contribution.spec.js +2 -1
- package/lib/browser/skills-variable-contribution.spec.js.map +1 -1
- package/lib/browser/theia-variable-contribution.js +3 -3
- package/lib/browser/theia-variable-contribution.js.map +1 -1
- package/lib/browser/workspace-relative-path-resolver.d.ts +20 -0
- package/lib/browser/workspace-relative-path-resolver.d.ts.map +1 -0
- package/lib/browser/workspace-relative-path-resolver.js +78 -0
- package/lib/browser/workspace-relative-path-resolver.js.map +1 -0
- package/lib/common/agents-md.d.ts +20 -0
- package/lib/common/agents-md.d.ts.map +1 -0
- package/lib/common/agents-md.js +38 -0
- package/lib/common/agents-md.js.map +1 -0
- package/lib/common/ai-core-preferences.d.ts.map +1 -1
- package/lib/common/ai-core-preferences.js +6 -3
- package/lib/common/ai-core-preferences.js.map +1 -1
- package/lib/common/ai-variable-resource.js +3 -3
- package/lib/common/ai-variable-resource.js.map +1 -1
- package/lib/common/capability-variable-contribution.js +1 -1
- package/lib/common/capability-variable-contribution.js.map +1 -1
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +2 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/language-model-service.d.ts.map +1 -1
- package/lib/common/language-model-service.js +7 -0
- package/lib/common/language-model-service.js.map +1 -1
- package/lib/common/language-model-service.spec.js +21 -0
- package/lib/common/language-model-service.spec.js.map +1 -1
- package/lib/common/language-model.d.ts +10 -3
- package/lib/common/language-model.d.ts.map +1 -1
- package/lib/common/language-model.js +17 -4
- package/lib/common/language-model.js.map +1 -1
- package/lib/common/language-model.spec.js +18 -0
- package/lib/common/language-model.spec.js.map +1 -1
- package/lib/common/prompt-service.d.ts +12 -0
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js.map +1 -1
- package/lib/common/prompt-service.spec.js +2 -1
- package/lib/common/prompt-service.spec.js.map +1 -1
- package/lib/common/tool-call-execution.d.ts +89 -0
- package/lib/common/tool-call-execution.d.ts.map +1 -0
- package/lib/common/tool-call-execution.js +67 -0
- package/lib/common/tool-call-execution.js.map +1 -0
- package/lib/common/tool-call-execution.spec.d.ts +2 -0
- package/lib/common/tool-call-execution.spec.d.ts.map +1 -0
- package/lib/common/tool-call-execution.spec.js +114 -0
- package/lib/common/tool-call-execution.spec.js.map +1 -0
- package/lib/common/tool-request-parameters.spec.d.ts +2 -0
- package/lib/common/tool-request-parameters.spec.d.ts.map +1 -0
- package/lib/common/tool-request-parameters.spec.js +62 -0
- package/lib/common/tool-request-parameters.spec.js.map +1 -0
- package/lib/common/variable-service.spec.js +2 -1
- package/lib/common/variable-service.spec.js.map +1 -1
- package/lib/node/ai-core-backend-module.d.ts.map +1 -1
- package/lib/node/ai-core-backend-module.js +2 -0
- package/lib/node/ai-core-backend-module.js.map +1 -1
- package/lib/node/model-snapshot-store.js +2 -2
- package/lib/node/model-snapshot-store.js.map +1 -1
- package/lib/node/proxy-util.js +2 -1
- package/lib/node/proxy-util.js.map +1 -1
- package/package.json +9 -9
- package/src/browser/agents-md-frontend-application-contribution.spec.ts +244 -0
- package/src/browser/agents-md-frontend-application-contribution.ts +291 -0
- package/src/browser/agents-md-migration-service.spec.ts +338 -0
- package/src/browser/agents-md-migration-service.ts +226 -0
- package/src/browser/agents-md-prompt-fragment-contribution.ts +73 -0
- package/src/browser/agents-md-service.spec.ts +258 -0
- package/src/browser/agents-md-service.ts +212 -0
- package/src/browser/agents-md-variable-contribution.spec.ts +180 -0
- package/src/browser/agents-md-variable-contribution.ts +182 -0
- package/src/browser/ai-core-frontend-module.ts +19 -0
- package/src/browser/file-variable-contribution.spec.ts +107 -0
- package/src/browser/file-variable-contribution.ts +8 -27
- package/src/browser/frontend-language-model-service.ts +1 -10
- package/src/browser/frontend-prompt-customization-service.spec.ts +24 -0
- package/src/browser/frontend-prompt-customization-service.ts +13 -1
- package/src/browser/index.ts +6 -0
- package/src/browser/reasoning-labels.spec.ts +28 -0
- package/src/browser/reasoning-labels.ts +41 -0
- package/src/browser/theia-variable-contribution.ts +3 -3
- package/src/browser/workspace-relative-path-resolver.ts +71 -0
- package/src/common/agents-md.ts +37 -0
- package/src/common/ai-core-preferences.ts +6 -3
- package/src/common/ai-variable-resource.ts +1 -1
- package/src/common/index.ts +2 -0
- package/src/common/language-model-service.spec.ts +23 -1
- package/src/common/language-model-service.ts +11 -1
- package/src/common/language-model.spec.ts +21 -0
- package/src/common/language-model.ts +26 -6
- package/src/common/prompt-service.ts +14 -0
- package/src/common/tool-call-execution.spec.ts +162 -0
- package/src/common/tool-call-execution.ts +161 -0
- package/src/common/tool-request-parameters.spec.ts +81 -0
- 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,
|
|
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
|
|
108
|
-
|
|
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,
|
|
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) {
|
package/src/browser/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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';
|
package/src/common/index.ts
CHANGED
|
@@ -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 {
|
|
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', () => {
|