feat: Multi-Provider support#2277
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: CodexAdapter hardcoded instanceId check breaks custom instances
- Removed the hardcoded
instanceId === "codex"check and replaced it with an unconditionalinput.modelSelectioncheck, matching the pattern already used in ClaudeAdapter.
- Removed the hardcoded
Or push these changes by commenting:
@cursor push 84b0d74e0a
Preview (84b0d74e0a)
diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts
--- a/apps/server/src/provider/Layers/CodexAdapter.ts
+++ b/apps/server/src/provider/Layers/CodexAdapter.ts
@@ -1368,10 +1368,8 @@
? { resumeCursor: input.resumeCursor }
: {}),
runtimeMode: input.runtimeMode,
- ...(input.modelSelection?.instanceId === "codex"
- ? { model: input.modelSelection.model }
- : {}),
- ...(input.modelSelection?.instanceId === "codex" &&
+ ...(input.modelSelection ? { model: input.modelSelection.model } : {}),
+ ...(input.modelSelection &&
getModelSelectionOptionValue(input.modelSelection, "fastMode") === true
? { serviceTier: "fast" }
: {}),You can send follow-ups to the cloud agent here.
ApprovabilityVerdict: Needs human review Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
8a82b53 to
29261cf
Compare
7a466fe to
fb6a229
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Hardcoded "codex" provider in unknown-driver error path
- Replaced the hardcoded
provider: "codex"withprovider: threadInstanceIdso the error correctly identifies the actual unknown driver slug.
- Replaced the hardcoded
- ✅ Fixed: Instance-level comparison blocks custom instances of same driver
- Added a
resolveDriverKindForInstanceIdhelper that maps instance IDs to their underlying driver kind viaServerSettings.providerInstances, so custom instances resolve to their driver for the narrowing check and the provider-switch guard now compares driver kinds instead of instance IDs.
- Added a
Or push these changes by commenting:
@cursor push b84bfe908a
Preview (b84bfe908a)
diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
--- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
+++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
@@ -170,6 +170,22 @@
),
);
+ const resolveDriverKindForInstanceId = (
+ instanceId: string,
+ ): Effect.Effect<ProviderKind | undefined> => {
+ if (Schema.is(ProviderKind)(instanceId)) {
+ return Effect.succeed(instanceId);
+ }
+ return serverSettingsService.getSettings.pipe(
+ Effect.map((settings) => {
+ const instances = settings.providerInstances as Record<string, { driver?: string }>;
+ const driver = instances[instanceId]?.driver;
+ return driver !== undefined && Schema.is(ProviderKind)(driver) ? driver : undefined;
+ }),
+ Effect.orElseSucceed(() => undefined),
+ );
+ };
+
const threadModelSelections = new Map<string, ModelSelection>();
const appendProviderFailureActivity = (input: {
@@ -277,29 +293,28 @@
? thread.session.providerName
: undefined;
const requestedModelSelection = options?.modelSelection;
- // The thread's model selection is keyed by `instanceId` (an open slug);
- // for built-in drivers the default instance id equals the driver id, so
- // narrow back to the closed `ProviderKind` here. Threads persisted
- // against a fork/unknown driver are surfaced via a structured error
- // rather than silently routed to the wrong provider.
- const threadProviderCandidate: string = currentProvider ?? thread.modelSelection.instanceId;
- if (!Schema.is(ProviderKind)(threadProviderCandidate)) {
+ const threadInstanceId: string = currentProvider ?? thread.modelSelection.instanceId;
+ const resolvedThreadDriver: ProviderKind | undefined =
+ yield* resolveDriverKindForInstanceId(threadInstanceId);
+ if (resolvedThreadDriver === undefined) {
return yield* new ProviderAdapterRequestError({
- provider: "codex",
+ provider: threadInstanceId,
method: "thread.turn.start",
- detail: `Thread '${threadId}' references unknown provider driver '${threadProviderCandidate}'. The driver is not installed in this build (rolled-back / fork mismatch).`,
+ detail: `Thread '${threadId}' references unknown provider driver '${threadInstanceId}'. The driver is not installed in this build (rolled-back / fork mismatch).`,
});
}
- const threadProvider: ProviderKind = threadProviderCandidate;
- if (
- requestedModelSelection !== undefined &&
- requestedModelSelection.instanceId !== threadProvider
- ) {
- return yield* new ProviderAdapterRequestError({
- provider: threadProvider,
- method: "thread.turn.start",
- detail: `Thread '${threadId}' is bound to provider '${threadProvider}' and cannot switch to '${requestedModelSelection.instanceId}'.`,
- });
+ const threadProvider: ProviderKind = resolvedThreadDriver;
+ if (requestedModelSelection !== undefined) {
+ const requestedDriver: ProviderKind | undefined = yield* resolveDriverKindForInstanceId(
+ requestedModelSelection.instanceId,
+ );
+ if (requestedDriver !== threadProvider) {
+ return yield* new ProviderAdapterRequestError({
+ provider: threadProvider,
+ method: "thread.turn.start",
+ detail: `Thread '${threadId}' is bound to provider '${threadProvider}' and cannot switch to '${requestedModelSelection.instanceId}'.`,
+ });
+ }
}
const preferredProvider: ProviderKind = threadProvider;
const desiredModelSelection = requestedModelSelection ?? thread.modelSelection;You can send follow-ups to the cloud agent here.
| options: Schema.optionalKey(ProviderOptionSelections), | ||
| }), | ||
| ]); | ||
| const ModelSelectionPatch = Schema.Struct({ |
There was a problem hiding this comment.
🟡 Medium src/settings.ts:195
ModelSelectionPatch accepts instanceId but callers still sending provider will have that key silently ignored, leaving instanceId undefined. This breaks backward compatibility for any API calls or persisted patches using the old field name. Consider adding a decode transformation that maps provider to instanceId if present.
Also found in 1 other location(s)
apps/server/src/provider/Layers/ClaudeAdapter.ts:557
The condition
input.modelSelection?.instanceId === "claudeAgent"will likely fail for most Claude instances. According toClaudeDrivermetadata,supportsMultipleInstances: true, meaning instances can have arbitrary IDs (e.g., "claude-work", "claude-personal"). The original code checkedprovider === "claudeAgent"which is a constant discriminant on all Claude adapters (as defined inClaudeAdapterShape). After this change, effort and model settings will only be applied when an instance happens to have the exact ID "claudeAgent", causingrawEffortto benullandclaudeModelto beundefinedfor all other Claude instances.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/contracts/src/settings.ts around line 195:
`ModelSelectionPatch` accepts `instanceId` but callers still sending `provider` will have that key silently ignored, leaving `instanceId` undefined. This breaks backward compatibility for any API calls or persisted patches using the old field name. Consider adding a decode transformation that maps `provider` to `instanceId` if present.
Evidence trail:
packages/contracts/src/settings.ts:195-199 (ModelSelectionPatch schema - no `provider` field), packages/contracts/src/orchestration.ts:75-104 (ModelSelection has provider→instanceId transform), packages/contracts/src/orchestration.test.ts:641 (test confirms ModelSelection migration), packages/contracts/src/ws.ts:112 and packages/contracts/src/rpc.ts:163 (ServerSettingsPatch exposed via API)
Also found in 1 other location(s):
- apps/server/src/provider/Layers/ClaudeAdapter.ts:557 -- The condition `input.modelSelection?.instanceId === "claudeAgent"` will likely fail for most Claude instances. According to `ClaudeDriver` metadata, `supportsMultipleInstances: true`, meaning instances can have arbitrary IDs (e.g., "claude-work", "claude-personal"). The original code checked `provider === "claudeAgent"` which is a constant discriminant on all Claude adapters (as defined in `ClaudeAdapterShape`). After this change, effort and model settings will only be applied when an instance happens to have the exact ID "claudeAgent", causing `rawEffort` to be `null` and `claudeModel` to be `undefined` for all other Claude instances.
Introduce two open branded slugs in contracts:
- `ProviderDriverId` — implementation kind (codex, claudeAgent, forks…)
- `ProviderInstanceId` — user-defined routing key. Threads, sessions,
runtime events, and persisted bindings now key by instance id so a
user can run multiple instances of the same driver with independent
config (e.g. `codex_personal` + `codex_work`).
Rewrite `ModelSelection` as `{instanceId, model, options?}`. Legacy
`{provider, model}` wire shapes decode forward via a transform so
persisted thread state, session bindings, and older clients round-trip
without data loss.
Add the instance-config envelope `ProviderInstanceConfig` and
`ServerSettings.providerInstances: Record<InstanceId, Config>` alongside
the legacy per-driver `providers` struct. Both exist side-by-side until
Pass 2 migrates callers.
Scaffold the Pass 2 SPI types (no runtime implementations yet):
- `ProviderDriver<Config, R>` — plain-value driver factory interface
- `ProviderInstance` — the per-instance record bundling snapshot,
adapter, and textGeneration as captured closures
- `ProviderInstanceRegistry` service tag
Migrate ~55 callsites reading `modelSelection.provider` to `.instanceId`
across server, web, and shared. Update test fixtures; fix a latent
runtime bug in `composerDraftStore` where a stale `NormalizedModelSelection`
type claimed a `.provider` field that `createModelSelection` no longer
writes.
All workspaces typecheck; full monorepo tests green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a per-instance card beneath the default driver cards in the Providers settings panel, letting users rename, toggle, edit, and delete configured provider instances. A "+" action in the section header opens the existing `AddProviderInstanceDialog`. Default cards now also show the driver icon, matching the instance cards. Extracts shared presentation helpers into `providerStatus.ts` (status-dot colors, summary headline/detail) and driver metadata into `providerDriverMeta.ts` (icon + editable-field definitions) so the default cards, the add dialog, and the instance cards all use the same keys and visual language. Unknown drivers render a read-only notice so forked configs round-trip safely. Introduces `DraftInput`, a thin wrapper over `Input` that buffers keystrokes in local state and only calls `onCommit` on blur or Enter. Swaps every provider-config input — binary path, server URL/password, CODEX_HOME, Claude launch args, Add-project base directory, display name, and per-driver instance fields — from the previous per-keystroke `updateSettings` call to a commit-on- blur flow. This avoids re-rendering the entire settings panel and firing a server RPC for every character, which made typing in these fields feel laggy whenever the server hydration echoed back. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the per-driver `Provider*Registry`/`Provider*Provider` services
with a central `ProviderInstanceRegistry` driven by the settings-level
`providerInstances` map plus a per-driver catalog. Hydration promotes
the legacy `ServerSettings.providers[kind]` blobs into explicit
`providerInstances[defaultInstanceId]` entries on first write so custom
and default slots share the same storage schema.
On the web side, the settings panel renders every built-in default and
every user-authored custom instance through the same
`ProviderInstanceCard` — the only remaining asymmetries are the delete
button (custom only) and the reset-to-factory button (default only).
Custom instances can carry their own `customModels` list; `modelSelection`
prefers the per-instance blob with a legacy per-kind fallback. Adds an
`Early Access` badge plumbing for drivers that need it (Cursor today)
and a contracts-level `PROVIDER_DISPLAY_NAMES` map so default slot
labels pick up canonical brand casing ("OpenCode", not "Opencode").
Includes supporting server refactors (`TextGenerationLive` routing,
session runtime migration 026, scoped safe teardown, provider event
loggers) and parallel work on the workspace feature.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- add workspace terminal and pane keybinding commands - persist workspace document state in browser storage - tighten provider instance/model selection hydration
Co-authored-by: codex <codex@users.noreply.github.com>
fb6a229 to
0c868cb
Compare
- persist provider_instance_id on thread sessions and projections - route turns by instance-compatible continuation identity - add Codex shadow-home layout for shared auth continuity
- carry continuation group keys through server provider snapshots - respect group affinity when selecting locked models in the UI - update provider instance tests and contract decoding
- Thread per-instance environment state through provider drivers and runtimes - Add Claude HOME handling and cache key isolation - Cover new environment behavior with tests
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Disabled instance check missing in text generation routing
- Added an
instance.enabledcheck inresolveInstanceso disabled provider instances now surface aTextGenerationErrorinstead of silently delegating to the underlying text generation closure.
- Added an
- ✅ Fixed: Empty registry when initial settings read fails
- Changed the fallback from an empty
ProviderInstanceConfigMaptoDEFAULT_SERVER_SETTINGSso that built-in driver default instances are always materialized even when the initial settings read fails.
- Changed the fallback from an empty
Or push these changes by commenting:
@cursor push 1a12ea9124
Preview (1a12ea9124)
diff --git a/apps/server/src/git/Layers/TextGenerationLive.ts b/apps/server/src/git/Layers/TextGenerationLive.ts
--- a/apps/server/src/git/Layers/TextGenerationLive.ts
+++ b/apps/server/src/git/Layers/TextGenerationLive.ts
@@ -51,7 +51,14 @@
registry.getInstance(instanceId).pipe(
Effect.flatMap((instance) =>
instance
- ? Effect.succeed(instance.textGeneration)
+ ? instance.enabled
+ ? Effect.succeed(instance.textGeneration)
+ : Effect.fail(
+ new TextGenerationError({
+ operation,
+ detail: `Provider instance '${instanceId}' is disabled.`,
+ }),
+ )
: Effect.fail(
new TextGenerationError({
operation,
diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
--- a/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
+++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
@@ -42,6 +42,7 @@
* @module provider/Layers/ProviderInstanceRegistryHydration
*/
import {
+ DEFAULT_SERVER_SETTINGS,
defaultInstanceIdForDriver,
type ProviderInstanceConfig,
type ProviderInstanceConfigMap,
@@ -158,13 +159,10 @@
> = Layer.unwrap(
Effect.gen(function* () {
const serverSettings = yield* ServerSettingsService;
- const initialSettings: ServerSettings | undefined = yield* serverSettings.getSettings.pipe(
- Effect.orElseSucceed(() => undefined),
+ const initialSettings: ServerSettings = yield* serverSettings.getSettings.pipe(
+ Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS),
);
- const initialConfigMap =
- initialSettings === undefined
- ? ({} as ProviderInstanceConfigMap)
- : deriveProviderInstanceConfigMap(initialSettings);
+ const initialConfigMap = deriveProviderInstanceConfigMap(initialSettings);
const mutableLayer = ProviderInstanceRegistryMutableLayer({
drivers: BUILT_IN_DRIVERS,You can send follow-ups to the cloud agent here.
- Stop probing `claude auth status` for account metadata - Surface Claude instance status in settings with the provider icon badge
- Thread adapter-bound instance ids through Claude, Codex, and OpenCode - Fix provider registry subscription startup to avoid missed change events - Add coverage for custom instance model and option mapping
- Attribute provider adapter errors to the resolved instance or driver when lookups fail - Add tests covering provider error label fallbacks
- Derive Claude auth and slash commands from initialization results - Keep provider registry tests aligned with the new status flow
- include `enabled` on provider instance snapshots and adapters - update provider tests and Claude status handling for the new field
- Switch provider snapshots and registries to `driverKind` - Update built-in driver wiring and related tests
- Default picker tests to explicit provider instance IDs - Keep sidebar/search assertions aligned with active provider
- Replace stringly provider kinds with typed driver and instance IDs - Update server, web, and contract tests for the new registry model
- Split session recovery, routing, and turn handling into clearer flows - Preserve runtime payload and stale-session cleanup behavior
- Switch provider registry mocks/tests to instance-id keys - Resolve selectable web provider instances only from enabled, available entries
- cover provider instance lookup and routing metadata - make provider instance ordering assertions deterministic
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Metric test asserts branded type against plain string attribute
- Changed the test setup to use ProviderDriverKind.make() for both the initial value and the reassignment, so the recorded attribute uses a branded type consistent with the assertion and real production usage.
Or push these changes by commenting:
@cursor push c9731a0748
Preview (c9731a0748)
diff --git a/apps/server/src/observability/Metrics.test.ts b/apps/server/src/observability/Metrics.test.ts
--- a/apps/server/src/observability/Metrics.test.ts
+++ b/apps/server/src/observability/Metrics.test.ts
@@ -76,10 +76,10 @@
Effect.gen(function* () {
const counter = Metric.counter("with_metrics_lazy_total");
const timer = Metric.timer("with_metrics_lazy_duration");
- let provider = "unknown";
+ let provider: string = ProviderDriverKind.make("unknown");
yield* Effect.sync(() => {
- provider = "codex";
+ provider = ProviderDriverKind.make("codex");
}).pipe(
withMetrics({
counter,You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 01f2b68. Configure here.
- keep cached providers available when refresh or sync errors occur - update provider service to rely on instance enablement - add coverage for refresh recovery and shutdown error handling
- Persist hidden, favorite, and ordered models per provider instance - Reuse shared sorting for picker and settings views - Keep composer and selection state aligned with instance-specific preferences
Co-authored-by: codex <codex@users.noreply.github.com>
- make Claude/Codex home resolution effectful and injectable - tighten shadow-home materialization and provider status caching - update tests for the new path and filesystem behavior
- Harden provider instance migrations for partial upgrades - Update Codex home layout handling and add provider picker UI
- Reject mismatched instance IDs only when a model selection is present - Preserve turns that do not specify a model selection
- Drop the external ui.sh picker script from `apps/web/index.html`
Multi-Provider PR (pingdotgg#2277) rewrote deriveEffectiveComposerModelState to route through resolveAppModelSelection*, which is provider-instance aware and naturally rejects cross-provider carry-overs. Our item 3 guard and its tests are now obsolete; remove the test block and renumber strategy doc items 4-8 accordingly. Also fix the verification command in the strategy doc — plain `bun test` invokes Bun's runner, not vitest, and produces spurious failures. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings the orchestrator/external-session feature in line with upstream main's multi-provider architecture (pingdotgg#2277): - ProviderDriverKind is now a branded type. All `provider: "claudeAgent"` literals updated to `ProviderDriverKind.make(...)`. ModelSelection now uses `instanceId` (ProviderInstanceId) instead of the legacy `provider` field; resume/spawn flows go through `defaultInstanceIdForDriver`. - ClaudeAdapter no longer yields OrchestratorService / ProviderSessionDirectory / ProjectionSnapshotQuery / OrchestrationEngineService directly. Instead a new ClaudeOrchestratorBridge service bundles the four into one tag, kept narrow on ClaudeDriverEnv. The driver yields the bridge and passes it to makeClaudeAdapter via the new `orchestrator` adapter option. - New apps/server/src/orchestrator/ClaudeOrchestratorBridge.ts. Provides a Live layer composed at the server's runtime layer plus a NoOpClaudeOrchestratorBridgeLayer for tests that don't exercise the master/worker path. - server.ts: collapses the per-service `Layer.provideMerge` calls into a single `ProjectAuxiliaryServicesLive` group so the runtime pipe stays under TypeScript's 20-arg overload limit. - Tests: ClaudeAdapter, ProviderRegistry, ProviderInstanceRegistryLive, server tests all updated to mock the new bridge / branded provider.
The multi-provider refactor (pingdotgg#2277) added a `providerInstanceId` routing key as a required field on every ProviderSessionRuntime row. Three of our binding-upsert call sites still passed only the legacy `provider` driver kind, so attempts to spawn a worker, promote a fresh thread, or resume an external CLI session crashed with: Provider validation failed in ProviderSessionDirectory.upsert: providerInstanceId is required for provider session runtime bindings. Wire `defaultInstanceIdForDriver(driverKind)` through: - workerMcpServer.spawn_worker — was failing every time the master tried to spawn a worker; the master typically fell back to Claude Code's built-in Agent tool after seeing the validation error. - OrchestratorService.promote/demote — the fresh-binding branch failed; the existing-binding branch now also forwards the providerInstanceId from the existing row (or backfills via the default if a legacy row didn't have one). - ws.ts externalSessions.bindResume — same fix; was already computing providerInstanceId for the modelSelection but not threading it into the binding upsert.
Registered feature dependency DAG. Updated skills to v0.6.1. Impact assessment for upstream Multi-Provider (pingdotgg#2277). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 upstream commits analyzed including Multi-Provider (pingdotgg#2277). ProviderKind replaced with open ProviderDriverKind. New Driver pattern replaces Service tags. Detailed step-by-step handoff for creating CopilotDriver.ts and re-wiring all features. Skills updated to v0.6.1. Feature deps registered. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete reconciliation against upstream Multi-Provider (pingdotgg#2277): - New CopilotDriver.ts following the Driver pattern (ClaudeDriver template) - CopilotAdapter adapted: makeCopilotAdapter exported, ProviderDriverKind - CopilotProvider adapted: ServerProviderDraft return type - CopilotTextGeneration moved to textGeneration/ directory - CopilotSettings using makeProviderSettingsSchema with form annotations - Registered in BUILT_IN_DRIVERS, providerDriverMeta, session-logic - All model defaults, aliases, display names added - Build script: asarUnpack, npm force-install for cross-platform - SDK v0.3.0 dependency All adapter-internal features preserved (plan-compaction, turn-timing, skill-discovery, command-events, resource-events, skill-controls). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All features reconciled against upstream Multi-Provider (pingdotgg#2277). Cross-commit patches expected — will be scoped per-feature on future individual changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: codex <codex@users.noreply.github.com>


Core foundation for multi-providers with some UX improvements to nicely support multi-account codex or custom claude code instances for example
Note
Medium Risk
Changes the core wiring for all git text-generation calls to resolve by provider instance id, so misconfiguration or missing registry entries could break commit/PR/title generation across providers.
Overview
Switches git text-generation from kind-based routing (
RoutingTextGenerationLive) to instance-based routing via a newTextGenerationLivethat looks upmodelSelection.instanceIdinProviderInstanceRegistryand delegates to that instance’s boundtextGenerationimplementation.Refactors Codex/Claude/Cursor/OpenCode text-generation implementations into per-instance factories (
make*TextGeneration(settings, env)), removes singleton*TextGenerationLivelayers and provider-kind validation checks, and updates tests/integration harnesses to useProviderDriverKind/ProviderInstanceIdplus adapter-registry mocks and no-op provider event loggers. Also extends desktop persisted client settings to includeproviderModelPreferences.Reviewed by Cursor Bugbot for commit 245ec79. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add multi-provider instance support routing all provider interactions by ProviderInstanceId
ProviderKindstring union with open brandedProviderDriverKindandProviderInstanceIdtypes throughout contracts, server, and web, allowing multiple configured instances of the same driver (e.g. two Codex accounts).ProviderDriverSPI with built-in implementations for Codex, Claude, Cursor, and OpenCode; each driver'screate()produces aProviderInstancewith adapter, text-generation, and snapshot closures bound to per-instance config.ProviderInstanceRegistryandProviderInstanceRegistryLiveto manage live instances;ProviderRegistryLiveis refactored to key snapshots and cache files byinstanceIdand react dynamically to instance add/remove/rebuild events.ModelSelectionwire format migrates from{ provider, model }to{ instanceId, model }with a decode-time transform that promotes legacyproviderstrings;OrchestrationSessiongains an optionalproviderInstanceId.AddProviderInstanceDialogandProviderInstanceCardfor creating and editing provider instances;ModelPickerContent,ModelPickerSidebar, andChatComposerare refactored to select and display models per-instance.provider_instance_idcolumns and indexes toprovider_session_runtimeandprojection_thread_sessions.ModelSelectionshape changes fromprovidertoinstanceIdon the wire; any client or stored event that contains the old shape will be migrated at decode time, but callers constructing raw objects must switch toinstanceId.Macroscope summarized 245ec79.