// ***************************************************************************** // Copyright (C) 2024 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 { LanguageModelService } from '@theia/ai-core/lib/browser'; import { Agent, AgentSpecificVariables, getTextOfResponse, LanguageModelRegistry, LanguageModelRequirement, PromptService, PromptVariantSet, UserRequest } from '@theia/ai-core/lib/common'; import { generateUuid, ILogger, nls, ProgressService } from '@theia/core'; import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; import { inject, injectable, named } from '@theia/core/shared/inversify'; import * as monaco from '@theia/monaco-editor-core'; import { codeCompletionPrompts } from './code-completion-prompt-template'; import { CodeCompletionPostProcessor } from './code-completion-postprocessor'; import { CodeCompletionVariableContext } from './code-completion-variable-context'; import { FILE, LANGUAGE, PREFIX, SUFFIX } from './code-completion-variables'; export const CodeCompletionAgent = Symbol('CodeCompletionAgent'); export interface CodeCompletionAgent extends Agent { provideInlineCompletions(model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.InlineCompletionContext, token: monaco.CancellationToken): Promise } @injectable() export class CodeCompletionAgentImpl implements CodeCompletionAgent { @inject(LanguageModelService) protected languageModelService: LanguageModelService; async provideInlineCompletions( model: monaco.editor.ITextModel, position: monaco.Position, context: monaco.languages.InlineCompletionContext, token: monaco.CancellationToken ): Promise { const progress = await this.progressService.showProgress( { text: nls.localize('theia/ai/code-completion/progressText', 'Calculating AI code completion...'), options: { location: 'window' } } ); try { const languageModel = await this.languageModelRegistry.selectLanguageModel({ agent: this.id, ...this.languageModelRequirements[0], }); if (!languageModel) { this.logger.error( 'No language model found for code-completion-agent' ); return undefined; } const variableContext: CodeCompletionVariableContext = { model, position, context }; if (token.isCancellationRequested) { return undefined; } const prompt = await this.promptService .getResolvedPromptFragment('code-completion-system', undefined, variableContext) .then(p => p?.text); if (!prompt) { this.logger.error('No prompt found for code-completion-agent'); return undefined; } const variantInfo = this.promptService.getPromptVariantInfo('code-completion-system'); // since we do not actually hold complete conversions, the request/response pair is considered a session const sessionId = generateUuid(); const requestId = generateUuid(); const request: UserRequest = { messages: [{ type: 'text', actor: 'user', text: prompt }], settings: { stream: false }, // Inline completion is a one-shot text replacement; reasoning/thinking adds latency // and can cause models (e.g. Anthropic adaptive thinking) to emit no text at all. reasoning: { level: 'off' }, agentId: this.id, sessionId, requestId, cancellationToken: token, promptVariantId: variantInfo?.variantId, isPromptVariantCustomized: variantInfo?.isCustomized }; if (token.isCancellationRequested) { return undefined; } const response = await this.languageModelService.sendRequest(languageModel, request); if (token.isCancellationRequested) { return undefined; } const completionText = await getTextOfResponse(response); if (token.isCancellationRequested) { return undefined; } const postProcessedCompletionText = this.postProcessor.postProcess(completionText); return { items: [{ insertText: postProcessedCompletionText, range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column) }], enableForwardStability: true }; } catch (e) { if (!token.isCancellationRequested) { this.logger.error(e.message, e); } } finally { progress.cancel(); } } @inject(ILogger) @named('ai-code-completion:CodeCompletionAgentImpl') protected readonly logger: ILogger; @inject(LanguageModelRegistry) protected languageModelRegistry: LanguageModelRegistry; @inject(PromptService) protected promptService: PromptService; @inject(ProgressService) protected progressService: ProgressService; @inject(CodeCompletionPostProcessor) protected postProcessor: CodeCompletionPostProcessor; id = 'Code Completion'; name = 'Code Completion'; description = nls.localize('theia/ai/completion/agent/description', 'This agent provides inline code completion in the code editor in {0}.', FrontendApplicationConfigProvider.get().applicationName); prompts: PromptVariantSet[] = codeCompletionPrompts; languageModelRequirements: LanguageModelRequirement[] = [ { purpose: 'code-completion', identifier: 'default/code-completion', }, ]; readonly variables: string[] = []; readonly functions: string[] = []; readonly agentSpecificVariables: AgentSpecificVariables[] = [ { name: FILE.id, description: nls.localize('theia/ai/completion/agent/vars/file/description', 'The URI of the file being edited'), usedInPrompt: true }, { name: PREFIX.id, description: nls.localize('theia/ai/completion/agent/vars/prefix/description', 'The code before the current cursor position'), usedInPrompt: true }, { name: SUFFIX.id, description: nls.localize('theia/ai/completion/agent/vars/suffix/description', 'The code after the current cursor position'), usedInPrompt: true }, { name: LANGUAGE.id, description: nls.localize('theia/ai/completion/agent/vars/language/description', 'The languageId of the file being edited'), usedInPrompt: true } ]; }