// ***************************************************************************** // Copyright (C) 2025 EclipseSource GmbH. // // 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 { ChatMode, ChatRequestModel, ChatService, ChatSession, MutableChatModel, MutableChatRequestModel } from '@theia/ai-chat/lib/common'; import { inject, injectable, named } from '@theia/core/shared/inversify'; import { CODER_SYSTEM_PROMPT_ID, CODER_EDIT_TEMPLATE_ID, CODER_AGENT_MODE_TEMPLATE_ID, CODER_AGENT_MODE_NEXT_TEMPLATE_ID, getCoderAgentModePromptTemplate, getCoderAgentModeNextPromptTemplate, getCoderPromptTemplateEdit } from '../common/coder-replace-prompt-template'; import { LanguageModelRequirement, PromptVariantSet } from '@theia/ai-core'; import { ILogger, nls } from '@theia/core'; import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering'; import { AI_CHAT_HOME, ChatCommands } from '@theia/ai-chat-ui/lib/browser/chat-view-commands'; import { AbstractModeAwareChatAgent } from './mode-aware-chat-agent'; import { AgentModeConfirmationService } from './agent-mode-confirmation-service'; export const CoderAgentId = 'Coder'; @injectable() export class CoderAgent extends AbstractModeAwareChatAgent { @inject(ILogger) @named('ai-ide:CoderAgent') protected override readonly logger: ILogger; @inject(ChatService) protected readonly chatService: ChatService; @inject(AgentModeConfirmationService) protected readonly agentModeConfirmation: AgentModeConfirmationService; id: string = CoderAgentId; name = CoderAgentId; languageModelRequirements: LanguageModelRequirement[] = [{ purpose: 'chat', identifier: 'default/code', }]; protected defaultLanguageModelPurpose: string = 'chat'; override iconClass: string = 'codicon codicon-code'; override description = nls.localize('theia/ai/workspace/coderAgent/description', 'An AI assistant integrated into {0}, designed to assist software developers. This agent can access the users workspace, it can get a list of all available files' + ' and folders and retrieve their content. Furthermore, it can suggest modifications of files to the user. It can therefore assist the user with coding tasks or other' + ' tasks involving file changes.', FrontendApplicationConfigProvider.get().applicationName); protected readonly modeDefinitions: Omit[] = [ { id: CODER_EDIT_TEMPLATE_ID, name: nls.localize('theia/ai/ide/coderAgent/mode/edit', 'Edit Mode') }, { id: CODER_AGENT_MODE_TEMPLATE_ID, name: nls.localizeByDefault('Agent Mode') }, { id: CODER_AGENT_MODE_NEXT_TEMPLATE_ID, name: nls.localize('theia/ai/ide/coderAgent/mode/agentNext', 'Agent Mode (Next)') }, ]; override prompts: PromptVariantSet[] = [{ id: CODER_SYSTEM_PROMPT_ID, defaultVariant: getCoderAgentModePromptTemplate(), variants: [getCoderPromptTemplateEdit(), getCoderAgentModeNextPromptTemplate()] }]; protected override systemPromptId: string | undefined = CODER_SYSTEM_PROMPT_ID; private useSettingsDefaultMode = false; override async invoke(request: MutableChatRequestModel): Promise { if (this.isAgentModeRequest(request) && !this.agentModeConfirmation.isAcknowledged()) { const confirmed = await this.agentModeConfirmation.requestConfirmation(request); if (!confirmed) { await this.switchToEditMode(); // Continue the same request using Edit Mode's prompt by ignoring // the request's original agent mode modeId for variant resolution. this.useSettingsDefaultMode = true; try { await super.invoke(request); this.suggest(request); } finally { this.useSettingsDefaultMode = false; } return; } } await super.invoke(request); this.suggest(request); } protected override getEffectiveVariantIdWithMode(modeId?: string): string | undefined { if (this.useSettingsDefaultMode) { return super.getEffectiveVariantIdWithMode(undefined); } return super.getEffectiveVariantIdWithMode(modeId); } protected async switchToEditMode(): Promise { if (this.systemPromptId) { await this.promptService.updateSelectedVariantId(this.id, this.systemPromptId, CODER_EDIT_TEMPLATE_ID); } } protected isAgentModeRequest(request: MutableChatRequestModel): boolean { const modeId = request.request.modeId; if (modeId) { return modeId === CODER_AGENT_MODE_TEMPLATE_ID || modeId === CODER_AGENT_MODE_NEXT_TEMPLATE_ID; } const effectiveVariantId = this.getEffectiveVariantIdWithMode(undefined); return effectiveVariantId === CODER_AGENT_MODE_TEMPLATE_ID || effectiveVariantId === CODER_AGENT_MODE_NEXT_TEMPLATE_ID; } async suggest(context: ChatSession | ChatRequestModel): Promise { const contextIsRequest = ChatRequestModel.is(context); const model = contextIsRequest ? context.session : context.model; const session = contextIsRequest ? this.chatService.getSessions().find(candidate => candidate.model.id === model.id) : context; if (!(model instanceof MutableChatModel) || !session) { return; } if (model.isEmpty()) { model.setSuggestions([ { kind: 'callback', callback: () => this.chatService.sendRequest(session.id, { text: `@Coder ${nls.localize('theia/ai/ide/coderAgent/suggestion/fixProblems/prompt', 'please look at {1} and fix any problems.', '#_f')}` }), content: nls.localize('theia/ai/ide/coderAgent/suggestion/fixProblems/content', '[Fix problems]({0}) in the current file.', '_callback') }, ]); } else { model.setSuggestions([new MarkdownStringImpl(nls.localize('theia/ai/ide/coderAgent/suggestion/startNewChat', 'Keep chats short and focused. [Start a new chat]({0}) for a new task or [start a new chat with a summary of this one]({1}).', `command:${AI_CHAT_HOME.id}`, `command:${ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id}`))]); } } }