Skip to content

Commit c8d6615

Browse files
authored
prompt files: add hovers (microsoft#253013)
1 parent 376a0d7 commit c8d6615

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
7+
import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
8+
import { Disposable } from '../../../../../../base/common/lifecycle.js';
9+
import { Position } from '../../../../../../editor/common/core/position.js';
10+
import { Range } from '../../../../../../editor/common/core/range.js';
11+
import { Hover, HoverContext, HoverProvider } from '../../../../../../editor/common/languages.js';
12+
import { ITextModel } from '../../../../../../editor/common/model.js';
13+
import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js';
14+
import { localize } from '../../../../../../nls.js';
15+
import { ILanguageModelsService } from '../../languageModels.js';
16+
import { ILanguageModelToolsService, ToolSet } from '../../languageModelToolsService.js';
17+
import { InstructionsHeader } from '../parsers/promptHeader/instructionsHeader.js';
18+
import { PromptModelMetadata } from '../parsers/promptHeader/metadata/model.js';
19+
import { PromptToolsMetadata } from '../parsers/promptHeader/metadata/tools.js';
20+
import { ModeHeader } from '../parsers/promptHeader/modeHeader.js';
21+
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId } from '../promptTypes.js';
22+
import { IPromptsService } from '../service/promptsService.js';
23+
24+
export class PromptHeaderHoverProvider extends Disposable implements HoverProvider {
25+
/**
26+
* Debug display name for this provider.
27+
*/
28+
public readonly _debugDisplayName: string = 'PromptHeaderHoverProvider';
29+
30+
constructor(
31+
@IPromptsService private readonly promptsService: IPromptsService,
32+
@ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService,
33+
@ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService,
34+
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
35+
) {
36+
super();
37+
38+
this._register(this.languageService.hoverProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, this));
39+
}
40+
41+
private createHover(contents: string, range: Range): Hover {
42+
return {
43+
contents: [new MarkdownString(contents)],
44+
range
45+
};
46+
}
47+
48+
public async provideHover(
49+
model: ITextModel,
50+
position: Position,
51+
token: CancellationToken,
52+
_context?: HoverContext
53+
): Promise<Hover | undefined> {
54+
55+
const promptType = getPromptsTypeForLanguageId(model.getLanguageId());
56+
if (!promptType) {
57+
// if the model is not a prompt, we don't provide any completions
58+
return undefined;
59+
}
60+
61+
const parser = this.promptsService.getSyntaxParserFor(model);
62+
await parser.start(token).settled();
63+
64+
if (token.isCancellationRequested) {
65+
return undefined;
66+
}
67+
68+
const header = parser.header;
69+
if (!header) {
70+
return undefined;
71+
}
72+
73+
await header.settled;
74+
75+
if (header instanceof InstructionsHeader) {
76+
const descriptionRange = header.metadataUtility.description?.range;
77+
if (descriptionRange?.containsPosition(position)) {
78+
return this.createHover(localize('promptHeader.instructions.description', 'The description of the instruction file. It can be used to provide additional context or information about the instructions and is passed to the language model as part of the prompt.'), descriptionRange);
79+
}
80+
const applyToRange = header.metadataUtility.applyTo?.range;
81+
if (applyToRange?.containsPosition(position)) {
82+
return this.createHover(localize('promptHeader.instructions.applyToRange', 'One or more glob pattern (separated by comma) that describe for which files the instructions apply to. Based on these patterns, the file is automatically included in the prompt, when the context contains a file that matches one or more of these patterns. Use `**` when you want this file to always be added.\nExample: **/*.ts, **/*.js, client/**'), applyToRange);
83+
}
84+
85+
} else if (header instanceof ModeHeader) {
86+
const descriptionRange = header.metadataUtility.description?.range;
87+
if (descriptionRange?.containsPosition(position)) {
88+
return this.createHover(localize('promptHeader.mode.description', 'The description of the mode file. It can be used to provide additional context or information about the mode to the mode author.'), descriptionRange);
89+
}
90+
const model = header.metadataUtility.model;
91+
if (model?.range.containsPosition(position)) {
92+
return this.getModelHover(model, model.range, localize('promptHeader.mode.model', 'The model to use in this mode.'));
93+
}
94+
const tools = header.metadataUtility.tools;
95+
if (tools?.range?.containsPosition(position)) {
96+
return this.getToolHover(tools, position, localize('promptHeader.mode.tools', 'The tools to use in this mode.'));
97+
}
98+
} else {
99+
const descriptionRange = header.metadataUtility.description?.range;
100+
if (descriptionRange?.containsPosition(position)) {
101+
return this.createHover(localize('promptHeader.prompt.description', 'The description of the prompt file. It can be used to provide additional context or information about the prompt to the prompt author.'), descriptionRange);
102+
}
103+
const model = header.metadataUtility.model;
104+
if (model?.range.containsPosition(position)) {
105+
return this.getModelHover(model, model.range, localize('promptHeader.prompt.model', 'The model to use in this prompt.'));
106+
}
107+
const tools = header.metadataUtility.tools;
108+
if (tools?.range?.containsPosition(position)) {
109+
return this.getToolHover(tools, position, localize('promptHeader.prompt.tools', 'The tools to use in this prompt.'));
110+
}
111+
const modeRange = header.metadataUtility.mode?.range;
112+
if (modeRange?.containsPosition(position)) {
113+
return this.createHover(localize('promptHeader.prompt.mode', 'The mode (ask, edit or agent) to use when running this prompt.'), modeRange);
114+
}
115+
}
116+
return undefined;
117+
}
118+
119+
private getToolHover(node: PromptToolsMetadata, position: Position, baseMessage: string): Hover | undefined {
120+
if (node.value) {
121+
122+
for (const toolName of node.value) {
123+
const toolRange = node.getToolRange(toolName);
124+
if (toolRange?.containsPosition(position)) {
125+
const tool = this.languageModelToolsService.getToolByName(toolName);
126+
if (tool) {
127+
return this.createHover(tool.displayName, toolRange);
128+
}
129+
const toolSet = this.languageModelToolsService.getToolSetByName(toolName);
130+
if (toolSet) {
131+
return this.getToolsetHover(toolSet, toolRange);
132+
}
133+
}
134+
}
135+
}
136+
return this.createHover(baseMessage, node.range);
137+
}
138+
139+
private getToolsetHover(toolSet: ToolSet, range: Range): Hover | undefined {
140+
const lines: string[] = [];
141+
lines.push(localize('toolSetName', 'ToolSet: {0}\n\n', toolSet.referenceName));
142+
if (toolSet.description) {
143+
lines.push(toolSet.description);
144+
}
145+
for (const tool of toolSet.getTools()) {
146+
lines.push(`- ${tool.toolReferenceName ?? tool.displayName} (${tool.displayName})`);
147+
}
148+
return this.createHover(lines.join('\n'), range);
149+
}
150+
151+
private getModelHover(node: PromptModelMetadata, range: Range, baseMessage: string): Hover | undefined {
152+
if (node.value) {
153+
154+
for (const id of this.languageModelsService.getLanguageModelIds()) {
155+
const meta = this.languageModelsService.lookupLanguageModel(id);
156+
if (meta) {
157+
const lines: string[] = [];
158+
lines.push(baseMessage + '\n');
159+
lines.push(localize('modelName', '{0}', meta.description ?? meta.name));
160+
lines.push(localize('modelFamily', '- Family: {0}', meta.family));
161+
lines.push(localize('modelVendor', '- Vendor: {0}', meta.vendor));
162+
return this.createHover(lines.join('\n'), range);
163+
}
164+
}
165+
}
166+
return this.createHover(baseMessage, range);
167+
}
168+
169+
}

src/vs/workbench/contrib/chat/common/promptSyntax/promptFileContributions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PromptHeaderDiagnosticsInstanceManager } from './languageProviders/prom
1313
import { isWindows } from '../../../../../base/common/platform.js';
1414
import { PromptPathAutocompletion } from './languageProviders/promptPathAutocompletion.js';
1515
import { PromptHeaderAutocompletion } from './languageProviders/promptHeaderAutocompletion.js';
16+
import { PromptHeaderHoverProvider } from './languageProviders/promptHeaderHovers.js';
1617

1718

1819
/**
@@ -45,6 +46,7 @@ export function registerPromptFileContributions(): void {
4546
registerContribution(PromptPathAutocompletion);
4647
}
4748
registerContribution(PromptHeaderAutocompletion);
49+
registerContribution(PromptHeaderHoverProvider);
4850
registerContribution(ConfigMigration);
4951
}
5052

0 commit comments

Comments
 (0)