Avoid dash placeholder in agent host URIs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Rob Lourens
2026-05-25 17:03:02 -07:00
co-authored by Copilot
parent c334e14362
commit de6579dab9
4 changed files with 44 additions and 16 deletions
@@ -260,7 +260,7 @@ export abstract class AHPFileSystemProvider extends Disposable implements IFileS
* client side. Registered under the `vscode-agent-host` scheme.
*
* ```
* vscode-agent-host://[connectionAuthority]/[originalScheme]/[originalAuthority]/[originalPath]
* vscode-agent-host://[connectionAuthority]/[originalScheme]/[originalAuthority][originalPath]
* ```
*/
export class AgentHostFileSystemProvider extends AHPFileSystemProvider {
@@ -15,7 +15,7 @@ import type { ResourceLabelFormatter } from '../../label/common/label.js';
* remote resource can be represented without assuming `file://`:
*
* ```
* vscode-agent-host://[connectionAuthority]/[originalScheme]/[originalAuthority]/[originalPath]
* vscode-agent-host://[connectionAuthority]/[originalScheme]/[originalAuthority][originalPath]
* ```
*
* For example, `file:///home/user/foo.ts` on remote `my-server` becomes:
@@ -39,12 +39,12 @@ export function toAgentHostUri(originalUri: URI, connectionAuthority: string): U
return originalUri;
}
// Path format: /[originalScheme]/[originalAuthority]/[originalPath]
// Path format: /[originalScheme]/[originalAuthority][originalPath]
const originalAuthority = originalUri.authority || '';
return URI.from({
scheme: AGENT_HOST_SCHEME,
authority: connectionAuthority,
path: `/${originalUri.scheme}/${originalAuthority || '-'}${originalUri.path}`,
path: `/${originalUri.scheme}/${originalAuthority}${originalUri.path}`,
query: originalUri.query,
fragment: originalUri.fragment,
});
@@ -60,7 +60,7 @@ export function fromAgentHostUri(agentHostUri: URI): URI {
return agentHostUri;
}
// Path: /[originalScheme]/[originalAuthority]/[rest of original path]
// Path: /[originalScheme]/[originalAuthority][rest of original path]
const path = agentHostUri.path;
// Find first segment boundary after leading /
@@ -124,7 +124,7 @@ suite('toAgentHostUri / fromAgentHostUri', () => {
test('round-trips query and fragment for synthetic content URIs', () => {
const original = URI.from({
scheme: 'git-blob',
path: '/src/app.ts',
path: '/hello_count.txt',
query: JSON.stringify({ sessionUri: 'copilot:/abc', sha: 'cafe1234' }),
fragment: 'L1',
});
@@ -143,6 +143,34 @@ suite('toAgentHostUri / fromAgentHostUri', () => {
});
});
test('wraps empty original authority without a dash placeholder', () => {
const original = URI.from({ scheme: 'git-blob', path: '/hello_count.txt', query: JSON.stringify({ sessionUri: 'copilot:/abc', sha: 'cafe1234' }) });
const wrapped = toAgentHostUri(original, 'local');
assert.deepStrictEqual({
path: wrapped.path,
unwrapped: fromAgentHostUri(wrapped).toString(),
}, {
path: '/git-blob//hello_count.txt',
unwrapped: original.toString(),
});
});
test('unwraps legacy dash placeholder for empty original authority', () => {
const legacy = URI.from({
scheme: AGENT_HOST_SCHEME,
authority: 'local',
path: '/git-blob/-/hello_count.txt',
query: JSON.stringify({ sessionUri: 'copilot:/abc', sha: 'cafe1234' }),
});
assert.strictEqual(fromAgentHostUri(legacy).toString(), URI.from({
scheme: 'git-blob',
path: '/hello_count.txt',
query: legacy.query,
}).toString());
});
test('local authority returns original URI unchanged', () => {
const original = URI.file('/workspace/test.ts');
const result = toAgentHostUri(original, 'local');
@@ -204,16 +232,16 @@ suite('AGENT_HOST_LABEL_FORMATTER', () => {
assert.strictEqual(stripped, '/snap/before');
});
test('stripPathSegments preserves label-friendly git-blob paths', () => {
test('stripPathSegments preserves label-friendly root git-blob paths', () => {
const originalUri = URI.from({
scheme: 'git-blob',
path: '/src/app.ts',
path: '/hello_count.txt',
query: JSON.stringify({ sessionUri: 'copilot:/abc', sha: 'cafe1234' }),
});
const encodedUri = toAgentHostUri(originalUri, 'remote-host');
const encodedUri = toAgentHostUri(originalUri, 'local');
const stripped = stripPath(encodedUri.path, AGENT_HOST_LABEL_FORMATTER.formatting.stripPathSegments!);
assert.strictEqual(stripped, '/src/app.ts');
assert.deepStrictEqual({ path: encodedUri.path, stripped }, { path: '/git-blob//hello_count.txt', stripped: '/hello_count.txt' });
});
});
@@ -85,7 +85,7 @@ suite('SimpleFileDialog - scoped path prefix', () => {
const displayPath = labelFormatterDisplay(uri.path, AGENT_HOST_LABEL_FORMATTER.formatting.stripPathSegments!);
const prefix = computeScopedPathPrefix(uri, displayPath);
assert.strictEqual(prefix, '/file/-');
assert.strictEqual(prefix, '/file/');
assert.strictEqual(displayPath, '/Users/roblou/code');
});
@@ -115,7 +115,7 @@ suite('SimpleFileDialog - scoped path prefix', () => {
test('pathFromUri strips prefix to show clean path', () => {
const authority = agentHostAuthority('localhost:8089');
const uri = agentHostUri(authority, '/Users/roblou/code');
const prefix = '/file/-';
const prefix = '/file/';
assert.strictEqual(pathFromUri(uri, prefix), '/Users/roblou/code');
});
@@ -123,7 +123,7 @@ suite('SimpleFileDialog - scoped path prefix', () => {
test('pathFromUri with trailing separator', () => {
const authority = agentHostAuthority('localhost:8089');
const uri = agentHostUri(authority, '/Users/roblou/code');
const prefix = '/file/-';
const prefix = '/file/';
assert.strictEqual(pathFromUri(uri, prefix, true), '/Users/roblou/code/');
});
@@ -135,14 +135,14 @@ suite('SimpleFileDialog - scoped path prefix', () => {
test('remoteUriFrom re-adds prefix to reconstruct encoded URI', () => {
const authority = agentHostAuthority('localhost:8089');
const prefix = '/file/-';
const prefix = '/file/';
const cleanPath = '/Users/roblou/code';
const result = remoteUriFrom(cleanPath, AGENT_HOST_SCHEME, authority, prefix);
assert.strictEqual(result.scheme, AGENT_HOST_SCHEME);
assert.strictEqual(result.authority, authority);
assert.strictEqual(result.path, '/file/-/Users/roblou/code');
assert.strictEqual(result.path, '/file//Users/roblou/code');
});
test('full round-trip: URI -> pathFromUri -> remoteUriFrom -> same URI', () => {
@@ -167,7 +167,7 @@ suite('SimpleFileDialog - scoped path prefix', () => {
test('createBackItem root detection with prefix', () => {
const authority = agentHostAuthority('localhost:8089');
const prefix = '/file/-';
const prefix = '/file/';
// Simulate root folder: path = prefix + '/'
const rootUri = URI.from({ scheme: AGENT_HOST_SCHEME, authority, path: prefix + '/' });