diff --git a/packages/ai-ide/src/browser/architect-prompt-template.ts b/packages/ai-ide/src/browser/architect-prompt-template.ts index fce6b35643260..87bec35bf4a4b 100644 --- a/packages/ai-ide/src/browser/architect-prompt-template.ts +++ b/packages/ai-ide/src/browser/architect-prompt-template.ts @@ -19,6 +19,7 @@ import { GET_WORKSPACE_FILE_LIST_FUNCTION_ID, FILE_CONTENT_FUNCTION_ID, SEARCH_IN_WORKSPACE_FUNCTION_ID, FIND_FILES_BY_PATTERN_FUNCTION_ID } from '../common/workspace-functions'; import { CONTEXT_FILES_VARIABLE_ID, TASK_CONTEXT_SUMMARY_VARIABLE_ID } from '../common/context-variables'; +import { OPEN_EDITORS_HINT_FRAGMENT_ID } from '../common/open-editors-hint-fragment-id'; import { CREATE_TASK_CONTEXT_FUNCTION_ID, GET_TASK_CONTEXT_FUNCTION_ID, @@ -194,6 +195,8 @@ When a diagram clarifies an architectural concept or how something is implemente {{prompt:project-info}} +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} + {{${TASK_CONTEXT_SUMMARY_VARIABLE_ID}}} ` }, @@ -231,6 +234,8 @@ Always look at the relevant files to understand your task using the function ~{$ {{${CONTEXT_FILES_VARIABLE_ID}}} {{prompt:project-info}} + +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} ` }, { @@ -464,6 +469,8 @@ When a diagram clarifies an architectural concept or how something is implemente {{prompt:project-info}} +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} + {{${TASK_CONTEXT_SUMMARY_VARIABLE_ID}}} ` }] diff --git a/packages/ai-ide/src/browser/frontend-module.ts b/packages/ai-ide/src/browser/frontend-module.ts index 275fbdd97ac78..8c423d70544b8 100644 --- a/packages/ai-ide/src/browser/frontend-module.ts +++ b/packages/ai-ide/src/browser/frontend-module.ts @@ -119,6 +119,8 @@ import { AddressGhReviewCommandContribution } from './address-pr-review-command- import { AppTesterCapabilityContribution } from './apptester-capability-contribution'; import { GitHubCapabilityContribution } from './github-capability-contribution'; import { ShellExecutionCapabilityContribution } from './shell-execution-capability-contribution'; +import { OpenEditorsHintContribution } from './open-editors-prompt-fragment'; +import { OpenEditorsContextContribution } from './open-editors-context-contribution'; import { AgentModeConfirmationService, AgentModeConfirmationServiceImpl } from './agent-mode-confirmation-service'; import { ExploreAgent } from './explore-agent'; import { CodeReviewerAgent } from './code-reviewer-agent'; @@ -347,6 +349,8 @@ export default new ContainerModule((bind, _unbind, _isBound, rebind) => { bind(FrontendApplicationContribution).to(AppTesterCapabilityContribution); bind(FrontendApplicationContribution).to(GitHubCapabilityContribution); bind(FrontendApplicationContribution).to(ShellExecutionCapabilityContribution); + bind(FrontendApplicationContribution).to(OpenEditorsHintContribution); + bind(FrontendApplicationContribution).to(OpenEditorsContextContribution); bind(FrontendApplicationContribution).to(CodeReviewCapabilityContribution); bind(FrontendApplicationContribution).to(PRReviewCapabilityContribution); diff --git a/packages/ai-ide/src/browser/open-editors-context-contribution.ts b/packages/ai-ide/src/browser/open-editors-context-contribution.ts new file mode 100644 index 0000000000000..4398b10b69306 --- /dev/null +++ b/packages/ai-ide/src/browser/open-editors-context-contribution.ts @@ -0,0 +1,96 @@ +// ***************************************************************************** +// Copyright (C) 2026 EclipseSource GmbH 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 { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { PreferenceService } from '@theia/core'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { PREFERENCE_NAME_AUTO_ADD_OPEN_EDITORS } from '../common/ai-ide-preferences'; +import URI from '@theia/core/lib/common/uri'; +import { FILE_VARIABLE } from '@theia/ai-core/lib/browser/file-variable-contribution'; +import { ChatContextManager, ChatService, isSessionCreatedEvent } from '@theia/ai-chat/lib/common'; +import { EditorManager } from '@theia/editor/lib/browser'; +import { WorkspaceService } from '@theia/workspace/lib/browser'; + +/** + * Automatically populates the active chat session's context with open editor files. + * + * When a new chat session is created, all currently open workspace editors are added + * as file context variables. When a new editor is opened while an active session exists, + * that file is added to the session's context as well. Files outside the workspace are + * ignored, and duplicates are prevented by the existing deduplication in {@link ChatContextManager.addVariables}. + * + * The added files appear as removable context pills in the chat input UI, allowing + * users to discard irrelevant files before sending a message. + */ +@injectable() +export class OpenEditorsContextContribution implements FrontendApplicationContribution { + @inject(ChatService) + protected readonly chatService: ChatService; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + @inject(WorkspaceService) + protected readonly workspaceService: WorkspaceService; + + @inject(PreferenceService) + protected readonly preferenceService: PreferenceService; + + protected get isEnabled(): boolean { + return this.preferenceService.get(PREFERENCE_NAME_AUTO_ADD_OPEN_EDITORS, true); + } + + onStart(): void { + this.chatService.onSessionEvent(event => { + if (isSessionCreatedEvent(event)) { + if (!this.isEnabled) { + return; + } + const activeSession = this.chatService.getActiveSession(); + if (activeSession && activeSession.id === event.sessionId) { + for (const editor of this.editorManager.all) { + const uri = editor.getResourceUri(); + if (uri) { + this.addFileToContext(uri, activeSession.model.context); + } + } + } + } + }); + + this.editorManager.onCreated(widget => { + if (!this.isEnabled) { + return; + } + const uri = widget.getResourceUri(); + if (uri) { + const activeSession = this.chatService.getActiveSession(); + if (activeSession) { + this.addFileToContext(uri, activeSession.model.context); + } + } + }); + } + + protected addFileToContext(uri: URI, context: ChatContextManager): void { + const rootUri = this.workspaceService.getWorkspaceRootUri(uri); + if (!rootUri) { + return; + } + const relativePath = this.workspaceService.getRootPrefixedPath(uri); + context.addVariables({ variable: FILE_VARIABLE, arg: relativePath }); + } +} diff --git a/packages/ai-ide/src/browser/open-editors-prompt-fragment.ts b/packages/ai-ide/src/browser/open-editors-prompt-fragment.ts new file mode 100644 index 0000000000000..c6ceb647ffd92 --- /dev/null +++ b/packages/ai-ide/src/browser/open-editors-prompt-fragment.ts @@ -0,0 +1,40 @@ +// ***************************************************************************** +// Copyright (C) 2026 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 { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { PromptService } from '@theia/ai-core/lib/common'; +import { OPEN_EDITORS_HINT_FRAGMENT_ID } from '../common/open-editors-hint-fragment-id'; + +export { OPEN_EDITORS_HINT_FRAGMENT_ID }; + +@injectable() +export class OpenEditorsHintContribution implements FrontendApplicationContribution { + + @inject(PromptService) + protected readonly promptService: PromptService; + + onStart(): void { + this.promptService.addBuiltInPromptFragment({ + id: OPEN_EDITORS_HINT_FRAGMENT_ID, + template: `## Open Editors +The following files are currently open in the user's editor. This is provided as contextual information only \ +— these files may or may not be relevant to the current request. Do not assume they are related unless the user explicitly refers to them. + +{{openEditors}}` + }); + } +} diff --git a/packages/ai-ide/src/common/ai-ide-preferences.ts b/packages/ai-ide/src/common/ai-ide-preferences.ts index 89bf883bdfe9d..2a26250982260 100644 --- a/packages/ai-ide/src/common/ai-ide-preferences.ts +++ b/packages/ai-ide/src/common/ai-ide-preferences.ts @@ -21,6 +21,7 @@ import { nls, PreferenceSchema, PreferenceScope } from '@theia/core'; export const PREFERENCE_NAME_ENABLE_AI = 'ai-features.AiEnable.enableAI'; export const PREFERENCE_NAME_ORCHESTRATOR_EXCLUSION_LIST = 'ai-features.orchestrator.excludedAgents'; export const PREFERENCE_NAME_AGENT_MODE_ENABLED = 'ai-features.agentMode.enabled'; +export const PREFERENCE_NAME_AUTO_ADD_OPEN_EDITORS = 'ai-features.chat.autoAddOpenEditors'; export const aiIdePreferenceSchema: PreferenceSchema = { properties: { [PREFERENCE_NAME_ENABLE_AI]: { @@ -57,6 +58,15 @@ export const aiIdePreferenceSchema: PreferenceSchema = { type: 'boolean', default: false, scope: PreferenceScope.User + }, + [PREFERENCE_NAME_AUTO_ADD_OPEN_EDITORS]: { + title: AI_CORE_PREFERENCES_TITLE, + description: nls.localize('theia/ai/ide/autoAddOpenEditors/description', + 'Automatically add open editor files to the chat context. ' + + 'When enabled, all open editors are added to new chat sessions, ' + + 'and newly opened files are added to the active session.'), + type: 'boolean', + default: true, } } }; diff --git a/packages/ai-ide/src/common/coder-replace-prompt-template.ts b/packages/ai-ide/src/common/coder-replace-prompt-template.ts index f1b9e6dac18bf..6116fc2864001 100644 --- a/packages/ai-ide/src/common/coder-replace-prompt-template.ts +++ b/packages/ai-ide/src/common/coder-replace-prompt-template.ts @@ -36,6 +36,7 @@ import { } from './file-changeset-function-ids'; import { GET_TASK_CONTEXT_FUNCTION_ID } from './task-context-function-ids'; import { ArchitectAgentId, ExploreAgentId } from './agent-ids'; +import { OPEN_EDITORS_HINT_FRAGMENT_ID } from './open-editors-hint-fragment-id'; export const CODER_SYSTEM_PROMPT_ID = 'coder-system'; @@ -259,6 +260,8 @@ Always retrieve relevant files using ~{${FILE_CONTENT_FUNCTION_ID}} to understan ## Project Info {{prompt:project-info}} +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} + {{${TASK_CONTEXT_SUMMARY_VARIABLE_ID}}} # Final Instruction @@ -549,6 +552,8 @@ Always retrieve relevant files using ~{${FILE_CONTENT_FUNCTION_ID}} to understan ## Project Info {{prompt:project-info}} +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} + {{${TASK_CONTEXT_SUMMARY_VARIABLE_ID}}} # Final Instruction @@ -639,6 +644,8 @@ You have previously proposed changes for the following files. Some suggestions m {{prompt:project-info}} +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} + {{${TASK_CONTEXT_SUMMARY_VARIABLE_ID}}} ## Final Instruction diff --git a/packages/ai-ide/src/common/open-editors-hint-fragment-id.ts b/packages/ai-ide/src/common/open-editors-hint-fragment-id.ts new file mode 100644 index 0000000000000..9a9bdb59b04a7 --- /dev/null +++ b/packages/ai-ide/src/common/open-editors-hint-fragment-id.ts @@ -0,0 +1,17 @@ +// ***************************************************************************** +// Copyright (C) 2026 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 +// ***************************************************************************** + +export const OPEN_EDITORS_HINT_FRAGMENT_ID = 'open-editors-hint'; diff --git a/packages/ai-ide/src/common/universal-prompt-template.ts b/packages/ai-ide/src/common/universal-prompt-template.ts index 3bbfcf6858f58..9d4010193f1fc 100644 --- a/packages/ai-ide/src/common/universal-prompt-template.ts +++ b/packages/ai-ide/src/common/universal-prompt-template.ts @@ -11,6 +11,7 @@ import { BasePromptFragment } from '@theia/ai-core/lib/common'; import { CHAT_CONTEXT_DETAILS_VARIABLE_ID } from '@theia/ai-chat'; +import { OPEN_EDITORS_HINT_FRAGMENT_ID } from './open-editors-hint-fragment-id'; export const universalTemplate: BasePromptFragment = { id: 'universal-system-default', @@ -23,6 +24,8 @@ You are an assistant integrated into {{productName}}, designed to assist softwar ## Current Context Some files and other pieces of data may have been added by the user to the context of the chat. If any have, the details can be found below. {{${CHAT_CONTEXT_DETAILS_VARIABLE_ID}}} + +{{prompt:${OPEN_EDITORS_HINT_FRAGMENT_ID}}} ` };