diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a9054c45fb..9cf210d64c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -39,6 +39,15 @@ Most of the core components are implemented in Rust. These components are packag - **qsc_rir/**: Runtime Intermediate Representation - **fuzz/**: Fuzz testing infrastructure for the compiler - **language_service/**: Q# language service for editor features + - **src/code_action.rs**: Code actions + - **src/code_lens/**: Code lens + - **src/completion/**: Completions + - **src/definition/**: Go to definition + - **src/hover/**: Hover + - **src/references/**: Find references + - **src/rename/**: Inline rename + - **src/signature_help/**: Signature help + - **src/state/**: Language service state management - **noisy_simulator/**: Simulator for quantum noise modeling - **resource_estimator/**: Quantum Resource Estimator implementation - **wasm/**: WebAssembly bindings for core components @@ -49,6 +58,22 @@ Most of the core components are implemented in Rust. These components are packag **Python** - **pip/**: The `qsharp` Python package + - **qsharp/**: Python package source + - **src/**: Rust implementation for Python bindings + - **tests/**: Unit tests + - **tests-integration/**: Integration tests with Qiskit, PyQIR, QIR Runner, and simulators + - **interop_qiskit/**: Qiskit interoperability tests + - **test_circuits/**: Test circuit definitions + - **test_gateset_qasm.py**: QASM gate set tests + - **test_gate_correctness.py**: Gate correctness validation + - **test_qir.py**: QIR integration with Qiskit + - **test_qsharp.py**: Q# and Qiskit interop tests + - **test_re.py**: Resource estimator tests + - **test_run_sim.py**: Simulation runtime tests + - **resources/**: Test resources for adaptive QIR + - **test_adaptive_ri_qir.py**: Adaptive Result Interop QIR tests + - **test_adaptive_rif_qir.py**: Adaptive Result+Feedback Interop QIR tests + - **test_base_qir.py**: Base QIR functionality tests - **jupyterlab/**: JupyterLab extension for Q# - **widgets/**: Q# Jupyter widgets @@ -71,6 +96,43 @@ Most of the core components are implemented in Rust. These components are packag - **npm/**: The `qsharp-lang` npm package - **playground/**: Q# Playground website - **vscode/**: Visual Studio Code extension for Q# + - **src/**: Extension source code + - **language-service/**: Language service integration + - **activate.ts**: Language service activation + - **codeActions.ts**: Code actions implementation + - **codeLens.ts**: Code lens provider + - **completion.ts**: Autocompletion provider + - **definition.ts**: Go to definition + - **diagnostics.ts**: Diagnostics handling + - **format.ts**: Code formatting + - **hover.ts**: Hover information provider + - **notebook.ts**: Notebook integration + - **references.ts**: Find references + - **rename.ts**: Symbol renaming + - **signature.ts**: Signature help provider + - **testExplorer.ts**: Test explorer integration + - **webview/**: UI components + - **docview.tsx**: Documentation view + - **editor.tsx**: Editor components + - **help.tsx**: Help view + - **webview.tsx**: Webview base + - **theme.ts**: Theme handling + - **azure/**: Azure integration + - **debugger/**: Debugger implementation + - **copilot/**: Copilot integration + - **gh-copilot/**: GitHub Copilot integration + - **extension.ts**: Main extension entry point + - **circuit.ts**: Circuit representation + - **circuitEditor.ts**: Circuit editor + - **diagnostics.ts**: Diagnostic handling + - **projectSystem.ts**: Project management + - **qirGeneration.ts**: QIR generation + - **notebook.ts**: Notebook integration + - **telemetry.ts**: Telemetry collection + - **resources/**: Extension resources + - **syntaxes/**: Q# syntax definitions + - **test/**: Extension tests + - **wasm/**: WebAssembly integration ## Development workflow diff --git a/.gitignore b/.gitignore index 8587b8fcf9..6834c8d93d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ __pycache__/ .pytest_cache/ .idea/ *.so +# temp - local dev stuff for reference +reference.json +language-model-tools-reference.json.md diff --git a/eslint.config.mjs b/eslint.config.mjs index 96c46336f9..1fe8a06f78 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,6 +20,7 @@ export default tseslint.config( "jupyterlab/lib/", "jupyterlab/qsharp-jupyterlab/labextension/", "**/.*/", + "vscode/extract-tools.js", "vscode/out/", "vscode/test/out/", "widgets/src/qsharp_widgets/static/", diff --git a/vscode/extract-tools.js b/vscode/extract-tools.js new file mode 100644 index 0000000000..5e7e684125 --- /dev/null +++ b/vscode/extract-tools.js @@ -0,0 +1,53 @@ +const fs = require("fs"); +const path = require("path"); + +const name = process.argv[2] || "package.json"; +if (!fs.existsSync(name)) { + console.error(`File not found: ${name}`); + process.exit(1); +} + +// Read package.json file +const packageJsonPath = path.join(process.cwd(), name); +const packageJson = require(packageJsonPath); + +// Extract the languageModelTools array +const tools = packageJson.contributes.languageModelTools; + +// Prepare the markdown content +let markdownContent = "# Q# Language Model Tools\n\n"; + +// Loop through each tool and extract the required fields +tools.forEach((tool) => { + markdownContent += `## ${tool.displayName}\n\n`; + markdownContent += `- **Name**: \`${tool.name}\`\n`; + markdownContent += `- **Description**: ${tool.modelDescription}\n\n`; + + // Extract and format input parameters if available + if (tool.inputSchema && tool.inputSchema.properties) { + markdownContent += "### Input Parameters\n\n"; + + // Create a parameters table with name, type, required, and description + markdownContent += "| Parameter | Type | Required | Description |\n"; + markdownContent += "|-----------|------|----------|-------------|\n"; + + const properties = tool.inputSchema.properties; + const requiredParams = tool.inputSchema.required || []; + + Object.keys(properties).forEach((paramName) => { + const param = properties[paramName]; + const isRequired = requiredParams.includes(paramName) ? "Yes" : "No"; + markdownContent += `| \`${paramName}\` | \`${param.type}\` | ${isRequired} | ${param.description} |\n`; + }); + + markdownContent += "\n"; + } +}); + +// Write the markdown content to a file +const outputPath = path.join(process.cwd(), `language-model-tools-${name}.md`); +fs.writeFileSync(outputPath, markdownContent); + +console.log( + `Successfully extracted language model tools info to ${outputPath}`, +); diff --git a/vscode/language-model-tools-package.json.md b/vscode/language-model-tools-package.json.md new file mode 100644 index 0000000000..e8c4edb2d6 --- /dev/null +++ b/vscode/language-model-tools-package.json.md @@ -0,0 +1,143 @@ +# Q# Language Model Tools + +## Get Azure Quantum Jobs + +- **Name**: `azure-quantum-get-jobs` +- **Description**: Get a list of recent jobs that have been run by the customer, along with their statuses, in the currently active workspace. Call this when you need to know what jobs have been run recently or need a history of jobs run, for example when a customer asks 'What are my recent jobs?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| + +## Get Azure Quantum Job Details + +- **Name**: `azure-quantum-get-job` +- **Description**: Get the job information for a customer's job given its id. Call this whenever you need to know information about a specific job, for example when a customer asks 'What is the status of my job?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `job_id` | `string` | Yes | Job's unique identifier. | + +## Connect to Azure Quantum Workspace + +- **Name**: `azure-quantum-connect-to-workspace` +- **Description**: Starts the UI flow to connect to an existing Azure Quantum Workspace. Call this when the customer does not have an active workspace, and agrees to connect to a workspace. + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| + +## Download Azure Quantum Job Results + +- **Name**: `azure-quantum-download-job-results` +- **Description**: Download and display the results from a customer's job given its id. Call this when you need to download or display as a histogram the results for a job, for example when a customer asks 'What are the results of my last job?' or 'Can you show me the histogram for this job?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `job_id` | `string` | Yes | Job's unique identifier. | + +## Get Azure Quantum Workspaces + +- **Name**: `azure-quantum-get-workspaces` +- **Description**: Get a list of workspaces the customer has access to, in the form of workspace ids. Call this when you need to know what workspaces the customer has access to, for example when a customer asks 'What are my workspaces?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| + +## Submit to Azure Quantum Target + +- **Name**: `azure-quantum-submit-to-target` +- **Description**: Submit the current Q# program to Azure Quantum with the provided information. Call this when you need to submit or run a Q# program with Azure Quantum, for example when a customer asks 'Can you submit this program to Azure?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `job_name` | `string` | Yes | The string to name the created job. | +| `target_id` | `string` | Yes | The ID or name of the target to submit the job to. | +| `number_of_shots` | `number` | Yes | The number of shots to use for the job. | + +## Get Active Azure Quantum Workspace + +- **Name**: `azure-quantum-get-active-workspace` +- **Description**: Get the id of the active workspace for this conversation. Call this when you need to know what workspace is the active workspace being used in the context of the current conversation, for example when a customer asks 'What is the workspace that's being used?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| + +## Set Active Azure Quantum Workspace + +- **Name**: `azure-quantum-set-active-workspace` +- **Description**: Set the active workspace for this conversation by id. Call this when you need to set what workspace is the active workspace being used in the context of the current conversation, for example when a customer says 'Please use this workspace for my requests.' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `workspace_id` | `string` | Yes | The id of the workspace to set as active. | + +## Get Azure Quantum Providers + +- **Name**: `azure-quantum-get-providers` +- **Description**: Get a list of hardware providers available to the customer, along with their provided targets. Call this when you need to know what providers or targets are available, for example when a customer asks 'What are the available providers?' or 'What targets do I have available?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| + +## Get Azure Quantum Target + +- **Name**: `azure-quantum-get-target` +- **Description**: Get the target information for a specified target given its id. Call this whenever you need to know information about a specific target, for example when a customer asks 'What is the status of this target?' + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `target_id` | `string` | Yes | The ID of the target to get. | + +## Run Q# Program + +- **Name**: `qsharp-run-program` +- **Description**: Executes a Q# program. The path to a .qs file must be specified in the `filePath` parameter. A quantum simulator will be run locally. Q# does not provide any CLI tools, so call this tool whenever you need to execute Q#, instead of running any CLI commands. The `shots` parameter controls the number of times to repeat the simulation. If the number of shots is greater than 1, a histogram will be generated, and the results will be displayed in a dedicated panel. + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `filePath` | `string` | Yes | The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program. | +| `shots` | `number` | No | The number of times to run the program. Defaults to 1 if not specified. | + +## Generate Q# Circuit Diagram + +- **Name**: `qsharp-generate-circuit` +- **Description**: Generates a visual circuit diagram for a Q# program. The path to a .qs file must be specified in the `filePath` parameter. This tool will compile the Q# program and generate a quantum circuit diagram that visually represents the quantum operations in the code. The diagram will be displayed in a dedicated circuit panel. + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `filePath` | `string` | Yes | The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program. | + +## Run Q# Resource Estimator + +- **Name**: `qsharp-run-resource-estimator` +- **Description**: Runs the quantum resource estimator on a Q# program to calculate the required physical resources. The path to a .qs file must be specified in the `filePath` parameter. This tool will analyze the Q# program and generate estimates of the quantum resources required to run the algorithm, such as the number of qubits, T gates, and other quantum operations. Results will be displayed in a dedicated resource estimator panel. + +### Input Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `filePath` | `string` | Yes | The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program. | + diff --git a/vscode/package.json b/vscode/package.json index dffc0a7469..3c6387bd65 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -398,7 +398,7 @@ "command": "qsharp-vscode.copilotClear", "category": "Q#", "title": "Clear Quantum Copilot chat", - "enablement": "!qdkCopilotIsBusy", + "enablement": "config.Q#.chat.enabled && !qdkCopilotIsBusy", "icon": "$(clear-all)" }, { @@ -806,6 +806,88 @@ ], "additionalProperties": false } + }, + { + "name": "qsharp-run-program", + "tags": [ + "azure-quantum", + "qsharp", + "qdk" + ], + "toolReferenceName": "qsharpRunProgram", + "displayName": "Run Q# Program", + "modelDescription": "Executes a Q# program. The path to a .qs file must be specified in the `filePath` parameter. A quantum simulator will be run locally. Q# does not provide any CLI tools, so call this tool whenever you need to execute Q#, instead of running any CLI commands. The `shots` parameter controls the number of times to repeat the simulation. If the number of shots is greater than 1, a histogram will be generated, and the results will be displayed in a dedicated panel.", + "canBeReferencedInPrompt": true, + "icon": "./resources/file-icon-light.svg", + "inputSchema": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program." + }, + "shots": { + "type": "number", + "description": "The number of times to run the program. Defaults to 1 if not specified." + } + }, + "required": [ + "filePath" + ], + "additionalProperties": false + } + }, + { + "name": "qsharp-generate-circuit", + "tags": [ + "azure-quantum", + "qsharp", + "qdk" + ], + "toolReferenceName": "qsharpGenerateCircuit", + "displayName": "Generate Q# Circuit Diagram", + "modelDescription": "Generates a visual circuit diagram for a Q# program. The path to a .qs file must be specified in the `filePath` parameter. This tool will compile the Q# program and generate a quantum circuit diagram that visually represents the quantum operations in the code. The diagram will be displayed in a dedicated circuit panel.", + "canBeReferencedInPrompt": true, + "icon": "./resources/file-icon-light.svg", + "inputSchema": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program." + } + }, + "required": [ + "filePath" + ], + "additionalProperties": false + } + }, + { + "name": "qsharp-run-resource-estimator", + "tags": [ + "azure-quantum", + "qsharp", + "qdk" + ], + "toolReferenceName": "qsharpRunResourceEstimator", + "displayName": "Run Q# Resource Estimator", + "modelDescription": "Runs the quantum resource estimator on a Q# program to calculate the required physical resources. The path to a .qs file must be specified in the `filePath` parameter. This tool will analyze the Q# program and generate estimates of the quantum resources required to run the algorithm, such as the number of qubits, T gates, and other quantum operations. Results will be displayed in a dedicated resource estimator panel.", + "canBeReferencedInPrompt": true, + "icon": "./resources/file-icon-light.svg", + "inputSchema": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "The absolute path to the .qs file. If this file is part of a project, as defined by being in a folder with a qsharp.json file, the whole project will be compiled as the program." + } + }, + "required": [ + "filePath" + ], + "additionalProperties": false + } } ] }, diff --git a/vscode/src/copilot/azqTools.ts b/vscode/src/copilot/azqTools.ts index 1931608ea6..2ff6366bd0 100644 --- a/vscode/src/copilot/azqTools.ts +++ b/vscode/src/copilot/azqTools.ts @@ -15,11 +15,11 @@ import { WorkspaceTreeProvider, } from "../azure/treeView.js"; import { getJobFiles, submitJob } from "../azure/workspaceActions.js"; -import { HistogramData } from "./shared.js"; import { getQirForVisibleQs } from "../qirGeneration.js"; +import { sendMessageToPanel } from "../webviewPanel.js"; +import { HistogramData } from "./shared.js"; import { CopilotToolError, ToolResult, ToolState } from "./tools.js"; import { CopilotWebviewViewProvider as CopilotView } from "./webviewViewProvider.js"; -import { sendMessageToPanel } from "../webviewPanel.js"; /** * These tool definitions correspond to the ones declared diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts index 5d5bae5053..b9e70cf292 100644 --- a/vscode/src/createProject.ts +++ b/vscode/src/createProject.ts @@ -65,7 +65,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) { } // Call updateCopilotInstructions to update the Copilot instructions file - await updateCopilotInstructions(folderUri); + await updateCopilotInstructions(folderUri, true); }, ), ); diff --git a/vscode/src/gh-copilot/instructions.ts b/vscode/src/gh-copilot/instructions.ts index 4414075ad1..0498d9d1e7 100644 --- a/vscode/src/gh-copilot/instructions.ts +++ b/vscode/src/gh-copilot/instructions.ts @@ -133,6 +133,8 @@ the IQ# Jupyter kernel, or the \`dotnet\` command line tools. Job management is now via tool calls integration into GitHub Copilot, or via Python code using the \`qsharp\` and \`azure-quantum\` packages. +To execute Q# code, use the provided tools. + ## Response formatting Avoid using LaTeX in your responses to the user. @@ -174,7 +176,7 @@ async function hasQSharpCopilotInstructions( * Command to update or create the Copilot instructions file for Q#. * Shows a prompt to the user and updates the file if confirmed. */ -async function updateGhCopilotInstructionsCommand() { +async function updateGhCopilotInstructionsCommand(userInvoked: boolean) { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { vscode.window.showErrorMessage("No workspace folder is open"); @@ -191,10 +193,13 @@ async function updateGhCopilotInstructionsCommand() { resourceUri = currentDoc ?? workspaceFolders[0].uri; } - return await updateCopilotInstructions(resourceUri); + return await updateCopilotInstructions(resourceUri, userInvoked); } -export async function updateCopilotInstructions(resource: vscode.Uri) { +export async function updateCopilotInstructions( + resource: vscode.Uri, + userInvoked: boolean, +): Promise { // Always add copilot instructions in the workspace root const workspaceFolder = vscode.workspace.getWorkspaceFolder(resource)?.uri; if (!workspaceFolder) { @@ -206,16 +211,32 @@ export async function updateCopilotInstructions(resource: vscode.Uri) { return; } - sendTelemetryEvent(EventType.UpdateCopilotInstructionsStart, {}, {}); + sendTelemetryEvent( + EventType.UpdateCopilotInstructionsStart, + { + trigger: userInvoked ? "user" : "startup", + }, + {}, + ); + + const buttons = [{ title: "Yes" }, { title: "No", isCloseAffordance: true }]; + if (!userInvoked) { + buttons.push({ title: "Don't show again" }); + } + + const modal = userInvoked; - // Show a yes/no prompt to the user const response = await vscode.window.showInformationMessage( - "We're about to update your `copilot-instructions.md` file.\n\n" + - "This file helps GitHub Copilot understand and work better with Q# files and features provided by the Quantum Development Kit extension.\n\n" + - "Would you like to proceed with updating `copilot-instructions.md`?", - { modal: true }, - { title: "Yes" }, - { title: "No", isCloseAffordance: true }, + "Add Q# guidance to copilot-instructions.md?\n\n" + + "Updating this file will help GitHub Copilot understand and work better with Q# files and other Quantum Development Kit features.\n\n" + + "Learn more at " + + (modal + ? "https://aka.ms/qdk.copilot" // links don't render in modal dialogs + : "[https://aka.ms/qdk.copilot](https://aka.ms/qdk.copilot)"), + { + modal, + }, + ...buttons, ); if (response?.title !== "Yes") { @@ -223,7 +244,13 @@ export async function updateCopilotInstructions(resource: vscode.Uri) { reason: "User canceled", flowStatus: UserFlowStatus.Aborted, }); - return; // User canceled or dismissed the dialog + + vscode.window.showInformationMessage( + "To add Q# guidance to copilot-instructions.md at any time, " + + 'run the command "Q#: Update Copilot instructions file for Q#".', + ); + + return response; // User dismissed the dialog } try { @@ -274,7 +301,8 @@ export async function updateCopilotInstructions(resource: vscode.Uri) { { flowStatus: UserFlowStatus.Failed, reason: "Error" }, {}, ); - return; + + return response; } // Send telemetry event for successful completion @@ -292,7 +320,24 @@ export function registerGhCopilotInstructionsCommand( context.subscriptions.push( vscode.commands.registerCommand( "qsharp-vscode.updateCopilotInstructions", - updateGhCopilotInstructionsCommand, + () => updateGhCopilotInstructionsCommand(true), ), ); + + // Also do a one-time prompt at startup + if ( + context.globalState.get( + "showUpdateCopilotInstructionsPromptAtStartup", + true, + ) + ) { + updateGhCopilotInstructionsCommand(false).then((response) => { + if (response?.title === "Don't show again") { + context.globalState.update( + "showUpdateCopilotInstructionsPromptAtStartup", + false, + ); + } + }); + } } diff --git a/vscode/src/gh-copilot/qsharpTools.ts b/vscode/src/gh-copilot/qsharpTools.ts new file mode 100644 index 0000000000..331f92b076 --- /dev/null +++ b/vscode/src/gh-copilot/qsharpTools.ts @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { log, QscEventTarget } from "qsharp-lang"; +import vscode from "vscode"; +import { loadCompilerWorker } from "../common"; +import { getPauliNoiseModel } from "../config"; +import { CopilotToolError } from "../copilot/tools"; +import { getProgramForDocument } from "../programConfig"; +import { sendMessageToPanel } from "../webviewPanel.js"; +// import { ConfigurationTarget } from "vscode"; + +export class QSharpTools { + constructor(private extensionUri: vscode.Uri) { + // some test code to try out configuration updates + // log.info("QSharpTools initialized"); + // const cfg = vscode.workspace.getConfiguration("chat"); + // const val = cfg.inspect("agent")?.globalValue; + // cfg.update("agent.enabled", true, ConfigurationTarget.Global).then( + // () => { + // const lastVal = cfg.inspect("agent")?.globalValue; + // log.info("Agent config value: ", lastVal); + // }, + // (e) => { + // log.error("Failed to update agent config", e); + // }, + // ); + // log.info("Agent config value: ", val); + } + + /** + * Runs the current Q# program in the editor + */ + async runProgram(input: { + filePath: string; + shots?: number; + }): Promise { + const shots = input.shots ?? 1; + try { + const docUri = vscode.Uri.file(input.filePath); + if (!docUri) { + throw new CopilotToolError( + "No active Q# document found. Please open a Q# file first.", + ); + } + + // Check if the program can be compiled + const programResult = await getProgramForDocument(docUri); + if (!programResult.success) { + throw new CopilotToolError( + `Cannot run the program: ${programResult.errorMsg}`, + ); + } + + // Create an event target to capture results + const evtTarget = new QscEventTarget(true); + const outputResults: string[] = []; + const measurementResults: Record = {}; + + // Capture standard outputs and results + evtTarget.addEventListener("Message", (evt) => { + outputResults.push(evt.detail); + }); + + evtTarget.addEventListener("Result", (evt) => { + // Handle both string results and diagnostics + if (!evt.detail.success && evt.detail.value.message !== undefined) { + outputResults.push(JSON.stringify(evt.detail.value)); + } else { + const result = `${evt.detail.value}`; + outputResults.push(result); + + // Collect measurement results for histogram if multiple shots + if (shots > 1) { + if (measurementResults[result]) { + measurementResults[result]++; + } else { + measurementResults[result] = 1; + } + } + } + }); + + const worker = await loadCompilerWorker(this.extensionUri!); + + try { + // Get the noise model (if configured) + const noise = getPauliNoiseModel(); + + // Run the program with the compiler worker + await worker.runWithPauliNoise( + programResult.programConfig, + "", // No specific entry expression + shots, + noise, + evtTarget, + ); + + // Format and return the results + if (outputResults.length === 0) { + return "Program executed successfully but produced no output."; + } else { + // If shots > 1, display histogram + if (shots > 1) { + const buckets: Array<[string, number]> = + Object.entries(measurementResults); + const histogram = { + buckets, + shotCount: shots, + }; + + const panelId = programResult.programConfig.projectName; + + // Show the histogram + sendMessageToPanel( + { panelType: "histogram", id: panelId }, + true, // reveal the panel + histogram, + ); + + return `Program executed successfully with ${shots} shots.\n Results: ${JSON.stringify(histogram)}`; + } + + return `Program executed successfully.\nOutput:\n${outputResults.join("\n")}`; + } + } catch { + throw new CopilotToolError( + `Program execution failed: ${outputResults.join("\n")}`, + ); + } finally { + // Always terminate the worker when done + worker.terminate(); + } + } catch (e) { + log.error("Failed to run program. ", e); + throw new CopilotToolError( + "Failed to run the Q# program: " + + (e instanceof Error ? e.message : String(e)), + ); + } + } /** + * Generates a circuit diagram for the specified Q# file + */ + async generateCircuit(input: { filePath: string }): Promise { + try { + // Get the Q# document from the file path + const docUri = vscode.Uri.file(input.filePath); + if (!docUri) { + throw new CopilotToolError( + "Invalid file path. Please provide a valid path to a Q# file.", + ); + } + + // Check if the program can be compiled + const programResult = await getProgramForDocument(docUri); + if (!programResult.success) { + throw new CopilotToolError( + `Cannot generate circuit: ${programResult.errorMsg}`, + ); + } + + // TODO: pass file path + // Generate the circuit diagram (without specifying an operation - will show all) + await vscode.commands.executeCommand("qsharp-vscode.showCircuit"); + + return "Circuit diagram generated and displayed in the circuit panel."; + } catch (e) { + log.error("Failed to generate circuit diagram. ", e); + throw new CopilotToolError( + "Failed to generate circuit diagram: " + + (e instanceof Error ? e.message : String(e)), + ); + } + } + + /** + * Runs the resource estimator on the specified Q# file + */ + async runResourceEstimator(input: { filePath: string }): Promise { + try { + // Get the Q# document from the file path + const docUri = vscode.Uri.file(input.filePath); + if (!docUri) { + throw new CopilotToolError( + "Invalid file path. Please provide a valid path to a Q# file.", + ); + } + + // Check if the program can be compiled + const programResult = await getProgramForDocument(docUri); + if (!programResult.success) { + throw new CopilotToolError( + `Cannot run resource estimator: ${programResult.errorMsg}`, + ); + } + + // TODO: pass file path + // Call the showRe command from the VS Code extension + await vscode.commands.executeCommand("qsharp-vscode.showRe"); + + return "Resource estimation started. Results will be displayed in the resource estimator panel."; + } catch (e) { + log.error("Failed to run resource estimator. ", e); + throw new CopilotToolError( + "Failed to run resource estimator: " + + (e instanceof Error ? e.message : String(e)), + ); + } + } +} diff --git a/vscode/src/gh-copilot/tools.ts b/vscode/src/gh-copilot/tools.ts index fec379965a..3ed65db41c 100644 --- a/vscode/src/gh-copilot/tools.ts +++ b/vscode/src/gh-copilot/tools.ts @@ -1,10 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { log } from "qsharp-lang"; import * as vscode from "vscode"; import * as azqTools from "../copilot/azqTools"; import { ToolState } from "../copilot/tools"; -import { log } from "qsharp-lang"; +import { QSharpTools } from "./qsharpTools"; + +// state +const workspaceState: ToolState = {}; +let qsharpTools: QSharpTools | undefined; const toolDefinitions: { name: string; @@ -12,84 +17,86 @@ const toolDefinitions: { confirm?: (input: any) => vscode.PreparedToolInvocation; }[] = [ // match these to the "languageModelTools" entries in package.json - { name: "azure-quantum-get-jobs", tool: getJobs }, - { name: "azure-quantum-get-job", tool: getJob }, - { name: "azure-quantum-connect-to-workspace", tool: connectToWorkspace }, - { name: "azure-quantum-download-job-results", tool: downloadJobResults }, - { name: "azure-quantum-get-workspaces", tool: getWorkspaces }, + { + name: "azure-quantum-get-jobs", + tool: async (input) => + (await azqTools.getJobs(workspaceState, input)).result, + }, + { + name: "azure-quantum-get-job", + tool: async (input: { job_id: string }) => + (await azqTools.getJob(workspaceState, input)).result, + }, + { + name: "azure-quantum-connect-to-workspace", + tool: async () => + (await azqTools.connectToWorkspace(workspaceState)).result, + }, + { + name: "azure-quantum-download-job-results", + tool: async (input: { job_id: string }) => + (await azqTools.downloadJobResults(workspaceState, input)).result, + }, + { + name: "azure-quantum-get-workspaces", + tool: async () => (await azqTools.getWorkspaces()).result, + }, { name: "azure-quantum-submit-to-target", - tool: submitToTarget, - confirm: submitToTargetConfirm, + tool: async (input: { + job_name: string; + target_id: string; + number_of_shots: number; + }) => (await azqTools.submitToTarget(workspaceState, input, false)).result, + confirm: (input: { + job_name: string; + target_id: string; + number_of_shots: number; + }): vscode.PreparedToolInvocation => ({ + confirmationMessages: { + title: "Submit Azure Quantum job", + message: `Submit job "${input.job_name}" to ${input.target_id} for ${input.number_of_shots} shots?`, + }, + }), + }, + { + name: "azure-quantum-get-active-workspace", + tool: async () => + (await azqTools.getActiveWorkspace(workspaceState)).result, + }, + { + name: "azure-quantum-set-active-workspace", + tool: async (input: { workspace_id: string }) => + (await azqTools.setActiveWorkspace(workspaceState, input)).result, + }, + { + name: "azure-quantum-get-providers", + tool: async () => (await azqTools.getProviders(workspaceState)).result, + }, + { + name: "azure-quantum-get-target", + tool: async (input: { target_id: string }) => + (await azqTools.getTarget(workspaceState, input)).result, + }, + { + name: "qsharp-run-program", + tool: async (input: { filePath: string; shots: number }) => + await qsharpTools!.runProgram(input), + }, + { + name: "qsharp-generate-circuit", + tool: async (input: { filePath: string }) => + await qsharpTools!.generateCircuit(input), + }, + { + name: "qsharp-run-resource-estimator", + tool: async (input: { filePath: string }) => + await qsharpTools!.runResourceEstimator(input), }, - { name: "azure-quantum-get-active-workspace", tool: getActiveWorkspace }, - { name: "azure-quantum-set-active-workspace", tool: setActiveWorkspace }, - { name: "azure-quantum-get-providers", tool: getProviders }, - { name: "azure-quantum-get-target", tool: getTarget }, ]; -const workspaceState: ToolState = {}; - -async function getJobs(input: { lastNDays: number }): Promise { - return (await azqTools.getJobs(workspaceState, input)).result; -} - -async function getJob(input: { job_id: string }): Promise { - return (await azqTools.getJob(workspaceState, input)).result; -} - -async function connectToWorkspace(): Promise { - return (await azqTools.connectToWorkspace(workspaceState)).result; -} - -async function downloadJobResults(input: { job_id: string }): Promise { - return (await azqTools.downloadJobResults(workspaceState, input)).result; -} - -async function getWorkspaces(): Promise { - return (await azqTools.getWorkspaces()).result; -} - -async function submitToTarget(input: { - job_name: string; - target_id: string; - number_of_shots: number; -}): Promise { - return (await azqTools.submitToTarget(workspaceState, input, false)).result; -} - -function submitToTargetConfirm(input: { - job_name: string; - target_id: string; - number_of_shots: number; -}): vscode.PreparedToolInvocation { - return { - confirmationMessages: { - title: "Submit Azure Quantum job", - message: `Submit job "${input.job_name}" to ${input.target_id} for ${input.number_of_shots} shots?`, - }, - }; -} - -async function getActiveWorkspace(): Promise { - return (await azqTools.getActiveWorkspace(workspaceState)).result; -} - -async function setActiveWorkspace(input: { - workspace_id: string; -}): Promise { - return (await azqTools.setActiveWorkspace(workspaceState, input)).result; -} - -async function getProviders(): Promise { - return (await azqTools.getProviders(workspaceState)).result; -} - -async function getTarget(input: { target_id: string }): Promise { - return (await azqTools.getTarget(workspaceState, input)).result; -} - export function registerLanguageModelTools(context: vscode.ExtensionContext) { + qsharpTools = new QSharpTools(context.extensionUri); for (const { name, tool: fn, confirm: confirmFn } of toolDefinitions) { context.subscriptions.push( vscode.lm.registerTool(name, tool(fn, confirmFn)), diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts index d30d1ecb0d..2ca86a7d6c 100644 --- a/vscode/src/telemetry.ts +++ b/vscode/src/telemetry.ts @@ -282,7 +282,9 @@ type EventTypes = { }; }; [EventType.UpdateCopilotInstructionsStart]: { - properties: Empty; + properties: { + trigger: "user" | "startup"; + }; measurements: Empty; }; [EventType.UpdateCopilotInstructionsEnd]: { diff --git a/vscode/tool-calling.md b/vscode/tool-calling.md new file mode 100644 index 0000000000..3c4a050ec6 --- /dev/null +++ b/vscode/tool-calling.md @@ -0,0 +1,10 @@ +# Tool calling best practices + +To get the best performance out of Claude when using tools, follow these guidelines: + +- Provide extremely detailed descriptions. This is by far the most important factor in tool performance. Your descriptions should explain every detail about the tool, including: + - What the tool does + - When it should be used (and when it shouldn’t) + - What each parameter means and how it affects the tool’s behavior + - Any important caveats or limitations, such as what information the tool does not return if the tool name is unclear. The more context you can give Claude about your tools, the better it will be at deciding when and how to use them. Aim for at least 3-4 sentences per tool description, more if the tool is complex. +- Prioritize descriptions over examples. While you can include examples of how to use a tool in its description or in the accompanying prompt, this is less important than having a clear and comprehensive explanation of the tool’s purpose and parameters. Only add examples after you’ve fully fleshed out the description.