mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
Human-readable remote agent host address (#303758)
This commit is contained in:
35
.github/instructions/remoteAgentHost.instructions.md
vendored
Normal file
35
.github/instructions/remoteAgentHost.instructions.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
description: Architecture documentation for remote agent host connections. Use when working in `src/vs/sessions/contrib/remoteAgentHost`
|
||||
applyTo: src/vs/sessions/contrib/remoteAgentHost/**
|
||||
---
|
||||
|
||||
# Remote Agent Host
|
||||
|
||||
The remote agent host feature connects the sessions app to agent host processes running on other machines over WebSocket.
|
||||
|
||||
## Key Files
|
||||
|
||||
- `ARCHITECTURE.md` - full architecture documentation (URI conventions, registration flow, data flow diagram)
|
||||
- `REMOTE_AGENT_HOST_RECONNECTION.md` - reconnection lifecycle spec (15 numbered requirements)
|
||||
- `browser/remoteAgentHost.contribution.ts` - central orchestrator
|
||||
- `browser/agentHostFileSystemProvider.ts` - read-only FS provider for remote browsing
|
||||
|
||||
## Architecture Documentation
|
||||
|
||||
When making changes to this feature area, **review and update `ARCHITECTURE.md`** if your changes affect:
|
||||
|
||||
- Connection lifecycle (connect, disconnect, reconnect)
|
||||
- Agent registration flow
|
||||
- URI conventions or naming
|
||||
- Session creation flow
|
||||
- The data flow diagram
|
||||
|
||||
The doc lives at `src/vs/sessions/contrib/remoteAgentHost/ARCHITECTURE.md`.
|
||||
|
||||
## Related Code Outside This Folder
|
||||
|
||||
- `src/vs/platform/agentHost/common/remoteAgentHostService.ts` - service interface (`IRemoteAgentHostService`)
|
||||
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostServiceImpl.ts` - Electron implementation
|
||||
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostProtocolClient.ts` - WebSocket protocol client
|
||||
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts` - session list sidebar
|
||||
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts` - session content provider
|
||||
@@ -98,9 +98,13 @@ Remote addresses are encoded into URI-safe authority strings via
|
||||
`agentHostAuthority(address)`:
|
||||
|
||||
- Alphanumeric addresses pass through unchanged
|
||||
- Others are url-safe base64 encoded with a `b64-` prefix
|
||||
- "Normal" addresses (`[a-zA-Z0-9.:-]`) get colons replaced with `__`
|
||||
- Everything else is url-safe base64 encoded with a `b64-` prefix
|
||||
|
||||
Example: `http://127.0.0.1:3000` → `b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`
|
||||
Examples:
|
||||
- `localhost:8081` → `localhost__8081`
|
||||
- `192.168.1.1:8080` → `192.168.1.1__8080`
|
||||
- `http://127.0.0.1:3000` → `b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`
|
||||
|
||||
## Agent Registration
|
||||
|
||||
@@ -110,10 +114,10 @@ When `_registerAgent()` is called for a discovered copilot agent from address `X
|
||||
|
||||
| Concept | Value | Example |
|
||||
|---------|-------|---------|
|
||||
| **Authority** | `agentHostAuthority(address)` | `b64-aHR0cA` |
|
||||
| **Session type** | `remote-${authority}-${provider}` | `remote-b64-aHR0cA-copilot` |
|
||||
| **Agent ID** | same as session type | `remote-b64-aHR0cA-copilot` |
|
||||
| **Vendor** | same as session type | `remote-b64-aHR0cA-copilot` |
|
||||
| **Authority** | `agentHostAuthority(address)` | `localhost__8081` |
|
||||
| **Session type** | `remote-${authority}-${provider}` | `remote-localhost__8081-copilot` |
|
||||
| **Agent ID** | same as session type | `remote-localhost__8081-copilot` |
|
||||
| **Vendor** | same as session type | `remote-localhost__8081-copilot` |
|
||||
| **Display name** | `configuredName \|\| "${displayName} (${address})"` | `dev-box` |
|
||||
|
||||
### Four Registrations Per Agent
|
||||
@@ -134,23 +138,23 @@ When `_registerAgent()` is called for a discovered copilot agent from address `X
|
||||
|
||||
4. **Language model provider** - `AgentHostLanguageModelProvider` registers
|
||||
models under the vendor descriptor. Model IDs are prefixed with the session
|
||||
type (e.g., `remote-b64-xxx-copilot:claude-sonnet-4-20250514`).
|
||||
type (e.g., `remote-localhost__8081-copilot:claude-sonnet-4-20250514`).
|
||||
|
||||
## URI Conventions
|
||||
|
||||
| Context | Scheme | Format | Example |
|
||||
|---------|--------|--------|---------|
|
||||
| New session resource | `<sessionType>` | `<sessionType>:/untitled-<uuid>` | `remote-b64-xxx-copilot:/untitled-abc` |
|
||||
| Existing session | `<sessionType>` | `<sessionType>:/<rawId>` | `remote-b64-xxx-copilot:/abc-123` |
|
||||
| New session resource | `<sessionType>` | `<sessionType>:/untitled-<uuid>` | `remote-localhost__8081-copilot:/untitled-abc` |
|
||||
| Existing session | `<sessionType>` | `<sessionType>:/<rawId>` | `remote-localhost__8081-copilot:/abc-123` |
|
||||
| Backend session state | `<provider>` | `<provider>:/<rawId>` | `copilot:/abc-123` |
|
||||
| Root state subscription | (string) | `agenthost:/root` | - |
|
||||
| Remote filesystem | `agenthost` | `agenthost://<authority>/<path>` | `agenthost://b64-aHR0cA/home/user/project` |
|
||||
| Language model ID | - | `<sessionType>:<rawModelId>` | `remote-b64-xxx-copilot:claude-sonnet-4-20250514` |
|
||||
| Remote filesystem | `agenthost` | `agenthost://<authority>/<path>` | `agenthost://localhost__8081/home/user/project` |
|
||||
| Language model ID | - | `<sessionType>:<rawModelId>` | `remote-localhost__8081-copilot:claude-sonnet-4-20250514` |
|
||||
|
||||
### Key distinction: session resource vs backend session URI
|
||||
|
||||
- The **session resource** URI uses the session type as its scheme
|
||||
(e.g., `remote-b64-xxx-copilot:/untitled-abc`). This is the URI visible to
|
||||
(e.g., `remote-localhost__8081-copilot:/untitled-abc`). This is the URI visible to
|
||||
the chat UI and session management.
|
||||
- The **backend session** URI uses the provider as its scheme
|
||||
(e.g., `copilot:/abc-123`). This is sent over the agent host protocol to the
|
||||
@@ -173,8 +177,8 @@ remote host, then picks a folder on the remote filesystem. This produces a
|
||||
`SessionWorkspace` with an `agenthost://` URI:
|
||||
|
||||
```
|
||||
agenthost://b64-aHR0cA/home/user/myproject
|
||||
↑ authority ↑ remote filesystem path
|
||||
agenthost://localhost__8081/home/user/myproject
|
||||
↑ authority ↑ remote filesystem path
|
||||
```
|
||||
|
||||
### 2. Session Target Resolution
|
||||
@@ -184,7 +188,7 @@ resolves the matching session type via `getRemoteAgentHostSessionTarget()`
|
||||
(defined in `remoteAgentHost.contribution.ts`):
|
||||
|
||||
```typescript
|
||||
// authority "b64-aHR0cA" → find connection → "remote-b64-aHR0cA-copilot"
|
||||
// authority "localhost__8081" → find connection → "remote-localhost__8081-copilot"
|
||||
const target = getRemoteAgentHostSessionTarget(connections, authority);
|
||||
```
|
||||
|
||||
@@ -194,7 +198,7 @@ const target = getRemoteAgentHostSessionTarget(connections, authority);
|
||||
|
||||
```typescript
|
||||
URI.from({ scheme: target, path: `/untitled-${generateUuid()}` })
|
||||
// → remote-b64-aHR0cA-copilot:/untitled-abc-123
|
||||
// → remote-localhost__8081-copilot:/untitled-abc-123
|
||||
```
|
||||
|
||||
### 4. Session Object Creation
|
||||
|
||||
@@ -34,14 +34,21 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
|
||||
* Encode a remote address into an identifier that is safe for use in
|
||||
* both URI schemes and URI authorities, and is collision-free.
|
||||
*
|
||||
* If the address contains only alphanumeric characters it is returned as-is.
|
||||
* Otherwise it is url-safe base64-encoded (no padding) to guarantee the
|
||||
* result contains only `[A-Za-z0-9_-]`.
|
||||
* Three tiers:
|
||||
* 1. Purely alphanumeric addresses are returned as-is.
|
||||
* 2. "Normal" addresses containing only `[a-zA-Z0-9.:-]` get colons
|
||||
* replaced with `__` (double underscore) for human readability.
|
||||
* Addresses containing `_` skip this tier to keep the encoding
|
||||
* collision-free (`__` can only appear from colon replacement).
|
||||
* 3. Everything else is url-safe base64-encoded with a `b64-` prefix.
|
||||
*/
|
||||
export function agentHostAuthority(address: string): string {
|
||||
if (/^[a-zA-Z0-9]+$/.test(address)) {
|
||||
return address;
|
||||
}
|
||||
if (/^[a-zA-Z0-9.:\-]+$/.test(address)) {
|
||||
return address.replaceAll(':', '__');
|
||||
}
|
||||
return 'b64-' + encodeBase64(VSBuffer.fromString(address), false, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,13 +52,26 @@ suite('AgentHostAuthority - encoding', () => {
|
||||
assert.strictEqual(agentHostAuthority('localhost'), 'localhost');
|
||||
});
|
||||
|
||||
test('address with special characters is base64-encoded', () => {
|
||||
const authority = agentHostAuthority('localhost:8081');
|
||||
assert.ok(authority.startsWith('b64-'));
|
||||
test('normal host:port address uses human-readable encoding', () => {
|
||||
assert.strictEqual(agentHostAuthority('localhost:8081'), 'localhost__8081');
|
||||
assert.strictEqual(agentHostAuthority('192.168.1.1:8080'), '192.168.1.1__8080');
|
||||
assert.strictEqual(agentHostAuthority('my-host:9090'), 'my-host__9090');
|
||||
assert.strictEqual(agentHostAuthority('host.name:80'), 'host.name__80');
|
||||
});
|
||||
|
||||
test('address with underscore falls through to base64', () => {
|
||||
const authority = agentHostAuthority('host_name:8080');
|
||||
assert.ok(authority.startsWith('b64-'), `expected base64 for underscore address, got: ${authority}`);
|
||||
});
|
||||
|
||||
test('address with exotic characters is base64-encoded', () => {
|
||||
assert.ok(agentHostAuthority('user@host:8080').startsWith('b64-'));
|
||||
assert.ok(agentHostAuthority('host with spaces').startsWith('b64-'));
|
||||
assert.ok(agentHostAuthority('http://myhost:3000').startsWith('b64-'));
|
||||
});
|
||||
|
||||
test('different addresses produce different authorities', () => {
|
||||
const cases = ['localhost:8080', 'localhost:8081', '192.168.1.1:8080', 'host-name:80', 'host.name:80'];
|
||||
const cases = ['localhost:8080', 'localhost:8081', '192.168.1.1:8080', 'host-name:80', 'host.name:80', 'host_name:80', 'user@host:8080'];
|
||||
const results = cases.map(agentHostAuthority);
|
||||
const unique = new Set(results);
|
||||
assert.strictEqual(unique.size, cases.length, 'all authorities must be unique');
|
||||
|
||||
Reference in New Issue
Block a user