Path: blob/main/extensions/copilot/src/extension/intents/node/promptOverride.ts
13399 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 { Raw } from '@vscode/prompt-tsx';6import * as yaml from 'js-yaml';7import type { LanguageModelToolInformation, Uri } from 'vscode';8import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';9import { ILogService } from '../../../platform/log/common/logService';10import { URI } from '../../../util/vs/base/common/uri';1112interface PromptOverrideConfig {13readonly systemPrompt?: string;14readonly toolDescriptions?: Record<string, { readonly description: string }>;15}1617interface PromptOverrideResult {18readonly messages: Raw.ChatMessage[];19readonly tools: LanguageModelToolInformation[];20}2122const INLINE_PROMPT_OVERRIDE_SOURCE = 'inlinePromptOverrideString';2324/** Tracks which override sources have already had a warning logged, to avoid spamming. */25const warnedSources = new Set<string>();2627export async function applyConfiguredPromptOverrides(28inlinePromptOverride: string | null,29promptOverrideFile: string | null,30messages: readonly Raw.ChatMessage[],31tools: readonly LanguageModelToolInformation[],32fileSystemService: IFileSystemService,33logService: ILogService,34): Promise<PromptOverrideResult> {35const normalizedInlinePromptOverride = inlinePromptOverride?.trim();36const normalizedPromptOverrideFile = promptOverrideFile?.trim();3738if (normalizedInlinePromptOverride) {39if (normalizedPromptOverrideFile) {40logService.trace('[PromptOverride] Both inline prompt override text and prompt override file are configured; using inline prompt override text');41}4243return applyPromptOverridesFromString(normalizedInlinePromptOverride, messages, tools, logService);44}4546if (normalizedPromptOverrideFile) {47return applyPromptOverrides(URI.file(normalizedPromptOverrideFile), messages, tools, fileSystemService, logService);48}4950return clonePromptOverrideResult(messages, tools);51}5253/**54* Applies debug prompt overrides from a YAML file.55* Reads the file via IFileSystemService, parses it, and applies system prompt and/or tool description overrides.56* Warnings for unreadable/unparseable files are logged once per file path.57*/58export async function applyPromptOverrides(59fileUri: Uri,60messages: readonly Raw.ChatMessage[],61tools: readonly LanguageModelToolInformation[],62fileSystemService: IFileSystemService,63logService: ILogService,64): Promise<PromptOverrideResult> {65const key = fileUri.toString();66let content: string;67try {68const buffer = await fileSystemService.readFile(fileUri);69content = new TextDecoder().decode(buffer);70} catch (err) {71logPromptOverrideFailure(logService, key, `Failed to read prompt override file "${key}"`, err);72return clonePromptOverrideResult(messages, tools);73}7475const config = parsePromptOverrideConfig(content, key, `prompt override file "${key}"`, logService);76if (!config) {77return clonePromptOverrideResult(messages, tools);78}7980return applyPromptOverrideConfig(config, messages, tools, logService);81}828384export function applyPromptOverridesFromString(85content: string,86messages: readonly Raw.ChatMessage[],87tools: readonly LanguageModelToolInformation[],88logService: ILogService,89): PromptOverrideResult {90const config = parsePromptOverrideConfig(content, INLINE_PROMPT_OVERRIDE_SOURCE, `inline prompt override setting "${INLINE_PROMPT_OVERRIDE_SOURCE}"`, logService);91if (!config) {92return clonePromptOverrideResult(messages, tools);93}9495return applyPromptOverrideConfig(config, messages, tools, logService);96}9798function parsePromptOverrideConfig(99content: string,100sourceKey: string,101sourceDescription: string,102logService: ILogService,103): PromptOverrideConfig | undefined {104let config: PromptOverrideConfig;105try {106config = yaml.load(content) as PromptOverrideConfig;107} catch (err) {108logPromptOverrideFailure(logService, sourceKey, `Failed to parse prompt override from ${sourceDescription}`, err);109return undefined;110}111112// On successful parsing, clear any previous warning so a new error is re-surfaced as warn.113warnedSources.delete(sourceKey);114115if (!config || typeof config !== 'object') {116return undefined;117}118119return config;120}121122function applyPromptOverrideConfig(123config: PromptOverrideConfig,124messages: readonly Raw.ChatMessage[],125tools: readonly LanguageModelToolInformation[],126logService: ILogService,127): PromptOverrideResult {128let resultMessages = [...messages];129let resultTools = [...tools];130131if (typeof config.systemPrompt === 'string') {132resultMessages = applySystemPromptOverride(resultMessages, config.systemPrompt);133logService.trace('[PromptOverride] Applied system prompt override');134}135136if (config.toolDescriptions && typeof config.toolDescriptions === 'object') {137resultTools = applyToolDescriptionOverrides(resultTools, config.toolDescriptions);138logService.trace('[PromptOverride] Applied tool description overrides');139}140141return { messages: resultMessages, tools: resultTools };142}143144function clonePromptOverrideResult(145messages: readonly Raw.ChatMessage[],146tools: readonly LanguageModelToolInformation[],147): PromptOverrideResult {148return { messages: [...messages], tools: [...tools] };149}150151function logPromptOverrideFailure(logService: ILogService, sourceKey: string, message: string, err: unknown): void {152if (!warnedSources.has(sourceKey)) {153warnedSources.add(sourceKey);154logService.warn(`[PromptOverride] ${message}: ${err}`);155} else {156logService.trace(`[PromptOverride] ${message}: ${err}`);157}158}159160/**161* Resets the internal warning deduplication state.162* Exported for testing only.163*/164export function resetPromptOverrideWarnings(): void {165warnedSources.clear();166}167168function applySystemPromptOverride(messages: Raw.ChatMessage[], systemPrompt: string): Raw.ChatMessage[] {169const nonSystemMessages = messages.filter(m => m.role !== Raw.ChatRole.System);170return [171{172role: Raw.ChatRole.System,173content: [{ type: Raw.ChatCompletionContentPartKind.Text, text: systemPrompt }],174},175...nonSystemMessages,176];177}178179function applyToolDescriptionOverrides(180tools: readonly LanguageModelToolInformation[],181overrides: Record<string, { readonly description: string }>,182): LanguageModelToolInformation[] {183return tools.map(tool => {184const override = overrides[tool.name];185if (override && typeof override.description === 'string') {186return { ...tool, description: override.description };187}188return tool;189});190}191192193