Skip to content

feat: Multi-Provider support#2277

Merged
juliusmarminge merged 34 commits into
mainfrom
t3code/provider-instance-registry
Apr 29, 2026
Merged

feat: Multi-Provider support#2277
juliusmarminge merged 34 commits into
mainfrom
t3code/provider-instance-registry

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Apr 21, 2026

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 new TextGenerationLive that looks up modelSelection.instanceId in ProviderInstanceRegistry and delegates to that instance’s bound textGeneration implementation.

Refactors Codex/Claude/Cursor/OpenCode text-generation implementations into per-instance factories (make*TextGeneration(settings, env)), removes singleton *TextGenerationLive layers and provider-kind validation checks, and updates tests/integration harnesses to use ProviderDriverKind/ProviderInstanceId plus adapter-registry mocks and no-op provider event loggers. Also extends desktop persisted client settings to include providerModelPreferences.

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

  • Replaces the closed ProviderKind string union with open branded ProviderDriverKind and ProviderInstanceId types throughout contracts, server, and web, allowing multiple configured instances of the same driver (e.g. two Codex accounts).
  • Introduces ProviderDriver SPI with built-in implementations for Codex, Claude, Cursor, and OpenCode; each driver's create() produces a ProviderInstance with adapter, text-generation, and snapshot closures bound to per-instance config.
  • Adds ProviderInstanceRegistry and ProviderInstanceRegistryLive to manage live instances; ProviderRegistryLive is refactored to key snapshots and cache files by instanceId and react dynamically to instance add/remove/rebuild events.
  • ModelSelection wire format migrates from { provider, model } to { instanceId, model } with a decode-time transform that promotes legacy provider strings; OrchestrationSession gains an optional providerInstanceId.
  • Web settings UI gains AddProviderInstanceDialog and ProviderInstanceCard for creating and editing provider instances; ModelPickerContent, ModelPickerSidebar, and ChatComposer are refactored to select and display models per-instance.
  • Sensitive provider instance environment variables are stored in a secret store and redacted before returning settings to clients.
  • Adds DB migrations 27 and 28 adding nullable provider_instance_id columns and indexes to provider_session_runtime and projection_thread_sessions.
  • Risk: ModelSelection shape changes from provider to instanceId on 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 to instanceId.

Macroscope summarized 245ec79.


Open in Devin Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d4436d2e-a060-4b92-a429-f1085c437e1b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/provider-instance-registry

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Apr 21, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 unconditional input.modelSelection check, matching the pattern already used in ClaudeAdapter.

Create PR

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.

Comment thread apps/server/src/provider/Layers/CodexAdapter.ts
Comment thread apps/server/src/provider/Layers/ProviderEventLoggers.ts
Comment thread apps/web/src/workspace/store.ts Outdated
Comment thread apps/web/src/hooks/useWorkspaceCommandExecutor.ts Outdated
Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts Outdated
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 21, 2026

Approvability

Verdict: 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.

@juliusmarminge juliusmarminge force-pushed the t3code/provider-array-refactor branch from 8a82b53 to 29261cf Compare April 21, 2026 23:41
Base automatically changed from t3code/provider-array-refactor to main April 23, 2026 13:35
Comment thread apps/web/src/modelSelection.ts Outdated
Comment thread apps/server/src/git/Layers/TextGenerationLive.ts
Comment thread apps/web/src/composerDraftStore.ts Outdated
@juliusmarminge juliusmarminge force-pushed the t3code/provider-instance-registry branch from 7a466fe to fb6a229 Compare April 23, 2026 15:51
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" with provider: threadInstanceId so the error correctly identifies the actual unknown driver slug.
  • ✅ Fixed: Instance-level comparison blocks custom instances of same driver
    • Added a resolveDriverKindForInstanceId helper that maps instance IDs to their underlying driver kind via ServerSettings.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.

Create PR

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.

Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts Outdated
options: Schema.optionalKey(ProviderOptionSelections),
}),
]);
const ModelSelectionPatch = Schema.Struct({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 === &#34;claudeAgent&#34; 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 === &#34;claudeAgent&#34; 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.

🚀 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.

Comment thread apps/web/src/components/settings/AddProviderInstanceDialog.tsx
Comment thread apps/web/src/workspace/store.ts Outdated
juliusmarminge and others added 5 commits April 27, 2026 11:22
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>
@juliusmarminge juliusmarminge force-pushed the t3code/provider-instance-registry branch from fb6a229 to 0c868cb Compare April 27, 2026 18:26
Comment thread apps/web/src/components/settings/ProviderInstanceCard.tsx
- 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
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.enabled check in resolveInstance so disabled provider instances now surface a TextGenerationError instead of silently delegating to the underlying text generation closure.
  • ✅ Fixed: Empty registry when initial settings read fails
    • Changed the fallback from an empty ProviderInstanceConfigMap to DEFAULT_SERVER_SETTINGS so that built-in driver default instances are always materialized even when the initial settings read fails.

Create PR

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.

Comment thread apps/server/src/git/Layers/TextGenerationLive.ts
Comment thread apps/server/src/provider/Layers/CursorProvider.ts
Comment thread apps/web/src/components/settings/ProviderInstanceCard.tsx Outdated
Comment thread apps/web/src/components/settings/AddProviderInstanceDialog.tsx
Comment thread apps/server/src/serverSettings.ts
Comment thread apps/web/src/components/settings/SettingsPanels.tsx Outdated
- 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
Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
- 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
Comment thread packages/contracts/src/providerRuntime.ts Outdated
- 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
@juliusmarminge juliusmarminge changed the title Introduce provider instance registry and unify settings parity feat: Multi-Provider support Apr 29, 2026
Comment thread apps/server/src/provider/Layers/ProviderRegistry.ts Outdated
Comment thread apps/server/src/provider/Layers/ProviderRegistry.ts
- 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
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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.

Create PR

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.

Comment thread apps/server/src/observability/Metrics.test.ts Outdated
juliusmarminge and others added 4 commits April 29, 2026 10:57
- 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
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
- Harden provider instance migrations for partial upgrades
- Update Codex home layout handling and add provider picker UI
Comment thread apps/web/index.html Outdated
- 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`
@juliusmarminge juliusmarminge merged commit 08e6d4c into main Apr 29, 2026
12 checks passed
@juliusmarminge juliusmarminge deleted the t3code/provider-instance-registry branch April 29, 2026 23:13
slan48 pushed a commit to slan48/t3code that referenced this pull request Apr 30, 2026
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>
0xpaperhead added a commit to 0xpaperhead/t3code that referenced this pull request May 1, 2026
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.
0xpaperhead added a commit to 0xpaperhead/t3code that referenced this pull request May 1, 2026
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.
unwarlikepoodle pushed a commit to tesseracode/t3code that referenced this pull request May 2, 2026
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>
unwarlikepoodle pushed a commit to tesseracode/t3code that referenced this pull request May 3, 2026
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>
unwarlikepoodle pushed a commit to tesseracode/t3code that referenced this pull request May 3, 2026
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>
unwarlikepoodle pushed a commit to tesseracode/t3code that referenced this pull request May 3, 2026
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>
Morphexe pushed a commit to Morphexe/t3code that referenced this pull request May 4, 2026
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant