Skip to content

Commit d6bb543

Browse files
feat: dynamic welcome page improvements
- ensure at least one language model is configured (e.g. API Key present) - report configuration errors for language models, to help the user identify what they need to do - unset the Ollama default models (not relevant defaults; also makes it impossible to distinguish whether ollama is actually configured) - remove the fallback "Default Agent" - make sure the user explicitly selects its own "Default Agent" (with some recommendations) - improve error messages when using an invalid/disabled Agent
1 parent 0cc0062 commit d6bb543

16 files changed

+640
-34
lines changed

packages/ai-anthropic/src/node/anthropic-language-models-manager-impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export class AnthropicLanguageModelsManagerImpl implements AnthropicLanguageMode
119119
protected getStatusForApiKey(effectiveApiKey: string | undefined): LanguageModelStatus {
120120
return effectiveApiKey
121121
? { status: 'ready' }
122-
: { status: 'unavailable', message: 'No API key set' };
122+
: { status: 'unavailable', message: 'No Anthropic API key set' };
123123
}
124124
}
125125

packages/ai-chat-ui/src/browser/chat-tree-view/chat-view-tree-widget.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
} from '@theia/ai-chat';
3030
import { AIVariableService } from '@theia/ai-core';
3131
import { AIActivationService } from '@theia/ai-core/lib/browser';
32-
import { CommandRegistry, ContributionProvider, Disposable, DisposableCollection, Emitter } from '@theia/core';
32+
import { CommandRegistry, ContributionProvider, Disposable, DisposableCollection, Emitter, Event } from '@theia/core';
3333
import {
3434
codicon,
3535
CompositeTreeNode,
@@ -89,6 +89,10 @@ export const ChatWelcomeMessageProvider = Symbol('ChatWelcomeMessageProvider');
8989
export interface ChatWelcomeMessageProvider {
9090
renderWelcomeMessage?(): React.ReactNode;
9191
renderDisabledMessage?(): React.ReactNode;
92+
readonly hasReadyModels?: boolean;
93+
readonly modelRequirementBypassed?: boolean;
94+
readonly defaultAgent?: string;
95+
readonly onStateChanged?: Event<void>;
9296
}
9397

9498
@injectable()
@@ -210,6 +214,14 @@ export class ChatViewTreeWidget extends TreeWidget {
210214
})
211215
]);
212216

217+
if (this.welcomeMessageProvider?.onStateChanged) {
218+
this.toDispose.push(
219+
this.welcomeMessageProvider.onStateChanged(() => {
220+
this.update();
221+
})
222+
);
223+
}
224+
213225
// Initialize lastScrollTop with current scroll position
214226
this.lastScrollTop = this.getCurrentScrollTop(undefined);
215227
}

packages/ai-chat-ui/src/browser/chat-view-widget.tsx

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import { CommandService, deepClone, Emitter, Event, MessageService, PreferenceSe
1717
import { ChatRequest, ChatRequestModel, ChatService, ChatSession, isActiveSessionChangedEvent, MutableChatModel } from '@theia/ai-chat';
1818
import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, StatefulWidget } from '@theia/core/lib/browser';
1919
import { nls } from '@theia/core/lib/common/nls';
20-
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
20+
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
2121
import { AIChatInputWidget } from './chat-input-widget';
22-
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
22+
import { ChatViewTreeWidget, ChatWelcomeMessageProvider } from './chat-tree-view/chat-view-tree-widget';
2323
import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
2424
import { AIVariableResolutionRequest } from '@theia/ai-core';
2525
import { ProgressBarFactory } from '@theia/core/lib/browser/progress-bar-factory';
2626
import { FrontendVariableService } from '@theia/ai-core/lib/browser';
27+
import { FrontendLanguageModelRegistry } from '@theia/ai-core/lib/common';
2728

2829
export namespace ChatViewWidget {
2930
export interface State {
@@ -59,6 +60,12 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
5960
@inject(ProgressBarFactory)
6061
protected readonly progressBarFactory: ProgressBarFactory;
6162

63+
@inject(FrontendLanguageModelRegistry)
64+
protected readonly languageModelRegistry: FrontendLanguageModelRegistry;
65+
66+
@inject(ChatWelcomeMessageProvider) @optional()
67+
protected readonly welcomeProvider?: ChatWelcomeMessageProvider;
68+
6269
protected chatSession: ChatSession;
6370

6471
protected _state: ChatViewWidget.State = { locked: false, temporaryLocked: false };
@@ -114,17 +121,50 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
114121

115122
this.initListeners();
116123

117-
this.inputWidget.setEnabled(this.activationService.isActive);
118-
this.treeWidget.setEnabled(this.activationService.isActive);
124+
this.updateInputEnabledState();
119125

120126
this.activationService.onDidChangeActiveStatus(change => {
121127
this.treeWidget.setEnabled(change);
122-
this.inputWidget.setEnabled(change);
128+
this.updateInputEnabledState();
123129
this.update();
124130
});
131+
132+
this.toDispose.push(
133+
this.languageModelRegistry.onChange(() => {
134+
this.updateInputEnabledState();
135+
})
136+
);
137+
138+
if (this.welcomeProvider?.onStateChanged) {
139+
this.toDispose.push(this.welcomeProvider.onStateChanged(() => {
140+
this.updateInputEnabledState();
141+
this.update();
142+
}));
143+
}
144+
125145
this.toDispose.push(this.progressBarFactory({ container: this.node, insertMode: 'prepend', locationId: 'ai-chat' }));
126146
}
127147

148+
protected async updateInputEnabledState(): Promise<void> {
149+
const shouldEnable = this.activationService.isActive && await this.shouldEnableInput();
150+
this.inputWidget.setEnabled(shouldEnable);
151+
this.treeWidget.setEnabled(this.activationService.isActive);
152+
}
153+
154+
protected async shouldEnableInput(): Promise<boolean> {
155+
if (!this.welcomeProvider) {
156+
return true;
157+
}
158+
const hasReadyModels = await this.hasReadyLanguageModels();
159+
const modelRequirementBypassed = this.welcomeProvider.modelRequirementBypassed ?? false;
160+
return hasReadyModels || modelRequirementBypassed;
161+
}
162+
163+
protected async hasReadyLanguageModels(): Promise<boolean> {
164+
const models = await this.languageModelRegistry.getLanguageModels();
165+
return models.some(model => model.status.status === 'ready');
166+
}
167+
128168
protected initListeners(): void {
129169
this.toDispose.pushAll([
130170
this.chatService.onSessionEvent(event => {

packages/ai-chat/src/browser/frontend-chat-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class FrontendChatServiceImpl extends ChatServiceImpl {
5151
const configuredDefaultChatAgentId = this.preferenceService.get<string>(DEFAULT_CHAT_AGENT_PREF, undefined);
5252
const configuredDefaultChatAgent = configuredDefaultChatAgentId ? this.chatAgentService.getAgent(configuredDefaultChatAgentId) : undefined;
5353
if (configuredDefaultChatAgentId && !configuredDefaultChatAgent) {
54-
this.logger.warn(`The configured default chat agent with id '${configuredDefaultChatAgentId}' does not exist.`);
54+
this.logger.warn(`The configured default chat agent with id '${configuredDefaultChatAgentId}' does not exist or is disabled.`);
5555
}
5656
return configuredDefaultChatAgent;
5757
}

packages/ai-chat/src/common/ai-chat-preferences.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,31 @@ import { nls, PreferenceSchema } from '@theia/core';
1919

2020
export const DEFAULT_CHAT_AGENT_PREF = 'ai-features.chat.defaultChatAgent';
2121
export const PIN_CHAT_AGENT_PREF = 'ai-features.chat.pinChatAgent';
22+
export const BYPASS_MODEL_REQUIREMENT_PREF = 'ai-features.chat.bypassModelRequirement';
2223

2324
export const aiChatPreferences: PreferenceSchema = {
2425
properties: {
2526
[DEFAULT_CHAT_AGENT_PREF]: {
2627
type: 'string',
2728
description: nls.localize('theia/ai/chat/defaultAgent/description',
2829
'Optional: <agent-name> of the Chat Agent that shall be invoked, if no agent is explicitly mentioned with @<agent-name> in the user query. \
29-
If no Default Agent is configured, Theia´s defaults will be applied.'),
30+
If no Default Agent is configured, Theia´s defaults will be applied.'),
3031
title: AI_CORE_PREFERENCES_TITLE,
3132
},
3233
[PIN_CHAT_AGENT_PREF]: {
3334
type: 'boolean',
3435
description: nls.localize('theia/ai/chat/pinChatAgent/description',
3536
'Enable agent pinning to automatically keep a mentioned chat agent active across prompts, reducing the need for repeated mentions.\
36-
You can manually unpin or switch agents anytime.'),
37+
You can manually unpin or switch agents anytime.'),
3738
default: true,
3839
title: AI_CORE_PREFERENCES_TITLE,
40+
},
41+
[BYPASS_MODEL_REQUIREMENT_PREF]: {
42+
type: 'boolean',
43+
description: nls.localize('theia/ai/chat/bypassModelRequirement/description',
44+
'Bypass the language model requirement check. Enable this if you are using external agents (e.g., Claude Code) that do not require Theia language models.'),
45+
default: false,
46+
title: AI_CORE_PREFERENCES_TITLE,
3947
}
4048
}
4149
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2025 EclipseSource GmbH.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
export const ChatAgentRecommendationService = Symbol('ChatAgentRecommendationService');
18+
19+
export interface RecommendedAgent {
20+
readonly id: string;
21+
readonly label: string;
22+
readonly description?: string;
23+
}
24+
25+
/**
26+
* Service that provides recommended chat agents to be displayed in the welcome screen.
27+
* This allows different Theia-based products to customize which agents are shown as quick actions.
28+
*/
29+
export interface ChatAgentRecommendationService {
30+
/**
31+
* Returns the list of recommended agents to display in the welcome screen.
32+
* These agents will be shown as quick-action buttons that users can click to set as default.
33+
*/
34+
getRecommendedAgents(): RecommendedAgent[];
35+
}

packages/ai-chat/src/common/chat-service.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ export class ChatServiceImpl implements ChatService {
283283
session.pinnedAgent = agent;
284284

285285
if (agent === undefined) {
286-
const error = 'No ChatAgents available to handle request!';
286+
const error = 'No agent was found to handle this request. ' +
287+
'Please ensure you have configured a default agent in the preferences and that the agent is enabled in the AI Configuration view. ' +
288+
'Alternatively, mention a specific agent with @AgentName.';
287289
this.logger.error(error);
288290
const chatResponseModel = new ErrorChatResponseModel(generateUuid(), new Error(error));
289291
return {
@@ -407,18 +409,13 @@ export class ChatServiceImpl implements ChatService {
407409
if (agentPart) {
408410
return this.chatAgentService.getAgent(agentPart.agentId);
409411
}
410-
let chatAgent = undefined;
411412
if (this.defaultChatAgentId) {
412-
chatAgent = this.chatAgentService.getAgent(this.defaultChatAgentId.id);
413+
return this.chatAgentService.getAgent(this.defaultChatAgentId.id);
413414
}
414-
if (!chatAgent && this.fallbackChatAgentId) {
415-
chatAgent = this.chatAgentService.getAgent(this.fallbackChatAgentId.id);
415+
if (this.fallbackChatAgentId) {
416+
return this.chatAgentService.getAgent(this.fallbackChatAgentId.id);
416417
}
417-
if (chatAgent) {
418-
return chatAgent;
419-
}
420-
this.logger.warn('Neither the default chat agent nor the fallback chat agent are configured or available. Falling back to the first registered agent');
421-
return this.chatAgentService.getAgents()[0] ?? undefined;
418+
return undefined;
422419
}
423420

424421
protected getMentionedAgent(parsedRequest: ParsedChatRequest): ParsedChatRequestAgentPart | undefined {

packages/ai-chat/src/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// *****************************************************************************
1616
export * from './chat-agents';
1717
export * from './chat-agent-service';
18+
export * from './chat-agent-recommendation-service';
1819
export * from './chat-model';
1920
export * from './chat-model-serialization';
2021
export * from './chat-content-deserializer';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2025 EclipseSource GmbH.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { injectable } from '@theia/core/shared/inversify';
18+
import { ChatAgentRecommendationService, RecommendedAgent } from '@theia/ai-chat/lib/common';
19+
import { nls } from '@theia/core/lib/common/nls';
20+
21+
@injectable()
22+
export class DefaultChatAgentRecommendationService implements ChatAgentRecommendationService {
23+
24+
getRecommendedAgents(): RecommendedAgent[] {
25+
return [
26+
{
27+
id: 'Coder',
28+
label: nls.localize('theia/ai/chat/agent/coder', 'Coder'),
29+
description: nls.localize('theia/ai/chat/agent/coder/description', 'Code generation and modification')
30+
},
31+
{
32+
id: 'Architect',
33+
label: nls.localize('theia/ai/chat/agent/architect', 'Architect'),
34+
description: nls.localize('theia/ai/chat/agent/architect/description', 'High-level design and architecture')
35+
},
36+
{
37+
id: 'Universal',
38+
label: nls.localize('theia/ai/chat/agent/universal', 'Universal'),
39+
description: nls.localize('theia/ai/chat/agent/universal/description', 'General-purpose assistant')
40+
}
41+
];
42+
}
43+
}

packages/ai-ide/src/browser/frontend-module.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import '../../src/browser/style/index.css';
1818

1919
import { ContainerModule } from '@theia/core/shared/inversify';
20-
import { ChatAgent, DefaultChatAgentId, FallbackChatAgentId } from '@theia/ai-chat/lib/common';
20+
import { ChatAgent, ChatAgentRecommendationService } from '@theia/ai-chat/lib/common';
2121
import { Agent, AIVariableContribution, bindToolProvider } from '@theia/ai-core/lib/common';
2222
import { ArchitectAgent } from './architect-agent';
2323
import { CoderAgent } from './coder-agent';
@@ -60,8 +60,8 @@ import {
6060
DefaultFileChangeSetTitleProvider,
6161
ReplaceContentInFileFunctionHelperV2
6262
} from './file-changeset-functions';
63-
import { OrchestratorChatAgent, OrchestratorChatAgentId } from '../common/orchestrator-chat-agent';
64-
import { UniversalChatAgent, UniversalChatAgentId } from '../common/universal-chat-agent';
63+
import { OrchestratorChatAgent } from '../common/orchestrator-chat-agent';
64+
import { UniversalChatAgent } from '../common/universal-chat-agent';
6565
import { AppTesterChatAgent } from './app-tester-chat-agent';
6666
import { GitHubChatAgent } from './github-chat-agent';
6767
import { CommandChatAgent } from '../common/command-chat-agents';
@@ -78,6 +78,7 @@ import { TemplatePreferenceContribution } from './template-preference-contributi
7878
import { AIMCPConfigurationWidget } from './ai-configuration/mcp-configuration-widget';
7979
import { ChatWelcomeMessageProvider } from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
8080
import { IdeChatWelcomeMessageProvider } from './ide-chat-welcome-message-provider';
81+
import { DefaultChatAgentRecommendationService } from './default-chat-agent-recommendation-service';
8182
import { AITokenUsageConfigurationWidget } from './ai-configuration/token-usage-configuration-widget';
8283
import { TaskContextSummaryVariableContribution } from './task-background-summary-variable';
8384
import { GitHubRepoVariableContribution } from './github-repo-variable-contribution';
@@ -146,10 +147,8 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
146147
bind(Agent).toService(CommandChatAgent);
147148
bind(ChatAgent).toService(CommandChatAgent);
148149

149-
bind(DefaultChatAgentId).toConstantValue({ id: OrchestratorChatAgentId });
150-
bind(FallbackChatAgentId).toConstantValue({ id: UniversalChatAgentId });
151-
152-
bind(ChatWelcomeMessageProvider).to(IdeChatWelcomeMessageProvider);
150+
bind(ChatWelcomeMessageProvider).to(IdeChatWelcomeMessageProvider).inSingletonScope();
151+
bind(ChatAgentRecommendationService).to(DefaultChatAgentRecommendationService).inSingletonScope();
153152

154153
bindToolProvider(GetWorkspaceFileList, bind);
155154
bindToolProvider(FileContentFunction, bind);

0 commit comments

Comments
 (0)