import { Autowired, Injectable } from '@opensumi/di'; import { WorkbenchEditorService } from '@opensumi/ide-editor/lib/common/editor'; import { IWorkspaceService } from '@opensumi/ide-workspace'; import { SerializedContext } from '../llm-context'; export const ChatAgentPromptProvider = Symbol('ChatAgentPromptProvider'); export interface ChatAgentPromptProvider { /** * 提供上下文提示 * @param context 上下文 */ provideContextPrompt(context: SerializedContext, userMessage: string): Promise; } @Injectable() export class DefaultChatAgentPromptProvider implements ChatAgentPromptProvider { @Autowired(WorkbenchEditorService) protected readonly workbenchEditorService: WorkbenchEditorService; @Autowired(IWorkspaceService) protected readonly workspaceService: IWorkspaceService; async provideContextPrompt(context: SerializedContext, userMessage: string) { const currentFileInfo = context.attachedFiles.length > 0 || context.attachedFolders.length > 0 ? null : await this.getCurrentFileInfo(); return this.buildPromptTemplate({ recentFiles: this.buildRecentFilesSection(context.recentlyViewFiles), attachedFiles: this.buildAttachedFilesSection(context.attachedFiles), attachedFolders: this.buildAttachedFoldersSection(context.attachedFolders), currentFile: currentFileInfo, userMessage, }); } private async getCurrentFileInfo() { const editor = this.workbenchEditorService.currentEditor; const currentModel = editor?.currentDocumentModel; if (!currentModel?.uri) { return null; } const currentPath = (await this.workspaceService.asRelativePath(currentModel.uri))?.path || currentModel.uri.codeUri.fsPath; return { path: currentPath, languageId: currentModel.languageId, content: currentModel.getText(), }; } private buildPromptTemplate({ recentFiles, attachedFiles, attachedFolders, currentFile, userMessage, }: { recentFiles: string; attachedFiles: string; attachedFolders: string; currentFile: { path: string; languageId: string; content: string } | null; userMessage: string; }) { const sections = [ '', 'Below are some potentially helpful/relevant pieces of information for figuring out to respond', recentFiles, attachedFiles, attachedFolders, this.buildCurrentFileSection(currentFile), '', '', userMessage, '', ].filter(Boolean); return sections.join('\n'); } private buildRecentFilesSection(files: string[]): string { if (!files.length) { return ''; } return ` ${files.map((file, idx) => ` ${idx + 1}: ${file}`).join('\n')} `; } private buildAttachedFilesSection(files: { path: string; content: string; lineErrors: string[] }[]): string { if (!files.length) { return ''; } const fileContents = files .map((file) => { const sections = [ this.buildFileContentSection(file), file.lineErrors.length ? this.buildLineErrorsSection(file.lineErrors) : '', ].filter(Boolean); return sections.join('\n'); }) .filter(Boolean) .join('\n'); return `\n${fileContents}\n`; } private buildFileContentSection(file: { path: string; content: string }): string { return ` \`\`\`${file.path} ${file.content} \`\`\` `; } private buildLineErrorsSection(errors: string[]): string { if (!errors.length) { return ''; } return `\n${errors.join('\n')}\n`; } private buildAttachedFoldersSection(folders: string[]): string { if (!folders.length) { return ''; } return `\n${folders.join('\n')}`; } private buildCurrentFileSection(fileInfo: { path: string; languageId: string; content: string } | null): string { if (!fileInfo) { return ''; } return ` \`\`\`${fileInfo.languageId} ${fileInfo.path} ${fileInfo.content} \`\`\` `; } }