Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/Migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ Please see the latest version (`master`) for the most up-to-date information. Pl

### General

_AI Configuration View_:

The AI Configuration view (`@theia/ai-ide`) has been reworked from a tabbed dock panel into a master–detail tree driven by a new public contribution point, `AiConfigurationCategory`.

- **New package dependency edges:** `@theia/ai-ide` and `@theia/ai-mcp` now depend on `@theia/ai-core-ui`, which hosts the contribution point (`AiConfigurationCategory`, `AiConfigurationCategoryRegistry`), the shared selection model (`AiConfigurationSelectionModel`), and the shared page primitives (`AiSettingsRow`, `AiConfigurationSection`, `SinglePageCategoryRenderer`, `CollectionCategoryRenderer`, …). If you consume the AI configuration UI, add `@theia/ai-core-ui` to your dependencies. `@theia/ai-core-ui` itself now depends on `@theia/preferences`, for the Settings UI's "Open settings.json" command (`PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR`) that the setting rows delegate to for complex values.
- **Removed widgets:** the per-tab widgets and their `WidgetFactory` registrations were removed — `AIAgentConfigurationWidget`, `AIVariableConfigurationWidget`, `ModelAliasesConfigurationWidget`, `AIToolsConfigurationWidget`, `AISkillsConfigurationWidget`, `AITokenUsageConfigurationWidget`, `AIPromptFragmentsConfigurationWidget` (all `@theia/ai-ide`) and `AIMCPConfigurationWidget` (`@theia/ai-mcp`). The dead base classes (`AIConfigurationBaseWidget`, `AICardGridConfigurationWidget`, `AI{List,Table,Hierarchical}ConfigurationWidget`) were removed as well. Each surface now lives in an `AiConfigurationCategory` contribution with a renderer built from the shared primitives.
Comment thread
ndoschek marked this conversation as resolved.
- **Removed components and API:** the widget-era building blocks `ConfigurationSection`, `ExpandableSection` and `PromptVariantRenderer` (`template-settings-renderer.tsx`) were removed from `@theia/ai-ide`; use `AiConfigurationSection` and `VariantSetCard` from `@theia/ai-core-ui` instead. `MCPServerEditor.openEditServer` was dropped from the interface — the MCP server category owns the edit flow now; `openAddServer` and `installFromEntry` are unchanged.
- **Adding custom categories:** adopters that added their own AI configuration tabs should now register an `AiConfigurationCategory` (bind it to the `AiConfigurationCategory` service identifier). Set `contributed: true` to have it grouped under "Contributed by extensions". See `examples/api-samples` (`SampleChatToolbarConfigurationCategory`) for a minimal end-to-end example.
- **Selection:** category/item navigation now routes through `AiConfigurationSelectionModel` (`@theia/ai-core-ui`). `AIConfigurationSelectionService` (`@theia/ai-ide`) is retained for the agent/alias domain events it still carries.
- **Stable entry points:** the command ids `aiConfiguration:open` (`OPEN_AI_CONFIG_VIEW`) and `aiConfiguration:openTools` (`OPEN_AI_CONFIG_VIEW_TOOLS`) and the chat-view toolbar button are unchanged. `OPEN_AI_CONFIG_VIEW(tabId)` still accepts the legacy per-tab widget ids and maps them onto the corresponding category ids.

_ESBuild_:

In addition to `webpack`, Theia is now also supporting [`ESBuild`](https://esbuild.github.io/) for bundling the application (frontend+backend). We will soon deprecate and then remove the `webpack` bundling option. Adopters can already use the ESBuild based bundler simply by deleting their `webpack.config.js`, which will automatically generate an `esbuild.mjs` file upon the next build.
Expand Down
1 change: 1 addition & 0 deletions examples/api-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@theia/ai-chat-ui": "1.74.0",
"@theia/ai-code-completion": "1.74.0",
"@theia/ai-core": "1.74.0",
"@theia/ai-core-ui": "1.74.0",
"@theia/ai-mcp": "1.74.0",
"@theia/ai-mcp-server": "1.74.0",
"@theia/core": "1.74.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Emitter, Event, nls } from '@theia/core';
import { codicon } from '@theia/core/lib/browser';
import { DisposableCollection } from '@theia/core/lib/common';
import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import {
AiConfigurationCategory,
AiConfigurationRenderContext,
AiConfigurationSearchItem,
AiConfigurationSearchProvider
} from '@theia/ai-core-ui/lib/browser/ai-configuration/ai-configuration-category';
import { SinglePageCategoryRenderer } from '@theia/ai-core-ui/lib/browser/ai-configuration/renderers/single-page-category-renderer';
import { AiConfigurationSection } from '@theia/ai-core-ui/lib/browser/ai-configuration/components/ai-configuration-primitives';
import { AiSettingsRow } from '@theia/ai-core-ui/lib/browser/ai-configuration/components/ai-settings-row';
import { AiSettingsRowService } from '@theia/ai-core-ui/lib/browser/ai-configuration/components/ai-settings-row-service';
import { SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF } from '../chat/sample-chat-node-toolbar-preferences';

/**
* Sample `single-page` AI configuration category contributed by `@theia/api-samples`. It sets
* `contributed: true`, so it appears under "Contributed by extensions" in the AI Configuration view, and it
* surfaces the {@link SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF} setting that gates the sample chat-response
* action button — so toggling it here has a real, observable effect on the chat UI. It uses only the public
* {@link AiConfigurationCategory} contribution point and the shared primitives, proving an extension can add a
* category with no privileged access, render like the built-ins, and participate in deep search.
*/
@injectable()
export class SampleChatToolbarConfigurationCategory extends SinglePageCategoryRenderer implements AiConfigurationCategory, AiConfigurationSearchProvider {

readonly id = 'sample-extension';
readonly label = nls.localize('theia/api-samples/sampleExtension/label', 'API Samples');
readonly description = nls.localize('theia/api-samples/sampleExtension/description',
'Example settings contributed by the @theia/api-samples extension.');
readonly iconClass = codicon('lightbulb');
readonly kind = 'single-page' as const;
readonly contributed = true;

@inject(AiSettingsRowService)
protected readonly settingsRowService: AiSettingsRowService;

protected readonly onDidChangeEmitter = new Emitter<void>();
readonly onDidChange: Event<void> = this.onDidChangeEmitter.event;
protected readonly toDispose = new DisposableCollection(this.onDidChangeEmitter);

get renderer(): this {
return this;
}

get search(): AiConfigurationSearchProvider {
return this;
}

@postConstruct()
protected init(): void {
this.toDispose.push(this.settingsRowService.onPreferenceChanged(() => this.onDidChangeEmitter.fire()));
}

dispose(): void {
this.toDispose.dispose();
}

protected renderSections(ctx: AiConfigurationRenderContext): React.ReactNode {
return <AiConfigurationSection title={nls.localizeByDefault('Chat')}>
<AiSettingsRow
service={this.settingsRowService}
preferenceId={SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF}
scope={ctx.scope}
control={{ type: 'boolean' }}
onDidChange={ctx.update}
/>
</AiConfigurationSection>;
}

getSearchItems(): AiConfigurationSearchItem[] {
const described = this.settingsRowService.describe(SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF);
return [{
label: described.label ?? SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF,
typeLabel: nls.localizeByDefault('Setting'),
categoryId: this.id,
target: { categoryId: this.id, highlight: { rowId: SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF } },
keywords: SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF
}];
}
}

export function bindSampleChatToolbarConfigurationCategory(bind: interfaces.Bind): void {
bind(SampleChatToolbarConfigurationCategory).toSelf().inSingletonScope();
bind(AiConfigurationCategory).toService(SampleChatToolbarConfigurationCategory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { bindOriginalStateTestAgentContribution } from './chat/original-state-te
import { bindCustomResponseContentRendererContribution } from './chat/custom-response-content-agent-contribution';
import { bindSampleChatCommandContribution } from './chat/sample-chat-command-contribution';
import { bindSampleCodeCompletionVariableContribution } from './ai-code-completion/sample-code-completion-variable-contribution';
import { bindSampleChatNodeToolbarPreferences } from './chat/sample-chat-node-toolbar-preferences';
import { bindSampleChatToolbarConfigurationCategory } from './ai-configuration/sample-chat-toolbar-configuration-category';
import { bindSamplePreferenceContribution } from './preferences/sample-preferences-contribution';
import { MCPFrontendContribution } from '@theia/ai-mcp-server/lib/browser/mcp-frontend-contribution';
import { SampleFrontendMCPContribution } from './mcp/sample-frontend-mcp-contribution';
Expand Down Expand Up @@ -73,6 +75,8 @@ export default new ContainerModule((
bindSampleFileSystemCapabilitiesCommands(bind);
rebindOVSXClientFactory(rebind);
bindSampleCodeCompletionVariableContribution(bind);
bindSampleChatNodeToolbarPreferences(bind);
bindSampleChatToolbarConfigurationCategory(bind);
bindSamplePreferenceContribution(bind);
bind(CommandContribution).to(TelemetrySampleCommandContribution).inSingletonScope();
bind(MCPFrontendContribution).to(SampleFrontendMCPContribution).inSingletonScope();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,35 @@ import {
RequestNode,
ResponseNode
} from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
import { Emitter, PreferenceService } from '@theia/core/lib/common';
import { interfaces } from '@theia/core/shared/inversify';
import { SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF } from './sample-chat-node-toolbar-preferences';

export function bindChatNodeToolbarActionContribution(bind: interfaces.Bind): void {
bind(ChatNodeToolbarActionContribution).toDynamicValue(context => ({
getToolbarActions: (args: RequestNode | ResponseNode) => {
if (isResponseNode(args)) {
return [{
commandId: 'sample-command',
icon: 'codicon codicon-feedback',
tooltip: 'API Samples: Example command'
}];
} else {
bind(ChatNodeToolbarActionContribution).toDynamicValue(context => {
const preferences = context.container.get<PreferenceService>(PreferenceService);
// Fire `onDidChange` when the gating setting is toggled, so the chat view re-renders its node
// toolbars live (rather than only picking up the change on the next response).
const onDidChangeEmitter = new Emitter<void>();
preferences.onPreferenceChanged(event => {
if (event.preferenceName === SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF) {
onDidChangeEmitter.fire();
}
});
return {
onDidChange: onDidChangeEmitter.event,
getToolbarActions: (args: RequestNode | ResponseNode) => {
// Gated by a contributed setting (surfaced in the AI Configuration view): when disabled, the
// sample action is not rendered on response nodes.
if (isResponseNode(args) && preferences.get<boolean>(SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF, true)) {
return [{
commandId: 'sample-command',
icon: 'codicon codicon-feedback',
tooltip: 'API Samples: Example command'
}];
}
return [];
}
}
}));
};
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { nls, PreferenceContribution } from '@theia/core';
import { interfaces } from '@theia/core/shared/inversify';
import { PreferenceSchema } from '@theia/core/lib/common/preferences/preference-schema';

/**
* Whether the API Samples extension shows its example action button on chat response nodes
* (see {@link bindChatNodeToolbarActionContribution}). Surfaced as a contributed setting in the AI
* Configuration view so toggling it has a real, observable effect on the chat UI.
*/
export const SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF = 'sampleChatNodeToolbar.enabled';

export const sampleChatNodeToolbarPreferenceSchema: PreferenceSchema = {
properties: {
[SAMPLE_CHAT_NODE_TOOLBAR_ENABLED_PREF]: {
type: 'boolean',
title: nls.localize('theia/api-samples/sampleChatNodeToolbar/enabled/title', 'Show Sample Chat Response Action'),
markdownDescription: nls.localize('theia/api-samples/sampleChatNodeToolbar/enabled/description',
'When enabled, the `@theia/api-samples` extension adds an example action button to each chat response. '
+ 'Turn it off to hide that sample action.'),
default: true
}
}
};

export function bindSampleChatNodeToolbarPreferences(bind: interfaces.Bind): void {
bind(PreferenceContribution).toConstantValue({ schema: sampleChatNodeToolbarPreferenceSchema });
}
3 changes: 3 additions & 0 deletions examples/api-samples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
{
"path": "../../packages/ai-core"
},
{
"path": "../../packages/ai-core-ui"
},
{
"path": "../../packages/ai-mcp"
},
Expand Down
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion packages/ai-anthropic/src/common/anthropic-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { AI_CORE_PREFERENCES_TITLE, PREFERENCE_NAME_SERVER_SIDE_COMPACTION } from '@theia/ai-core/lib/common/ai-core-preferences';
import {
AI_CORE_PREFERENCES_TITLE, MODEL_PROVIDER_TYPE_DETAIL, ModelProviderTypeDetail, PREFERENCE_NAME_SERVER_SIDE_COMPACTION
} from '@theia/ai-core/lib/common/ai-core-preferences';
import { SERVER_SIDE_COMPACTION_TOKEN_THRESHOLD_MINIMUM } from '@theia/ai-core/lib/common/language-model';
import { LINUX_ENV_HINT, nls, PreferenceSchema } from '@theia/core';

Expand All @@ -28,6 +30,7 @@ export const AnthropicPreferencesSchema: PreferenceSchema = {
properties: {
[API_KEY_PREF]: {
type: 'string',
typeDetails: { [MODEL_PROVIDER_TYPE_DETAIL]: { label: 'Anthropic' } satisfies ModelProviderTypeDetail },
markdownDescription: nls.localize('theia/ai/anthropic/apiKey/description',
'Enter an API Key of your official Anthropic Account. **Please note:** By using this preference the Anthropic API key will be stored in clear text\
on the machine running Theia. Use the environment variable `ANTHROPIC_API_KEY` to set the key securely.') + LINUX_ENV_HINT,
Expand Down Expand Up @@ -77,6 +80,9 @@ export const AnthropicPreferencesSchema: PreferenceSchema = {
},
[CUSTOM_ENDPOINTS_PREF]: {
type: 'array',
typeDetails: {
[MODEL_PROVIDER_TYPE_DETAIL]: { label: nls.localize('theia/ai/anthropic/customProvider/label', '{0} (Custom)', 'Anthropic') } satisfies ModelProviderTypeDetail
},
title: AI_CORE_PREFERENCES_TITLE,
markdownDescription: nls.localize('theia/ai/anthropic/customEndpoints/mdDescription',
'Integrate custom models compatible with the Anthropic API. The required attributes are `model` and `url`.\
Expand Down
Loading
Loading