agentHost: Claude per-session provider selection - #329331
Merged
Tyler James Leonhardt (TylerLeonhardt) merged 8 commits intoAug 7, 2026
Merged
Conversation
Copilot started reviewing on behalf of
Tyler James Leonhardt (TylerLeonhardt)
August 6, 2026 06:27
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Adds per-session Claude provider selection, routing models through either Anthropic’s native SDK or the Copilot proxy.
Changes:
- Adds provider-qualified model IDs and merged catalogs.
- Supports deferred cross-transport model switching.
- Adds provider grouping and expanded unit/E2E coverage.
Show a summary per file
| File | Description |
|---|---|
agentHostLanguageModelProvider.test.ts |
Tests Claude provider grouping. |
agentHostChatContribution.ts |
Registers Anthropic picker metadata. |
claudeAgentHostE2E.integrationTest.ts |
Allows both Claude model providers. |
claudeModelSelection.test.ts |
Tests codec, merging, and routing. |
claudeAgent.test.ts |
Tests catalogs, authentication, and switching. |
claudeSdkPipeline.ts |
Buffers configuration for transport rebinds. |
claudeSdkOptions.ts |
Decodes qualified SDK model IDs. |
claudeModelSelection.ts |
Implements model qualification and routing. |
claudeAgentSession.ts |
Adds deferred transport switching. |
claudeAgent.ts |
Integrates per-session routing and catalogs. |
claudeProviders.ts |
Defines shared provider tokens. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 4
- Review effort level: Balanced
| // register the matching vendor so the picker labels their group "Anthropic". | ||
| // Copilot-routed Claude models group under the global `copilot` vendor. Dormant | ||
| // while the feature is off — no Claude model carries this provider then. | ||
| vendor: CLAUDE_PROVIDER_ANTHROPIC, |
Comment on lines
+2309
to
+2311
| const isSwitch = | ||
| sess.isPipelineReady && | ||
| claudeTransportForProvider(parseClaudeModelSelection(model).provider) !== sess.transportKind; |
Comment on lines
+649
to
+659
| // Reuse the transport captured at materialize for an ordinary rebuild, | ||
| // so a runtime flip of the host default (config change / Copilot | ||
| // sign-in mutating the agent's live transport mode) never reroutes the | ||
| // live conversation. Only a deliberate per-session provider switch | ||
| // (`_pendingTransportSwitch`) re-resolves from the session's *current* | ||
| // provisional model, rebuilding onto the new transport; a signed-out | ||
| // switch-to-proxy throws here (from `_ensureAuthenticated`), landing in | ||
| // the catch below so the diffs stay dirty and the next send retries. | ||
| // The `_materializedTransport` fallback is defensive — it is always set | ||
| // once `materialize` has run, but re-resolving keeps a rebuild correct | ||
| // rather than crashing if it somehow has not. |
|
|
||
| /** Re-id each model with its provider-qualified selection id and re-stamp its `provider` with the same token, leaving all other fields intact. */ | ||
| function withQualifiedProvider(models: readonly IAgentModelInfo[], provider: string): IAgentModelInfo[] { | ||
| return models.map(model => ({ ...model, id: toClaudeModelSelectionId(provider, model.id), provider })); |
Tyler James Leonhardt (TylerLeonhardt)
requested a balanced review from Copilot
August 7, 2026 01:10
Copilot started reviewing on behalf of
Tyler James Leonhardt (TylerLeonhardt)
August 7, 2026 01:11
View session
Contributor
There was a problem hiding this comment.
Review details
Suppressed comments (3)
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts:322
anthropicis already contributed as a language-model vendor byextensions/copilot/package.json:1748. This add is therefore rejected as a duplicate, but the unconditional disposer later removes that pre-existing vendor (and its provider/model cache). Only include the fallback descriptor when this contribution actually owns the registration.
...(agent.provider === CLAUDE_AGENT_PROVIDER_ID ? [{
src/vs/platform/agentHost/node/claude/claudeAgentSession.ts:678
- This long inline narrative exceeds the repository's one-line limit for comments inside method bodies and obscures the transport choice it explains. Keep the non-obvious pinning rule in one line and let the named fields and guard express the control flow.
// Reuse the transport captured at materialize for an ordinary rebuild,
// so a runtime flip of the host default (config change / Copilot
// sign-in mutating the agent's live transport mode) never reroutes the
// live conversation. A deliberate per-session provider switch instead
// rebuilds onto the transport the agent pushed in through `send`'s
src/vs/platform/agentHost/node/claude/claudeAgentSession.ts:969
- This bypasses the codec's bare/legacy-id semantics:
parseClaudeModelSelectionlabels a bare id as Copilot only as a fallback, whileresolveClaudeSessionTransportintentionally routes it through the host default. For example, with a native host default and a live explicitly-Copilot session, changing to a bare id is treated as same-transport and remains on the proxy instead of switching to native. Resolve the desired transport inClaudeAgentand pass it intosetModel, just as the send path does.
const crossesTransport =
this.isPipelineReady &&
claudeTransportForProvider(parseClaudeModelSelection(model).provider) !== this._transportKind;
- Files reviewed: 11/11 changed files
- Comments generated: 1
- Review effort level: Balanced
Comment on lines
+603
to
+607
| } catch (err) { | ||
| // GitHub sign-in itself succeeded; only the Copilot proxy failed to | ||
| // start. Don't fail sign-in — the merged catalog still serves any native | ||
| // models, and a Copilot-routed model surfaces `AHP_AUTH_REQUIRED` on its | ||
| // first send (which re-drives sign-in, retrying `start()`). Leave |
Tyler James Leonhardt (TylerLeonhardt)
marked this pull request as ready for review
August 7, 2026 07:58
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>
…laude
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>
…d-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>
…n 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>
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>
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>
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>
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>
Tyler James Leonhardt (TylerLeonhardt)
force-pushed
the
claude-per-session-provider
branch
from
August 7, 2026 08:12
40857fa to
3498168
Compare
Tyler James Leonhardt (TylerLeonhardt)
enabled auto-merge (squash)
August 7, 2026 08:12
Justin Chen (justschen)
approved these changes
Aug 7, 2026
Tyler James Leonhardt (TylerLeonhardt)
deleted the
claude-per-session-provider
branch
August 7, 2026 08:29
Tyler James Leonhardt (TylerLeonhardt)
added a commit
that referenced
this pull request
Aug 7, 2026
Since #329331 the Claude model picker shows Copilot-proxy and native-Anthropic models together, each carrying a provider-qualified id, and a session routes on the provider of the model it was started with (resolveClaudeSessionTransport). That makes the host-global override redundant — and, worse, incoherent. Model enumeration (_refreshModels) gates the proxy half on holding a GitHub token and the native half on detectExistingClaudeSetup; neither consulted the setting. getProtectedResources() advertises the Copilot resource required:false unconditionally. So `claudeUseCopilotProxy: false` never stopped Copilot-routed Claude from being offered or used by a signed-in user, which is exactly what its title ("Route Claude Through Copilot") promised. Its only remaining effect was on the model-less fallback, where an explicit `true` broke the one state the signed-out feature exists to serve: opt-in on, signed out, local Anthropic credential present — forced to proxy and dead-ended on AHP_AUTH_REQUIRED. resolveClaudeTransportMode drops its explicitProxy input and reduces to three: opt-in off => proxy; signed in => proxy; else local setup => native; else proxy. Clean deletion, not a deprecation: the key was never forwarded from a VS Code setting (AgentHostRootConfigForwarder never managed it) and was only ever hand-written into agent-host-config.json. A profile carrying a stale value starts with no error and drops the key on the first config write. Also corrects mergeClaudeModelCatalogs' claim that proxy-first ordering makes Copilot the session default. It does not: the picker re-buckets by the _meta vendor token, so the Anthropic group sorts first and the pre-selected model routes native. Verified end-to-end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tyler James Leonhardt (TylerLeonhardt)
added a commit
that referenced
this pull request
Aug 7, 2026
Since #329331 the Claude model picker shows Copilot-proxy and native-Anthropic models together, each carrying a provider-qualified id, and a session routes on the provider of the model it was started with (resolveClaudeSessionTransport). That makes the host-global override redundant — and, worse, incoherent. Model enumeration (_refreshModels) gates the proxy half on holding a GitHub token and the native half on detectExistingClaudeSetup; neither consulted the setting. getProtectedResources() advertises the Copilot resource required:false unconditionally. So `claudeUseCopilotProxy: false` never stopped Copilot-routed Claude from being offered or used by a signed-in user, which is exactly what its title ("Route Claude Through Copilot") promised. Its only remaining effect was on the model-less fallback, where an explicit `true` broke the one state the signed-out feature exists to serve: opt-in on, signed out, local Anthropic credential present — forced to proxy and dead-ended on AHP_AUTH_REQUIRED. resolveClaudeTransportMode drops its explicitProxy input and reduces to three: opt-in off => proxy; signed in => proxy; else local setup => native; else proxy. Clean deletion, not a deprecation: the key was never forwarded from a VS Code setting (AgentHostRootConfigForwarder never managed it) and was only ever hand-written into agent-host-config.json. A profile carrying a stale value starts with no error and drops the key on the first config write. Also corrects mergeClaudeModelCatalogs' claim that proxy-first ordering makes Copilot the session default. It does not: the picker re-buckets by the _meta vendor token, so the Anthropic group sorts first and the pre-selected model routes native. Verified end-to-end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tyler James Leonhardt (TylerLeonhardt)
added a commit
that referenced
this pull request
Aug 7, 2026
Since #329331 the Claude model picker shows Copilot-proxy and native-Anthropic models together, each carrying a provider-qualified id, and a session routes on the provider of the model it was started with (resolveClaudeSessionTransport). That makes the host-global override redundant — and, worse, incoherent. Model enumeration (_refreshModels) gates the proxy half on holding a GitHub token and the native half on detectExistingClaudeSetup; neither consulted the setting. getProtectedResources() advertises the Copilot resource required:false unconditionally. So `claudeUseCopilotProxy: false` never stopped Copilot-routed Claude from being offered or used by a signed-in user, which is exactly what its title ("Route Claude Through Copilot") promised. Its only remaining effect was on the model-less fallback, where an explicit `true` broke the one state the signed-out feature exists to serve: opt-in on, signed out, local Anthropic credential present — forced to proxy and dead-ended on AHP_AUTH_REQUIRED. resolveClaudeTransportMode drops its explicitProxy input and reduces to three: opt-in off => proxy; signed in => proxy; else local setup => native; else proxy. Clean deletion, not a deprecation: the key was never forwarded from a VS Code setting (AgentHostRootConfigForwarder never managed it) and was only ever hand-written into agent-host-config.json. A profile carrying a stale value starts with no error and drops the key on the first config write. Also corrects mergeClaudeModelCatalogs' claim that proxy-first ordering makes Copilot the session default. It does not: the picker re-buckets by the _meta vendor token, so the Anthropic group sorts first and the pre-selected model routes native. Verified end-to-end. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Tyler James Leonhardt (TylerLeonhardt)
added a commit
that referenced
this pull request
Aug 8, 2026
…sts (#329718) * agentHost: require GitHub for Claude unless a BYO-Anthropic setup exists Since #329331 ClaudeAgent advertised the GitHub Copilot protected resource as `required: false` unconditionally, on the reasoning that per-session routing means no host-global mode can make Copilot strictly required. But `required` is not a question about routing -- it is read only by protectedResourcesRequireGitHubCopilotSignIn, which backs the Agents window gate and the per-session-type gate. Answering it "optional" for a user with no Anthropic credential claimed Claude was usable without GitHub when every model the merged catalog can offer for them is Copilot-routed. Restore the pre-#329331 condition: the resource is optional only when a local BYOK setup is discovered. New private _hasUsableNativeSetup() is the single "can Claude run without GitHub right now?" fact -- the allowSignedOutWhenUsable opt-in AND detectExistingClaudeSetup (ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN in the process env or the `env` block of ~/.claude/settings.json). _defaultTransportMode() now feeds hasExistingSetup from the same helper, so the advertised requirement and the fallback transport cannot disagree. Two deliberate choices. The resource is still *kept* in the list rather than dropped, so the silent probe survives: authenticateProtectedResources matches on `resource` and ignores `required`, so an already-signed-in user still has a token forwarded and authenticate() still acquires the proxy handle Copilot-routed models need. And sign-in state is deliberately not an input -- whether GitHub is *required* is a property of what the user can run without it, not of whether they happen to be connected at this instant. Making this derived rather than constant introduces a staleness risk, so it was verified live: AgentSideEffects already republishes agent infos on RootConfigChanged, and _publishAgentInfos' `equals` guard limits the dispatch to real changes. Verified end-to-end against a signed-out Agents window (--use-mock-keychain), reading the published protectedResources off the AHP JSONL log: - no credential, opt-in on => required=true, "Sign in to use Agents" gate - ANTHROPIC_API_KEY, opt-in on => required=false, window opens signed out with Claude selected and the discovered-config nudge - ANTHROPIC_API_KEY, opt-in off => required=true, gate (kill switch holds) The third run also exercised the republish path: the host started with a stale `true` in its agent-host-config.json, published required=false, then flipped to required=true 93ms after the client pushed the setting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: close the Copilot-optional test hole; trim comments Review feedback on #329718. The protected-resource tests left the opt-in at its default `false` for the no-credential context, so the matrix never covered opt-in ON + no credential -- the exact regression this PR fixes. An implementation deciding `required` purely from the opt-in passed every assertion. Replaced with the full 2x2; verified by mutation (stubbing _hasUsableNativeSetup to read only the opt-in now fails on `optInOnNoCredential`, and passed before). Also trimmed the comments on _hasUsableNativeSetup and getProtectedResources to the non-obvious contract. The JSDoc had claimed a credential appearing "takes effect without restarting the host", which is wrong: nothing watches ~/.claude/settings.json at agent level (the customization watcher is per-session) and AgentModelRefreshScheduler only ticks providers that have started a turn. Claim removed rather than kept; the underlying staleness predates this change via the models==0 path and is tracked separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
Closes #329656
What
Lets each Claude agent-host session pick a model whose provider decides its transport, instead of the whole host being pinned to one transport:
anthropic-provider models route to the native Anthropic SDK.copilot(and any unknown) provider routes to the Copilot-CAPI proxy.This mirrors how the Codex provider already behaves, and is always-on (no experimental flag).
How
claudeModelSelection.ts) encodes/decodes@provider=anthropic:…/@provider=copilot:…. Bare/legacy ids (no explicit provider) resume on the host default transport, so already-persisted sessions need no migration.provider: 'anthropic' | 'copilot'. The picker groups the rows by vendor ("Anthropic" / "Copilot").resolveClaudeSessionTransport({ model, defaultMode })maps the selected model's provider to a transport at materialize time.Notes / intended behavioral consequences
agent-host-claude).required: false; sign-in defers to first send of a Copilot-routed model (matches Codex).Testing
claudeModelSelection.test.ts,claudeAgent.test.ts(codec, merged-catalog refresh, per-session transport resolution, live cross-transport switch, signed-out native bootstrap).test/node/e2e/) withmodelProviders: ['copilot', 'anthropic'].npm run typecheck-client.🤖 Generated with Claude Code