microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
32bcffe86a3647ddd45528ecb551f8feddd749ab

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/vscode/src/gh-copilot/qsharpTools.ts

324lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import { VSDiagnostic } from "qsharp-lang";
5import vscode from "vscode";
6import { CircuitOrError, showCircuitCommand } from "../circuit";
7import { loadCompilerWorker, toVsCodeDiagnostic } from "../common";
8import { createDebugConsoleEventTarget } from "../debugger/output";
9import { resourceEstimateTool } from "../estimate";
10import { FullProgramConfig, getProgramForDocument } from "../programConfig";
11import {
12 determineDocumentType,
13 EventType,
14 sendTelemetryEvent,
15 UserTaskInvocationType,
16} from "../telemetry";
17import { getRandomGuid } from "../utils";
18import { sendMessageToPanel } from "../webviewPanel.js";
19import { CopilotToolError, HistogramData } from "./types";
20
21/**
22 * In general, tool calls that deal with Q# should include this project
23 * info in their output. Since Copilot just passes in a file path, and isn't
24 * familiar with how we expand the project or how we determine target profile,
25 * this output will give Copilot context to understand what just happened.
26 */
27type ProjectInfo = {
28 project: {
29 name: string;
30 targetProfile: string;
31 };
32};
33
34type RunProgramResult = ProjectInfo &
35 (
36 | {
37 output: string;
38 result: string | vscode.Diagnostic;
39 }
40 | {
41 histogram: HistogramData;
42 sampleFailures: vscode.Diagnostic[];
43 message: string;
44 }
45 );
46
47export class QSharpTools {
48 constructor(private extensionUri: vscode.Uri) {}
49
50 /**
51 * Implements the `qsharp-run-program` tool call.
52 */
53 async runProgram(input: {
54 filePath: string;
55 shots?: number;
56 }): Promise<RunProgramResult> {
57 const shots = input.shots ?? 1;
58
59 const program = await this.getProgram(input.filePath);
60 const programConfig = program.program.programConfig;
61
62 const output: string[] = [];
63 let finalHistogram: HistogramData | undefined;
64 let sampleFailures: vscode.Diagnostic[] = [];
65 const panelId = programConfig.projectName;
66
67 const start = performance.now();
68 const associationId = getRandomGuid();
69 if (shots > 1) {
70 sendTelemetryEvent(
71 EventType.TriggerHistogram,
72 {
73 associationId,
74 documentType: program.telemetryDocumentType,
75 invocationType: UserTaskInvocationType.ChatToolCall,
76 },
77 {},
78 );
79 sendTelemetryEvent(EventType.HistogramStart, { associationId }, {});
80 }
81
82 await this.runQsharp(
83 programConfig,
84 shots,
85 (msg) => {
86 output.push(msg);
87 },
88 (histogram, failures) => {
89 finalHistogram = histogram;
90 const uniqueFailures = new Set<string>();
91 sampleFailures = [];
92 for (const failure of failures) {
93 const failureKey = `${failure.message}-${failure.range?.start.line}-${failure.range?.start.character}`;
94 if (!uniqueFailures.has(failureKey)) {
95 uniqueFailures.add(failureKey);
96 sampleFailures.push(failure);
97 }
98 if (sampleFailures.length === 3) {
99 break;
100 }
101 }
102 if (
103 shots > 1 &&
104 histogram.buckets.filter((b) => b[0] !== "ERROR").length > 0
105 ) {
106 // Display the histogram panel only if we're running multiple shots,
107 // and we have at least one successful result.
108 sendMessageToPanel(
109 { panelType: "histogram", id: panelId },
110 true, // reveal the panel
111 histogram,
112 );
113 }
114 },
115 );
116
117 if (shots > 1) {
118 sendTelemetryEvent(
119 EventType.HistogramEnd,
120 { associationId },
121 { timeToCompleteMs: performance.now() - start },
122 );
123 }
124
125 const project = {
126 name: programConfig.projectName,
127 targetProfile: programConfig.profile,
128 };
129
130 if (shots === 1) {
131 // Return the output and results directly
132 return {
133 project,
134 output: output.join("\n"),
135 result:
136 sampleFailures.length > 0
137 ? sampleFailures[0]
138 : (finalHistogram?.buckets[0][0] as string),
139 };
140 } else {
141 // No output, return the histogram
142 return {
143 project,
144 sampleFailures,
145 histogram: finalHistogram!,
146 message: `Results are displayed in the Histogram panel.`,
147 };
148 }
149 }
150
151 /**
152 * Implements the `qsharp-generate-circuit` tool call.
153 */
154 async generateCircuit(input: { filePath: string }): Promise<
155 ProjectInfo &
156 CircuitOrError & {
157 message?: string;
158 }
159 > {
160 const program = await this.getProgram(input.filePath);
161 const programConfig = program.program.programConfig;
162
163 const circuitOrError = await showCircuitCommand(
164 this.extensionUri,
165 undefined,
166 UserTaskInvocationType.ChatToolCall,
167 program.telemetryDocumentType,
168 programConfig,
169 );
170
171 const result = {
172 project: {
173 name: programConfig.projectName,
174 targetProfile: programConfig.profile,
175 },
176 ...circuitOrError,
177 };
178
179 if (circuitOrError.result === "success") {
180 return {
181 ...result,
182 message: "Circuit is displayed in the Circuit panel.",
183 };
184 } else {
185 return {
186 ...result,
187 };
188 }
189 }
190
191 /**
192 * Implements the `qsharp-run-resource-estimator` tool call.
193 */
194 async runResourceEstimator(input: {
195 filePath: string;
196 qubitTypes?: string[];
197 errorBudget?: number;
198 }): Promise<
199 ProjectInfo & {
200 estimates?: object[];
201 message: string;
202 }
203 > {
204 const program = await this.getProgram(input.filePath);
205 const programConfig = program.program.programConfig;
206
207 const project = {
208 name: programConfig.projectName,
209 targetProfile: programConfig.profile,
210 };
211
212 try {
213 const qubitTypes = input.qubitTypes ?? ["qubit_gate_ns_e3"];
214 const errorBudget = input.errorBudget ?? 0.001;
215
216 const estimates = await resourceEstimateTool(
217 this.extensionUri,
218 programConfig,
219 program.telemetryDocumentType,
220 qubitTypes,
221 errorBudget,
222 );
223
224 return {
225 project,
226 estimates,
227 message: "Results are displayed in the resource estimator panel.",
228 };
229 } catch (e) {
230 throw new CopilotToolError(
231 "Failed to run resource estimator: " +
232 (e instanceof Error ? e.message : String(e)),
233 );
234 }
235 }
236
237 private async getProgram(filePath: string) {
238 const docUri = vscode.Uri.file(filePath);
239
240 const doc = await vscode.workspace.openTextDocument(docUri);
241 const telemetryDocumentType = determineDocumentType(doc);
242
243 const program = await getProgramForDocument(doc);
244 if (!program.success) {
245 throw new CopilotToolError(
246 `Cannot get program for the file ${filePath}\n\n${program.diagnostics ? JSON.stringify(program.diagnostics) : program.errorMsg}`,
247 );
248 }
249 return { program, telemetryDocumentType };
250 }
251
252 private async runQsharp(
253 program: FullProgramConfig,
254 shots: number,
255 out: (message: string) => void,
256 resultUpdate: (
257 histogram: HistogramData,
258 failures: vscode.Diagnostic[],
259 ) => void,
260 ) {
261 let histogram: HistogramData | undefined;
262 const evtTarget = createDebugConsoleEventTarget((msg) => {
263 out(msg);
264 }, true /* captureEvents */);
265
266 // create a promise that we'll resolve when the run is done
267 let resolvePromise: () => void = () => {};
268 const allShotsDone = new Promise<void>((resolve) => {
269 resolvePromise = resolve;
270 });
271
272 evtTarget.addEventListener("uiResultsRefresh", () => {
273 const results = evtTarget.getResults();
274 const resultCount = evtTarget.resultCount(); // compiler errors come through here too
275 const buckets = new Map();
276 const failures = [];
277 for (let i = 0; i < resultCount; ++i) {
278 const key = results[i].result;
279 const strKey = typeof key !== "string" ? "ERROR" : key;
280 const newValue = (buckets.get(strKey) || 0) + 1;
281 buckets.set(strKey, newValue);
282 if (!results[i].success) {
283 failures.push(toVsCodeDiagnostic(results[i].result as VSDiagnostic));
284 }
285 }
286 histogram = {
287 buckets: Array.from(buckets.entries()) as [string, number][],
288 shotCount: resultCount,
289 };
290 resultUpdate(histogram!, failures);
291 if (shots === resultCount || failures.length > 0) {
292 // TODO: ugh
293 resolvePromise();
294 }
295 });
296
297 const compilerRunTimeoutMs = 1000 * 60 * 5; // 5 minutes
298 const compilerTimeout = setTimeout(() => {
299 worker.terminate();
300 }, compilerRunTimeoutMs);
301 const worker = loadCompilerWorker(this.extensionUri!);
302
303 try {
304 await worker.run(program, "", shots, evtTarget);
305 // We can still receive events after the above call is done
306 await allShotsDone;
307 } catch {
308 // Compiler errors can come through here. But the error object here doesn't contain enough
309 // information to be useful. So wait for the one that comes through the event target.
310 await allShotsDone;
311
312 const failures = evtTarget
313 .getResults()
314 .filter((result) => !result.success)
315 .map((result) => toVsCodeDiagnostic(result.result as VSDiagnostic));
316
317 throw new CopilotToolError(
318 `Program failed with compilation errors. ${JSON.stringify(failures)}`,
319 );
320 }
321 clearTimeout(compilerTimeout);
322 worker.terminate();
323 }
324}
325