Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/safeElements.tsx
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BasePromptElementProps, PromptElement, PromptElementProps, PromptReference, TextChunk } from '@vscode/prompt-tsx';6import type * as vscode from 'vscode';7import { isScenarioAutomation } from '../../../../platform/env/common/envService';8import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';9import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';10import { ILogService } from '../../../../platform/log/common/logService';11import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';12import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';13import { createFencedCodeBlock } from '../../../../util/common/markdown';14import { basename } from '../../../../util/vs/base/common/resources';15import { ExtensionMode } from '../../../../vscodeTypes';161718export abstract class SafePromptElement<P extends BasePromptElementProps, S = void> extends PromptElement<P, S> {1920constructor(props: P,21@IVSCodeExtensionContext protected readonly _contextService: IVSCodeExtensionContext,22@ITelemetryService protected readonly _telemetryService: ITelemetryService,23@ILogService protected readonly _logService: ILogService,24@IIgnoreService protected readonly _ignoreService: IIgnoreService,25) {26super(props);27}2829protected _handleFoulPrompt(): undefined {30// REPORT error telemetry31// FAIL when running tests32const err = new Error('BAD PROMPT');33this._logService.error(err);3435if (this._contextService.extensionMode !== ExtensionMode.Production && !isScenarioAutomation) {36throw err;37}3839/* __GDPR__40"prompt.invalidreference": {41"owner": "jrieken",42"comment": "Tracks bad prompt references",43"stack": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Error stack" }44}45*/46this._telemetryService.sendMSFTTelemetryErrorEvent('prompt.invalidreference', { stack: err.stack });47}48}49// --- SafeCodeBlock5051export type CodeBlockProps = PromptElementProps<{52readonly includeFilepath?: boolean;53readonly uri: vscode.Uri;54readonly code: string;55readonly languageId?: string;56readonly references?: PromptReference[];5758/**59* If set, each line of the prompt will be in its own text chunk with60* descending priority so that it may be trimmed if over the token budget.61*/62readonly lineBasedPriority?: boolean;6364/**65* Invokes `code.trim()`66*67* @default true68*/69readonly shouldTrim?: boolean;7071/**72* Fence style, defaults to '```'. An empty string omits the fence.73*/74readonly fence?: string;75}>;7677export class CodeBlock extends SafePromptElement<CodeBlockProps> {7879constructor(props: CodeBlockProps,80@IVSCodeExtensionContext _contextService: IVSCodeExtensionContext,81@ITelemetryService _telemetryService: ITelemetryService,82@ILogService _logService: ILogService,83@IIgnoreService _ignoreService: IIgnoreService,84@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService,85) {86super(props, _contextService, _telemetryService, _logService, _ignoreService);87}8889async render() {90const isIgnored = this.props.uri ? await this._ignoreService.isCopilotIgnored(this.props.uri) : false;91if (isIgnored) {92return this._handleFoulPrompt();93}94const filePath = this.props.includeFilepath ? this._promptPathRepresentationService.getFilePath(this.props.uri) : undefined;95const code = createFencedCodeBlock(this.props.languageId ?? '', this.props.code, this.props.shouldTrim ?? true, filePath, this.props.fence);96const reference = this.props.references && <references value={this.props.references} />;9798if (this.props.lineBasedPriority) {99const lines = code.split('\n');100// Ensure priority is highest for the last line too so that we don't101// have an incomplete code block during trimming:102return <>103{lines.map((line, i) => <TextChunk priority={i === lines.length - 1 ? lines.length : lines.length - i}>104{i === 0 && reference}{line}{i === lines.length - 1 ? '' : '\n'}105</TextChunk>)}106</>;107}108109return <TextChunk>{reference}{code}</TextChunk>;110}111}112113export type ExampleCodeBlockProps = PromptElementProps<{114readonly examplePath?: string;115readonly isSummarized?: boolean;116readonly includeFilepath?: boolean;117readonly range?: vscode.Range;118readonly code: string;119readonly languageId?: string;120readonly shouldTrim?: boolean;121readonly minNumberOfBackticks?: number;122}>;123124125export class ExampleCodeBlock extends SafePromptElement<ExampleCodeBlockProps> {126constructor(props: ExampleCodeBlockProps,127@IVSCodeExtensionContext _contextService: IVSCodeExtensionContext,128@ITelemetryService _telemetryService: ITelemetryService,129@ILogService _logService: ILogService,130@IIgnoreService _ignoreService: IIgnoreService,131@IPromptPathRepresentationService private readonly _promptPathRepresentationService: IPromptPathRepresentationService,132) {133super(props, _contextService, _telemetryService, _logService, _ignoreService);134}135136async render() {137const filePath = this.props.includeFilepath ? this._promptPathRepresentationService.getExampleFilePath(this.props.examplePath ?? '/path/to/file') : undefined;138const code = createFencedCodeBlock(this.props.languageId ?? '', this.props.code, this.props.shouldTrim ?? true, filePath, this.props.minNumberOfBackticks);139return <TextChunk>{code}</TextChunk>;140}141}142143144// --- SafeUri145146export const enum UriMode {147Basename,148Path149}150151export type UriProps = PromptElementProps<{152value: vscode.Uri;153mode?: UriMode;154}>;155156export class Uri extends SafePromptElement<UriProps> {157158override get insertLineBreakBefore(): boolean {159return false;160}161162async render() {163const isIgnored = await this._ignoreService.isCopilotIgnored(this.props.value);164if (isIgnored) {165return this._handleFoulPrompt();166}167168let value: string;169if (this.props.mode === UriMode.Path) {170value = this.props.value.path;171} else {172value = basename(this.props.value);173}174return <>{value}</>;175}176}177178179