Skip to content

Commit 8b5518b

Browse files
authored
Show combination tool approval args (#308747)
1 parent a6e4912 commit 8b5518b

13 files changed

Lines changed: 197 additions & 97 deletions

File tree

.github/instructions/api-version.instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Read this when changing proposed API in vscode.proposed.*.d.ts file
33
applyTo: 'src/vscode-dts/**/vscode.proposed.*.d.ts'
44
---
55

6-
The following is only required for proposed API related to chat and languageModel proposals. It's optional for other proposed API, but recommended.
6+
The following is only useful for proposed API related to chat and languageModel proposals. It's optional for other proposed API, and not recommended.
77

88
When a proposed API is changed in a non-backwards-compatible way, the version number at the top of the file must be incremented. If it doesn't have a version number, we must add one. The format of the number like this:
99

@@ -16,3 +16,5 @@ No semver, just a basic incrementing integer. See existing examples in `vscode.p
1616
An example of a non-backwards-compatible change is removing a non-optional property or changing the type to one that is incompatible with the previous type.
1717

1818
An example of a backwards-compatible change is an additive change or deleting a property that was already optional.
19+
20+
Whenever possible, make a backwards-compatible change!

extensions/copilot/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6399,7 +6399,7 @@
63996399
"node-gyp": "npm:node-gyp@10.3.1",
64006400
"zod": "3.25.76"
64016401
},
6402-
"vscodeCommit": "afba0a4a1fc1e34dae9073d6787b6b541bda23eb",
6402+
"vscodeCommit": "94c8e2adc50e26ef70af85a0de3a9efed757acaa",
64036403
"__metadata": {
64046404
"id": "7ec7d6e6-b89e-4cc5-a59b-d6c4d238246f",
64056405
"publisherId": {

extensions/copilot/script/build/vscodeDtsCheck.js

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,47 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
// Usage: node script/build/vscodeDtsCheck.js
7-
// Reads vscodeCommit from package.json, re-downloads proposed d.ts files
8-
// at that commit, checks if any differ from what's committed, then restores
9-
// the originals. Exits with code 1 if files are out of date.
7+
// Compares proposed d.ts files in src/extension/ against the repo's
8+
// src/vscode-dts/ directory. Exits with code 1 if files are out of date.
109

11-
const { execSync } = require('child_process');
1210
const fs = require('fs');
1311
const path = require('path');
1412

13+
const vscodeDtsDir = path.resolve('..', '..', 'src', 'vscode-dts');
1514
const targetDir = path.resolve('src', 'extension');
1615

1716
function main() {
1817
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
19-
const sha = pkg.vscodeCommit;
20-
if (!sha) {
21-
console.error('No vscodeCommit found in package.json. Run "npm run vscode-dts:update" first.');
18+
const proposals = pkg.enabledApiProposals;
19+
if (!proposals || proposals.length === 0) {
20+
console.error('No enabledApiProposals found in package.json.');
2221
process.exit(1);
2322
}
2423

25-
console.log(`Checking proposed d.ts files against vscodeCommit: ${sha}`);
24+
console.log('Checking proposed d.ts files against src/vscode-dts/...');
2625

27-
// Download proposed d.ts files using the commit SHA
28-
execSync(`node node_modules/@vscode/dts/index.js dev ${sha}`, { stdio: 'inherit' });
29-
30-
// Compare downloaded files with committed ones
31-
const downloaded = fs.readdirSync('.').filter(f => f.startsWith('vscode.') && f.endsWith('.ts'));
3226
const mismatched = [];
3327

34-
for (const f of downloaded) {
35-
const committedPath = path.join(targetDir, f);
36-
const newContent = fs.readFileSync(f, 'utf-8');
28+
for (const proposal of proposals) {
29+
const fileName = `vscode.proposed.${proposal}.d.ts`;
30+
const sourcePath = path.join(vscodeDtsDir, fileName);
31+
const committedPath = path.join(targetDir, fileName);
32+
33+
if (!fs.existsSync(sourcePath)) {
34+
console.warn(`Warning: ${fileName} not found in src/vscode-dts/, skipping`);
35+
continue;
36+
}
37+
38+
const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
3739

3840
if (!fs.existsSync(committedPath)) {
39-
mismatched.push(f + ' (missing)');
41+
mismatched.push(fileName + ' (missing)');
4042
} else {
41-
const oldContent = fs.readFileSync(committedPath, 'utf-8');
42-
if (oldContent !== newContent) {
43-
mismatched.push(f);
43+
const committedContent = fs.readFileSync(committedPath, 'utf-8');
44+
if (sourceContent !== committedContent) {
45+
mismatched.push(fileName);
4446
}
4547
}
46-
47-
// Clean up the downloaded file
48-
fs.unlinkSync(f);
4948
}
5049

5150
if (mismatched.length > 0) {

extensions/copilot/script/build/vscodeDtsUpdate.js

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,44 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
// Usage: node script/build/vscodeDtsUpdate.js [branch]
7-
// Downloads proposed API d.ts files from the given branch (default: main)
8-
// of microsoft/vscode and writes the resolved commit SHA to package.json.
6+
// Usage: node script/build/vscodeDtsUpdate.js
7+
// Copies proposed API d.ts files from the repo's src/vscode-dts/ directory
8+
// into this extension's src/extension/ folder based on enabledApiProposals.
99

1010
const { execSync } = require('child_process');
1111
const fs = require('fs');
1212
const path = require('path');
13-
const https = require('https');
1413

15-
const branch = process.argv[2] || 'main';
14+
const vscodeDtsDir = path.resolve('..', '..', 'src', 'vscode-dts');
15+
const targetDir = path.resolve('src', 'extension');
1616

17-
function resolveCommitSha(branch) {
18-
return new Promise((resolve, reject) => {
19-
const options = {
20-
hostname: 'api.github.com',
21-
path: `/repos/microsoft/vscode/commits/${encodeURIComponent(branch)}`,
22-
headers: { 'User-Agent': 'vscode-copilot-chat', 'Accept': 'application/vnd.github.sha' }
23-
};
24-
https.get(options, res => {
25-
if (res.statusCode !== 200) {
26-
reject(new Error(`Failed to resolve commit for branch "${branch}": HTTP ${res.statusCode}`));
27-
return;
28-
}
29-
let data = '';
30-
res.on('data', chunk => data += chunk);
31-
res.on('end', () => resolve(data.trim()));
32-
}).on('error', reject);
33-
});
34-
}
35-
36-
async function main() {
37-
const sha = await resolveCommitSha(branch);
38-
console.log(`Resolved branch "${branch}" to commit ${sha}`);
39-
40-
// Download proposed d.ts files using the commit SHA
41-
execSync(`node node_modules/@vscode/dts/index.js dev ${sha}`, { stdio: 'inherit' });
17+
function main() {
18+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
19+
const proposals = pkg.enabledApiProposals;
20+
if (!proposals || proposals.length === 0) {
21+
console.error('No enabledApiProposals found in package.json.');
22+
process.exit(1);
23+
}
4224

43-
// Move downloaded files to src/extension/
44-
const files = fs.readdirSync('.').filter(f => f.startsWith('vscode.') && f.endsWith('.ts'));
45-
for (const f of files) {
46-
fs.renameSync(f, path.join('src', 'extension', f));
25+
let copied = 0;
26+
for (const proposal of proposals) {
27+
const fileName = `vscode.proposed.${proposal}.d.ts`;
28+
const sourcePath = path.join(vscodeDtsDir, fileName);
29+
if (!fs.existsSync(sourcePath)) {
30+
console.warn(`Warning: ${fileName} not found in src/vscode-dts/`);
31+
continue;
32+
}
33+
fs.copyFileSync(sourcePath, path.join(targetDir, fileName));
34+
copied++;
4735
}
36+
console.log(`Copied ${copied} proposed API type definitions from src/vscode-dts/.`);
4837

49-
// Write the commit SHA to package.json
38+
// Write the current commit SHA to package.json for reference
39+
const sha = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
5040
const pkgPath = path.resolve('package.json');
51-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
5241
pkg.vscodeCommit = sha;
5342
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n');
5443
console.log(`Wrote vscodeCommit: ${sha} to package.json`);
5544
}
5645

57-
main().catch(err => {
58-
console.error(err);
59-
process.exit(1);
60-
});
46+
main();

extensions/copilot/src/extension/tools/node/vscodeCmdTool.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,25 @@ class VSCodeCmdTool implements vscode.LanguageModelTool<IVSCodeCmdToolToolInput>
9494
// Populate the Quick Open box with command ID rather than command name to avoid issues where Copilot didn't use the precise name,
9595
// or when the Copilot response language (Spanish, French, etc.) might be different here than the UI one.
9696
const commandStr = commandUri(quickOpenCommand, ['>' + commandId]);
97-
const markdownString = new MarkdownString(l10n.t(`Copilot will execute the [{0}]({1}) (\`{2}\`) command.`, options.input.name, commandStr, options.input.commandId));
97+
const hasArguments = !!options.input.args?.length;
98+
const markdownString = new MarkdownString();
99+
markdownString.appendMarkdown(l10n.t(`Copilot will execute the [{0}]({1}) (\`{2}\`) command.`, options.input.name, commandStr, options.input.commandId));
100+
if (hasArguments) {
101+
markdownString.appendMarkdown(`\n\n${l10n.t('Arguments')}:\n\n`);
102+
markdownString.appendCodeblock(JSON.stringify(options.input.args, undefined, 2), 'json');
103+
}
98104
markdownString.isTrusted = { enabledCommands: [quickOpenCommand] };
99105
return {
100106
invocationMessage,
101107
confirmationMessages: {
102108
title: l10n.t`Run Command \`${options.input.name}\` (\`${options.input.commandId}\`)?`,
103109
message: markdownString,
104-
approveCombination: options.input.args ? l10n.t`Allow running command \`${options.input.commandId}\` with specific arguments` : l10n.t`Allow running command \`${options.input.commandId}\` without arguments`,
110+
approveCombination: {
111+
message: hasArguments
112+
? l10n.t`Allow running command \`${options.input.commandId}\` with specific arguments`
113+
: l10n.t`Allow running command \`${options.input.commandId}\` without arguments`,
114+
arguments: hasArguments ? JSON.stringify(options.input.args) : undefined,
115+
},
105116
},
106117
};
107118
}

extensions/copilot/src/extension/vscode.proposed.toolInvocationApproveCombination.d.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,22 @@ declare module 'vscode' {
1010
export interface LanguageModelToolConfirmationMessages {
1111
/**
1212
* When set, a button will be shown allowing the user to approve this particular
13-
* combination of tool and arguments. The value is shown as the label for the
14-
* approval option.
13+
* combination of tool and arguments.
1514
*
16-
* For example, a tool that reads files could set this to `"Allow reading 'foo.txt'"`,
15+
* For example, a tool that reads files could set this to
16+
* `{ message: "Allow reading 'foo.txt'", arguments: JSON.stringify({ file: 'foo.txt' }) }`,
1717
* so that the user can approve that specific file without approving all invocations
1818
* of the tool.
1919
*/
20-
approveCombination?: string | MarkdownString;
20+
approveCombination?: {
21+
/**
22+
* The label shown for the approval option.
23+
*/
24+
message: string | MarkdownString;
25+
/**
26+
* A string representation of the arguments that can be shown to the user.
27+
*/
28+
arguments?: string;
29+
};
2130
}
2231
}

src/vs/workbench/api/common/extHostLanguageModelTools.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,9 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
311311
checkProposedApiEnabled(item.extension, 'toolInvocationApproveCombination');
312312
}
313313

314-
const approveCombinationLabel = result.confirmationMessages?.approveCombination
315-
? typeConvert.MarkdownString.fromStrict(result.confirmationMessages.approveCombination)
314+
const approveCombination = result.confirmationMessages?.approveCombination;
315+
const approveCombinationLabel = approveCombination
316+
? typeConvert.MarkdownString.fromStrict(approveCombination.message)
316317
: undefined;
317318
const approveCombinationKey = approveCombinationLabel
318319
? await computeCombinationKey(toolId, context.parameters)
@@ -322,7 +323,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
322323
confirmationMessages: result.confirmationMessages ? {
323324
title: typeof result.confirmationMessages.title === 'string' ? result.confirmationMessages.title : typeConvert.MarkdownString.from(result.confirmationMessages.title),
324325
message: typeof result.confirmationMessages.message === 'string' ? result.confirmationMessages.message : typeConvert.MarkdownString.from(result.confirmationMessages.message),
325-
approveCombination: approveCombinationLabel && approveCombinationKey ? { label: approveCombinationLabel, key: approveCombinationKey } : undefined,
326+
approveCombination: approveCombinationLabel && approveCombinationKey ? { label: approveCombinationLabel, key: approveCombinationKey, arguments: approveCombination!.arguments } : undefined,
326327
} : undefined,
327328
invocationMessage: typeConvert.MarkdownString.fromStrict(result.invocationMessage),
328329
pastTenseMessage: typeConvert.MarkdownString.fromStrict(result.pastTenseMessage),

0 commit comments

Comments
 (0)