Skip to content

Commit

Permalink
fix: disabling an agent also disables its UIContribution
Browse files Browse the repository at this point in the history
- agent service had a loop so that a single instance was not created
- code completion agents now check whether they are active
- terminal agent now check whether it is active
- chat-service correctly handles no available agents now

fixes eclipse-theia#14167
  • Loading branch information
eneufeld committed Sep 17, 2024
1 parent 1baa1a6 commit 204b8af
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 10 deletions.
7 changes: 7 additions & 0 deletions packages/ai-chat/src/common/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
13 changes: 12 additions & 1 deletion packages/ai-chat/src/common/chat-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = unknown[]> {
args: T;
Expand All @@ -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<monaco.languages.CompletionList | undefined> {
if (!this.agentService.isEnabled(this.agent.id)) {
return;
}
if (!this.preferenceService.get(PREF_AI_CODE_COMPLETION_PRECOMPUTE, false)) {
const result = {
suggestions: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<monaco.languages.InlineCompletions | undefined> {
if (!this.agentService.isEnabled(this.agent.id)) {
return;
}
if (this.agent.provideInlineCompletions) {
return this.agent.provideInlineCompletions(model, position, context, token);
}
Expand Down
15 changes: 7 additions & 8 deletions packages/ai-core/src/common/agent-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -57,17 +57,16 @@ export class AgentServiceImpl implements AgentService {

protected disabledAgents = new Set<string>();

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[] {
Expand Down
6 changes: 5 additions & 1 deletion packages/ai-terminal/src/browser/ai-terminal-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand All @@ -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
Expand Down

0 comments on commit 204b8af

Please sign in to comment.