Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vs/server/node/server.cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { PipeCommand } from '../../workbench/api/node/extHostCLIServer.js';
import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from '../../platform/environment/node/stdin.js';
import { DeferredPromise } from '../../base/common/async.js';
import { FileAccess } from '../../base/common/network.js';
import { hasAgentCommand } from './server.cliAgent.js';

/*
* Implements a standalone CLI app that opens VS Code from a remote terminal.
Expand Down Expand Up @@ -95,6 +96,12 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
return;
}

if (hasAgentCommand(args)) {
console.error(`The 'agent' command is not supported by the remote CLI.`);
process.exitCode = 1;
return;
}

// take the local options and remove the ones that don't apply
const options: OptionDescriptions<Required<RemoteParsedArgs>> = { ...OPTIONS, gitCredential: { type: 'string' }, openExternal: { type: 'boolean' } };
const isSupported = cliCommand ? isSupportedForCmd : isSupportedForPipe;
Expand Down
48 changes: 48 additions & 0 deletions src/vs/server/node/server.cliAgent.ts
Original file line number Diff line number Diff line change
@@ -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 { OPTIONS, type Option } from '../../platform/environment/node/argv.js';

export function hasAgentCommand(args: readonly string[]): boolean {
let valueForOption = false;
for (const arg of args) {
if (valueForOption) {
valueForOption = false;
continue;
}
if (arg === '--') {
return false;
}
if (arg === 'agent') {
return true;
}
Comment thread
connor4312 marked this conversation as resolved.
if (Object.entries(OPTIONS).some(([id, option]) => id === arg && option.type === 'subcommand')) {
return false;
}
const option = getOption(arg);
if (option?.type === 'string' || option?.type === 'string[]') {
valueForOption = true;
}
}
return false;
}

function getOption(arg: string): Option<'boolean'> | Option<'string'> | Option<'string[]'> | undefined {
if (!arg.startsWith('-') || arg.includes('=')) {
return undefined;
}
const id = arg.startsWith('--') ? arg.slice(2) : arg.slice(1);
for (const [optionId, option] of Object.entries(OPTIONS)) {
if (option.type !== 'subcommand' && (id === option.alias || id === optionId || option.deprecates?.includes(id))) {
return option;
}
}
for (const [optionId, option] of Object.entries(OPTIONS['agent'].options)) {
if (id === option.alias || id === optionId) {
return option;
}
}
return undefined;
}
44 changes: 44 additions & 0 deletions src/vs/server/test/node/server.cliAgent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js';
import { hasAgentCommand } from '../../node/server.cliAgent.js';

suite('Server CLI agent command guard', () => {
ensureNoDisposablesAreLeakedInTestSuite();

test('detects agent endpoints', () => {
assert.strictEqual(hasAgentCommand(['agent', 'endpoints', '--user-data-dir', '/home/tester/.vscode-remote']), true);
});

test('detects agent after global native CLI options', () => {
assert.strictEqual(hasAgentCommand(['--cli-data-dir', '/x', 'agent', 'endpoints', '--user-data-dir', '/y']), true);
});

test('ignores non-agent commands', () => {
assert.strictEqual(hasAgentCommand(['--version']), false);
});

test('does not treat a global option value as an agent command', () => {
assert.strictEqual(hasAgentCommand(['--profile', 'agent']), false);
});

test('does not treat a deprecated global option value as an agent command', () => {
assert.strictEqual(hasAgentCommand(['--extensionHomePath', 'agent']), false);
});

test('stops at the first recognized subcommand', () => {
assert.deepStrictEqual([
hasAgentCommand(['chat', 'agent']),
hasAgentCommand(['serve-web', 'agent']),
hasAgentCommand(['tunnel', 'agent']),
], [false, false, false]);
});

test('does not detect agent after the option terminator', () => {
assert.strictEqual(hasAgentCommand(['--', 'agent', 'endpoints']), false);
});
});