This commit is contained in:
Matthew Rayermann 2023-11-22 12:23:41 -08:00
Родитель 7840c81720
Коммит 36a84d3e59
19 изменённых файлов: 967 добавлений и 529 удалений

4
.vscode/settings.json поставляемый
Просмотреть файл

@ -1,7 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"editor.detectIndentation": false,
"editor.formatOnSave": true,

Просмотреть файл

@ -6,36 +6,28 @@
import * as vscode from "vscode";
import { ext } from '../extensionVariables';
import { getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "./copilotInteractions";
import { brainstormSlashCommand } from "./slashBrainstorm";
import { createFunctionAppSlashCommand } from "./slashCreateFunctionApp";
import { createFunctionProjectSlashCommand } from "./slashCreateFunctionProject";
import { defaultSlashCommand } from "./slashDefault";
import { deploySlashCommand } from "./slashDeploy";
import { learnSlashCommand } from "./slashLearn";
import { agentDescription, agentFullName, agentName } from "./agentConsts";
import { AgentBenchmarker } from "./benchmarking/benchmarking";
import { verbatimCopilotInteraction } from "./copilotInteractions";
import { functionsSlashCommand } from "./functions/slashFunctions";
import {
FallbackSlashCommandHandlers,
InvokeableSlashCommands,
SlashCommandHandlerResult,
SlashCommandOwner
} from "./slashCommands";
export type SlashCommandName = string;
export type SlashCommandHandlerResult = { chatAgentResult: vscode.ChatAgentResult2, followUp?: vscode.ChatAgentFollowup[] };
export type SlashCommandHandler = (userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken) => Promise<SlashCommandHandlerResult>;
export type SlashCommandConfig = { shortDescription: string, longDescription: string, determineCommandDescription?: string, handler: SlashCommandHandler };
export type AgentSlashCommand = [SlashCommandName, SlashCommandConfig]
const agentName = "azure-functions";
const agentFullName = "Azure Functions Extension";
const agentDescription = "Agent for Azure Functions development";
export const slashCommands = new Map([
learnSlashCommand,
brainstormSlashCommand,
createFunctionAppSlashCommand,
createFunctionProjectSlashCommand,
deploySlashCommand,
// debuggingHelpSlashCommand,
const agentSlashCommands: InvokeableSlashCommands = new Map([
functionsSlashCommand
]);
const slashCommandsMarkdown = Array.from(slashCommands).map(([name, config]) => `- \`/${name}\` - ${config.longDescription || config.shortDescription}`).join("\n");
const fallbackSlashCommandHandlers: FallbackSlashCommandHandlers = {
noInput: noInputHandler,
default: defaultHandler,
};
const agentSlashCommandOwner = new SlashCommandOwner(agentSlashCommands, fallbackSlashCommandHandlers);
let previousSlashCommandHandlerResult: SlashCommandHandlerResult | undefined;
const agentBenchmarker = new AgentBenchmarker(agentSlashCommandOwner);
export function registerAgent() {
try {
@ -50,66 +42,41 @@ export function registerAgent() {
}
}
async function handler(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<vscode.ProviderResult<vscode.ChatAgentResult2>> {
const prompt = request.prompt.trim();
const command = request.slashCommand?.name;
async function handler(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<vscode.ChatAgentResult2 | undefined> {
const handleResult = await agentBenchmarker.handleRequestOrPrompt(request, context, progress, token) ||
await agentSlashCommandOwner.handleRequestOrPrompt(request, context, progress, token);
let handler: SlashCommandHandler | undefined;
if (!handler && prompt === "" && !command) {
handler = giveNoInputResponse;
if (handleResult !== undefined) {
return handleResult.chatAgentResult;
} else {
return undefined;
}
if (!handler) {
const slashCommand = slashCommands.get(command || "");
if (slashCommand !== undefined) {
handler = slashCommand.handler;
}
}
if (!handler) {
const maybeCommand = await determineCommand(prompt, context, progress, token);
if (maybeCommand !== undefined) {
const slashCommand = slashCommands.get(maybeCommand);
if (slashCommand !== undefined) {
handler = slashCommand.handler;
}
}
}
if (!handler) {
handler = defaultSlashCommand[1].handler;
}
previousSlashCommandHandlerResult = await handler(prompt, context, progress, token);
return previousSlashCommandHandlerResult.chatAgentResult;
}
function followUpProvider(result: vscode.ChatAgentResult2, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.ChatAgentFollowup[]> {
if (result === previousSlashCommandHandlerResult?.chatAgentResult) {
return previousSlashCommandHandlerResult?.followUp || [];
} else {
return [];
}
function followUpProvider(result: vscode.ChatAgentResult2, token: vscode.CancellationToken): vscode.ProviderResult<vscode.ChatAgentFollowup[]> {
return agentBenchmarker.getFollowUpForLastHandledSlashCommand(result, token) || agentSlashCommandOwner.getFollowUpForLastHandledSlashCommand(result, token) || [];
}
function getSlashCommands(_token: vscode.CancellationToken): vscode.ProviderResult<vscode.ChatAgentSlashCommand[]> {
return Array.from(slashCommands.entries()).map(([name, config]) => {
return { name: name, description: config.shortDescription };
});
return Array
.from(agentSlashCommands.entries())
.map(([name, config]) => ({ name: name, description: config.shortDescription }))
}
async function giveNoInputResponse(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
progress.report({ content: new vscode.MarkdownString(`Hi! I can help you with tasks related to Azure Functions development. If you know what you'd like to do, you can use the following commands to ask me for help:\n\n${slashCommandsMarkdown}\n\nOtherwise feel free to ask or tell me anything and I'll do my best to help.`) });
async function defaultHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
const defaultSystemPrompt1 = `You are an expert in using the Azure Extensions for VS Code. The user needs your help with something related to either Azure, VS Code, and/or the Azure Extensions for VS Code. Do your best to answer their question. The user is currently using VS Code and has one or more Azure Extensions for VS Code installed. Do not overwhelm the user with too much information. Keep responses short and sweet.`;
const { copilotResponded } = await verbatimCopilotInteraction(defaultSystemPrompt1, userContent, progress, token);
if (!copilotResponded) {
progress.report({ content: vscode.l10n.t("Sorry, I can't help with that right now.\n") });
return { chatAgentResult: {}, followUp: [], };
} else {
return { chatAgentResult: {}, followUp: [], };
}
}
async function noInputHandler(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
const slashCommandsMarkdown = Array.from(agentSlashCommands).map(([name, config]) => `- \`/${name}\` - ${config.longDescription || config.shortDescription}`).join("\n");
progress.report({ content: `Hi! I can help you with tasks related to Azure Functions development. If you know what you'd like to do, you can use the following commands to ask me for help:\n\n${slashCommandsMarkdown}\n\nOtherwise feel free to ask or tell me anything and I'll do my best to help.` });
return { chatAgentResult: {}, followUp: [] };
}
async function determineCommand(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
const availableCommandsJoined = Array.from(slashCommands.entries()).map(([name, config]) => `'${name}' (${config.determineCommandDescription || config.longDescription})`).join(", ")
const maybeJsonCopilotResponse = await getResponseAsStringCopilotInteraction(determineCommandSystemPrompt1(availableCommandsJoined), userContent, progress, token);
return getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "command");
}
const determineCommandSystemPrompt1 = (availableCommandsJoined) => `
You are an expert in Azure function development. You have several commands users can use to interact with you. The available commands are: ${availableCommandsJoined}. Your job is to determine which command would most help the user based on their query. Choose one of the available commands as the best command. Only repsond with a JSON object containing the command you choose. Do not respond in a coverstaional tone, only JSON.
`;

8
src/chat/agentConsts.ts Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const agentName = "azure-extensions";
export const agentFullName = "Azure Extensions Agent";
export const agentDescription = "Agent for the Azure Extensions for VS Code";

Просмотреть файл

@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { agentName } from "../agentConsts";
import { FallbackSlashCommandHandlers, SlashCommand, SlashCommandConfig, SlashCommandHandlerResult, SlashCommandOwner } from "../slashCommands";
import { functionsBenchmarks } from "./functionsBenchmarks";
export type AgentBenchmark = {
/**
* The name of the benchmark.
*/
name: string,
/**
* The simulated user input.
*/
prompt: string,
/**
* Acceptable handler chains for the `prompt`.
*/
acceptableHandlerChains: string[][],
followUps?: {
/**
* Follow ups that must be returned for the `prompt`.
* - For command follow ups, it will be verified that the `commandId` matches EXACTLY.
* - For reply follow ups, it will be verified that the `message` contains the expected message.
*/
required: ({ type: "command", commandId: string } | { type: "reply", message: string })[],
/**
* Follow ups that must be returned for the `prompt`.
* - For command follow ups, it will be verified that the `commandId` matches EXACTLY.
* - For reply follow ups, it will be verified that the `message` contains the expected message.
*/
acceptable: ({ type: "command", commandId: string } | { type: "reply", message: string })[],
},
};
const benchmarks: AgentBenchmark[] = [
...functionsBenchmarks
];
type AgentBenchmarkRunStats = {
startTime: number,
endTime: number,
handlerChainValid: boolean;
followUps: {
allRequiredFollowUpsFound: boolean,
allFollowUpsRequiredOrAcceptable: boolean,
},
};
const benchmarksRunsStats: AgentBenchmarkRunStats[][] = benchmarks.map(() => []);
const benchmarkCommandName = "benchmark";
const benchmarkStatsCommandName = "benchmarkStats";
export class AgentBenchmarker {
private _agentSlashCommandOwner: SlashCommandOwner;
private _benchmarkerSlashCommandOwner: SlashCommandOwner;
private _continuationIndex: number;
constructor(agentSlashCommandOwner: SlashCommandOwner) {
this._agentSlashCommandOwner = agentSlashCommandOwner;
const slashCommands = new Map([this._getBenchmarkSlashCommand(), this._getBenchmarkStatsSlashCommand()]);
const fallbackSlashCommandHandlers: FallbackSlashCommandHandlers = { noInput: undefined, default: undefined };
this._benchmarkerSlashCommandOwner = new SlashCommandOwner(slashCommands, fallbackSlashCommandHandlers);
this._continuationIndex = 0;
}
public handleRequestOrPrompt(request: vscode.ChatAgentRequest | string, context: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
return this._benchmarkerSlashCommandOwner.handleRequestOrPrompt(request, context, progress, token, true);
}
public getFollowUpForLastHandledSlashCommand(result: vscode.ChatAgentResult2, token: vscode.CancellationToken): vscode.ChatAgentFollowup[] | undefined {
return this._benchmarkerSlashCommandOwner.getFollowUpForLastHandledSlashCommand(result, token);
}
private async _benchmarkAgent(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
const followUps: vscode.ChatAgentFollowup[] = [];
const requestedBenchmarkIndex = parseInt(userContent);
if (isNaN(requestedBenchmarkIndex) || requestedBenchmarkIndex >= benchmarks.length) {
await this._runBenchmark(this._continuationIndex, ctx, progress, token);
this._continuationIndex++;
if (this._continuationIndex === benchmarks.length) {
this._debugBenchmarking(progress, `🎉 Done benchmarking!`);
followUps.push({ message: `@${agentName} /${benchmarkStatsCommandName}` });
this._continuationIndex = 0;
}
followUps.push({ message: `@${agentName} /${benchmarkCommandName}` });
} else {
await this._runBenchmark(requestedBenchmarkIndex, ctx, progress, token);
followUps.push({ message: `@${agentName} /${benchmarkCommandName}` });
followUps.push({ message: `@${agentName} /${benchmarkCommandName} ${requestedBenchmarkIndex}` });
followUps.push({ message: `@${agentName} /${benchmarkCommandName} ${requestedBenchmarkIndex === benchmarks.length - 1 ? 0 : requestedBenchmarkIndex + 1}` });
followUps.push({ message: `@${agentName} /${benchmarkStatsCommandName}` });
}
return {
chatAgentResult: {},
followUp: followUps,
};
}
private async _runBenchmark(benchmarkIdx: number, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<void> {
const benchmark = benchmarks[benchmarkIdx];
this._debugBenchmarking(progress, `📋 Benchmark (${this._continuationIndex}/${benchmarks.length}): ${benchmark.name}\n💭 Prompt: '${benchmark.prompt}'...`);
const startTime = Date.now();
const handleResult = await this._agentSlashCommandOwner.handleRequestOrPrompt({ prompt: benchmark.prompt, variables: {}, }, ctx, progress, token);
const endTime = Date.now();
if (handleResult) {
let validationString = "🔍 Automated Validation:\n";
const handlerChainIsAcceptable = this._validateHandlerChain(handleResult.handlerChain || [], benchmark.acceptableHandlerChains);
validationString += handlerChainIsAcceptable ? `✅ Handler chain is acceptable (${JSON.stringify(handleResult.handlerChain)}).\n` : `❌ Handler chain is unacceptable. Expected one of: ${JSON.stringify(benchmark.acceptableHandlerChains)}, Actual: ${JSON.stringify(handleResult.handlerChain)}\n`;
const followUps = handleResult.followUp || [];
if (followUps.length > 0) {
this._debugBenchmarking(progress, `⏭️ Follow Ups:\n${followUps.map((followUp) => JSON.stringify(followUp)).join("\n")}`);
}
const followUpValidation = benchmark.followUps;
const { allFollowUpsRequiredOrAcceptable, allRequiredFollowUpsFound } = !followUpValidation ? { allFollowUpsRequiredOrAcceptable: true, allRequiredFollowUpsFound: true } : this._validateFollowUps(followUps, followUpValidation);
validationString += allRequiredFollowUpsFound ? `✅ All required follow ups found.\n` : `❌ Not all required follow ups found.\n`;
validationString += allFollowUpsRequiredOrAcceptable ? `✅ All follow ups required or acceptable.\n` : `❌ Not all follow ups required or acceptable.\n`;
this._debugBenchmarking(progress, validationString);
const stats: AgentBenchmarkRunStats = {
startTime: startTime,
endTime: endTime,
handlerChainValid: handlerChainIsAcceptable,
followUps: {
allRequiredFollowUpsFound: allRequiredFollowUpsFound,
allFollowUpsRequiredOrAcceptable: allFollowUpsRequiredOrAcceptable,
}
};
benchmarksRunsStats[benchmarkIdx].push(stats);
}
}
private async _benchmarkStats(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
benchmarks.forEach((benchmark, benchmarkIdx) => {
const benchmarkRunStats = benchmarksRunsStats[benchmarkIdx];
const numRuns = benchmarkRunStats.length;
const avgTime = benchmarkRunStats.reduce((acc, curr) => acc + curr.endTime - curr.startTime, 0) / numRuns;
const handlerChainValidCount = benchmarkRunStats.filter((runStat) => runStat.handlerChainValid).length;
const allRequiredFollowUpsFoundCount = benchmarkRunStats.filter((runStat) => runStat.followUps.allRequiredFollowUpsFound).length;
const allFollowUpsRequiredOrAcceptableCount = benchmarkRunStats.filter((runStat) => runStat.followUps.allFollowUpsRequiredOrAcceptable).length;
const handlerChainValidPercentage = handlerChainValidCount / numRuns;
const allRequiredFollowUpsFoundPercentage = allRequiredFollowUpsFoundCount / numRuns;
const allFollowUpsRequiredOrAcceptablePercentage = allFollowUpsRequiredOrAcceptableCount / numRuns;
const statsString = `📋 Benchmark (${benchmarkIdx}/${benchmarks.length}): ${benchmark.name}\n` +
`🔁 Number of runs: ${numRuns}\n` +
`⏱️ Average time to complete benchmark: ${avgTime}ms\n` +
`🔍 Handler chain valid: ${handlerChainValidCount} (${getColorEmojiForPercentage(handlerChainValidPercentage)} ${handlerChainValidPercentage * 100}%)\n` +
`🔍 All required follow ups found: ${allRequiredFollowUpsFoundCount} (${getColorEmojiForPercentage(allRequiredFollowUpsFoundPercentage)} ${allRequiredFollowUpsFoundPercentage * 100}%)\n` +
`🔍 All follow ups required or acceptable: ${allFollowUpsRequiredOrAcceptableCount} (${getColorEmojiForPercentage(allFollowUpsRequiredOrAcceptablePercentage)} ${allFollowUpsRequiredOrAcceptablePercentage * 100}%)\n`;
this._debugBenchmarking(progress, statsString);
});
return {
chatAgentResult: {},
followUp: [],
};
}
private _getBenchmarkSlashCommand(): SlashCommand {
const config: SlashCommandConfig = {
shortDescription: "",
longDescription: "",
determineCommandDescription: "",
handler: (userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken) => this._benchmarkAgent(userContent, ctx, progress, token),
};
return [benchmarkCommandName, config];
}
private _getBenchmarkStatsSlashCommand(): SlashCommand {
const config: SlashCommandConfig = {
shortDescription: "",
longDescription: "",
determineCommandDescription: "",
handler: (userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken) => this._benchmarkStats(userContent, ctx, progress, token),
};
return [benchmarkStatsCommandName, config];
}
private _debugBenchmarking(progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, msg: string) {
const lines = msg.trim().split("\n");
progress.report({ content: "\n```" });
for (const line of lines) {
progress.report({ content: `\n${line}` });
}
progress.report({ content: "\n```\n\n" });
}
private _validateHandlerChain(handlerChain: string[], acceptableHandlerChains: string[][]): boolean {
return acceptableHandlerChains.some((acceptableHandlerChain) => acceptableHandlerChain.every((acceptableHandler, index) => acceptableHandler === handlerChain[index]));
}
private _validateFollowUps(followUps: vscode.ChatAgentFollowup[], followUpValidation: NonNullable<AgentBenchmark["followUps"]>): { allFollowUpsRequiredOrAcceptable: boolean, allRequiredFollowUpsFound: boolean } {
let allFollowUpsRequiredOrAcceptable = true;
const foundRequiredFollowUps: boolean[] = new Array<boolean>(followUpValidation.required.length).fill(false);
for (const followUp of followUps) {
if (followUpIsCommandFollowUp(followUp)) {
const requiredFollowUpIndex = followUpValidation.required.findIndex((requiredFollowUp) => requiredFollowUp.type === "command" && requiredFollowUp.commandId === followUp.commandId);
if (requiredFollowUpIndex !== -1) {
foundRequiredFollowUps[requiredFollowUpIndex] = true;
} else {
const acceptableFollowUpIndex = followUpValidation.acceptable.findIndex((acceptableFollowUp) => acceptableFollowUp.type === "command" && acceptableFollowUp.commandId === followUp.commandId);
if (acceptableFollowUpIndex === -1) {
allFollowUpsRequiredOrAcceptable = false;
}
}
} else {
const requiredFollowUpIndex = followUpValidation.required.findIndex((requiredFollowUp) => requiredFollowUp.type === "reply" && followUp.message.includes(requiredFollowUp.message));
if (requiredFollowUpIndex !== -1) {
foundRequiredFollowUps[requiredFollowUpIndex] = true;
} else {
const acceptableFollowUpIndex = followUpValidation.acceptable.findIndex((acceptableFollowUp) => acceptableFollowUp.type === "reply" && followUp.message.includes(acceptableFollowUp.message));
if (acceptableFollowUpIndex === -1) {
allFollowUpsRequiredOrAcceptable = false;
}
}
}
}
const allRequiredFollowUpsFound = foundRequiredFollowUps.every((foundRequiredFollowUp) => foundRequiredFollowUp);
return {
allFollowUpsRequiredOrAcceptable: allFollowUpsRequiredOrAcceptable,
allRequiredFollowUpsFound: allRequiredFollowUpsFound
};
}
}
function followUpIsCommandFollowUp(followUp: vscode.ChatAgentFollowup): followUp is vscode.ChatAgentCommandFollowup {
return !!(followUp as vscode.ChatAgentCommandFollowup).commandId;
}
function getColorEmojiForPercentage(percentage: number): string {
if (percentage >= 0.9) {
return "🟢";
} else if (percentage >= 0.8) {
return "🟡";
} else {
return "🔴";
}
}

Просмотреть файл

@ -0,0 +1,42 @@
import { AgentBenchmark } from "./benchmarking";
export const functionsBenchmarks: AgentBenchmark[] = [
{
name: "Create Function Project - Blob Trigger Template 1",
prompt: "I want to create a function project with the blob trigger template.",
acceptableHandlerChains: [
["functions", "createFunctionProject"],
],
followUps: {
required: [
{ type: "command", commandId: "azureFunctions.createNewProject" }
],
acceptable: [
{ type: "reply", message: "@azure-extensions create a project" }
],
},
},
{
name: "Create Function Project - Blob Trigger Template 2",
prompt: "I want to create a function project that will help me run code whenever a blob is changed.",
acceptableHandlerChains: [
["functions", "createFunctionProject"],
],
},
{
name: "Brainstorm - Learn/Brainstorm About Blob Triggers",
prompt: "How can I use azure functions to run code whenever a blob is changed?",
acceptableHandlerChains: [
["functions", "learn"],
["functions", "brainstorm"],
],
},
{
name: "Learn - Functions vs WebApps",
prompt: "What is the difference between azure functions and azure web apps?",
acceptableHandlerChains: [
["functions", "learn"],
["functions", "brainstorm"],
],
}
];

Просмотреть файл

@ -5,242 +5,241 @@
declare module 'vscode' {
export interface InteractiveEditorSlashCommand {
command: string;
detail?: string;
refer?: boolean;
/**
* Whether the command should execute as soon
* as it is entered. Defaults to `false`.
*/
executeImmediately?: boolean;
// kind: CompletionItemKind;
}
export interface InteractiveEditorSlashCommand {
command: string;
detail?: string;
refer?: boolean;
/**
* Whether the command should execute as soon
* as it is entered. Defaults to `false`.
*/
executeImmediately?: boolean;
// kind: CompletionItemKind;
}
// todo@API make classes
export interface InteractiveEditorSession {
placeholder?: string;
input?: string;
slashCommands?: InteractiveEditorSlashCommand[];
wholeRange?: Range;
message?: string;
}
// todo@API make classes
export interface InteractiveEditorSession {
placeholder?: string;
input?: string;
slashCommands?: InteractiveEditorSlashCommand[];
wholeRange?: Range;
message?: string;
}
// todo@API make classes
export interface InteractiveEditorRequest {
prompt: string;
selection: Selection;
wholeRange: Range;
attempt: number;
live: boolean;
}
// todo@API make classes
export interface InteractiveEditorRequest {
prompt: string;
selection: Selection;
wholeRange: Range;
attempt: number;
live: boolean;
}
// todo@API make classes
export interface InteractiveEditorResponse {
edits: TextEdit[] | WorkspaceEdit;
placeholder?: string;
wholeRange?: Range;
}
// todo@API make classes
export interface InteractiveEditorResponse {
edits: TextEdit[] | WorkspaceEdit;
placeholder?: string;
wholeRange?: Range;
}
// todo@API make classes
export interface InteractiveEditorMessageResponse {
contents: MarkdownString;
placeholder?: string;
wholeRange?: Range;
}
// todo@API make classes
export interface InteractiveEditorMessageResponse {
contents: MarkdownString;
placeholder?: string;
wholeRange?: Range;
}
export interface InteractiveEditorProgressItem {
message?: string;
edits?: TextEdit[];
editsShouldBeInstant?: boolean;
slashCommand?: InteractiveEditorSlashCommand;
content?: string | MarkdownString;
}
export interface InteractiveEditorProgressItem {
message?: string;
edits?: TextEdit[];
editsShouldBeInstant?: boolean;
slashCommand?: InteractiveEditorSlashCommand;
content?: string | MarkdownString;
}
export enum InteractiveEditorResponseFeedbackKind {
Unhelpful = 0,
Helpful = 1,
Undone = 2,
Accepted = 3
}
export enum InteractiveEditorResponseFeedbackKind {
Unhelpful = 0,
Helpful = 1,
Undone = 2,
Accepted = 3
}
export interface TextDocumentContext {
document: TextDocument;
selection: Selection;
}
export interface TextDocumentContext {
document: TextDocument;
selection: Selection;
}
export interface InteractiveEditorSessionProviderMetadata {
label: string;
}
export interface InteractiveEditorSessionProviderMetadata {
label: string;
}
export interface InteractiveEditorSessionProvider<S extends InteractiveEditorSession = InteractiveEditorSession, R extends InteractiveEditorResponse | InteractiveEditorMessageResponse = InteractiveEditorResponse | InteractiveEditorMessageResponse> {
export interface InteractiveEditorSessionProvider<S extends InteractiveEditorSession = InteractiveEditorSession, R extends InteractiveEditorResponse | InteractiveEditorMessageResponse = InteractiveEditorResponse | InteractiveEditorMessageResponse> {
// Create a session. The lifetime of this session is the duration of the editing session with the input mode widget.
prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult<S>;
// Create a session. The lifetime of this session is the duration of the editing session with the input mode widget.
prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult<S>;
provideInteractiveEditorResponse(session: S, request: InteractiveEditorRequest, progress: Progress<InteractiveEditorProgressItem>, token: CancellationToken): ProviderResult<R>;
provideInteractiveEditorResponse(session: S, request: InteractiveEditorRequest, progress: Progress<InteractiveEditorProgressItem>, token: CancellationToken): ProviderResult<R>;
// eslint-disable-next-line local/vscode-dts-provider-naming
handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void;
}
handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void;
}
export interface InteractiveSessionState { }
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface InteractiveSessionState { }
export interface InteractiveSessionParticipantInformation {
name: string;
export interface InteractiveSessionParticipantInformation {
name: string;
/**
* A full URI for the icon of the participant.
*/
icon?: Uri;
}
/**
* A full URI for the icon of the participant.
*/
icon?: Uri;
}
export interface InteractiveSession {
requester: InteractiveSessionParticipantInformation;
responder: InteractiveSessionParticipantInformation;
inputPlaceholder?: string;
export interface InteractiveSession {
requester: InteractiveSessionParticipantInformation;
responder: InteractiveSessionParticipantInformation;
inputPlaceholder?: string;
saveState?(): InteractiveSessionState;
}
saveState?(): InteractiveSessionState;
}
export interface InteractiveSessionRequestArgs {
command: string;
args: any;
}
export interface InteractiveSessionRequestArgs {
command: string;
args: any;
}
export interface InteractiveRequest {
session: InteractiveSession;
message: string;
}
export interface InteractiveRequest {
session: InteractiveSession;
message: string;
}
export interface InteractiveResponseErrorDetails {
message: string;
responseIsIncomplete?: boolean;
responseIsFiltered?: boolean;
}
export interface InteractiveResponseErrorDetails {
message: string;
responseIsIncomplete?: boolean;
responseIsFiltered?: boolean;
}
export interface InteractiveResponseForProgress {
errorDetails?: InteractiveResponseErrorDetails;
}
export interface InteractiveResponseForProgress {
errorDetails?: InteractiveResponseErrorDetails;
}
export interface InteractiveContentReference {
reference: Uri | Location;
}
export interface InteractiveContentReference {
reference: Uri | Location;
}
export interface InteractiveInlineContentReference {
inlineReference: Uri | Location;
title?: string; // eg symbol name
}
export interface InteractiveInlineContentReference {
inlineReference: Uri | Location;
title?: string; // eg symbol name
}
export interface InteractiveProgressContent {
content: string | MarkdownString;
}
export interface InteractiveProgressContent {
content: string | MarkdownString;
}
export interface InteractiveProgressId {
responseId: string;
}
export interface InteractiveProgressId {
responseId: string;
}
export interface InteractiveProgressTask {
placeholder: string;
resolvedContent: Thenable<InteractiveProgressContent | InteractiveProgressFileTree>;
}
export interface InteractiveProgressTask {
placeholder: string;
resolvedContent: Thenable<InteractiveProgressContent | InteractiveProgressFileTree>;
}
export interface FileTreeData {
label: string;
uri: Uri;
children?: FileTreeData[];
}
export interface FileTreeData {
label: string;
uri: Uri;
children?: FileTreeData[];
}
export interface InteractiveProgressFileTree {
treeData: FileTreeData;
}
export interface InteractiveProgressFileTree {
treeData: FileTreeData;
}
export interface DocumentContext {
uri: Uri;
version: number;
ranges: Range[];
}
export interface DocumentContext {
uri: Uri;
version: number;
ranges: Range[];
}
export interface InteractiveProgressUsedContext {
documents: DocumentContext[];
}
export interface InteractiveProgressUsedContext {
documents: DocumentContext[];
}
export type InteractiveProgress =
| InteractiveProgressContent
| InteractiveProgressId
| InteractiveProgressTask
| InteractiveProgressFileTree
| InteractiveProgressUsedContext
| InteractiveContentReference
| InteractiveInlineContentReference;
export type InteractiveProgress =
| InteractiveProgressContent
| InteractiveProgressId
| InteractiveProgressTask
| InteractiveProgressFileTree
| InteractiveProgressUsedContext
| InteractiveContentReference
| InteractiveInlineContentReference;
export interface InteractiveResponseCommand {
commandId: string;
args?: any[];
title: string; // supports codicon strings
when?: string;
}
export interface InteractiveResponseCommand {
commandId: string;
args?: any[];
title: string; // supports codicon strings
when?: string;
}
export interface InteractiveSessionSlashCommand {
command: string;
kind: CompletionItemKind;
detail?: string;
shouldRepopulate?: boolean;
followupPlaceholder?: string;
executeImmediately?: boolean;
yieldTo?: ReadonlyArray<{ readonly command: string }>;
}
export interface InteractiveSessionSlashCommand {
command: string;
kind: CompletionItemKind;
detail?: string;
shouldRepopulate?: boolean;
followupPlaceholder?: string;
executeImmediately?: boolean;
yieldTo?: ReadonlyArray<{ readonly command: string }>;
}
export interface InteractiveSessionReplyFollowup {
message: string;
tooltip?: string;
title?: string;
export interface InteractiveSessionReplyFollowup {
message: string;
tooltip?: string;
title?: string;
// Extensions can put any serializable data here, such as an ID/version
metadata?: any;
}
// Extensions can put any serializable data here, such as an ID/version
metadata?: any;
}
export type InteractiveSessionFollowup = InteractiveSessionReplyFollowup | InteractiveResponseCommand;
export type InteractiveSessionFollowup = InteractiveSessionReplyFollowup | InteractiveResponseCommand;
export type InteractiveWelcomeMessageContent = string | MarkdownString | InteractiveSessionReplyFollowup[];
export type InteractiveWelcomeMessageContent = string | MarkdownString | InteractiveSessionReplyFollowup[];
export interface InteractiveSessionProvider<S extends InteractiveSession = InteractiveSession> {
provideWelcomeMessage?(token: CancellationToken): ProviderResult<InteractiveWelcomeMessageContent[]>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<InteractiveSessionReplyFollowup[]>;
provideFollowups?(session: S, token: CancellationToken): ProviderResult<(string | InteractiveSessionFollowup)[]>;
provideSlashCommands?(session: S, token: CancellationToken): ProviderResult<InteractiveSessionSlashCommand[]>;
export interface InteractiveSessionProvider<S extends InteractiveSession = InteractiveSession> {
provideWelcomeMessage?(token: CancellationToken): ProviderResult<InteractiveWelcomeMessageContent[]>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<InteractiveSessionReplyFollowup[]>;
provideFollowups?(session: S, token: CancellationToken): ProviderResult<(string | InteractiveSessionFollowup)[]>;
provideSlashCommands?(session: S, token: CancellationToken): ProviderResult<InteractiveSessionSlashCommand[]>;
prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult<S>;
provideResponseWithProgress(request: InteractiveRequest, progress: Progress<InteractiveProgress>, token: CancellationToken): ProviderResult<InteractiveResponseForProgress>;
prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult<S>;
provideResponseWithProgress(request: InteractiveRequest, progress: Progress<InteractiveProgress>, token: CancellationToken): ProviderResult<InteractiveResponseForProgress>;
// eslint-disable-next-line local/vscode-dts-provider-naming
removeRequest(session: S, requestId: string): void;
}
removeRequest(session: S, requestId: string): void;
}
export interface InteractiveSessionDynamicRequest {
/**
* The message that will be displayed in the UI
*/
message: string;
export interface InteractiveSessionDynamicRequest {
/**
* The message that will be displayed in the UI
*/
message: string;
/**
* Any extra metadata/context that will go to the provider.
* NOTE not actually used yet.
*/
metadata?: any;
}
/**
* Any extra metadata/context that will go to the provider.
* NOTE not actually used yet.
*/
metadata?: any;
}
export namespace interactive {
// current version of the proposal.
export const _version: 1 | number;
export namespace interactive {
// current version of the proposal.
export const _version: 1 | number;
export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable;
export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable;
export function sendInteractiveRequestToProvider(providerId: string, message: InteractiveSessionDynamicRequest): void;
export function sendInteractiveRequestToProvider(providerId: string, message: InteractiveSessionDynamicRequest): void;
export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable;
export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable;
export function transferChatSession(session: InteractiveSession, toWorkspace: Uri): void;
}
export function transferChatSession(session: InteractiveSession, toWorkspace: Uri): void;
}
}

Просмотреть файл

@ -5,76 +5,70 @@
declare module 'vscode' {
export enum InteractiveSessionVoteDirection {
Down = 0,
Up = 1
}
export enum InteractiveSessionVoteDirection {
Down = 0,
Up = 1
}
export interface InteractiveSessionVoteAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'vote';
// sessionId: string;
responseId: string;
direction: InteractiveSessionVoteDirection;
}
export interface InteractiveSessionVoteAction {
kind: 'vote';
// sessionId: string;
responseId: string;
direction: InteractiveSessionVoteDirection;
}
export enum InteractiveSessionCopyKind {
// Keyboard shortcut or context menu
Action = 1,
Toolbar = 2
}
export enum InteractiveSessionCopyKind {
// Keyboard shortcut or context menu
Action = 1,
Toolbar = 2
}
export interface InteractiveSessionCopyAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'copy';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
copyType: InteractiveSessionCopyKind;
copiedCharacters: number;
totalCharacters: number;
copiedText: string;
}
export interface InteractiveSessionCopyAction {
kind: 'copy';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
copyType: InteractiveSessionCopyKind;
copiedCharacters: number;
totalCharacters: number;
copiedText: string;
}
export interface InteractiveSessionInsertAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'insert';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
totalCharacters: number;
newFile?: boolean;
}
export interface InteractiveSessionInsertAction {
kind: 'insert';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
totalCharacters: number;
newFile?: boolean;
}
export interface InteractiveSessionTerminalAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'runInTerminal';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
languageId?: string;
}
export interface InteractiveSessionTerminalAction {
kind: 'runInTerminal';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
languageId?: string;
}
export interface InteractiveSessionCommandAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'command';
command: InteractiveResponseCommand;
}
export interface InteractiveSessionCommandAction {
kind: 'command';
command: InteractiveResponseCommand;
}
export interface InteractiveSessionFollowupAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'followUp';
followup: InteractiveSessionReplyFollowup;
}
export interface InteractiveSessionFollowupAction {
kind: 'followUp';
followup: InteractiveSessionReplyFollowup;
}
export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionTerminalAction | InteractiveSessionCommandAction;
export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionTerminalAction | InteractiveSessionCommandAction;
export interface InteractiveSessionUserActionEvent {
action: InteractiveSessionUserAction;
providerId: string;
}
export interface InteractiveSessionUserActionEvent {
action: InteractiveSessionUserAction;
providerId: string;
}
export namespace interactive {
export const onDidPerformUserAction: Event<InteractiveSessionUserActionEvent>;
}
export namespace interactive {
export const onDidPerformUserAction: Event<InteractiveSessionUserActionEvent>;
}
}

Просмотреть файл

@ -5,18 +5,20 @@
import * as vscode from "vscode";
let cachedAccess: vscode.ChatAccess | undefined;
const maxCachedAccessAge = 1000 * 30;
let cachedAccess: { access: vscode.ChatAccess, requestedAt: number } | undefined;
async function getChatAccess(): Promise<vscode.ChatAccess> {
if (cachedAccess === undefined || cachedAccess.isRevoked) {
cachedAccess = await vscode.chat.requestChatAccess("copilot");
if (cachedAccess === undefined || cachedAccess.access.isRevoked || cachedAccess.requestedAt < Date.now() - maxCachedAccessAge) {
const newAccess = await vscode.chat.requestChatAccess("copilot");
cachedAccess = { access: newAccess, requestedAt: Date.now() };
}
return cachedAccess;
return cachedAccess.access;
}
const debug = false;
export function debugProgress(progress: vscode.Progress<vscode.InteractiveProgress>, msg: string) {
if (debug) {
progress.report({ content: new vscode.MarkdownString(`\n\n${new Date().toISOString()} >> \`${msg.replace(/\n/g, "").trim()}\`\n\n`) });
const showDebugCopilotInteractionAsProgress = false;
function debugCopilotInteraction(progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, msg: string) {
if (showDebugCopilotInteractionAsProgress) {
progress.report({ content: `\n\n${new Date().toISOString()} >> \`${msg.replace(/\n/g, "").trim()}\`\n\n` });
}
console.log(`${new Date().toISOString()} >> \`${msg.replace(/\n/g, "").trim()}\``);
}
@ -24,46 +26,63 @@ export function debugProgress(progress: vscode.Progress<vscode.InteractiveProgre
/**
* Feeds {@link systemPrompt} and {@link userContent} to Copilot and redirects the response directly to ${@link progress}.
*/
export async function verbatimCopilotInteraction(systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<{ copilotResponded: boolean, copilotResponse: string }> {
try {
const access = await getChatAccess();
const messages = [
{
role: vscode.ChatMessageRole.System,
content: systemPrompt
},
{
role: vscode.ChatMessageRole.User,
content: userContent
},
];
const request = access.makeRequest(messages, {}, token);
const copilotResponse = await new Promise<string>((resolve, reject) => {
request.onDidStartResponseStream(async (stream) => {
try {
let joinedFragements = "";
for await (const fragment of stream.response) {
joinedFragements += fragment;
progress.report({ content: new vscode.MarkdownString(fragment) });
}
resolve(joinedFragements);
} catch (e) {
reject(e);
}
});
});
return { copilotResponded: true, copilotResponse: copilotResponse };
} catch (e) {
console.log(e);
}
return { copilotResponded: false, copilotResponse: "" };
export async function verbatimCopilotInteraction(systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<{ copilotResponded: boolean, copilotResponse: string }> {
let joinedFragements = "";
await queueCopilotInteraction((fragment) => {
joinedFragements += fragment;
progress.report({ content: fragment });
}, systemPrompt, userContent, progress, token);
return { copilotResponded: true, copilotResponse: joinedFragements };
}
/**
* Feeds {@link systemPrompt} and {@link userContent} to Copilot and directly returns its response.
*/
export async function getResponseAsStringCopilotInteraction(systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
export async function getResponseAsStringCopilotInteraction(systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
let joinedFragements = "";
await queueCopilotInteraction((fragment) => {
joinedFragements += fragment;
}, systemPrompt, userContent, progress, token);
return joinedFragements;
}
let copilotInteractionQueueRunning = false;
type CopilotInteractionQueueItem = { onResponseFragment: (fragment: string) => void, systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken, resolve: () => void };
const copilotInteractionQueue: CopilotInteractionQueueItem[] = [];
export async function queueCopilotInteraction(onResponseFragment: (fragment: string) => void, systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<void> {
return new Promise<void>((resolve) => {
copilotInteractionQueue.push({ onResponseFragment, systemPrompt, userContent, progress, token, resolve });
if (!copilotInteractionQueueRunning) {
copilotInteractionQueueRunning = true;
void runCopilotInteractionQueue();
}
});
}
let lastCopilotInteractionRunTime: number = 0;
const timeBetweenCopilotInteractions = 1500
async function runCopilotInteractionQueue() {
while (copilotInteractionQueue.length > 0) {
const queueItem = copilotInteractionQueue.shift();
if (queueItem === undefined) {
continue;
}
const timeSinceLastCopilotInteraction = Date.now() - lastCopilotInteractionRunTime;
if (timeSinceLastCopilotInteraction < timeBetweenCopilotInteractions) {
await new Promise((resolve) => setTimeout(resolve, timeBetweenCopilotInteractions - timeSinceLastCopilotInteraction));
}
lastCopilotInteractionRunTime = Date.now();
await doCopilotInteraction(queueItem.onResponseFragment, queueItem.systemPrompt, queueItem.userContent, queueItem.progress, queueItem.token);
queueItem.resolve();
}
copilotInteractionQueueRunning = false;
}
async function doCopilotInteraction(onResponseFragment: (fragment: string) => void, systemPrompt: string, userContent: string, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<void> {
try {
const access = await getChatAccess();
const messages = [
@ -77,31 +96,13 @@ export async function getResponseAsStringCopilotInteraction(systemPrompt: string
},
];
if (access.isRevoked) {
return undefined;
}
const request = access.makeRequest(messages, {}, token);
const copilotResponse = await new Promise<string>((resolve, reject) => {
request.onDidStartResponseStream(async (stream) => {
try {
let joinedFragements = "";
for await (const fragment of stream.response) {
joinedFragements += fragment;
}
resolve(joinedFragements);
} catch (e) {
reject(e);
}
});
});
debugProgress(progress, copilotResponse);
return copilotResponse;
for await (const fragment of request.response) {
onResponseFragment(fragment);
}
} catch (e) {
console.log(e);
debugCopilotInteraction(progress, `Error: ${e}`);
}
return undefined;
}
/**

Просмотреть файл

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { agentName } from "./agentConsts";
import { getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "./copilotInteractions";
import { WellKnownFunctionProjectLanguage, WellKnownTemplate, getWellKnownCSharpTemplate, getWellKnownTypeScriptTemplate, isWellKnownFunctionProjectLanguage, wellKnownCSharpTemplateDisplayNames, wellKnownTypeScriptTemplateDisplayNames } from "./wellKnownThings";
@ -15,7 +16,7 @@ import { WellKnownFunctionProjectLanguage, WellKnownTemplate, getWellKnownCSharp
* - Deploying a function project
* - Creating a function app
*/
export async function generateGeneralInteractionFollowUps(userContent: string, copilotContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
export async function generateGeneralInteractionFollowUps(userContent: string, copilotContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
try {
const nextQuestionFollowUpsPromise = generateNextQuestionsFollowUps(userContent, copilotContent, ctx, progress, token);
const createFunctionProjectFollowUpsPromise = generateCreateFunctionProjectFollowUps(userContent, copilotContent, ctx, progress, token);
@ -37,36 +38,36 @@ export async function generateAlternativeCreateFunctionProjectFollowUps(language
const result: vscode.InteractiveSessionReplyFollowup[] = [];
if (language !== undefined && template !== undefined) {
if (language === "C#" && getWellKnownTypeScriptTemplate(template)) {
result.push({ message: `@azure-functions create a project using the TypeScript ${template} template` });
result.push({ message: `@${agentName} create a project using the TypeScript ${template} template` });
}
if (language === "TypeScript" && getWellKnownCSharpTemplate(template)) {
result.push({ message: `@azure-functions create a project using the C# ${template} template` });
result.push({ message: `@${agentName} create a project using the C# ${template} template` });
}
}
return result;
}
async function generateCreateFunctionProjectFollowUps(userContent: string, copilotContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
async function generateCreateFunctionProjectFollowUps(userContent: string, copilotContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
const createFunctionProjectFollowUps: vscode.InteractiveSessionReplyFollowup[] = [];
const functionProjectionSuggestion = await wasCreatingFunctionProjectSuggested(userContent, copilotContent, ctx, progress, token);
if (functionProjectionSuggestion !== false) {
if (functionProjectionSuggestion.language !== undefined && functionProjectionSuggestion.template !== undefined) {
createFunctionProjectFollowUps.push({ message: `@azure-functions create a function project using the ${functionProjectionSuggestion.language} ${functionProjectionSuggestion.template} template` });
createFunctionProjectFollowUps.push({ message: `@${agentName} create a function project using the ${functionProjectionSuggestion.language} ${functionProjectionSuggestion.template} template` });
} else if (functionProjectionSuggestion.language !== undefined) {
createFunctionProjectFollowUps.push({ message: `@azure-functions create a ${functionProjectionSuggestion.language} function project` });
createFunctionProjectFollowUps.push({ message: `@${agentName} create a ${functionProjectionSuggestion.language} function project` });
} else if (functionProjectionSuggestion.template !== undefined) {
if (getWellKnownCSharpTemplate(functionProjectionSuggestion.template)) {
createFunctionProjectFollowUps.push({ message: `@azure-functions create a function project using the C# ${functionProjectionSuggestion.template} template` });
createFunctionProjectFollowUps.push({ message: `@${agentName} create a function project using the C# ${functionProjectionSuggestion.template} template` });
}
if (getWellKnownTypeScriptTemplate(functionProjectionSuggestion.template)) {
createFunctionProjectFollowUps.push({ message: `@azure-functions create a function project using the TypeScript ${functionProjectionSuggestion.template} template` });
createFunctionProjectFollowUps.push({ message: `@${agentName} create a function project using the TypeScript ${functionProjectionSuggestion.template} template` });
}
}
}
return createFunctionProjectFollowUps;
}
export async function generateNextQuestionsFollowUps(_userContent: string, copilotContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
export async function generateNextQuestionsFollowUps(_userContent: string, copilotContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<vscode.InteractiveSessionReplyFollowup[]> {
const maybeJsonCopilotResponseLanguage = await getResponseAsStringCopilotInteraction(generateNextQuestionsFollowUpsSystemPrompt1, copilotContent, progress, token);
const copilotGeneratedFollowUpQuestions = [
getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponseLanguage, "followUpOne")?.trim(),
@ -76,7 +77,7 @@ export async function generateNextQuestionsFollowUps(_userContent: string, copil
return copilotGeneratedFollowUpQuestions
.map((q) => {
if (q !== undefined && q !== "") {
return { message: `@azure-functions ${q}` };
return { message: `@${agentName} ${q}` };
} else {
return undefined;
}
@ -87,7 +88,7 @@ export async function generateNextQuestionsFollowUps(_userContent: string, copil
/**
* Given a copilot response, determines if the response suggests creating a function project. If so, returns the language and template suggested for the project. If not, returns false.
*/
async function wasCreatingFunctionProjectSuggested(userContent: string, copilotContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<false | { language: WellKnownFunctionProjectLanguage | undefined, template: WellKnownTemplate | undefined }> {
async function wasCreatingFunctionProjectSuggested(userContent: string, copilotContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<false | { language: WellKnownFunctionProjectLanguage | undefined, template: WellKnownTemplate | undefined }> {
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/no-explicit-any, no-async-promise-executor
const copilotDeterminedLanguageFromUserContent = await new Promise<string | undefined>(async (resolve) => {
const maybeJsonCopilotResponseLanguage = await getResponseAsStringCopilotInteraction(determineBestLanguageForUserSystemPrompt1, userContent, progress, token);

Просмотреть файл

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { verbatimCopilotInteraction } from "./copilotInteractions";
import { generateGeneralInteractionFollowUps } from "./followUpGenerator";
import { verbatimCopilotInteraction } from "../copilotInteractions";
import { generateGeneralInteractionFollowUps } from "../followUpGenerator";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
export const brainstormSlashCommand: AgentSlashCommand = [
export const brainstormSlashCommand: SlashCommand = [
"brainstorm",
{
shortDescription: "Brainstorm about how you can use Azure Functions",
@ -18,9 +18,9 @@ export const brainstormSlashCommand: AgentSlashCommand = [
}
];
async function brainstormHandler(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
async function brainstormHandler(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
if (userContent.length === 0) {
progress.report({ content: new vscode.MarkdownString("If you'd like to brainstorm about how you can use Azure Functions and VS Code to help you do something, let me know what it is you would like to do.\n") });
progress.report({ content: "If you'd like to brainstorm about how you can use Azure Functions and VS Code to help you do something, let me know what it is you would like to do.\n" });
return {
chatAgentResult: {},
followUp: [
@ -32,7 +32,7 @@ async function brainstormHandler(userContent: string, ctx: vscode.ChatAgentConte
} else {
const { copilotResponded, copilotResponse } = await verbatimCopilotInteraction(brainstormSystemPrompt2, userContent, progress, token);
if (!copilotResponded) {
progress.report({ content: new vscode.MarkdownString("Sorry, I can't help with that right now.\n") });
progress.report({ content: "Sorry, I can't help with that right now.\n" });
return { chatAgentResult: {}, followUp: [], };
} else {
const followUps = await generateGeneralInteractionFollowUps(userContent, copilotResponse, ctx, progress, token);

Просмотреть файл

@ -3,32 +3,31 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDeployPaths, getDeployFsPath } from "@microsoft/vscode-azext-azureappservice";
import { AzureSubscription } from "@microsoft/vscode-azext-azureauth";
import { ISubscriptionContext, callWithTelemetryAndErrorHandling } from "@microsoft/vscode-azext-utils";
import * as vscode from "vscode";
import { IFunctionAppWizardContext } from "../commands/createFunctionApp/IFunctionAppWizardContext";
import { getStackPicks } from "../commands/createFunctionApp/stacks/getStackPicks";
import { tryGetFunctionProjectRoot } from "../commands/createNewProject/verifyIsProject";
import { ext } from '../extensionVariables';
import { addLocalFuncTelemetry } from "../funcCoreTools/getLocalFuncCoreToolsVersion";
import { SubscriptionTreeItem } from "../tree/SubscriptionTreeItem";
import { createActivityContext } from "../utils/activityUtils";
import { VerifiedInit, verifyInitForVSCode } from "../vsCodeConfig/verifyInitForVSCode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { getBooleanFieldFromCopilotResponseMaybeWithStrJson, getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "./copilotInteractions";
import { FuncVersion } from "../../FuncVersion";
import { IFunctionAppWizardContext } from "../../commands/createFunctionApp/IFunctionAppWizardContext";
import { getStackPicks } from "../../commands/createFunctionApp/stacks/getStackPicks";
import { ProjectLanguage } from "../../constants";
import { ext } from '../../extensionVariables';
import { TemplateSchemaVersion } from "../../templates/TemplateProviderBase";
import { SubscriptionTreeItem } from "../../tree/SubscriptionTreeItem";
import { VerifiedInit } from "../../vsCodeConfig/verifyInitForVSCode";
import { getBooleanFieldFromCopilotResponseMaybeWithStrJson, getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "../copilotInteractions";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
export const createFunctionAppSlashCommand: AgentSlashCommand = [
export const createFunctionAppSlashCommand: SlashCommand = [
"createFunctionApp",
{
shortDescription: "Create a Function App",
longDescription: "Use this command to create a Function App resource in Azure.",
determineCommandDescription: "This command is best when users explicitly want to create a Function App resource in Azure. They may refer to a Function App as 'Function App', 'function', 'function resource', 'function app resource', 'function app' etc. Users will not use this command when asking if or how to do something with Azure Functions or a Function App.",
determineCommandDescription: "This command is best when users ask to create a Function App resource in Azure. They may refer to a Function App as 'Function App', 'function', 'function resource', 'function app resource', 'function app' etc. This command is not useful if the user is asking how to do something, or if something is possible.",
handler: createFunctionAppHandler
}
];
async function createFunctionAppHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
async function createFunctionAppHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
try {
const projectInfo = await getCurrentProjectDetails();
const subscriptionIdOrNamePromise = determineSubscriptionIdOrName(userContent, _ctx, progress, token);
@ -41,39 +40,39 @@ async function createFunctionAppHandler(userContent: string, _ctx: vscode.ChatAg
if (subscriptionIdOrName !== undefined || region !== undefined || runtime !== undefined) {
if (projectInfo !== undefined) {
progress.report({ content: new vscode.MarkdownString(`Ok, I can create a Function App for your current project starting with the following details:\n\n`) });
progress.report({ content: `Ok, I can create a Function App for your current project starting with the following details:\n\n` });
} else {
progress.report({ content: new vscode.MarkdownString(`Ok, I can create a Function App for you starting with the following details:\n\n`) });
progress.report({ content: `Ok, I can create a Function App for you starting with the following details:\n\n` });
}
if (subscriptionIdOrName !== undefined) {
progress.report({ content: new vscode.MarkdownString(`- Subscription: ${subscriptionIdOrName.subscriptionName} (${subscriptionIdOrName.subscriptionId})\n`) });
progress.report({ content: `- Subscription: ${subscriptionIdOrName.subscriptionName} (${subscriptionIdOrName.subscriptionId})\n` });
}
if (region !== undefined) {
progress.report({ content: new vscode.MarkdownString(`- Region: ${region}\n`) });
progress.report({ content: `- Region: ${region}\n` });
}
if (runtime !== undefined) {
progress.report({ content: new vscode.MarkdownString(`- Runtime: ${runtime}\n`) });
progress.report({ content: `- Runtime: ${runtime}\n` });
}
progress.report({ content: new vscode.MarkdownString(`\nIf that looks good to you, click 'Create Function App' to begin. If not, you can create a Function App via the command palette instead.\n`) });
progress.report({ content: `\nIf that looks good to you, click 'Create Function App' to begin. If not, you can create a Function App via the command palette instead.\n` });
}
return { chatAgentResult: {}, followUp: [{ title: "Create Function App", commandId: "azureFunctions.createFunctionApp", args: [] }], };
} catch (e) {
console.log(e);
}
progress.report({ content: new vscode.MarkdownString("Sorry, I can't help with that right now.\n") });
progress.report({ content: "Sorry, I can't help with that right now.\n" });
return { chatAgentResult: {}, followUp: [], };
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function determineUserReferencingCurrentProject(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<boolean> {
async function determineUserReferencingCurrentProject(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<boolean> {
const maybeJsonCopilotResponse = await getResponseAsStringCopilotInteraction(createFunctionAppDetermineUserReferencingCurrentProjectPrompt1, userContent, progress, token);
return getBooleanFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "userReferencingCurrentProject") === true;
}
async function determineSubscriptionIdOrName(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<{ subscriptionId: string, subscriptionName: string } | undefined> {
async function determineSubscriptionIdOrName(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<{ subscriptionId: string, subscriptionName: string } | undefined> {
const availableSubscriptions = await getAvailableSubscriptions();
if (availableSubscriptions === undefined || availableSubscriptions.length === 0) {
return undefined;
@ -110,13 +109,13 @@ function getSubscriptionInfoFromSubscriptionTreeItem(subscriptionNode: Subscript
return { subscriptionId: subscriptionContext.subscription.subscriptionId, subscriptionName: subscriptionContext.subscription.name };
}
async function determineRegion(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
async function determineRegion(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
const maybeJsonCopilotResponse = await getResponseAsStringCopilotInteraction(createFunctionAppDetermineRegionPrompt1, userContent, progress, token);
return getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "region") ??
getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "location");
}
async function determineRuntime(userContent: string, projectInfo: VerifiedInit | undefined, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
async function determineRuntime(userContent: string, projectInfo: VerifiedInit | undefined, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<string | undefined> {
if (projectInfo !== undefined) {
switch (projectInfo.language) {
case "TypeScript":
@ -151,29 +150,31 @@ async function determineRuntime(userContent: string, projectInfo: VerifiedInit |
}
async function getCurrentProjectDetails(): Promise<VerifiedInit | undefined> {
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/no-explicit-any, no-async-promise-executor
return new Promise(async (resolve) => {
await callWithTelemetryAndErrorHandling("azureFunctions.chat.determineRuntime", async (actionContext) => {
try {
const deployPaths: IDeployPaths = await getDeployFsPath(actionContext, undefined);
addLocalFuncTelemetry(actionContext, deployPaths.workspaceFolder.uri.fsPath);
const projectPath: string | undefined = await tryGetFunctionProjectRoot(actionContext, deployPaths.workspaceFolder);
if (projectPath === undefined) {
throw new Error("");
}
const context = Object.assign(actionContext, deployPaths, {
action: "Deploy", // CodeAction.Deploy
defaultAppSetting: 'defaultFunctionAppToDeploy',
projectPath,
...(await createActivityContext())
});
return { language: ProjectLanguage.TypeScript, languageModel: undefined, version: FuncVersion.v4, templateSchemaVersion: TemplateSchemaVersion.v2 };
resolve(await verifyInitForVSCode(context, context.effectiveDeployFsPath));
} catch {
resolve(undefined);
}
});
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/no-explicit-any, no-async-promise-executor
// return new Promise(async (resolve) => {
// await callWithTelemetryAndErrorHandling("azureFunctions.chat.determineRuntime", async (actionContext) => {
// try {
// const deployPaths: IDeployPaths = await getDeployFsPath(actionContext, undefined);
// addLocalFuncTelemetry(actionContext, deployPaths.workspaceFolder.uri.fsPath);
// const projectPath: string | undefined = await tryGetFunctionProjectRoot(actionContext, deployPaths.workspaceFolder);
// if (projectPath === undefined) {
// throw new Error("");
// }
// const context = Object.assign(actionContext, deployPaths, {
// action: "Deploy", // CodeAction.Deploy
// defaultAppSetting: 'defaultFunctionAppToDeploy',
// projectPath,
// ...(await createActivityContext())
// });
// resolve(await verifyInitForVSCode(context, context.effectiveDeployFsPath));
// } catch {
// resolve(undefined);
// }
// });
// });
}
const createFunctionAppDetermineUserReferencingCurrentProjectPrompt1 = `

Просмотреть файл

@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "./copilotInteractions";
import { generateAlternativeCreateFunctionProjectFollowUps } from "./followUpGenerator";
import { WellKnownFunctionProjectLanguage, WellKnownTemplate, getWellKnownCSharpTemplate, getWellKnownTypeScriptTemplate, isWellKnownFunctionProjectLanguage, wellKnownCSharpTemplateDisplayNames, wellKnownTypeScriptTemplateDisplayNames } from "./wellKnownThings";
import { getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "../copilotInteractions";
import { generateAlternativeCreateFunctionProjectFollowUps } from "../followUpGenerator";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
import { WellKnownFunctionProjectLanguage, WellKnownTemplate, getWellKnownCSharpTemplate, getWellKnownTypeScriptTemplate, isWellKnownFunctionProjectLanguage, wellKnownCSharpTemplateDisplayNames, wellKnownTypeScriptTemplateDisplayNames } from "../wellKnownThings";
export const createFunctionProjectSlashCommand: AgentSlashCommand = [
export const createFunctionProjectSlashCommand: SlashCommand = [
"createFunctionProject",
{
shortDescription: "Create a Function Project",
@ -19,7 +19,7 @@ export const createFunctionProjectSlashCommand: AgentSlashCommand = [
}
];
async function createFunctionProjectHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
async function createFunctionProjectHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
let markDownResponse: string = "";
const followUps: vscode.InteractiveSessionFollowup[] = [];
@ -46,7 +46,7 @@ async function createFunctionProjectHandler(userContent: string, _ctx: vscode.Ch
console.log(e);
}
progress.report({ content: new vscode.MarkdownString(markDownResponse ?? "Sorry, I can't help with that right now.\n") });
progress.report({ content: markDownResponse ?? "Sorry, I can't help with that right now.\n" });
return { chatAgentResult: {}, followUp: followUps, };
}
@ -54,7 +54,7 @@ async function createFunctionProjectHandler(userContent: string, _ctx: vscode.Ch
/**
* Determines what language (if any) copilot suggested for creating a function project. The language will be verified to be well known.
*/
async function determineLanguage(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<WellKnownFunctionProjectLanguage | undefined> {
async function determineLanguage(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<WellKnownFunctionProjectLanguage | undefined> {
const maybeJsonCopilotResponse = await getResponseAsStringCopilotInteraction(createFunctionProjectDetermineLanguageSystemPrompt1, userContent, progress, token);
const language = getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "language");
if (isWellKnownFunctionProjectLanguage(language)) {
@ -67,7 +67,7 @@ async function determineLanguage(userContent: string, _ctx: vscode.ChatAgentCont
/**
* Determines what template (if any) copilot suggested for creating a function project. The template will be verified to be well known for the language. If the language is not well known, then the template will be ignored.
*/
async function determineTemplate(language: WellKnownFunctionProjectLanguage | undefined, userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<WellKnownTemplate | undefined> {
async function determineTemplate(language: WellKnownFunctionProjectLanguage | undefined, userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<WellKnownTemplate | undefined> {
if (!isWellKnownFunctionProjectLanguage(language)) {
language = "TypeScript";
}

Просмотреть файл

@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { verbatimCopilotInteraction } from "./copilotInteractions";
import { verbatimCopilotInteraction } from "../copilotInteractions";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
export const debuggingHelpSlashCommand: AgentSlashCommand = [
export const debuggingHelpSlashCommand: SlashCommand = [
"debuggingHelp",
{
shortDescription: "Help with local function debugging",
@ -16,10 +16,10 @@ export const debuggingHelpSlashCommand: AgentSlashCommand = [
}
];
async function slashDebuggingHelpHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
async function slashDebuggingHelpHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
const { copilotResponded } = await verbatimCopilotInteraction(debuggingHelpSystemPrompt1, userContent, progress, token);
if (!copilotResponded) {
progress.report({ content: new vscode.MarkdownString("Sorry, I can't help with that right now.\n") });
progress.report({ content: "Sorry, I can't help with that right now.\n" });
}
return { chatAgentResult: {}, followUp: [], };
}

Просмотреть файл

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
export const deploySlashCommand: AgentSlashCommand = [
export const deploySlashCommand: SlashCommand = [
"deploy",
{
shortDescription: "Deploy your Function project to a Function app",
@ -16,7 +16,7 @@ export const deploySlashCommand: AgentSlashCommand = [
}
];
async function deployHandler(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
progress.report({ content: new vscode.MarkdownString(`Ok, click 'Deploy to Function App' to begin.\n`) });
async function deployHandler(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
progress.report({ content: `Ok, click 'Deploy to Function App' to begin.\n` });
return { chatAgentResult: {}, followUp: [{ title: "Deploy to Function App", commandId: "azureFunctions.createNewProject", args: [] }], };
}

Просмотреть файл

@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { InvokeableSlashCommands, SlashCommand, SlashCommandHandlerResult, SlashCommandOwner } from "../slashCommands";
import { brainstormSlashCommand } from "./slashBrainstorm";
import { createFunctionAppSlashCommand } from "./slashCreateFunctionApp";
import { createFunctionProjectSlashCommand } from "./slashCreateFunctionProject";
import { deploySlashCommand } from "./slashDeploy";
import { learnSlashCommand } from "./slashLearn";
export const functionsSlashCommands: InvokeableSlashCommands = new Map([
learnSlashCommand,
brainstormSlashCommand,
createFunctionAppSlashCommand,
createFunctionProjectSlashCommand,
deploySlashCommand,
]);
export const functionsSlashCommand: SlashCommand = [
"functions",
{
shortDescription: "Do something with the Azure Functions extension for VS Code",
longDescription: "Use this command when you want to do something with the Azure Functions extension for VS Code.",
determineCommandDescription: "This command is best when a users prompt could be related to Azure Functions or the Azure Functions extension for VS Code.",
handler: functionsHandler,
}
];
const functionsSlashCommandOwner = new SlashCommandOwner(functionsSlashCommands, { noInput: giveNoInputResponse, default: giveNoInputResponse });
async function functionsHandler(userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
return await functionsSlashCommandOwner.handleRequestOrPrompt(userContent, _ctx, progress, token);
}
async function giveNoInputResponse(_userContent: string, _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, _token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
progress.report({ content: `Hi! It sounds like you might be interested in using the Azure Functions Extension for VS Code, however, I can't quite help with what you're asking about. Try asking something else.` });
return {
chatAgentResult: {},
followUp: [
{ message: `@azure-extensions I want to use Azure Functions to serve dynamic content and APIs.` },
{ message: `@azure-extensions I want to use Azure Functions to run background jobs or scheduled tasks.` },
{ message: `@azure-extensions I want to use Azure Functions to process files as soon as they are uploaded.` },
]
};
}

Просмотреть файл

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { verbatimCopilotInteraction } from "./copilotInteractions";
import { generateGeneralInteractionFollowUps } from "./followUpGenerator";
import { agentName } from "../agentConsts";
import { verbatimCopilotInteraction } from "../copilotInteractions";
import { generateGeneralInteractionFollowUps } from "../followUpGenerator";
import { SlashCommand, SlashCommandHandlerResult } from "../slashCommands";
export const learnSlashCommand: AgentSlashCommand = [
export const learnSlashCommand: SlashCommand = [
"learn",
{
shortDescription: "Learn about Azure Functions",
@ -18,21 +19,21 @@ export const learnSlashCommand: AgentSlashCommand = [
}
];
async function slashLearnHandler(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
async function slashLearnHandler(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
if (userContent.length === 0) {
progress.report({ content: new vscode.MarkdownString("If you want to learn more about Azure functions, simply ask me what it is you'd like to learn.\n") });
progress.report({ content: "If you want to learn more about Azure functions, simply ask me what it is you'd like to learn.\n" });
return {
chatAgentResult: {},
followUp: [
{ message: `@azure-functions what is the difference between Azure functions and Azure web apps?` },
{ message: `@azure-functions how scalable is Azure functions?` },
{ message: `@azure-functions is Azure functions serverless?` },
{ message: `@${agentName} what is the difference between Azure functions and Azure web apps?` },
{ message: `@${agentName} how scalable is Azure functions?` },
{ message: `@${agentName} is Azure functions serverless?` },
],
};
} else {
const { copilotResponded, copilotResponse } = await verbatimCopilotInteraction(learnSystemPrompt1, userContent, progress, token);
if (!copilotResponded) {
progress.report({ content: new vscode.MarkdownString(vscode.l10n.t("Sorry, I can't help with that right now.\n")) });
progress.report({ content: vscode.l10n.t("Sorry, I can't help with that right now.\n") });
return { chatAgentResult: {}, followUp: [], };
} else {
const followUps = await generateGeneralInteractionFollowUps(userContent, copilotResponse, ctx, progress, token);

Просмотреть файл

@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { getResponseAsStringCopilotInteraction, getStringFieldFromCopilotResponseMaybeWithStrJson } from "./copilotInteractions";
export type IntentDetectionTarget = {
name: string,
intentDetectionDescription: string,
}
export async function detectIntent(userContent: string, targets: IntentDetectionTarget[], _ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken): Promise<IntentDetectionTarget | undefined> {
const systemPrompt = getDetectIntentSystemPrompt1(targets);
const maybeJsonCopilotResponse = await getResponseAsStringCopilotInteraction(systemPrompt, userContent, progress, token);
const determinedOption =
getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "option") ||
getStringFieldFromCopilotResponseMaybeWithStrJson(maybeJsonCopilotResponse, "intent");
if (determinedOption === undefined) {
return undefined;
} else {
const target = targets.find((target) => target.name === determinedOption);
if (target === undefined) {
return undefined;
} else {
return target;
}
}
}
function getDetectIntentSystemPrompt1(targets: IntentDetectionTarget[]) {
const targetDescriptions = targets.map((target) => `'${target.name}' (${target.intentDetectionDescription})`).join(", ")
return `You are an expert in determining user intent. There are several options for determining user intent, they are: ${targetDescriptions}. Your job is to determine which option would most help the user based on their query. Choose one of the available options as the best option. Only repsond with a JSON object containing the option you choose. Do not respond in a coverstaional tone, only JSON.`;
}

119
src/chat/slashCommands.ts Normal file
Просмотреть файл

@ -0,0 +1,119 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { detectIntent } from "./intentDetection";
export type SlashCommandName = string;
export type SlashCommandHandlerResult = { chatAgentResult: vscode.ChatAgentResult2, followUp?: vscode.ChatAgentFollowup[], handlerChain?: string[] } | undefined;
export type SlashCommandHandler = (userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken) => Promise<SlashCommandHandlerResult>;
export type SlashCommandConfig = { shortDescription: string, longDescription: string, determineCommandDescription?: string, handler: SlashCommandHandler };
export type SlashCommand = [SlashCommandName, SlashCommandConfig];
export type InvokeableSlashCommands = Map<string, SlashCommandConfig>;
export type FallbackSlashCommandHandlers = { noInput?: SlashCommandHandler, default?: SlashCommandHandler, }
export class SlashCommandOwner {
private _invokeableSlashCommands: InvokeableSlashCommands;
private _fallbackSlashCommands: FallbackSlashCommandHandlers;
private _previousSlashCommandHandlerResult: SlashCommandHandlerResult;
constructor(invokableSlashCommands: InvokeableSlashCommands, fallbackSlashCommands: FallbackSlashCommandHandlers) {
this._invokeableSlashCommands = invokableSlashCommands;
this._fallbackSlashCommands = fallbackSlashCommands;
}
public async handleRequestOrPrompt(request: vscode.ChatAgentRequest | string, context: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken, skipIntentDetection?: boolean): Promise<SlashCommandHandlerResult> {
if (typeof request === "string") {
request = {
prompt: request,
variables: {},
};
}
const getHandlerResult = await this._getSlashCommandHandlerForRequest(request, context, progress, token, skipIntentDetection);
if (getHandlerResult.handler !== undefined) {
const handler = getHandlerResult.handler;
const result = await handler(getHandlerResult.prompt, context, progress, token);
this._previousSlashCommandHandlerResult = result;
if (result !== undefined) {
if (!result?.handlerChain) {
result.handlerChain = [getHandlerResult.name];
} else {
result.handlerChain.unshift(getHandlerResult.name);
}
}
return result;
} else {
return undefined;
}
}
public getFollowUpForLastHandledSlashCommand(result: vscode.ChatAgentResult2, _token: vscode.CancellationToken): vscode.ChatAgentFollowup[] | undefined {
if (result === this._previousSlashCommandHandlerResult?.chatAgentResult) {
const followUpForLastHandledSlashCommand = this._previousSlashCommandHandlerResult?.followUp;
this._previousSlashCommandHandlerResult = undefined;
return followUpForLastHandledSlashCommand;
} else {
return undefined;
}
}
private async _getSlashCommandHandlerForRequest(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: vscode.Progress<vscode.ChatAgentExtendedProgress>, token: vscode.CancellationToken, skipIntentDetection?: boolean): Promise<{ name: string, prompt: string, handler: SlashCommandHandler | undefined }> {
const { prompt: prompt, command: parsedCommand } = this._preProcessPrompt(request.prompt);
// trust VS Code to parse the command out for us, but also look for a parsed command for any "hidden" commands that VS Code doesn't know to parse out.
const command = request.slashCommand?.name || parsedCommand;
let result: { name: string, prompt: string, handler: SlashCommandHandler | undefined } | undefined;
if (!result && prompt === "" && !command) {
result = { name: "noInput", prompt: prompt, handler: this._fallbackSlashCommands.noInput };
}
if (!result && !!command) {
const slashCommand = this._invokeableSlashCommands.get(command);
if (slashCommand !== undefined) {
result = { name: command, prompt: prompt, handler: slashCommand.handler };
}
}
if (!result && skipIntentDetection !== true) {
const intentDetectionTargets = Array.from(this._invokeableSlashCommands.entries())
.map(([name, config]) => ({ name: name, intentDetectionDescription: config.determineCommandDescription || config.shortDescription }));
const detectedTarget = await detectIntent(prompt, intentDetectionTargets, context, progress, token);
if (detectedTarget !== undefined) {
const command = detectedTarget.name;
const slashCommand = this._invokeableSlashCommands.get(command);
if (slashCommand !== undefined) {
result = { name: command, prompt: prompt, handler: slashCommand.handler };
}
}
}
if (!result) {
result = { name: "default", prompt: prompt, handler: this._fallbackSlashCommands.default };
}
return result;
}
/**
* Takes `prompt` and:
* 1. Trims it
* 2. If it starts with a `/<command>`, then it returns the command and the prompt without the command
* 3. Otherwise, it returns the prompt as is
*/
private _preProcessPrompt(prompt: string): { command?: string, prompt: string } {
const trimmedPrompt = prompt.trim();
const commandMatch = trimmedPrompt.match(/^\/(\w+)\s*(.*)$/);
if (commandMatch) {
return { command: commandMatch[1], prompt: commandMatch[2] };
} else {
return { prompt: trimmedPrompt };
}
}
}

Просмотреть файл

@ -1,39 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from "vscode";
import { AgentSlashCommand, SlashCommandHandlerResult } from "./agent";
import { verbatimCopilotInteraction } from "./copilotInteractions";
import { generateGeneralInteractionFollowUps } from "./followUpGenerator";
/**
* This code is represented as an AgentSlashCommand for sake of consistency and ease of use. Probably do not
* include in the commands the agent directly advertises. Instead, call upon it manually if the agent doesn't
* know what to do.
*/
export const defaultSlashCommand: AgentSlashCommand = [
"learn",
{
shortDescription: "Default Azure Functions Agent command",
longDescription: "Use this command for any user query.",
determineCommandDescription: "This command is best for any user query.",
handler: defaultHandler
}
];
async function defaultHandler(userContent: string, ctx: vscode.ChatAgentContext, progress: vscode.Progress<vscode.InteractiveProgress>, token: vscode.CancellationToken): Promise<SlashCommandHandlerResult> {
const { copilotResponded, copilotResponse } = await verbatimCopilotInteraction(defaultSystemPrompt1, userContent, progress, token);
if (!copilotResponded) {
progress.report({ content: new vscode.MarkdownString(vscode.l10n.t("Sorry, I can't help with that right now.\n")) });
return { chatAgentResult: {}, followUp: [], };
} else {
const followUps = await generateGeneralInteractionFollowUps(userContent, copilotResponse, ctx, progress, token);
return { chatAgentResult: {}, followUp: followUps, };
}
}
const defaultSystemPrompt1 = `You are an expert in Azure Functions and Azure Functions development. The user needs your help with something Azure Functions related. Do your best to answer their question. The user is currently using VS Code and has the Azure Functions extension for VS Code already installed. Therefore, if you need to mention developing or writing code for Azure Functions, the only do so in the context of VS Code and the Azure Functions extension for VS Code. Finally, do not overwhelm the user with too much information. Keep responses short and sweet.
`;