TylerLeonhardt
90d · built 2026-09-08
Performance
What TylerLeonhardt shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
+1.5engineers
delivers like 2.5 (2.5x pre-AI)
Output (ETV)
38.9ETV
−12.4% vs 44.5 prior
Features share
35.0%
−3.0 pp vs prior window
Fixes share
13.3%
+9.2 pp vs prior window
Work mix
35% Features6.4% Maintenance41.8% Tests3.4% Docs13.3% Fixes
78 commits over 90 days, ending 2026-09-08.
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 53 %
- By Features share
- Top 36 %
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 10 by ETV in the last 90 days.
- 4.5ETVAsk the agent SDK about setup instead of guessing from the filesystem (#331739) * Ask the agent SDK about setup instead of guessing from the filesystem A developer who pays for Claude directly hit a GitHub Copilot sign-in wall, because session-type availability was inferred by sniffing for config files on disk. That guess was wrong in both directions: it gated users who had a working account, and it advertised agents to users who had none. Replace the inference with what the agent's own SDK reports, and make the SDK download an explicit choice rather than something that happens on startup. - Agents publish an SDK setup status (`notDownloaded` / `downloading` / `ready`) over the root config channel, plus the capabilities they offer for getting an account. The workbench derives "no account" from `ready` + zero models, so there is a single wire source per fact. - Agents declare capabilities only; every user-facing string is localized in the workbench via `vs/nls`. - The download is offered by a banner and performed on request. Consent is recorded per agent, so a later version bump re-downloads silently for that agent while a different agent still asks. - Background fetches stay invisible: only the explicit gesture registers download progress interest. - `AgentSdkSetupChannel` holds the nonce handling, in-flight latch and publish ordering once, so Claude and Codex differ only in their capability literals. Removes the filesystem-sniffing paths this replaces: `codexLocalAuth` and the "we discovered your existing configuration" notification, which asked users to sign in again after they had already declined the sign-in modal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix two setup-status races found in review Publish the SDK download status *after* the model catalog on both agents. Publishing `ready` at the top of `_refreshModels` meant the first refresh after a download announced "the SDK is here" while `_models` was still empty -- and `ready` plus zero models is exactly how the window renders "no account found". The invariant was already documented in the setup channel's own `_download()`; the refresh path contradicted it. Re-bind `AgentSdkSetupService` to root state on `onAgentHostStart`. `rootState` is a getter over a protocol client the host replaces on every restart and reconnect, so the single constructor-time subscription went quietly stale -- and because the service is `Delayed`, constructing before the connection bound the no-op state forever. Pending download requests are cleared on re-bind too: a request the previous host never answered never will be, so the Download button comes back rather than staying suppressed. Both fixes carry regression tests that were verified to fail without them. Also corrects the `explicitlyRequested` telemetry doc, which claimed to carry a click-vs-standing-consent split it does not have, and states the banner's ambient-host scope in `agentSdkSetupSessionType`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>github.com-microsoft-vscode · 2f898b82 · 2026-08-20
- 3.8ETVConditional agent-window auth for signed-out users (#328990) * Conditional agent-window auth for signed-out users Behind the experimentation setting `chat.agentHost.allowSignedOutWhenUsable` (default off), the Agents window no longer unconditionally forces GitHub sign-in. When the user is signed out and some registered session type can run on its own credentials — Claude in native mode with an existing local setup — the window opens and a calm chat-input notification explains what happened, offering sign-in for anyone who meant to use a Copilot subscription. Whether a type requires GitHub is derived from the agent's advertised protected resources rather than a static flag, and surfaces on the provider-agnostic `ISessionType.authRequirement` as `none | github | unusable`. The third state is load-bearing: a Claude pinned to native by an explicit `claudeUseCopilotProxy: false` with no credentials advertises the Copilot resource as `required: false`, so a boolean would report it usable. Its model catalog is what distinguishes it, which is why Claude now publishes an empty catalog in that case instead of the SDK's static `supportedModels()` list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR feedback: startup race, provider identity, storage target - The window gate's inputs resolve asynchronously (the agent host advertises Claude at `AfterRestored`), but the only subscription was installed after setup completed. A signed-out startup could therefore show the non-dismissible sign-in modal and never reconsider. The subscription is now lifetime-scoped and retires an open modal when the answer flips to usable. - `getAllSessionTypes()` deduplicates by id (first provider wins), so a usable type from a second provider was invisible to the gate. Added `getAllProviderSessionTypes()` and used it for the gate and the notification lookup; the signed-out session-type fallback now matches on provider too. - "Don't Show Again" used `StorageTarget.USER`, which settings sync carries across machines, contradicting the intended machine-local scope. Now `StorageTarget.MACHINE`. Also fixes two claudeAgent tests that asserted native enumeration without a credential present, and adds coverage for the empty-catalog case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · 59bca39f · 2026-08-04
- 2.8ETVAdd "Continue with Microsoft" sign in for GitHub (#332948) * Add "Continue with Microsoft" sign in for GitHub Brokers a GitHub session from an Entra token the built-in `microsoft` provider already holds, so someone signed in to Microsoft can reach Copilot without a second browser round trip. The flow is deliberately two exchanges. The first buys a `read:user` discovery token, just enough to `GET /user` and show which GitHub account the Entra identity maps to. Nothing is published until the user confirms that identity. The second exchange then mints the scopes the caller actually asked for. The discovery token is never persisted and never published as a session. Entra-brokered sessions live in memory for the life of the window and are never written to the Keychain. What survives a reload is the user's consent, recorded in global state as a GitHub label, a Microsoft label, and the GitHub user id. A fresh window mints the session again from that row, silently, re-verifying through discovery that the row still points at the same account. Rows are keyed by GitHub account label, because that is what VS Code itself keys an account by: `getAccounts` collapses sessions by label and the account preference is stored by label. The id is kept for one job only, checking that the token GitHub just returned belongs to the account the row names. Signing out of the Microsoft account drops the sessions, since nothing can renew them, but leaves the rows alone. The Microsoft account list is a per-window cache that reads empty for a moment while it repopulates, and the rows are global state shared by every window, so acting on a blink of that list would sign the user out everywhere with no way back. Dropping only the sessions self-heals: the next read mints them again from the row that is still there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix CI: hygiene warnings and a missing test stub The hygiene job fails on eslint warnings, and both warnings were in entraTokenExchange.test.ts: an `in` operator check and a double-quoted string outside of localization. The harness override is now a positive `noExchangeEndpoint` boolean, and the assertion uses single quotes. The browser test broke because main added @INativeManagedSettingsService to the DefaultAccountProvider constructor. The signIn helper now stubs both managed-settings services with their existing Null implementations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Address review comments on Microsoft-brokered sessions Verify the granted token against the account the user confirmed, not just the discovery token, and give that mismatch its own failure kind so a restore only forgets a link when GitHub positively names somebody else. Make a failed unlink write stick for the window that did it, so a sign out cannot leave a row behind that silently signs the user back in. Discard a token whose Microsoft account was signed out while the exchange was in flight, settle every expired session rather than only those with nothing to hand back, and warn when GitHub grants fewer scopes than asked. Adds a provider-level test suite driven through the real getSessions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>github.com-microsoft-vscode · 5ca078d7 · 2026-08-27
- 2.4ETVPhase 16: editable Claude customization resolution via disk scan (#322501) * Phase 16: editable Claude customization resolution via disk scan Resolve a Claude session's customizations (agents, skills, slash commands, rules, MCP servers) by scanning the file system for the real customization files and shipping each item's real editable file: URI, instead of synthesizing stub files from the SDK query payloads. - Add disk-scan resolver + per-scope (Workspace/User) container mapping (customizations/claudeSessionCustomizationDiscovery.ts + scan/). - Add read-only built-in agents/skills tier (claudeBuiltinCommands.ts); curated pre-materialize, SDK-derived post-materialize. - Inline the projection into getSessionCustomizations; delete the synthetic-stub bundler and the standalone projector. - Watcher-backed live refresh of the customization list. - Update phase16-plan.md, roadmap.md, and CONTEXT.md to match. * Address PR review: depth-cap recursive rule scan, encode built-in URIs, fix roadmap bold markers * Fix claudeAgent integration test: register IFileService + INativeEnvironmentService ClaudeAgentSession now reads userHome at construction (Phase 16 watcher); the integration test's ServiceCollections lacked the env/file services, so session construction threw 'Cannot read properties of undefined (reading userHome)'. Add the same in-memory file + mock env services the unit harness uses.github.com-microsoft-vscode · dd66c66b · 2026-06-23
- 1.9ETVagentHost: Claude per-session provider selection (#329331) * agentHost: add provider-qualified Claude model-selection id codec Pure precursor for per-session provider selection in the Claude harness. Mirrors Codex's `@provider=` convention: toClaudeModelSelectionId encodes a provider + model id into one opaque ModelSelection.id; parseClaudeModelSelection splits it back, with a bare/malformed/legacy id defaulting to the Copilot (proxy) provider so nothing needs a data migration. claudeTransportForProvider maps the token to a transport (anthropic -> native, everything else -> proxy). Dead code until the merged-catalog + per-session routing core wires it in; landed first because it's a leaf with zero behavioral risk. Fully unit-tested with no mocks, mirroring codexModelSelection.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: add merged-catalog + per-session transport resolvers for Claude Two pure precursors for per-session provider selection, co-located with the model-selection id codec and covered by the same test suite: - mergeClaudeModelCatalogs(proxy, native): flattens the two provider catalogs into one picker list, proxy-first (preserving models[0]-is-default), each id provider-qualified so a row carries its transport and the same model under both providers yields two non-colliding rows. Either side may be empty so one source failing to fetch never blanks the other. - resolveClaudeSessionTransport({ perSessionProviderEnabled, model, defaultMode }): the per-session counterpart to the host-global resolver — off, or no model, inherits the host default (identical to today); on, the selected model's provider decides. Both are dead code until the flag-gated wiring lands; kept as a separately reviewable, fully-tested unit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: wire Claude per-session provider flag into the backend read-path Slice 2a (flag plumbing) + 2b (backend read-path) of per-session provider selection for the Claude agent host, gated behind the off-by-default experimentation flag `chat.agentHost.claude.perSessionProvider`. Flag plumbing (2a): register the boolean setting (APPLICATION scope, experimental/advanced), its customization-config key, and the forwarder contribution. Backend read-path (2b): ClaudeAgent merges the proxy (Copilot-CAPI) and native (Anthropic) model catalogs into one provider-qualified picker list, and resolves each session's transport from its selected model's provider when the flag is on (inheriting the host default when off, or when no model is selected). Review fixes folded in: - A: gate the constructor / hydration model-refresh on the flag so the native catalog bootstraps signed-out without a manual refresh. - B: _resolveParentSession inherits a never-materialized parent's provisional model so a forked peer chat keeps its native transport. - C: a runtime flag toggle re-enumerates and repopulates the merged catalog. - D: a failing proxy start no longer fails native-default sign-in. - E: toClaudeSdkModelId strips the `@provider=` qualification before the SDK / CAPI boundary — the wrapper is unparseable downstream and would 400 both transports whenever the flag is on and a model is explicitly selected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: guard Claude model refresh against a mid-flight per-session provider toggle The refresh stale-write guards captured only the transport token/mode, not the per-session provider flag. Because `_startModelRefresh` supersedes an in-flight refresh as the coalescing target but never cancels it, a false-flip of the flag mid-refresh left the superseded refresh running; when it settled it published a stale wrong-mode catalog (merged provider-qualified over the bare single-transport list the flag-off refresh had already published, or vice versa), clobbering the correct one. Capture `_perSessionProviderEnabled` at the start of both `_refreshModelsSingle` and `_refreshModelsMerged` and bail in the stale-write guard when it moved, so the superseded refresh drops its result. Tests: a flag-on→off toggle mid-merge (native half parked on a gate so the merged refresh is provably still in-flight) asserts the bare single catalog survives the straggler; and a flag-off regression that a forked peer chat still inherits its never-materialized parent's explicit model (the inheritance in `_resolveParentSession` is intentionally not flag-gated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: group Claude models by transport provider in the picker With per-session provider selection on, the merged catalog now stamps each model's `provider` with its transport token (`copilot` for the Copilot-CAPI proxy, `anthropic` for the user's own Anthropic account) alongside the provider-qualified id, so the chat model picker — which buckets by `provider` — splits Claude into a Copilot group and an Anthropic group. The same model offered by both transports yields two distinct, separately selectable rows. - common/claudeProviders.ts (new): the two transport-provider tokens live in one `common` module so the backend that stamps `model.provider` and the frontend vendor descriptor that names the group are a single, compile-checked source of truth rather than two literals that can drift. - claudeModelSelection.ts: `withQualifiedProvider` re-stamps each model's `provider` with its transport token; re-exports the tokens for node callers. - agentHostChatContribution.ts: register the `anthropic` group vendor (localized "Anthropic"), mirroring the Codex `chatgpt` second-vendor registration, so the native group resolves a clean label. Copilot-routed Claude models keep grouping under the global `copilot` vendor. Dormant while the flag is off — no Claude model carries the `anthropic` provider then. - claudeAgent.ts: correct the stale `toAgentModelInfo` doc — the picker *groups* (does not filter) by `provider`. Flag-off path is unchanged: the single-catalog refresh still stamps the harness provider and no second vendor is registered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: live per-session provider switching, unconditional Complete the Claude per-session provider feature. Switching a live session's model to one on a different transport now re-routes the running subprocess, and the feature ships unconditionally (no experimental flag). Live provider switch: - Refactor IMaterializeContext.transport into a resolveTransport callback so the transport is re-resolved inside materialize and on every rebuild — a provider switch re-routes the rebuilt subprocess onto the new transport. - A cross-transport model change defers via a pending-switch flag; the next send() rebuilds onto the newly-selected transport and commits it once the new subprocess is live. Same-transport changes still hot-swap in place. Remove the experimental flag: - Delete chat.agentHost.claude.perSessionProvider, revert the generic flag plumbing, and delete the setting-to-root-config forwarder contribution. - Collapse the per-session-provider gates in claudeAgent.ts to always-on and remove the now-dead members. - Drop the perSessionProviderEnabled parameter from resolveClaudeSessionTransport; bare/legacy ids still resume on the host default transport with no migration. - Add modelProviders to the e2e CLAUDE_CONFIG and de-flag the unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: simplify Claude per-session transport resolution Follow-up cleanup on the per-session-provider read-path, now that the feature is unconditional and the single-catalog path is gone. - Collapse the cached `_transportMode` and its reactive `_resolveTransportMode`/`_applyTransportModeChange` machinery into a read-on-demand `_defaultTransportMode()`. The host default is only the fallback for model-less / bare-id sessions, so it needs no caching or config/sign-in re-resolve — the next session reads live availability. - `authenticate()`: a Copilot proxy-start failure is now uniformly soft. GitHub sign-in still succeeds and both `_githubToken` and `_proxyHandle` stay uncommitted, so a retry re-attempts `start()`; a Copilot-routed model re-drives sign-in on its first send. Drops the native/proxy default special-casing and the mid-flight transport-mode flip. - Replace `IMaterializeContext.resolveTransport` (a callback the session re-invoked on every rebuild) with a `transport` value the agent pins at materialize. A per-session provider switch is pushed in through `send`'s new `switchTransport` (staged in `_pendingSwitchTransport`); ordinary and SDK-recover rebuilds reuse the materialized transport. A throwing guard replaces the defensive re-resolve. - Move cross-transport switch detection into `ClaudeAgentSession.setModel` (the session owns it); drop the agent-computed `deferForTransportSwitch` option and expose `hasPendingTransportSwitch` in place of `transportKind`. - Inline the one-off `_settledCatalog` helper into `_refreshModels` and drop the `_refreshModels` -> `_refreshModelsMerged` forwarder. - Drop the now-unused provider-token re-export from `claudeModelSelection`; tests import the tokens from `common/claudeProviders` directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * agentHost: address Claude per-session provider review feedback Fixes five review comments on the per-session provider slice: - Keep IAgentModelInfo.provider as the `claude` routing owner and carry the transport/group token in `_meta.modelGroupId`, so a model-selected create_session no longer misroutes to a `copilot`/`anthropic` agent that the node provider registry can't resolve. - Drop the agent-host `anthropic` picker vendor that clobbered the Copilot extension's shared `anthropic` vendor on dispose; reuse the shared one. - On a replacement-token proxy start() failure, tear down the stale account (handle, token, and merged catalog) instead of leaving it live behind a "successful" sign-in that would silently serve the superseded account. - Guard setModel's cross-transport detection on an explicit provider so a bare/legacy id (parser-fallback `copilot`) can't spuriously reroute a native session. - Condense the over-long rematerializer transport-pinning comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>github.com-microsoft-vscode · 511fd2d2 · 2026-08-07
- 1.7ETVagentHost: route GitHub protected resources through a GitHub Enterprise endpoint service (#324159)github.com-microsoft-vscode · 62c7ad83 · 2026-07-07
- 1.7ETVDistribute Claude and Codex agent SDKs via product.json (#320709) * Add tar to REH dependencies and eslint allowlist The agent SDK downloader uses node-tar to extract the per-platform SDK tarballs it downloads from product.json — pure JS, zero native deps, so the agent host works on every server install footprint without relying on a system tar binary. * Distribute Claude and Codex agent SDKs via product.json Adds IAgentSdkDownloader, which fetches the per-platform Claude and Codex SDKs from a CDN configured through product.agentSdks (populated by vscode-distro), verifies the sha256 anchored in product.json, and caches the extracted root under userDataPath. Providers register iff the SDK is available — either a dev override env var or a product.agentSdks entry whose sha256 declares the current platform. Falls through to today's no-op behavior in OSS builds with neither. Tracks microsoft/vscode-internalbacklog#7885. * Fix lockfiles after merge resolve The previous merge took upstream's @anthropic-ai/sdk@0.82.0 in package.json but left the lock file's nested resolution tree pointing at 0.102.0, so npm ci rejected the workspace. Re-resolve via npm install. Also adds the remote/package-lock.json entry for tar that was missed in the first commit. * Address PR review feedback - nodeAgentHostStarter: pass process.env (not the local shell-env snapshot) to buildAgentSdkEnv so a developer's env-var dev override actually wins over a settings value. Matches electronAgentHostStarter. - agentSdkDownloader: write the .complete sentinel inside tmpDir BEFORE the move so cache publish is atomic. A crash between move and sentinel-write previously left a wedged cacheDir that subsequent runs could not recover from (rename-loser path requires a valid sentinel). - agentHostBootstrap: register RequestService with the DisposableStore so its config-change listener is cleaned up at shutdown. - agentSdkDownloader.test: build the fixture tarball via node-tar (already a dep) instead of spawning the host tar binary; drops the bsdtar/gnutar portability surface. - agentHostMain: comment referenced the renamed VSCODE_AGENT_HOST_*_PATH env var; corrected to VSCODE_AGENT_HOST_*_SDK_ROOT. * Drop test-stub fields not present in @anthropic-ai/sdk 0.82 The earlier merge took upstream's @anthropic-ai/sdk@0.82.0 over the stash's 0.102.0; some test stubs had been authored on a branch using 0.102.0 and reference fields that don't exist in 0.82 (output_tokens_details, estimated_tokens, diagnostics on BetaMessage). Strip the optional fields — they're shape-only filler in the test fixtures and aren't asserted on.github.com-microsoft-vscode · f57a83c8 · 2026-06-10
- 1.6ETVagentHost: implement Claude truncateSession (Phase 6.7 — Restore Checkpoint + Start Over) (#323197) * agentHost: implement Claude truncateSession (Phase 6.7 — Restore Checkpoint + Start Over) Implements IAgent.truncateSession for the Claude agent host so the workbench "Restore Checkpoint" and "Start Over" actions work end-to-end: - Point-restore (turnId): truncates the conversation in place on the same SDK session id / protocol URI via the SDK's `resumeSessionAt` option. The protocol turn is resolved to its SDK assistant-envelope uuid and staged as a one-shot anchor the next turn's rebuild applies (lazy — full history is preserved if the user restores then walks away). - Remove-all (no turnId, "Start Over"): tears down the live subprocess, deletes the on-disk transcript, and recreates a fresh provisional under the same id, preserving the model/agent/permissionMode overlay. Teardown awaits the subprocess's actual exit (Query.return() -> the SDK's memoized cleanup -> transport.waitForExit()) before deleting + respawning the same `--session-id`, fixing a "Session ID ... is already in use" race. Also fixes a pre-existing rebind consumer-loop handoff race surfaced by truncation (post-restore turn could hang), and simplifies the pipeline's query handles (single `_query` mirroring the warm subprocess; `_needsRebind` as the sole health signal). Unit + integration green (427 Claude tests); both flows live-E2E verified against real Claude. See node/claude/phase6.7-plan.md for the implementation log and node/claude/roadmap.md (Phase 6.7) marked done. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: address Phase 6.7 PR review - claudeAgentSession: retain the pending truncation anchor until a rebuild succeeds (read it without clearing, clear only after materialize / rebuild installs the pipeline). A throw/cancel after reading no longer silently drops `resumeSessionAt`, so the next send retries the checkpoint restore. + regression test. - claudeSdkPipeline: `_ensureQueryBound` now honors `_needsRebind` (rebuilds via the rematerializer like `send()` does) so pre-flight helpers (reloadPlugins / snapshotResolvedCustomizations) never operate on a dead stream after an abort/crash. - claudeAgent: cold remove-all fails fast when no working directory is available (SDK cwd absent and no live session), mirroring `_resumeSession` and the fork path, instead of recreating a provisional that fails later. - claudeSdkPipeline: pass the error object to the logger in shutdownAndWait instead of stringifying it, preserving stack traces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-vscode · cd6804d7 · 2026-06-26
- 1.4ETVClaude agent host: native (BYOK) transport (Phases 18–19) (#323037) * Claude agent host: native (BYO-Anthropic) transport + transport-branched models (Phases 18–19) - Phase 18: single transport-branched model source. Proxy mode fetches Claude models from CAPI; native mode enumerates via the SDK's query().supportedModels(). - Phase 19: direct (non-proxied) Claude access. claudeUseCopilotProxy: false routes the SDK to Anthropic on the user's own credentials, no proxy in path. - Native auth comes from the subprocess env (ANTHROPIC_API_KEY, or CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`). Fixes the SDK >=0.3 env-replace semantics by spreading process.env in native mode so the token + PATH actually reach the claude subprocess. - IClaudeAgentSdkService keeps its 1:1-with-SDK contract: adds a query() passthrough; enumeration orchestration moved to the caller and closes the throwaway query (leak guard, covered by a new test). - Cleanup: removed the speculative native-binary-path feature; moved ClaudeTransport into claudeProxyService.ts; dropped a trivial model-list helper; tightened buildSubprocessEnv env construction. * Address PR review: explicit enumeration teardown + plan accuracy - _fetchNativeModels: abort the enumeration options' abortController in the finally (alongside query.close()) so teardown is explicit. - buildModelEnumerationOptions doc: the caller, not the SDK service, aborts the controller at teardown. - phase19-plan: reflect the 1:1 query() passthrough (enumeration lifecycle lives in ClaudeAgent, not a supportedModels() service method); fix the ClaudeTransport home (claudeProxyService.ts) and helper signatures.github.com-microsoft-vscode · 8a73e455 · 2026-06-26
- 1.0ETVFunctional & accurate `languageModelToolInvoked` telemetry for Claude & Codex (#323660) * Centralize languageModelToolInvoked telemetry across agent-host providers The `languageModelToolInvoked` event was emitted only by CopilotAgentSession, so Claude and Codex agent-host sessions emitted ~0 per-tool events — the biggest telemetry parity gap for the agent host (no per-tool volume, error rate, or invocation latency for those providers). Lift the emission into the provider-agnostic AgentSideEffects layer (which already processes ChatToolCallComplete for every provider) and add an optional `provider` field so copilot/claude/codex are distinguishable. - New AgentHostToolCallTracker (mirrors AgentHostTurnTracker): stamps the tool start, emits on ChatToolCallComplete, computes invocationTimeMs via StopWatch, with a dedup guard and per-session leak guards. Result/sourceKind derivation is extracted into pure, unit-testable helpers. - AgentHostTelemetryReporter.toolInvoked emits the event with `provider` and a chat-channel->session normalized chatSessionId (matching the value CopilotAgentSession previously emitted). - Remove CopilotAgentSession's duplicate emission and the now-unused ITelemetryService dependency / startTimeMs tracking. - `provider` is optional on the shared event type so the workbench emitter is unaffected. Tests: pure-function unit tests for the derivation helpers plus AgentSideEffects integration tests (success / userCancelled / mcp / client / error, dedup, and the in-flight leak guard). Also verified live in the Agents window for all three providers (provider = claude / copilotcli / codex, toolSourceKind = agentHost). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review feedback - languageModelToolInvoked classification: use `copilotcli` (the real provider string) in the example, not `copilot`. - toolSourceKindFromContributor: add a default branch so an unrecognized contributor kind from a newer protocol version yields a valid telemetry value instead of `undefined`. - AgentSideEffects: resolve the provider from the signal's agent only (always present for an agent-driven tool call); drop the getAgent(sessionKey) fallback that could be passed an AHP chat-channel URI. - CopilotAgentSession: remove a duplicated doc comment on _activeToolCalls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Combine the two ChatToolCallStart guards into one block The tool-call-id->agent registration and the telemetry start-stamp shared the identical `ChatToolCallStart && agent` guard; merge them. The start-stamp now runs just before dispatchServerAction rather than just after — a negligible shift in the invocationTimeMs baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Use high-resolution timing for tool invocationTimeMs Tool calls often complete in under a millisecond, so StopWatch.create(false) (Date.now, 1ms granularity) reported invocationTimeMs=0 for fast tools (e.g. a Codex `echo`, verified live at ~0.55ms). Switch to StopWatch.create(true) (performance.now), matching the workbench languageModelToolInvoked emitter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Report accurate toolSourceKind and userCancelled for Claude/Codex tool telemetry The centralized `languageModelToolInvoked` event now fires for all agent-host providers, but the Claude/Codex mappers left two fields inaccurate: MCP tools reported `toolSourceKind: agentHost` instead of `mcp`, and a tool the user denied at the approval prompt reported `result: error` instead of `userCancelled`. Both are derived from the tool-call signal, so the fix is in the mappers. MCP source kind — stamp a `ToolCallContributorKind.MCP` contributor on the tool-call start so `toolSourceKindFromContributor` resolves `mcp`: - Codex carries a per-session server -> customizationId map on the map state, populated by the agent whenever it applies the MCP inventory (not only at session-customization time, which raced the async inventory discovery), and stamps it on `mcpToolCall` starts. - Claude enriches `ChatToolCallStart` signals for `mcp__` tools using the session's last customizations, keeping the MCP lookup out of the mapper. Cancellation result — a denied tool now carries `error.code` so `deriveToolInvokedResult` maps it to `userCancelled` rather than `error`: - Codex records tool-call ids the host declined and drains the flag once in a shared completion prologue, so every tool type (command, file change, MCP, dynamic, web search) is classified uniformly. - Claude classifies the `is_error` deny message via the new standalone `claudeToolDenial` helper (also breaks an import cycle and is exhaustively unit-tested); a genuine tool error stays `error`. Adds direct mapper tests for the new branches plus negative guards (no contributor without a customization; a non-deny error keeps no code). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Implement refreshChangesetCatalog in FakeChangesetService for main drift main added `refreshChangesetCatalog` to `IAgentHostChangesetService` after this branch forked. CI compiles the PR merged with main, so the test fake must implement the new member — a no-op, matching the sibling lifecycle stubs. Local `tsgo` typecheck-client passed on the branch alone because its own copy of the interface predates the addition; reproduced by materializing the merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>github.com-microsoft-vscode · f18c6b4f · 2026-06-30