agentHost/claude: Phase 5 + Phase 6 — IAgent provider, sendMessage (re-land)#314533
Merged
TylerLeonhardt merged 13 commits intomainfrom May 6, 2026
Merged
agentHost/claude: Phase 5 + Phase 6 — IAgent provider, sendMessage (re-land)#314533TylerLeonhardt merged 13 commits intomainfrom
TylerLeonhardt merged 13 commits intomainfrom
Conversation
- roadmap.md: mark Phase 4 as DONE, link merged PR #313780. - phase4-plan.md: record live-system smoke completion in §7.8; disabled-gate run skipped (covered by unit tests + env-var guard). - claudeAgent.test.ts: drop gratuitous 'as unknown as' cast in the CCAModel fixture (literal already matches CCAModelBilling exactly; plan §7.4 forbids unsafe casts in tests).
Handoff plan for Phase 5 (replace 7 throwing stubs in claudeAgent.ts). Locked against post-PR-#313841 reality (provisional sessions, onDidMaterializeSession, 30s empty-session GC) and the IAgent contract on origin/main. Decisions captured: - Non-fork createSession is synchronous and in-memory; fork deferred to Phase 6 (throws TODO). - IClaudeAgentSdkService surface mirrors IAgent (no dir parameter on listSessions); SDK loader caches resolved module, retries on failure, logs once. - listSessions joins SDK enumeration with workbench session DB metadata via ISessionDataService; per-entry try/catch resilience. - shutdown() routes per-session teardown through the same SequencerByKey<string> used by disposeSession() so concurrent shutdown/disposeSession cannot double-dispose a wrapper in Phase 6. - 14 unit tests defined (12 lifecycle + 2 resolved-config), including log-once contract and shutdown/disposeSession race guard.
Lands the ClaudeAgent IAgent provider behind the 'chat.agentHost.claudeAgent.enabled' setting (env gate VSCODE_AGENT_HOST_ENABLE_CLAUDE=1). Pins @anthropic-ai/claude-agent-sdk@0.2.112 in workspace + remote/. Implemented in this phase: * createSession - non-fork, in-memory wrapper only. Honors config.session for restore. The fork path and SDK session creation are deferred to Phase 6. * listSessions - SDK is source of truth; per-session DB read is a best-effort overlay (failure never excludes an entry). * disposeSession / shutdown - routed through a per-session SequencerByKey to serialize teardown. * getDescriptor, getProtectedResources, models, onDidSessionProgress, setClientCustomizations, setClientTools, onClientToolCallComplete, setCustomizationEnabled, authenticate, respondTo*Request - minimal Phase-5 wiring. Stubbed for Phase 6 (throw async 'TODO: Phase 6'): sendMessage, abortSession, changeModel, getSessionMessages, plus the createSession fork path. Tests: 29 unit tests in claudeAgent.test.ts cover the createSession restore-id path, listSessions overlay resilience, dispose serialization, and stub surfaces. Note: provisional / onDidMaterializeSession is intentionally omitted in Phase 5 (see plan section 3.3.1) - the workbench needs an immediate sessionAdded until the agent has real materialization work, which arrives in Phase 6 alongside SDK query() startup.
Implements the Phase 6 plan: provisional sessions materialize on first sendMessage, route a single-turn prompt through the Anthropic Claude Agent SDK's WarmQuery, and stream SDKMessages back as protocol AgentSignals via a pure mapSDKMessageToAgentSignals reducer. Tools remain denied (canUseTool: 'deny'); fork moves to Phase 6.5; Plan Mode UI moves to Phase 7. Highlights: - ClaudeAgent.sendMessage routes through _sessionSequencer to collapse concurrent first sends into one materialize + N ordered sends. - _materializeProvisional has two abort gates (post-startup + post-customizationDirectory write) so disposeSession landing mid-materialize cannot leak a WarmQuery subprocess. - ClaudeAgentSession owns the prompt iterator + per-turn deferreds; mapSDKMessageToAgentSignals is a pure reducer with state owned by the session. - IClaudeAgentSdkService gains startup() alongside listSessions(). Tests: 43 unit + 2 proxy-backed integration. Council-review fixes (C1 dispose race, C2 missing integration test, S1 cwd-less ratification) included.
… abort) Two Copilot-reviewer comments on #314216: 1. listSessions: wrap _sdkService.listSessions() in try/catch. AgentService.listSessions fans out across providers via Promise.all; an SDK dynamic-import failure would otherwise nuke every other provider's session list. Now logs and returns []. 2. dispose: abort _provisionalSessions AbortControllers before super.dispose(). Previously a racing first sendMessage parked inside _writeCustomizationDirectory could pass the materialize abort gates and call _sessions.set on a disposed DisposableMap, orphaning the WarmQuery. Aborting first triggers the existing post-customization-write abort gate, which asyncDisposes the WarmQuery. Tests: 2 new regressions (listSessions empty on SDK throw; agent.dispose() during racing materialize disposes the WarmQuery). 45/45 unit + 2/2 integration pass.
The new SDK no longer vendors native binaries inside the main package.
It now ships a ~200MB `claude` executable per platform via 8 optional
platform-specific packages, mirroring the @github/copilot pattern:
@anthropic-ai/claude-agent-sdk-{darwin,win32}-{x64,arm64}
@anthropic-ai/claude-agent-sdk-linux-{x64,arm64}{,-musl}
The SDK loader picks the right package at runtime via process.platform
/process.arch (and tries -musl first on linux).
To strip off-target packages from the build output:
- build/lib/claudeAgentSdk.ts mirrors build/lib/copilot.ts
- gulpfile.vscode.ts and gulpfile.reh.ts apply the filter alongside
the existing copilot one
- gulpfile.vscode.ts asar-unpacks @anthropic-ai/claude-agent-sdk-* so
the executable stays on disk (asar would break exec permissions)
- alpine-{arch} maps to linux-{arch}-musl (claude is statically linked
against libc and must match the host)
cglicenses.json gets 8 new entries mirroring the parent SDK's
"© Anthropic PBC. All rights reserved." text.
The new SDK Query interface adds a `readFile` method; FakeQuery and
RoundTripQuery test doubles get matching stubs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Re-lands the Claude agent-host provider’s Phase 5/6 work: provisional Claude sessions now materialize on first send, route prompts through the Claude SDK, and stream session signals back through the agent-host protocol. It also wires the new SDK dependency into agent-host packaging/build flows and updates the phase/smoke documentation around the Claude rollout.
Changes:
- Add the Claude SDK service, session wrapper, prompt resolver, event mapper, and
sendMessage/materialization flow in the agent-host Claude provider. - Register the Claude SDK service in both agent-host entrypoints and package the platform-specific Claude SDK binaries in desktop/REH builds.
- Add proxy-backed integration coverage and refresh the Phase 4/5/6 roadmap, plan, and smoke-test documentation.
Show a summary per file
| File | Description |
|---|---|
src/vs/platform/agentHost/test/node/claudeAgent.integrationTest.ts |
Adds proxy-backed integration tests for Claude agent materialization and proxy auth. |
src/vs/platform/agentHost/node/claude/smoke.md |
Expands the manual smoke plan for Phase 6 sendMessage/materialization behavior. |
src/vs/platform/agentHost/node/claude/roadmap.md |
Marks Phase 4 as completed in the Claude roadmap. |
src/vs/platform/agentHost/node/claude/phase6-plan.md |
Adds the detailed Phase 6 implementation plan. |
src/vs/platform/agentHost/node/claude/phase5-plan.md |
Adds the detailed Phase 5 implementation plan. |
src/vs/platform/agentHost/node/claude/phase4-plan.md |
Records the completed Phase 4 smoke evidence. |
src/vs/platform/agentHost/node/claude/claudePromptResolver.ts |
Introduces prompt/attachment-to-Claude-content-block conversion. |
src/vs/platform/agentHost/node/claude/claudeMapSessionEvents.ts |
Maps Claude SDK stream/result messages into agent-host session actions. |
src/vs/platform/agentHost/node/claude/claudeAgentSession.ts |
Adds the per-session query owner, prompt queue, and SDK message pump. |
src/vs/platform/agentHost/node/claude/claudeAgentSdkService.ts |
Adds the lazy Claude SDK wrapper with listSessions() and startup(). |
src/vs/platform/agentHost/node/claude/claudeAgent.ts |
Implements provisional session creation, materialization, config schema, session listing, and real sendMessage. |
src/vs/platform/agentHost/node/agentHostServerMain.ts |
Registers the Claude SDK service in the standalone agent-host server entrypoint. |
src/vs/platform/agentHost/node/agentHostMain.ts |
Registers the Claude SDK service in the regular agent-host entrypoint. |
src/vs/platform/agentHost/common/claudeSessionConfigKeys.ts |
Adds Claude-specific session config keys/types. |
remote/package.json |
Adds the Claude SDK dependency to the REH package manifest. |
package.json |
Adds the Claude SDK dependency to the root product manifest. |
package-lock.json |
Locks the Claude SDK and its platform-specific optional packages. |
eslint.config.js |
Allows importing the Claude SDK from the agent-host code. |
cglicenses.json |
Adds license overrides for the new Claude SDK platform packages. |
build/lib/claudeAgentSdk.ts |
Adds build helpers for keeping only the target Claude SDK platform package. |
build/gulpfile.vscode.ts |
Applies Claude SDK filtering/unpacking in desktop packaging. |
build/gulpfile.reh.ts |
Applies Claude SDK filtering in REH packaging. |
Copilot's findings
Files not reviewed (1)
- remote/package-lock.json: Language not supported
- Files reviewed: 22/24 changed files
- Comments generated: 4
roblourens
previously approved these changes
May 6, 2026
…universal app
Mirror the existing @github/copilot pattern in create-universal-app.ts.
Each darwin arch build only contains its own platform package (npm only
installs the optionalDependency matching the host CPU), so the universal
merger fails with files unique to one side.
Cross-copy node_modules/@anthropic-ai/claude-agent-sdk-{darwin-x64,darwin-arm64}/
between the two builds, skip them from the equality comparison, and tag
the per-arch `claude` executable as arch-specific so the merger keeps both.
Also extend verify-macho's skip list to ignore the single-arch `claude`
binaries inside the universal app.
# Conflicts: # package-lock.json
- claudeAgent: rewrite `rgPath` from node_modules.asar → node_modules.asar.unpacked before putting it on the Claude subprocess PATH. Mirrors copilotAgent.ts and the workbench search engine helpers; without this, packaged builds advertise a path that doesn't exist on disk. - cglicenses.json: add "// Reason: …" justification comments to the parent @anthropic-ai/claude-agent-sdk entry and each of the 8 new platform sub-packages, matching the file's convention.
The universal macOS app now carries platform-specific binaries for both x64 and arm64 — @github/copilot-darwin-* (~128MB each) and the new @anthropic-ai/claude-agent-sdk-darwin-* (~207MB each) — so the source filesystem the DMG is built from has crossed 1GB. dmgbuild fails with `No space left on device` when ditto can't fit the app inside the volume. Output DMG is LZMA-compressed (format = 'ULMO') so this only changes the build-time staging size, not the shipped artifact size.
3c602a0 to
8b29827
Compare
…specified path
Move `@anthropic-ai/claude-agent-sdk` from `dependencies` to
`devDependencies` so the ~200MB-per-arch platform binaries are no longer
shipped with VS Code. The SDK becomes opt-in and externally-installed.
User-facing surface:
- Replace boolean setting `chat.agentHost.claudeAgent.enabled` with
string setting `chat.agentHost.claudeAgent.path`. When the setting
is non-empty, the Claude provider registers; when empty (the default),
it does not.
- The setting value is forwarded to the agent host via the
`VSCODE_AGENT_HOST_CLAUDE_SDK_PATH` env var (replacing the previous
`VSCODE_AGENT_HOST_ENABLE_CLAUDE` flag).
- `agentHostServerMain` exposes a `--claude-sdk-path <path>` CLI flag
in place of the previous `--enable-claude-agent` flag.
Runtime loader:
- `ClaudeAgentSdkService._loadSdk()` now reads the env var and
dynamic-imports from there. If the path is a directory, the package's
main entry is resolved from `package.json` (`exports['.']` /
`main`) before the import — Node ESM does not support directory
imports of `file://` URLs.
Build/packaging cleanup (no longer needed once the SDK is gone from
production deps):
- Drop `build/lib/claudeAgentSdk.ts` and its callers in
`gulpfile.{vscode,reh}.ts`.
- Drop the `@anthropic-ai/claude-agent-sdk-*` glob from the
asar-unpack list in `gulpfile.vscode.ts`.
- Revert universal-app cross-copy + filesToSkip + x64ArchFiles entries
in `build/darwin/create-universal-app.ts` and the corresponding
skip patterns in `build/darwin/verify-macho.ts`.
- Revert DMG volume size from 2g back to 1g in
`build/darwin/dmg-settings.py.template` (was bumped earlier in
this branch to fit the bundled SDK; no longer needed).
- Remove the 9 `@anthropic-ai/claude-agent-sdk*` entries from
`cglicenses.json` (no longer shipped, no manifest to license).
Type imports of `@anthropic-ai/claude-agent-sdk` continue to work via
the devDependency, so source code that does `import type` from the
package still typechecks.
8b29827 to
d8fa2b6
Compare
roblourens
previously approved these changes
May 6, 2026
dmitrivMS
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Re-submits the Claude
IAgentprovider (Phase 5 + Phase 6) for the Agent Host. The original Phase 6 PR (#314216) was reverted in #314358; this PR re-lands that work plus follow-ups: PR-review fixes, a stale-dep cleanup, the SDK bump from 0.2.112 → 0.2.128, and — most importantly — a switch from bundling the SDK to loading it from a user-specified path at runtime.Implements phase5-plan.md and phase6-plan.md of the Claude-in-Agent-Host roadmap. Tools remain denied (
canUseTool: 'deny') — Phase 7 owns tool wiring.Commits in this stack
agentHost/claude: post-Phase-4 cleanupagentHost/claude: lock Phase 5 implementation planagentHost/claude: Phase 5 — IAgent provider skeletonagentHost/claude: Phase 6 — sendMessage, single-turn, no toolsagentHost/claude: address PR review (listSessions resilience, dispose abort)— new since agentHost/claude: Phase 6 — sendMessage, single-turn, no tools #314216Drop stale @anthropic-ai/sandbox-runtime dep from merge resolution— newBump @anthropic-ai/claude-agent-sdk 0.2.112 → 0.2.128— newagentHost/claude: address PR review— newagentHost/claude: don't bundle claude-agent-sdk; load it from a user-specified path— newPhase 6 highlights
ClaudeAgent.sendMessageroutes through_sessionSequencerso concurrent first sends collapse into one materialize + N ordered sends — no double-fork of the SDK subprocess._materializeProvisional. Post-sdk.startup()and post-_writeCustomizationDirectory()checks ensure that adisposeSessionlanding mid-materialize disposes the producedWarmQueryinstead of leaking a subprocess into_sessions.ClaudeAgentSessionowns the prompt iterator + per-turn deferreds;mapSDKMessageToAgentSignalsis a pure reducer with state owned by the session — independently testable, no shared mutable state.IClaudeAgentSdkService.startup()added alongsidelistSessions()so the agent can be tested with a fake SDK without spawning a real subprocess.SessionResponsePartis allocated oncontent_block_startbefore any delta arrives, so the protocol's "part-must-precede-delta" invariant holds by construction.Post-revert follow-ups
PR-review fixes (
3a4bcadb6cd):listSessionswraps_sdkService.listSessions()in try/catch.AgentService.listSessionsfans out across providers viaPromise.all; an SDK dynamic-import failure would otherwise nuke every other provider's session list. Now logs and returns[].disposeaborts_provisionalSessionsAbortControllers beforesuper.dispose(). Previously a racing firstsendMessageparked inside_writeCustomizationDirectorycould pass the materialize abort gates and call_sessions.seton a disposedDisposableMap, orphaning theWarmQuery. Aborting first triggers the existing post-customization-write abort gate, whichasyncDisposes theWarmQuery.Don't bundle the SDK; load it from a user-specified path (
8b298274182):After the SDK bump, the new SDK ships ~200MB of native
claudeexecutables (eight optional platform-specific packages:@anthropic-ai/claude-agent-sdk-{darwin,win32,linux}-{x64,arm64}{,-musl}). Bundling that into VS Code triggered a chain of build issues (universal-app cross-copy, DMG sizing, asar-unpack, cglicenses) and grew the install footprint significantly. After review, we don't bundle the SDK at all and load it from a user-installed location instead.@anthropic-ai/claude-agent-sdkis moved fromdependenciestodevDependencies.import typekeeps typechecking; the package and its eight platform sub-packages are not installed in production.chat.agentHost.claudeAgent.pathreplaces the booleanchat.agentHost.claudeAgent.enabled. When the value is non-empty, the Claude provider registers; otherwise it does not.VSCODE_AGENT_HOST_CLAUDE_SDK_PATH(replacingVSCODE_AGENT_HOST_ENABLE_CLAUDE).agentHostServerMainexposes--claude-sdk-path <path>in place of--enable-claude-agent.ClaudeAgentSdkService._loadSdk()reads the env var and dynamic-imports from it. If the path is a directory, the package's main entry is resolved frompackage.json(exports['.']/main) before the import — Node ESM does not support directory imports offile://URLs.build/lib/claudeAgentSdk.ts, the gulpfile filters ingulpfile.{vscode,reh}.ts, the@anthropic-ai/claude-agent-sdk-*asar-unpack glob, the universal-app cross-copy +filesToSkip+x64ArchFilesentries, theverify-machoskip patterns, the DMGsize = '2g'bump, and the ninecglicenses.jsonentries.Tests
claudeAgent.test.tscovering materialize lifecycle (provisional/abort/double-materialize), prompt iterator pumping, dispose ↔ shutdown ↔ send race matrix, the C1 dispose-during-customization-write regression, mapper output for text + thinking + result, the SDK loader's cache-and-log-once contract, plus the two new regressions added in3a4bcadb6cd.claudeAgent.integrationTest.ts: realClaudeProxyService(loopback HTTP server) + stubbed Copilot CAPI streaming cannedMessageStreamEvents — exercises the full agent → proxy → CAPI → SSE → agent pipeline including theANTHROPIC_AUTH_TOKENnonce contract.Out of scope (deferred per roadmap)
canUseTool: 'deny'(Phase 7)config.fork(Phase 6.5 — needs message-UUID lookup)getSessionMessages(Phase 13)