diff --git a/packages/ai-chat/src/common/chat-model.ts b/packages/ai-chat/src/common/chat-model.ts index a7b192f820af9..a71c273f04110 100644 --- a/packages/ai-chat/src/common/chat-model.ts +++ b/packages/ai-chat/src/common/chat-model.ts @@ -767,3 +767,10 @@ class ChatResponseModelImpl implements ChatResponseModel { return this._isError; } } + +export class ErrorChatResponseModelImpl extends ChatResponseModelImpl { + constructor(requestId: string, error: Error, agentId?: string) { + super(requestId, agentId); + this.error(error); + } +} diff --git a/packages/ai-chat/src/common/chat-service.ts b/packages/ai-chat/src/common/chat-service.ts index 99c167e005bc9..3aeb8dcd612a0 100644 --- a/packages/ai-chat/src/common/chat-service.ts +++ b/packages/ai-chat/src/common/chat-service.ts @@ -26,9 +26,10 @@ import { ChatRequest, ChatRequestModel, ChatResponseModel, + ErrorChatResponseModelImpl, } from './chat-model'; import { ChatAgentService } from './chat-agent-service'; -import { Emitter, ILogger } from '@theia/core'; +import { Emitter, ILogger, generateUuid } from '@theia/core'; import { ChatRequestParser } from './chat-request-parser'; import { ChatAgent, ChatAgentLocation } from './chat-agents'; import { ParsedChatRequestAgentPart, ParsedChatRequestVariablePart, ParsedChatRequest } from './parsed-chat-request'; @@ -161,6 +162,16 @@ export class ChatServiceImpl implements ChatService { const parsedRequest = this.chatRequestParser.parseChatRequest(request, session.model.location); const agent = this.getAgent(parsedRequest); + if (agent === undefined) { + const error = 'No ChatAgents available to handle request!'; + this.logger.error(error); + const chatResponseModel = new ErrorChatResponseModelImpl(generateUuid(), new Error(error)); + return { + requestCompleted: Promise.reject(error), + responseCreated: Promise.reject(error), + responseCompleted: Promise.resolve(chatResponseModel), + }; + } const requestModel = session.model.addRequest(parsedRequest, agent?.id); for (const part of parsedRequest.parts) { diff --git a/packages/ai-code-completion/src/browser/ai-code-completion-provider.ts b/packages/ai-code-completion/src/browser/ai-code-completion-provider.ts index b320dcbb878e5..b627ace9a0419 100644 --- a/packages/ai-code-completion/src/browser/ai-code-completion-provider.ts +++ b/packages/ai-code-completion/src/browser/ai-code-completion-provider.ts @@ -21,6 +21,7 @@ import { injectable, inject } from '@theia/core/shared/inversify'; import { PreferenceService } from '@theia/core/lib/browser'; import { CancellationTokenSource } from '@theia/core'; import { PREF_AI_CODE_COMPLETION_PRECOMPUTE } from './ai-code-completion-preference'; +import { AgentService } from '@theia/ai-core'; interface WithArgs { args: T; @@ -33,11 +34,17 @@ export class AICodeCompletionProvider implements monaco.languages.CompletionItem @inject(CodeCompletionAgent) protected readonly agent: CodeCompletionAgent; + @inject(AgentService) + private readonly agentService: AgentService; + @inject(PreferenceService) protected readonly preferenceService: PreferenceService; async provideCompletionItems(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.CompletionContext, token: monaco.CancellationToken): Promise { + if (!this.agentService.isEnabled(this.agent.id)) { + return; + } if (!this.preferenceService.get(PREF_AI_CODE_COMPLETION_PRECOMPUTE, false)) { const result = { suggestions: [{ diff --git a/packages/ai-code-completion/src/browser/ai-code-inline-completion-provider.ts b/packages/ai-code-completion/src/browser/ai-code-inline-completion-provider.ts index 22fb3847513e8..f47398c5bd412 100644 --- a/packages/ai-code-completion/src/browser/ai-code-inline-completion-provider.ts +++ b/packages/ai-code-completion/src/browser/ai-code-inline-completion-provider.ts @@ -19,14 +19,20 @@ import * as monaco from '@theia/monaco-editor-core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { CodeCompletionAgent } from '../common/code-completion-agent'; import { CompletionTriggerKind } from '@theia/core/shared/vscode-languageserver-protocol'; +import { AgentService } from '@theia/ai-core'; @injectable() export class AICodeInlineCompletionsProvider implements monaco.languages.InlineCompletionsProvider { @inject(CodeCompletionAgent) protected readonly agent: CodeCompletionAgent; + @inject(AgentService) + private readonly agentService: AgentService; async provideInlineCompletions(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.InlineCompletionContext, token: monaco.CancellationToken): Promise { + if (!this.agentService.isEnabled(this.agent.id)) { + return; + } if (this.agent.provideInlineCompletions) { return this.agent.provideInlineCompletions(model, position, context, token); } diff --git a/packages/ai-core/src/common/agent-service.ts b/packages/ai-core/src/common/agent-service.ts index e17097bb8fda4..8a44777749ba1 100644 --- a/packages/ai-core/src/common/agent-service.ts +++ b/packages/ai-core/src/common/agent-service.ts @@ -13,7 +13,7 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable, named } from '@theia/core/shared/inversify'; import { ContributionProvider } from '@theia/core'; import { Agent } from './agent'; @@ -57,17 +57,16 @@ export class AgentServiceImpl implements AgentService { protected disabledAgents = new Set(); - protected agents: Agent[] = []; + protected _agents: Agent[] = []; - @postConstruct() - init(): void { - for (const agent of this.agentsProvider.getContributions()) { - this.registerAgent(agent); - } + private get agents(): Agent[] { + // We can't collect the contributions at @postConstruct because this will lead to a circular dependency + // with agents reusing the chat agent service (e.g. orchestrator) which in turn injects the agent service + return [...this.agentsProvider.getContributions(), ...this._agents]; } registerAgent(agent: Agent): void { - this.agents.push(agent); + this._agents.push(agent); } getAgents(): Agent[] { diff --git a/packages/ai-terminal/src/browser/ai-terminal-contribution.ts b/packages/ai-terminal/src/browser/ai-terminal-contribution.ts index 58a8156ebff4b..715932af62d62 100644 --- a/packages/ai-terminal/src/browser/ai-terminal-contribution.ts +++ b/packages/ai-terminal/src/browser/ai-terminal-contribution.ts @@ -23,6 +23,7 @@ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-con import { TerminalWidgetImpl } from '@theia/terminal/lib/browser/terminal-widget-impl'; import { AiTerminalAgent } from './ai-terminal-agent'; import { AICommandHandlerFactory } from '@theia/ai-core/lib/browser/ai-command-handler-factory'; +import { AgentService } from '@theia/ai-core'; const AI_TERMINAL_COMMAND = { id: 'ai-terminal:open', @@ -41,6 +42,9 @@ export class AiTerminalCommandContribution implements CommandContribution, MenuC @inject(AICommandHandlerFactory) protected commandHandlerFactory: AICommandHandlerFactory; + @inject(AgentService) + private readonly agentService: AgentService; + registerKeybindings(keybindings: KeybindingRegistry): void { keybindings.registerKeybinding({ command: AI_TERMINAL_COMMAND.id, @@ -57,7 +61,7 @@ export class AiTerminalCommandContribution implements CommandContribution, MenuC registerCommands(commands: CommandRegistry): void { commands.registerCommand(AI_TERMINAL_COMMAND, this.commandHandlerFactory({ execute: () => { - if (this.terminalService.currentTerminal instanceof TerminalWidgetImpl) { + if (this.terminalService.currentTerminal instanceof TerminalWidgetImpl && this.agentService.isEnabled(this.terminalAgent.id)) { new AiTerminalChatWidget( this.terminalService.currentTerminal, this.terminalAgent