@theia/ai-core-ui 1.75.0-next.8 → 1.75.0
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/ai-configuration/ai-configuration-category-registry.d.ts +29 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.js +83 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.js.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.spec.d.ts +2 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.spec.js +122 -0
- package/lib/browser/ai-configuration/ai-configuration-category-registry.spec.js.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-category.d.ts +204 -0
- package/lib/browser/ai-configuration/ai-configuration-category.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-category.js +86 -0
- package/lib/browser/ai-configuration/ai-configuration-category.js.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-selection-model.d.ts +15 -0
- package/lib/browser/ai-configuration/ai-configuration-selection-model.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-selection-model.js +44 -0
- package/lib/browser/ai-configuration/ai-configuration-selection-model.js.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.d.ts +48 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.js +98 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.js.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.spec.d.ts +2 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.spec.js +70 -0
- package/lib/browser/ai-configuration/ai-configuration-setting-commands.spec.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-components.spec.d.ts +2 -0
- package/lib/browser/ai-configuration/components/ai-configuration-components.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-components.spec.js +596 -0
- package/lib/browser/ai-configuration/components/ai-configuration-components.spec.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-controls.d.ts +118 -0
- package/lib/browser/ai-configuration/components/ai-configuration-controls.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-controls.js +191 -0
- package/lib/browser/ai-configuration/components/ai-configuration-controls.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-item-row.d.ts +57 -0
- package/lib/browser/ai-configuration/components/ai-configuration-item-row.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-item-row.js +48 -0
- package/lib/browser/ai-configuration/components/ai-configuration-item-row.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-origin-badge.d.ts +49 -0
- package/lib/browser/ai-configuration/components/ai-configuration-origin-badge.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-origin-badge.js +79 -0
- package/lib/browser/ai-configuration/components/ai-configuration-origin-badge.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-primitives.d.ts +161 -0
- package/lib/browser/ai-configuration/components/ai-configuration-primitives.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-primitives.js +131 -0
- package/lib/browser/ai-configuration/components/ai-configuration-primitives.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-setting-row.d.ts +54 -0
- package/lib/browser/ai-configuration/components/ai-configuration-setting-row.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-configuration-setting-row.js +77 -0
- package/lib/browser/ai-configuration/components/ai-configuration-setting-row.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.d.ts +194 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.js +352 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.spec.d.ts +2 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.spec.js +233 -0
- package/lib/browser/ai-configuration/components/ai-settings-row-service.spec.js.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row.d.ts +50 -0
- package/lib/browser/ai-configuration/components/ai-settings-row.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/ai-settings-row.js +85 -0
- package/lib/browser/ai-configuration/components/ai-settings-row.js.map +1 -0
- package/lib/browser/ai-configuration/components/collapsible-list.d.ts +65 -0
- package/lib/browser/ai-configuration/components/collapsible-list.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/collapsible-list.js +82 -0
- package/lib/browser/ai-configuration/components/collapsible-list.js.map +1 -0
- package/lib/browser/ai-configuration/components/prompt-customization-dialogs.d.ts +35 -0
- package/lib/browser/ai-configuration/components/prompt-customization-dialogs.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/prompt-customization-dialogs.js +83 -0
- package/lib/browser/ai-configuration/components/prompt-customization-dialogs.js.map +1 -0
- package/lib/browser/ai-configuration/components/variant-set-card.d.ts +25 -0
- package/lib/browser/ai-configuration/components/variant-set-card.d.ts.map +1 -0
- package/lib/browser/ai-configuration/components/variant-set-card.js +133 -0
- package/lib/browser/ai-configuration/components/variant-set-card.js.map +1 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.d.ts +44 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.d.ts.map +1 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.js +106 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.js.map +1 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.spec.d.ts +2 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.spec.d.ts.map +1 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.spec.js +120 -0
- package/lib/browser/ai-configuration/renderers/collection-category-renderer.spec.js.map +1 -0
- package/lib/browser/ai-configuration/renderers/single-page-category-renderer.d.ts +15 -0
- package/lib/browser/ai-configuration/renderers/single-page-category-renderer.d.ts.map +1 -0
- package/lib/browser/ai-configuration/renderers/single-page-category-renderer.js +20 -0
- package/lib/browser/ai-configuration/renderers/single-page-category-renderer.js.map +1 -0
- package/lib/browser/ai-core-ui-frontend-module.d.ts +1 -0
- package/lib/browser/ai-core-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-core-ui-frontend-module.js +11 -0
- package/lib/browser/ai-core-ui-frontend-module.js.map +1 -1
- package/package.json +6 -5
- package/src/browser/ai-configuration/ai-configuration-category-registry.spec.ts +137 -0
- package/src/browser/ai-configuration/ai-configuration-category-registry.ts +79 -0
- package/src/browser/ai-configuration/ai-configuration-category.ts +246 -0
- package/src/browser/ai-configuration/ai-configuration-selection-model.ts +42 -0
- package/src/browser/ai-configuration/ai-configuration-setting-commands.spec.ts +82 -0
- package/src/browser/ai-configuration/ai-configuration-setting-commands.ts +104 -0
- package/src/browser/ai-configuration/components/ai-configuration-components.spec.ts +655 -0
- package/src/browser/ai-configuration/components/ai-configuration-controls.tsx +382 -0
- package/src/browser/ai-configuration/components/ai-configuration-item-row.tsx +127 -0
- package/src/browser/ai-configuration/components/ai-configuration-origin-badge.tsx +114 -0
- package/src/browser/ai-configuration/components/ai-configuration-primitives.tsx +359 -0
- package/src/browser/ai-configuration/components/ai-configuration-setting-row.tsx +154 -0
- package/src/browser/ai-configuration/components/ai-settings-row-service.spec.ts +275 -0
- package/src/browser/ai-configuration/components/ai-settings-row-service.ts +401 -0
- package/src/browser/ai-configuration/components/ai-settings-row.tsx +176 -0
- package/src/browser/ai-configuration/components/collapsible-list.tsx +205 -0
- package/src/browser/ai-configuration/components/prompt-customization-dialogs.ts +89 -0
- package/src/browser/ai-configuration/components/variant-set-card.tsx +200 -0
- package/src/browser/ai-configuration/renderers/collection-category-renderer.spec.ts +126 -0
- package/src/browser/ai-configuration/renderers/collection-category-renderer.tsx +164 -0
- package/src/browser/ai-configuration/renderers/single-page-category-renderer.tsx +41 -0
- package/src/browser/ai-core-ui-frontend-module.ts +14 -1
- package/src/browser/style/ai-configuration-components.css +1410 -0
- package/src/browser/style/index.css +17 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-core-ui-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/ai-core-ui-frontend-module.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;;
|
|
1
|
+
{"version":3,"file":"ai-core-ui-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/ai-core-ui-frontend-module.ts"],"names":[],"mappings":"AAgBA,OAAO,mCAAmC,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;;AAU/D,wBASG"}
|
|
@@ -15,12 +15,23 @@
|
|
|
15
15
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
16
|
// *****************************************************************************
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
require("../../src/browser/style/index.css");
|
|
18
19
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
19
20
|
const core_1 = require("@theia/core");
|
|
20
21
|
const agent_preferences_1 = require("@theia/ai-core/lib/common/agent-preferences");
|
|
21
22
|
const ai_core_preferences_1 = require("@theia/ai-core/lib/common/ai-core-preferences");
|
|
23
|
+
const ai_configuration_category_1 = require("./ai-configuration/ai-configuration-category");
|
|
24
|
+
const ai_configuration_category_registry_1 = require("./ai-configuration/ai-configuration-category-registry");
|
|
25
|
+
const ai_configuration_selection_model_1 = require("./ai-configuration/ai-configuration-selection-model");
|
|
26
|
+
const ai_settings_row_service_1 = require("./ai-configuration/components/ai-settings-row-service");
|
|
27
|
+
const ai_configuration_setting_commands_1 = require("./ai-configuration/ai-configuration-setting-commands");
|
|
22
28
|
exports.default = new inversify_1.ContainerModule(bind => {
|
|
23
29
|
bind(core_1.PreferenceContribution).toConstantValue({ schema: agent_preferences_1.AgentSettingsPreferenceSchema });
|
|
24
30
|
bind(core_1.PreferenceContribution).toConstantValue({ schema: ai_core_preferences_1.aiCorePreferenceSchema });
|
|
31
|
+
(0, core_1.bindRootContributionProvider)(bind, ai_configuration_category_1.AiConfigurationCategory);
|
|
32
|
+
bind(ai_configuration_category_registry_1.AiConfigurationCategoryRegistry).toSelf().inSingletonScope();
|
|
33
|
+
bind(ai_configuration_selection_model_1.AiConfigurationSelectionModel).toSelf().inSingletonScope();
|
|
34
|
+
bind(ai_settings_row_service_1.AiSettingsRowService).toSelf().inSingletonScope();
|
|
35
|
+
(0, ai_configuration_setting_commands_1.bindAiConfigurationSettingCommands)(bind);
|
|
25
36
|
});
|
|
26
37
|
//# sourceMappingURL=ai-core-ui-frontend-module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-core-ui-frontend-module.js","sourceRoot":"","sources":["../../src/browser/ai-core-ui-frontend-module.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;AAEhF,4DAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"ai-core-ui-frontend-module.js","sourceRoot":"","sources":["../../src/browser/ai-core-ui-frontend-module.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;AAEhF,6CAA2C;AAE3C,4DAA+D;AAC/D,sCAAmF;AACnF,mFAA4F;AAC5F,uFAAuF;AACvF,4FAAuF;AACvF,8GAAwG;AACxG,0GAAoG;AACpG,mGAA6F;AAC7F,4GAA0G;AAE1G,kBAAe,IAAI,2BAAe,CAAC,IAAI,CAAC,EAAE;IACtC,IAAI,CAAC,6BAAsB,CAAC,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,iDAA6B,EAAE,CAAC,CAAC;IACxF,IAAI,CAAC,6BAAsB,CAAC,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,4CAAsB,EAAE,CAAC,CAAC;IAEjF,IAAA,mCAA4B,EAAC,IAAI,EAAE,mDAAuB,CAAC,CAAC;IAC5D,IAAI,CAAC,oEAA+B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAClE,IAAI,CAAC,gEAA6B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAChE,IAAI,CAAC,8CAAoB,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACvD,IAAA,sEAAkC,EAAC,IAAI,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/ai-core-ui",
|
|
3
|
-
"version": "1.75.0
|
|
3
|
+
"version": "1.75.0",
|
|
4
4
|
"description": "Theia - AI Core UI",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/ai-core": "1.75.0
|
|
7
|
-
"@theia/core": "1.75.0
|
|
6
|
+
"@theia/ai-core": "1.75.0",
|
|
7
|
+
"@theia/core": "1.75.0",
|
|
8
|
+
"@theia/preferences": "1.75.0",
|
|
8
9
|
"tslib": "^2.8.1"
|
|
9
10
|
},
|
|
10
11
|
"main": "lib/common",
|
|
@@ -42,10 +43,10 @@
|
|
|
42
43
|
"watch": "theiaext watch"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@theia/ext-scripts": "1.
|
|
46
|
+
"@theia/ext-scripts": "1.75.0"
|
|
46
47
|
},
|
|
47
48
|
"nyc": {
|
|
48
49
|
"extends": "../../configs/nyc.json"
|
|
49
50
|
},
|
|
50
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "6d8e911f05f0fd9a13d76797e79b21ee3f038bc2"
|
|
51
52
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource 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 'reflect-metadata';
|
|
18
|
+
|
|
19
|
+
import { expect } from 'chai';
|
|
20
|
+
import { Emitter } from '@theia/core';
|
|
21
|
+
import { Container, ContainerModule, inject, injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import { bindContributionProvider } from '@theia/core/lib/common';
|
|
23
|
+
import { AiConfigurationCategory, AiConfigurationSearchItem } from './ai-configuration-category';
|
|
24
|
+
import { AiConfigurationCategoryRegistry } from './ai-configuration-category-registry';
|
|
25
|
+
|
|
26
|
+
class DummyCategory implements AiConfigurationCategory {
|
|
27
|
+
readonly renderer = {};
|
|
28
|
+
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
29
|
+
readonly onDidChange = this.onDidChangeEmitter.event;
|
|
30
|
+
constructor(
|
|
31
|
+
readonly id: string,
|
|
32
|
+
readonly order?: number,
|
|
33
|
+
readonly contributed?: boolean,
|
|
34
|
+
protected readonly searchItems: AiConfigurationSearchItem[] = []
|
|
35
|
+
) { }
|
|
36
|
+
readonly label = this.id;
|
|
37
|
+
readonly iconClass = 'codicon codicon-gear';
|
|
38
|
+
readonly kind = 'single-page' as const;
|
|
39
|
+
get search(): { getSearchItems(): AiConfigurationSearchItem[] } | undefined {
|
|
40
|
+
return this.searchItems.length ? { getSearchItems: () => this.searchItems } : undefined;
|
|
41
|
+
}
|
|
42
|
+
fireChange(): void {
|
|
43
|
+
this.onDidChangeEmitter.fire();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('AiConfigurationCategoryRegistry', () => {
|
|
48
|
+
|
|
49
|
+
function createRegistry(...categories: AiConfigurationCategory[]): AiConfigurationCategoryRegistry {
|
|
50
|
+
const container = new Container();
|
|
51
|
+
container.load(new ContainerModule(bind => {
|
|
52
|
+
bindContributionProvider(bind, AiConfigurationCategory);
|
|
53
|
+
categories.forEach(category => bind(AiConfigurationCategory).toConstantValue(category));
|
|
54
|
+
bind(AiConfigurationCategoryRegistry).toSelf().inSingletonScope();
|
|
55
|
+
}));
|
|
56
|
+
return container.get(AiConfigurationCategoryRegistry);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it('resolves to an empty list when nothing is contributed', () => {
|
|
60
|
+
const registry = createRegistry();
|
|
61
|
+
expect(registry.getCategories()).to.deep.equal([]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('aggregates an empty search index when nothing is contributed', () => {
|
|
65
|
+
const registry = createRegistry();
|
|
66
|
+
expect(registry.getSearchItems()).to.deep.equal([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('orders categories ascending by order', () => {
|
|
70
|
+
const registry = createRegistry(
|
|
71
|
+
new DummyCategory('c', 300),
|
|
72
|
+
new DummyCategory('a', 100),
|
|
73
|
+
new DummyCategory('b', 200)
|
|
74
|
+
);
|
|
75
|
+
expect(registry.getCategories().map(c => c.id)).to.deep.equal(['a', 'b', 'c']);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('sorts contributed categories after all built-ins regardless of order', () => {
|
|
79
|
+
const registry = createRegistry(
|
|
80
|
+
new DummyCategory('ext', 1, true),
|
|
81
|
+
new DummyCategory('builtin', 900)
|
|
82
|
+
);
|
|
83
|
+
expect(registry.getCategories().map(c => c.id)).to.deep.equal(['builtin', 'ext']);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('resolves a category by id', () => {
|
|
87
|
+
const registry = createRegistry(new DummyCategory('a', 100), new DummyCategory('b', 200));
|
|
88
|
+
expect(registry.getCategory('b')?.id).to.equal('b');
|
|
89
|
+
expect(registry.getCategory('missing')).to.equal(undefined);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('aggregates the search index across categories that provide one', () => {
|
|
93
|
+
const item = (label: string, categoryId: string): AiConfigurationSearchItem =>
|
|
94
|
+
({ label, typeLabel: 'Setting', categoryId, target: { categoryId } });
|
|
95
|
+
const registry = createRegistry(
|
|
96
|
+
new DummyCategory('a', 100, false, [item('a1', 'a')]),
|
|
97
|
+
new DummyCategory('b', 200),
|
|
98
|
+
new DummyCategory('c', 300, false, [item('c1', 'c'), item('c2', 'c')])
|
|
99
|
+
);
|
|
100
|
+
expect(registry.getSearchItems().map(i => i.label)).to.deep.equal(['a1', 'c1', 'c2']);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('refires onDidChange when a contributed category changes', () => {
|
|
104
|
+
const category = new DummyCategory('a', 100);
|
|
105
|
+
const registry = createRegistry(category);
|
|
106
|
+
let fired = 0;
|
|
107
|
+
registry.onDidChange(() => fired++);
|
|
108
|
+
category.fireChange();
|
|
109
|
+
expect(fired).to.equal(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('resolves without recursion when a contributed category injects the registry', () => {
|
|
113
|
+
// A category may inject the registry (e.g. to navigate across categories). The registry must not
|
|
114
|
+
// resolve its contributions during its own construction, or that closes a dependency cycle.
|
|
115
|
+
@injectable()
|
|
116
|
+
class RegistryInjectingCategory implements AiConfigurationCategory {
|
|
117
|
+
readonly id = 'self';
|
|
118
|
+
readonly label = 'self';
|
|
119
|
+
readonly iconClass = 'codicon codicon-gear';
|
|
120
|
+
readonly kind = 'single-page' as const;
|
|
121
|
+
readonly renderer = {};
|
|
122
|
+
@inject(AiConfigurationCategoryRegistry)
|
|
123
|
+
readonly registry: AiConfigurationCategoryRegistry;
|
|
124
|
+
}
|
|
125
|
+
const container = new Container();
|
|
126
|
+
container.load(new ContainerModule(bind => {
|
|
127
|
+
bindContributionProvider(bind, AiConfigurationCategory);
|
|
128
|
+
bind(RegistryInjectingCategory).toSelf().inSingletonScope();
|
|
129
|
+
bind(AiConfigurationCategory).toService(RegistryInjectingCategory);
|
|
130
|
+
bind(AiConfigurationCategoryRegistry).toSelf().inSingletonScope();
|
|
131
|
+
}));
|
|
132
|
+
const registry = container.get(AiConfigurationCategoryRegistry);
|
|
133
|
+
// Listening wires the aggregation, which resolves the contribution that injects this very registry.
|
|
134
|
+
expect(() => registry.onDidChange(() => { })).to.not.throw();
|
|
135
|
+
expect(registry.getCategories().map(category => category.id)).to.deep.equal(['self']);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource 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 { ContributionProvider, Emitter, Event } from '@theia/core';
|
|
18
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
19
|
+
import { AiConfigurationCategory, AiConfigurationSearchItem } from './ai-configuration-category';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Collects all contributed {@link AiConfigurationCategory} instances and exposes
|
|
23
|
+
* them ordered for the AI Configuration view. Refires an aggregated
|
|
24
|
+
* {@link onDidChange} whenever any category changes.
|
|
25
|
+
*/
|
|
26
|
+
@injectable()
|
|
27
|
+
export class AiConfigurationCategoryRegistry {
|
|
28
|
+
|
|
29
|
+
@inject(ContributionProvider) @named(AiConfigurationCategory)
|
|
30
|
+
protected readonly contributions: ContributionProvider<AiConfigurationCategory>;
|
|
31
|
+
|
|
32
|
+
protected readonly onDidChangeEmitter = new Emitter<void>();
|
|
33
|
+
/**
|
|
34
|
+
* Wires the aggregation lazily on first listen rather than in `@postConstruct`: a category may itself
|
|
35
|
+
* inject this registry (e.g. to navigate across categories), so resolving all contributions during the
|
|
36
|
+
* registry's own construction would create a dependency cycle.
|
|
37
|
+
*/
|
|
38
|
+
readonly onDidChange: Event<void> = (listener, thisArgs, disposables) => {
|
|
39
|
+
this.wire();
|
|
40
|
+
return this.onDidChangeEmitter.event(listener, thisArgs, disposables);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
protected wired = false;
|
|
44
|
+
|
|
45
|
+
/** Subscribes once to every category's own change event so this registry can refire an aggregated one. */
|
|
46
|
+
protected wire(): void {
|
|
47
|
+
if (this.wired) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.wired = true;
|
|
51
|
+
for (const category of this.contributions.getContributions()) {
|
|
52
|
+
category.onDidChange?.(() => this.onDidChangeEmitter.fire());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the categories ordered ascending by {@link AiConfigurationCategory.order}
|
|
58
|
+
* (defaulting to `0`); `contributed` categories always sort after built-ins.
|
|
59
|
+
*/
|
|
60
|
+
getCategories(): AiConfigurationCategory[] {
|
|
61
|
+
return this.contributions.getContributions().slice().sort((a, b) => {
|
|
62
|
+
const contributedA = a.contributed ? 1 : 0;
|
|
63
|
+
const contributedB = b.contributed ? 1 : 0;
|
|
64
|
+
if (contributedA !== contributedB) {
|
|
65
|
+
return contributedA - contributedB;
|
|
66
|
+
}
|
|
67
|
+
return (a.order ?? 0) - (b.order ?? 0);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getCategory(id: string): AiConfigurationCategory | undefined {
|
|
72
|
+
return this.contributions.getContributions().find(category => category.id === id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Aggregated deep-search index across all categories that provide one. */
|
|
76
|
+
getSearchItems(): AiConfigurationSearchItem[] {
|
|
77
|
+
return this.getCategories().flatMap(category => category.search?.getSearchItems() ?? []);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource 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 { Event } from '@theia/core';
|
|
18
|
+
// Type-only, so this contribution point stays free of a runtime dependency on the rendering component.
|
|
19
|
+
import type { AiConfigurationOrigin } from './components/ai-configuration-origin-badge';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Contribution point for a category shown in the AI Configuration view.
|
|
23
|
+
*
|
|
24
|
+
* Both built-in and externally contributed categories use this identical path;
|
|
25
|
+
* there is no privileged API. Categories are collected via a
|
|
26
|
+
* {@link ContributionProvider} and ordered by {@link AiConfigurationCategory.order}
|
|
27
|
+
* by the {@link AiConfigurationCategoryRegistry}.
|
|
28
|
+
*/
|
|
29
|
+
export const AiConfigurationCategory = Symbol('AiConfigurationCategory');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A `single-page` category renders one detail page; a `collection` category
|
|
33
|
+
* exposes tree children and renders an overview plus (optionally) per-item detail.
|
|
34
|
+
*/
|
|
35
|
+
export type AiConfigurationCategoryKind = 'single-page' | 'collection';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Spaced ordering scale for the built-in categories, leaving room for
|
|
39
|
+
* future built-ins and extensions to slot in between. Assigned to
|
|
40
|
+
* {@link AiConfigurationCategory.order}. Categories with `contributed: true`
|
|
41
|
+
* always sort after all built-ins, regardless of their `order`.
|
|
42
|
+
*/
|
|
43
|
+
export namespace AiConfigurationCategoryOrder {
|
|
44
|
+
export const GENERAL = 100;
|
|
45
|
+
export const MODELS = 200;
|
|
46
|
+
export const MODEL_ALIASES = 300;
|
|
47
|
+
export const AGENTS = 400;
|
|
48
|
+
export const PROMPTS_AND_SKILLS = 500;
|
|
49
|
+
export const PROMPT_SNIPPETS = 550;
|
|
50
|
+
export const VARIABLES = 600;
|
|
51
|
+
export const MCP_SERVERS = 700;
|
|
52
|
+
export const TOOLS = 800;
|
|
53
|
+
export const TOKEN_USAGE = 900;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Canonical ids of the built-in categories. Shared so that the categories
|
|
58
|
+
* (contributed from `ai-ide` and `ai-mcp`), navigation targets, and the legacy
|
|
59
|
+
* `OPEN_AI_CONFIG_VIEW` tab-id mapping all reference the same stable strings.
|
|
60
|
+
*/
|
|
61
|
+
export namespace AiConfigurationCategoryId {
|
|
62
|
+
export const GENERAL = 'general';
|
|
63
|
+
export const MODELS = 'models';
|
|
64
|
+
export const MODEL_ALIASES = 'model-aliases';
|
|
65
|
+
export const AGENTS = 'agents';
|
|
66
|
+
export const PROMPTS_AND_SKILLS = 'prompts-and-skills';
|
|
67
|
+
export const PROMPT_SNIPPETS = 'prompt-snippets';
|
|
68
|
+
export const VARIABLES = 'variables';
|
|
69
|
+
export const MCP_SERVERS = 'mcp-servers';
|
|
70
|
+
export const TOOLS = 'tools';
|
|
71
|
+
export const TOKEN_USAGE = 'token-usage';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Navigation into the Tools category's per-tool list. Shared here because the lists that link to it are
|
|
76
|
+
* spread across packages (an agent's used tools in `@theia/ai-ide`, a server's tools in `@theia/ai-mcp`)
|
|
77
|
+
* while the page itself lives in `@theia/ai-ide`, which those packages cannot all import.
|
|
78
|
+
*/
|
|
79
|
+
export namespace AiConfigurationTools {
|
|
80
|
+
/**
|
|
81
|
+
* Prefix of the `data-ai-config-row-id` the Tools page puts on each tool row. The tool's registry name
|
|
82
|
+
* completes it — for an MCP tool that is `mcp_<server>_<tool>`, not the bare name shown per server.
|
|
83
|
+
*/
|
|
84
|
+
export const ROW_ID_PREFIX = 'tool:';
|
|
85
|
+
|
|
86
|
+
/** Navigation target that scrolls to and flashes `toolName`'s row, where its confirmation mode is set. */
|
|
87
|
+
export function selectionFor(toolName: string): AiConfigurationSelection {
|
|
88
|
+
return {
|
|
89
|
+
categoryId: AiConfigurationCategoryId.TOOLS,
|
|
90
|
+
highlight: { rowId: ROW_ID_PREFIX + toolName }
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The scope a category's settings are read from and written to.
|
|
97
|
+
*
|
|
98
|
+
* The view currently always renders the `user` scope; selecting a scope is not implemented yet.
|
|
99
|
+
*/
|
|
100
|
+
export type AiConfigurationScope = 'user' | 'workspace' | 'folder';
|
|
101
|
+
|
|
102
|
+
export interface AiConfigurationCategory {
|
|
103
|
+
/** Stable id; used as the tree-node id and in navigation targets. */
|
|
104
|
+
readonly id: string;
|
|
105
|
+
/** Localized label shown in the tree and breadcrumb. */
|
|
106
|
+
readonly label: string;
|
|
107
|
+
/** Optional one-line description shown under the category name in the detail-page header. */
|
|
108
|
+
readonly description?: string;
|
|
109
|
+
/** `codicon(...)` class shown in the tree, breadcrumb, and cards. */
|
|
110
|
+
readonly iconClass: string;
|
|
111
|
+
/** Ascending sort; defaults to `0`. Built-ins use a spaced scale. */
|
|
112
|
+
readonly order?: number;
|
|
113
|
+
readonly kind: AiConfigurationCategoryKind;
|
|
114
|
+
/** When `true`, grouped under "Contributed by extensions". Built-ins omit or set `false`. */
|
|
115
|
+
readonly contributed?: boolean;
|
|
116
|
+
|
|
117
|
+
readonly renderer: AiConfigurationCategoryRenderer;
|
|
118
|
+
/** Optional: items contributed to the tree-level (deep) search index. */
|
|
119
|
+
readonly search?: AiConfigurationSearchProvider;
|
|
120
|
+
|
|
121
|
+
/** Fire to refresh tree children/count badge, overview, and search index. */
|
|
122
|
+
readonly onDidChange?: Event<void>;
|
|
123
|
+
dispose?(): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Produces the React nodes that drop into the detail-pane primitives.
|
|
128
|
+
*
|
|
129
|
+
* A `single-page` category implements {@link renderPage}; a `collection`
|
|
130
|
+
* category implements {@link getTreeChildren} + {@link renderOverview} and,
|
|
131
|
+
* optionally, {@link renderItemDetail}.
|
|
132
|
+
*/
|
|
133
|
+
export interface AiConfigurationCategoryRenderer {
|
|
134
|
+
/** Single-page body (e.g. General, Models, Prompts & Skills, Tools, Token Usage). */
|
|
135
|
+
renderPage?(ctx: AiConfigurationRenderContext): React.ReactNode;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Collection overview (category node selected): category-level settings +
|
|
139
|
+
* filter slot + item card grid + add action + empty state.
|
|
140
|
+
*/
|
|
141
|
+
renderOverview?(ctx: AiConfigurationRenderContext): React.ReactNode;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Collection children (items) shown inline in the tree and mirrored as
|
|
145
|
+
* overview cards. Order is preserved.
|
|
146
|
+
*/
|
|
147
|
+
getTreeChildren?(): AiConfigurationTreeItem[];
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Collection item detail (child selected). `itemId` is a {@link getTreeChildren} id.
|
|
151
|
+
* Optional: e.g. Variables have no editable detail (read-only card only).
|
|
152
|
+
*/
|
|
153
|
+
renderItemDetail?(itemId: string, ctx: AiConfigurationRenderContext): React.ReactNode;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The `ai-features.*` preference ids this category surfaces or manages. Used by the catch-all
|
|
157
|
+
* "Advanced" category to render only the preferences no dedicated category already covers, so that
|
|
158
|
+
* every AI preference stays reachable (AI preferences are hidden from the Settings UI) without being
|
|
159
|
+
* shown twice. Best-effort: a missing id merely shows up in the catch-all too, it is never lost.
|
|
160
|
+
*/
|
|
161
|
+
getOwnedPreferenceIds?(): string[];
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The "create item" action of a `collection` category (e.g. adding a custom agent or an MCP server).
|
|
165
|
+
* Rendered both on the overview and as an inline action on the category's tree node, so it is reachable
|
|
166
|
+
* without opening the page first. Omit it for categories whose items are not user-created.
|
|
167
|
+
*/
|
|
168
|
+
getAddAction?(ctx: AiConfigurationRenderContext): AiConfigurationAddDescriptor | undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A category's "create item" action: what to call it, and what to run. */
|
|
172
|
+
export interface AiConfigurationAddDescriptor {
|
|
173
|
+
readonly label: string;
|
|
174
|
+
readonly iconClass?: string;
|
|
175
|
+
run(): void;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface AiConfigurationTreeItem {
|
|
179
|
+
readonly id: string;
|
|
180
|
+
readonly label: string;
|
|
181
|
+
/**
|
|
182
|
+
* Short labels shown as badges next to the item's name, for what it *is* rather than what it is doing
|
|
183
|
+
* (which is {@link status}). Use {@link origins} for where the item came from, which is a badge of its
|
|
184
|
+
* own kind: it carries an icon and leads somewhere.
|
|
185
|
+
*/
|
|
186
|
+
readonly tags?: string[];
|
|
187
|
+
/**
|
|
188
|
+
* Where the item came from, when that is not the user - an AI registry entry, an Agent Plugin. Shown
|
|
189
|
+
* next to the item's name on the overview and, unchanged, on the item's detail header, so that
|
|
190
|
+
* provenance reads the same in both places.
|
|
191
|
+
*/
|
|
192
|
+
readonly origins?: AiConfigurationOrigin[];
|
|
193
|
+
/** Status badge shown on the overview card and the item detail header. */
|
|
194
|
+
readonly status?: AiConfigurationItemStatus;
|
|
195
|
+
/** Short text for the overview card. */
|
|
196
|
+
readonly description?: string;
|
|
197
|
+
/** Overrides the category icon for this item, e.g. a per-agent icon. */
|
|
198
|
+
readonly iconClass?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface AiConfigurationItemStatus {
|
|
202
|
+
readonly kind: 'on' | 'off' | 'warn' | 'error';
|
|
203
|
+
/** Short, human-readable badge text, e.g. "Enabled", "Ready". */
|
|
204
|
+
readonly label: string;
|
|
205
|
+
/** Optional longer hover detail; falls back to {@link label}. */
|
|
206
|
+
readonly tooltip?: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface AiConfigurationSearchProvider {
|
|
210
|
+
/**
|
|
211
|
+
* Called to (re)build this category's slice of the deep-search index.
|
|
212
|
+
* Rebuilt on the category's `onDidChange`.
|
|
213
|
+
*/
|
|
214
|
+
getSearchItems(): AiConfigurationSearchItem[];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface AiConfigurationSearchItem {
|
|
218
|
+
readonly label: string;
|
|
219
|
+
/** Small type badge, e.g. "MCP server", "Prompt", "Setting". */
|
|
220
|
+
readonly typeLabel: string;
|
|
221
|
+
readonly categoryId: string;
|
|
222
|
+
/** Where selecting the result navigates. */
|
|
223
|
+
readonly target: AiConfigurationSelection;
|
|
224
|
+
/** Extra text folded into the match key (ids, descriptions). */
|
|
225
|
+
readonly keywords?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface AiConfigurationSelection {
|
|
229
|
+
readonly categoryId: string;
|
|
230
|
+
/** `undefined` => single-page body / collection overview. */
|
|
231
|
+
readonly itemId?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Optional row/setting to scroll-to and flash after navigating. `rowId` is matched against the
|
|
234
|
+
* `data-ai-config-row-id` attribute the shared setting rows carry.
|
|
235
|
+
*/
|
|
236
|
+
readonly highlight?: { readonly rowId: string };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface AiConfigurationRenderContext {
|
|
240
|
+
/** Scope the rows read from and write to. Always `user` for now; per-scope editing is not wired up yet. */
|
|
241
|
+
readonly scope: AiConfigurationScope;
|
|
242
|
+
/** Select another node (used by cards, links, "used by" chips). */
|
|
243
|
+
navigate(selection: AiConfigurationSelection): void;
|
|
244
|
+
/** Ask the detail host to re-render the current category body. */
|
|
245
|
+
update(): void;
|
|
246
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource 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 { Emitter, Event } from '@theia/core';
|
|
18
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
import { AiConfigurationSelection } from './ai-configuration-category';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Shared selection bus for the AI Configuration view. The tree, the detail host,
|
|
23
|
+
* and renderers navigate through this single model instead of talking directly
|
|
24
|
+
* to each other.
|
|
25
|
+
*/
|
|
26
|
+
@injectable()
|
|
27
|
+
export class AiConfigurationSelectionModel {
|
|
28
|
+
|
|
29
|
+
protected selection: AiConfigurationSelection | undefined;
|
|
30
|
+
|
|
31
|
+
protected readonly onDidChangeSelectionEmitter = new Emitter<AiConfigurationSelection | undefined>();
|
|
32
|
+
readonly onDidChangeSelection: Event<AiConfigurationSelection | undefined> = this.onDidChangeSelectionEmitter.event;
|
|
33
|
+
|
|
34
|
+
getSelection(): AiConfigurationSelection | undefined {
|
|
35
|
+
return this.selection;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
select(selection: AiConfigurationSelection | undefined): void {
|
|
39
|
+
this.selection = selection;
|
|
40
|
+
this.onDidChangeSelectionEmitter.fire(selection);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource 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 { expect } from 'chai';
|
|
18
|
+
import { Command, CommandRegistry, PreferenceScope } from '@theia/core';
|
|
19
|
+
import {
|
|
20
|
+
AiConfigurationSettingCommandArgs,
|
|
21
|
+
AiConfigurationSettingCommandContribution,
|
|
22
|
+
AiConfigurationSettingCommands
|
|
23
|
+
} from './ai-configuration-setting-commands';
|
|
24
|
+
|
|
25
|
+
interface Handler { execute(args: unknown): unknown; isVisible?(args: unknown): boolean; isEnabled?(args: unknown): boolean }
|
|
26
|
+
|
|
27
|
+
function setup(): { handlers: Map<string, Handler>; setCalls: Array<[string, unknown, PreferenceScope, string | undefined]>; copied: string[] } {
|
|
28
|
+
const setCalls: Array<[string, unknown, PreferenceScope, string | undefined]> = [];
|
|
29
|
+
const copied: string[] = [];
|
|
30
|
+
const contribution = new AiConfigurationSettingCommandContribution();
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
(contribution as any).aiConfigurationService = {
|
|
33
|
+
set: (id: string, value: unknown, scope: PreferenceScope, resourceUri?: string) => { setCalls.push([id, value, scope, resourceUri]); return Promise.resolve(); }
|
|
34
|
+
};
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
(contribution as any).clipboardService = { writeText: (text: string) => { copied.push(text); } };
|
|
37
|
+
const handlers = new Map<string, Handler>();
|
|
38
|
+
contribution.registerCommands({ registerCommand: (command: Command, handler: Handler) => handlers.set(command.id, handler) } as unknown as CommandRegistry);
|
|
39
|
+
return { handlers, setCalls, copied };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const args: AiConfigurationSettingCommandArgs = { id: 'ai-features.chat.foo', value: true, scope: PreferenceScope.User };
|
|
43
|
+
|
|
44
|
+
describe('AiConfigurationSettingCommandContribution', () => {
|
|
45
|
+
|
|
46
|
+
it('resets a setting in the row scope via AiConfigurationService (not the Settings widget)', () => {
|
|
47
|
+
const { handlers, setCalls } = setup();
|
|
48
|
+
handlers.get(AiConfigurationSettingCommands.RESET.id)!.execute({ ...args, scope: PreferenceScope.Workspace, resourceUri: 'file:///ws' });
|
|
49
|
+
expect(setCalls).to.deep.equal([['ai-features.chat.foo', undefined, PreferenceScope.Workspace, 'file:///ws']]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('resets via the supplied callback for non-preference (e.g. per-agent) rows, without touching AiConfigurationService', () => {
|
|
53
|
+
const { handlers, setCalls } = setup();
|
|
54
|
+
let called = 0;
|
|
55
|
+
const reset = handlers.get(AiConfigurationSettingCommands.RESET.id)!;
|
|
56
|
+
expect(reset.isVisible!({ reset: () => called++ })).to.equal(true);
|
|
57
|
+
reset.execute({ reset: () => called++ });
|
|
58
|
+
expect(called).to.equal(1);
|
|
59
|
+
expect(setCalls).to.be.empty;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('hides the copy commands for non-preference (callback-only) rows', () => {
|
|
63
|
+
const { handlers } = setup();
|
|
64
|
+
expect(handlers.get(AiConfigurationSettingCommands.COPY_ID.id)!.isVisible!({ reset: () => undefined })).to.equal(false);
|
|
65
|
+
expect(handlers.get(AiConfigurationSettingCommands.COPY_JSON.id)!.isVisible!({ reset: () => undefined })).to.equal(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('copies the setting id and the JSON entry', () => {
|
|
69
|
+
const { handlers, copied } = setup();
|
|
70
|
+
handlers.get(AiConfigurationSettingCommands.COPY_ID.id)!.execute(args);
|
|
71
|
+
handlers.get(AiConfigurationSettingCommands.COPY_JSON.id)!.execute(args);
|
|
72
|
+
expect(copied).to.deep.equal(['ai-features.chat.foo', '"ai-features.chat.foo": true']);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('guards the commands against malformed arguments', () => {
|
|
76
|
+
const { handlers } = setup();
|
|
77
|
+
const reset = handlers.get(AiConfigurationSettingCommands.RESET.id)!;
|
|
78
|
+
expect(reset.isVisible!(args)).to.equal(true);
|
|
79
|
+
expect(reset.isVisible!({ id: 'x' })).to.equal(false); // missing scope
|
|
80
|
+
expect(reset.isVisible!(undefined)).to.equal(false);
|
|
81
|
+
});
|
|
82
|
+
});
|