Skip to content

Agent host: add an experimental generic ACP agent provider - #340863

Open
4ekuct25 wants to merge 9 commits into
microsoft:mainfrom
4ekuct25:acp/provider
Open

4ekuct25 wants to merge 9 commits into
microsoft:mainfrom
4ekuct25:acp/provider

Conversation

@4ekuct25

@4ekuct25 4ekuct25 commented Oct 10, 2026 •

Copy link
Copy Markdown

Part of #340860 (ACP provider proposal; follow-up to the locked #265496).

Adds an experimental, off-by-default Agent Host provider for agents that speak the Agent Client Protocol v1, so tools such as Qwen Code (qwen --acp) and OpenCode (opencode acp) can run in the Agents window next to Copilot, Claude and Codex.

What changes

  • Setting chat.agentHost.acpAgent.enabled (default true), owned by the new AcpAgents3PIntegration policy. It reuses thirdPartyAgentEnabledValue, so managed settings or disabled preview features turn ACP agents off like the Claude and Codex harnesses. The host registers ACP providers only while the mirrored acpAgentEnabled root key is true, and the workbench hides acp-* providers otherwise. policyData.jsonc contains only the generated entry for this policy, because export-policy-data needs the private distro product.json; a maintainer may want to re-run the export.
  • Setting chat.agentHost.acpAgents (experimental, APPLICATION scope, restricted, default []): a list of { id, displayName?, command, args?, env? }, mirrored to the host as the acpAgents root config key. Workspace settings cannot spawn commands. No built-in presets; the description gives Qwen Code and OpenCode as examples.
  • node/acp/acpClient.ts, acpProtocol.ts: an in-house JSON-RPC 2.0 / NDJSON stdio client modeled on codexAppServerClient.ts, plus hand-written types for the subset of the ACP v1 schema (schema-v1.25.0) that is used. No new npm dependency.
  • node/acp/acpAgent.ts: AcpAgent implements IAgent, one instance per configured agent with provider id acp-<id>. A single, lazily started process serves all chats.
    • createChat → session/new. The native session id is persisted in providerData.
    • materializeChat → session/resume, falling back to session/load.
    • sendMessage → session/prompt, which resolves at the end of the turn; progress arrives as session/update.
    • abort → session/cancel; dispose/release → session/close.
    • Model selection uses the model config option. Agents only report models per session, so an "Agent Default" model is published before the first session.
  • node/acp/acpTurnMapper.ts: maps session/update to chat actions: markdown/reasoning parts and deltas, the tool call start → ready → complete lifecycle, and the terminal turn action from the ACP stop reason. session/request_permission goes through the existing pending_confirmation flow, and the host's answer picks the matching ACP permission option.
  • Windows command shims: npm installs agents as .cmd files, so the launcher goes through formatSubprocessArguments, moved from extHostMcpNode.ts to base/node/processes.ts. MCP stdio servers and ACP agents now share the same CVE-2024-27980 handling. Not tested on Windows by me.
  • node/acp/acpAgentRegistration.ts: validates the entries and registers providers in agentHostMain.ts and agentHostServerMain.ts. Registration is one-way, like Codex's registerCodexIfEnabled: entries added later register on root-config change, while removals take effect after an agent host restart (IAgentHostProviderService has no unregister).

Deliberately out of scope

  • fs/* and terminal/* client capabilities are not advertised. Agents keep using their own file access and terminals, as they do from a shell. Qwen Code routes reads and writes of its own state (~/.qwen/...) through fs/* when offered, so confining fs/* to the session folder broke it. Tracking agent edits as VS Code edits can follow separately.
  • Discovery of native CLI sessions (session/list → onDidDiscoverChats) and a dynamic Harness filter in sessionsListFilters.ts are left for a follow-up PR.
  • MCP servers are not forwarded (mcpServers: []).

How to test

  1. Add to user settings:
    "chat.agentHost.acpAgents": [
      { "id": "qwen", "displayName": "Qwen Code", "command": "qwen", "args": ["--acp"] },
      { "id": "opencode", "displayName": "OpenCode", "command": "opencode", "args": ["acp"] }
    ]
  2. Open the Agents window, pick Qwen Code or OpenCode in the harness picker and send a prompt.

Verified manually in a dev build (./scripts/code.sh --agents) with Qwen Code 0.25.0 and OpenCode 1.18.34: both providers register, appear in the harness picker, stream a reply and complete the turn. A command that does not exist reports "Failed to start …" instead of crashing the agent host.

Unit tests are in src/vs/platform/agentHost/test/node/acp/ (35 tests: client, turn mapper, agent against an in-memory scripted ACP agent, registration). eslint and hygiene pass on the touched files. The existing agent host config, schema, policy and provider service tests, the MCP helper tests and the build policy tests still pass.

🤖 Generated with Claude Code

4ekuct25 and others added 8 commits October 10, 2026 16:27
Hand-written subset of the Agent Client Protocol v1 wire types
(schema-v1.25.0) and an NDJSON stdio JSON-RPC 2.0 client modelled on
codexAppServerClient. Groundwork for a generic ACP agent host provider
(microsoft#340860).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AcpTurnMapper turns the session/update stream of one ACP prompt turn
into agent host chat actions: markdown/reasoning parts and deltas, the
tool call start/ready/complete lifecycle (leaving ready to the host when
the agent asks for permission) and the terminal turn action derived
from the ACP stop reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AcpAgent implements IAgent for any agent that speaks the Agent Client
Protocol (e.g. `qwen --acp`, `opencode acp`). One lazily started process
serves all chats of a configured agent; chats map to ACP sessions
(session/new, resume or load on restore, prompt, cancel, close), model
selection uses the `model` config option, permission requests flow
through the host confirmation path, and fs/* requests are confined to
the session working directory.

Agents are configured with the experimental, application-scoped and
restricted `chat.agentHost.acpAgents` setting, mirrored to the agent
host as the `acpAgents` root config key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each well-formed `chat.agentHost.acpAgents` entry registers an AcpAgent
as `acp-<id>`; entries added later register on root config change.
Like the Codex provider, registration is one-way, so removing an entry
takes effect after an agent host restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A missing or non-executable command makes `spawn` emit `error` without
`exit`, which would crash the agent host. Treat it as an exit and report
"Failed to start <agent> (`<command>`)" from the failing chat operation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ACP agents report models only per session, so the Agents window listed
ACP harnesses as "No models available" and disabled them. Publish an
"Agent Default" model up front and add the agent's own models once a
session reports them; selecting the default (or a model the session
does not offer) leaves the agent's choice unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Qwen Code routes reads and writes of its own state (memories, session
records under ~/.qwen) through fs/* when the client offers it, so
confining fs/* to the session working directory broke the agent. The
agent process already runs with the user's permissions, so the
confinement protected nothing. Advertise no fs/* (or terminal/*)
capability and let agents use their own file access, as they do from a
shell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use hasKey instead of the `in` operator, throw Error objects in tests
and drop `any` from the fake ACP agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 14:05
@4ekuct25

Copy link
Copy Markdown
Author

4ekuct25 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Managed-policy bypass and cross-platform and lifecycle defects must be addressed.

4 open findings
What changed in this PR

Adds an experimental Agent Client Protocol provider to run user-configured ACP agents through Agent Host.

Changes:

  • Adds ACP configuration, validation, and provider registration.
  • Implements JSON-RPC transport, session lifecycle, permissions, models, and turn mapping.
  • Adds unit coverage for the client, agent, registration, and turn mapping.
File Description
src/​vs/​platform/​agentHost/​test/​node/​acp/​acpTurnMapper.test.ts Tests ACP response mapping.
src/​vs/​platform/​agentHost/​test/​node/​acp/​acpTestUtils.ts Provides an in-memory ACP process.
src/​vs/​platform/​agentHost/​test/​node/​acp/​acpClient.test.ts Tests JSON-RPC transport behavior.
src/​vs/​platform/​agentHost/​test/​node/​acp/​acpAgentRegistration.test.ts Tests configuration validation and registration.
src/​vs/​platform/​agentHost/​test/​node/​acp/​acpAgent.test.ts Tests ACP agent lifecycle and permissions.
src/​vs/​platform/​agentHost/​node/​agentHostServerMain.ts Registers ACP providers in server hosts.
src/​vs/​platform/​agentHost/​node/​agentHostMain.ts Registers ACP providers locally.
src/​vs/​platform/​agentHost/​node/​acp/​acpTurnMapper.ts Maps ACP updates to chat actions.
src/​vs/​platform/​agentHost/​node/​acp/​acpProtocol.ts Defines the used ACP wire types.
src/​vs/​platform/​agentHost/​node/​acp/​acpClient.ts Implements ACP JSON-RPC transport.
src/​vs/​platform/​agentHost/​node/​acp/​acpAgentRegistration.ts Validates and registers configured agents.
src/​vs/​platform/​agentHost/​node/​acp/​acpAgent.ts Implements the ACP-backed agent provider.
src/​vs/​platform/​agentHost/​common/​agentService.ts Defines the ACP setting identifier.
src/​vs/​platform/​agentHost/​common/​agentHostStarter.config.contribution.ts Registers the user-facing setting.
src/​vs/​platform/​agentHost/​common/​agentHostSchema.ts Adds ACP root configuration schema.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +403 to +406
scope: ConfigurationScope.APPLICATION,
restricted: true,
tags: ['experimental'],
agentHost: { key: AgentHostAcpAgentsConfigKey },

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2176b0d: new chat.agentHost.acpAgent.enabled owned by the AcpAgents3PIntegration policy (reuses thirdPartyAgentEnabledValue). The host registers ACP providers only while the mirrored acpAgentEnabled root key is true, and the workbench hides acp-* providers otherwise. Note: export-policy-data needs the private distro product.json, so policyData.jsonc contains only the generated entry for this policy — a maintainer may want to re-run the export.

Comment on lines +103 to +111
let msg: IWireMessage;
try {
msg = JSON.parse(line);
} catch {
// Agents sometimes print diagnostics on stdout; never echo the line, it may contain user content.
this._log('warn', `ignoring non-JSON line from agent (${line.length} chars)`);
continue;
}
this._dispatch(msg);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2176b0d: non-object JSON (null, numbers, strings, arrays) is now ignored with a warning; covered by a test.

Comment on lines +37 to +40
const child = spawn(config.command, [...(config.args ?? [])], {
env: { ...process.env, ...config.env },
stdio: ['pipe', 'pipe', 'pipe'],
});

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2176b0d: the launcher now goes through formatSubprocessArguments, moved from extHostMcpNode to base/node/processes so MCP stdio servers and ACP agents share the same CVE-2024-27980 handling. I couldn't test on Windows locally.

Comment on lines +270 to +274
const state = this._track(chat, result.sessionId, cwd, connection);
this._applySetup(state, result);
if (options?.model) {
await this._changeModel(chat, options.model);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2176b0d: a failure after session/new closes the native session and untracks the chat; a failed restore untracks it so it can be retried. Both covered by tests.

- Gate ACP providers on a new `chat.agentHost.acpAgent.enabled` setting
  owned by the `AcpAgents3PIntegration` policy, which reuses
  thirdPartyAgentEnabledValue so managed settings and disabled preview
  features turn ACP agents off like the Claude and Codex harnesses. The
  host registers providers only while the mirrored `acpAgentEnabled`
  root key is true, and the workbench hides `acp-*` providers otherwise.
  policyData.jsonc carries only the generated entry for the new policy
  (export-policy-data needs the private distro product.json).
- Ignore valid JSON lines that are not objects instead of throwing out
  of the stdout listener.
- Close the native session when setup after session/new fails, and drop
  the tracked chat when a restore fails so it can be retried.
- Launch Windows `.cmd`/`.bat` shims (npm-installed agents) through
  formatSubprocessArguments, moved from extHostMcpNode to
  base/node/processes so MCP stdio servers and ACP agents share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@4ekuct25

Copy link
Copy Markdown
Author

Note for maintainers: the VS Code PR Check failure is about build/lib/policies/policyData.jsonc. The new AcpAgents3PIntegration policy needs an entry there, otherwise agentHostPolicySupport "matches the exported policy catalog" fails. As a community contributor I can neither run npm run export-policy-data (the distro product.json returns 404) nor change that file. The entry in this PR is the one the export generates for this setting (produced with an empty distro product.json, keeping only this entry). Could a team member re-run the export and push it, or tell me if you'd prefer a different gating approach?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants