* agentHost: reconstruct prompt-less subagent transcripts on session reopen The Claude replay mapper only opened a turn on a `user-text` message and dropped any assistant message that arrived with no active turn. Subagent transcripts returned by `getSubagentMessages` carry a `parent_tool_use_id` on every envelope and have no synthetic spawning prompt, so they open directly with an assistant message — which meant every inner assistant message (including the subagent's final reply) was dropped and the transcript reconstructed as zero turns on reopen. Thread an `isInner` flag (set when `parent_tool_use_id !== null`) through the parsed assistant message and, when there is no active turn, synthesize an empty-prompt turn for inner messages instead of dropping them. Top-level assistant-before-user envelopes remain anomalous and are still dropped, so the change is scoped strictly to subagent transcripts. Adds unit fixtures covering the prompt-less subagent transcript shape and the unchanged top-level drop behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: enable Codex provider in real-SDK test server `startRealServer` forwarded `--codex-sdk-root` but never set the Codex enable flag, and the Codex agent defaults to disabled, so the provider was never registered and every Codex real-SDK test failed with "No agent provider registered for: codex". Set `VSCODE_AGENT_HOST_CODEX_AGENT_ENABLED` on the forked server when a codex SDK root is supplied so the provider registers for the real-SDK suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: bump Claude (0.3.187) and Codex (0.142.0) SDKs Update the @anthropic-ai/claude-agent-sdk and @openai/codex devDependencies (and the build/agent-sdk tarball pins) to 0.3.187 / 0.142.0. The new Claude SDK adds `setMcpPermissionModeOverride` to the `Query` interface, so the test doubles that implement `Query` are updated to satisfy it: `ImmediatelyDoneQuery` (and the `RecordingQuery` that extends it), `FakeQuery`, and `RoundTripQuery`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
build/agent-sdk
Per-platform agent SDK production. Each VS Code build (darwin-arm64,
linux-x64, Alpine REH, etc.) uploads its own platform's SDK tarballs
to main.vscode-cdn.net and stamps agentSdks into the shipped
product.json with a {version, urlTemplate} per SDK. Every platform
job emits the same urlTemplate per SDK — the runtime substitutes
{sdkTarget} per launch via resolveSdkTarget(), which is what lets
macOS Universal bundles share one product.json across arm64 + x64.
The runtime side (src/vs/platform/agentHost/) downloads and caches
the SDK tarball at first use. See IAgentSdkProductConfig in
src/vs/base/common/product.ts for the contract.
How the pipeline uses this
The platform packaging jobs (Linux, macOS, Windows, Alpine) each include
the shared template build/azure-pipelines/common/agent-sdk-produce.yml
before the existing gulp vscode-<platform>-<arch>-min-ci step:
- template: ../../common/agent-sdk-produce.yml@self
parameters:
vscodePlatform: linux
The template runs node build/agent-sdk/produce.ts --vscode-platform=<x> --arch=$(VSCODE_ARCH), which iterates the SDKs (SDKS = ['claude', 'codex']), figures out the matching sdkTarget for (vscode-platform, arch, sdk) via getSdkTargetForBuild, runs buildOne for each in
parallel, and drops the tarballs in
$(Build.SourcesDirectory)/.build/agent-sdk/tarballs/.
Publish vs test runs
produce.ts reads the pipeline variable VSCODE_PUBLISH from env (Azure
auto-injects all non-secret pipeline variables) to decide whether to
hit the CDN:
-
VSCODE_PUBLISH=true(real release builds) — the AzureCLI@2 step inside the template fetches CDN credentials,produce.tscallsuploadOnefor every tarball (HEAD-then-decide idempotent), writes the results JSON, and emits##vso[task.setvariable variable=AGENT_SDK_RESULTS_FILE]<path>. The downstream gulp packaging step then stampsproduct.agentSdksviareadAgentSdkResults(). -
VSCODE_PUBLISHunset or not'true'(PR runs, CI runs, manual test runs with the publish toggle off) — the AzureCLI credential step is skipped, the upload is skipped, no results file is written, andtask.setvariableis not emitted. The tarballs are still produced and published as a pipeline artifact namedagent_sdk_<vscodePlatform>_<arch>_tarballsso you can download and inspect them. product.json ships withoutagentSdks— same shape as a local dev build, so the runtime falls back to the per-provider env-var override.
Where the agentSdks gating lives
Inside packageTask's jsonEditor callback (the same one that injects
commit / date / checksums / version), readAgentSdkResults() loads
the results file (returns {} when the env var is unset) and merges
agentSdks into product.json. The REH gulpfile only writes agentSdks
for type === 'reh'; the REH-web variant skips it because the agent host
is node-only and the SDK config has no consumer in a browser-served
server.
Local gulp vscode-darwin-arm64 invocations don't set
AGENT_SDK_RESULTS_FILE and don't have VSCODE_PUBLISH=true, so
readAgentSdkResults() returns {} and product.json ships without
agentSdks — same UX as today's no-config build.
Why two steps, not inline-in-gulp
The agent SDK work is a distinct concern from the VS Code packaging gulp graph. As its own pipeline step:
- Visible in the build log — operators see a discrete "Agent SDK: build
- upload" step they can click into instead of grepping inside "Build client" output.
- Independently re-triggerable — if the SDK step fails, the operator can re-run just the platform job; if it succeeds but the gulp step fails, the SDK upload is already idempotent (HEAD-then-skip).
- Doesn't add async-stream complexity to the gulpfile.
packageTaskstays a sync stream-returning function; the only change is one synchronousreadAgentSdkResults()call inside the existingjsonEditorcallback.
Files
agents/<sdk>/— one folder per SDK we ship. Each contains apackage.json(single dependency: the SDK's own npm package, pinned to an exact version) and apackage-lock.json(full transitive graph). Folder name = SDK id = key underproduct.agentSdks= path segment in the CDN URL. The set of folders IS the SDK list — no parallel array to keep in sync.common.ts— types,getSdks()(discovers SDKs fromagents/),getAgentMeta()/getSdkVersion()(reads fromagents/<sdk>/package.json, rejects^/~ranges),getSdkTargetForBuild()((vscodePlatform, arch, sdk) → npm-suffix),buildCdnUrl()/buildCdnUrlTemplate(),sha256OfFile(),parseFlags()for CLI flag parsing, andreadAgentSdkResults()for the gulpfile-side reader.package.ts—buildOne({ sdk, sdkTarget, outDir }). Runs on any OS: copiesagents/<sdk>/{package.json,package-lock.json}into a scratch dir,npm ciwithnpm_config_libc/os/cpufetches the foreign platform binary verbatim from the locked graph, then node-tar+gzip with reproducible flags. Has a thin CLI at bottom.upload.ts—uploadOne(...). HEAD-then-decide: absent → upload; matching sha → skip (idempotent re-runs); different / no-metadata sha → fail loud, refusing to overwrite content-addressed history. Thin CLI.produce.ts— pipeline-step entry. For one(vscode-platform, arch), iterates the SDKs in parallel, callsbuildOne+uploadOnefor each that applies, writes results toAGENT_SDK_RESULTS_FILE, and emits##vso[task.setvariable]so downstream pipeline steps see the path.
Bumping an SDK version
- Edit the
dependenciesversion inbuild/agent-sdk/agents/<sdk>/package.jsonto the new exact version. - From that directory:
npm install --package-lock-only --ignore-scriptsto refreshpackage-lock.json. - Also bump the matching
devDependenciesentry in repo-rootpackage.json(the runtime imports types from that copy) so the shipped types and the build-time pin stay in lockstep. npm installat repo root to refresh the root lockfile.- Commit all four edits together.
The next pipeline run rebuilds + uploads each platform tarball at the
new content-addressed CDN path and re-stamps each product.json with
the new urlTemplate pointing at the bumped version.
No human-paste step into vscode-distro. No coordination between jobs.
Local dev
Build one tarball locally:
node build/agent-sdk/package.ts --sdk=claude --target=darwin-arm64 --out=/tmp/out
For OSS contributors who want to drive the agent host without going through the CDN, point the dev override env vars at a local SDK install:
VSCODE_AGENT_HOST_CLAUDE_SDK_ROOT=/path/to/anthropic-claude-sdk-install \
./scripts/code.sh
(See src/vs/platform/agentHost/common/agentService.ts for env var names.)