Reviewer tour

Fix secondary Codex reasoning selection.

Codex reasoning options have to follow the configured provider instance, not the driver kind. Otherwise multiple Codex accounts collapse into codex and secondary-account choices can fail to stick.

Why

The old path mixed driver identity with instance identity.

A single Codex provider survives that mistake. Multiple Codex accounts do not.

Failure mode

The composer could display or persist traits through ProviderDriverKind, such as codex, even when the active provider was a distinct ProviderInstanceId, such as codex_work.

A user selecting low on a secondary provider could be bounced back to the default bucket, making the dropdown look like it ignored the selection and risking the wrong turn payload.

reasoning-routing.log
$selectedProvider = codex
$selectedInstanceId = codex_secondary
$reasoningEffort = low
// previous read/write key: codex
// fixed key: codex_secondary
Tour

Instance-keyed reads, writes, and draft state.

The change is deliberately small: pass the selected provider instance through the existing traits path and use it as the model-option key.

01ChatComposer.tsx

Reads options from selectedInstanceId and passes the same id into the traits renderers.

modelOptions: composerModelOptions?.[selectedInstanceId]

renderProviderTraitsPicker({
  provider: selectedProvider,
  instanceId: selectedInstanceId,
  modelOptions: composerModelOptions?.[selectedInstanceId],
})

02composerProviderState.tsx

Threads instanceId through the shared render helper without creating a new rendering path.

type TraitsRenderInput = {
  provider: ProviderDriverKind;
  instanceId?: ProviderInstanceId;
};

<Component
  provider={provider}
  {...(instanceId ? { instanceId } : {})}
/>

03TraitsPicker.tsx

Writes reasoning changes back to the active instance, not the default driver bucket.

setProviderModelOptions(threadTarget, provider, nextOptions, {
  ...(instanceId ? { instanceId } : {}),
  model,
  persistSticky: true,
});

04composerDraftStore.ts

Resolves the option bucket from explicit instance id when present, while preserving the old fallback.

const instanceKey = options?.instanceId ??
  defaultInstanceIdForDriver(normalizedProvider);

05activeProvider

Pins the draft to the explicit instance after a trait change, preventing the dropdown from re-resolving to default Codex.

const nextDraft = {
  ...base,
  ...(options?.instanceId ? { activeProvider: instanceKey } : {}),
  modelSelectionByProvider: nextMap,
};

06composerDraftStore.test.ts

Regression coverage asserts the secondary instance owns the selection, the default bucket stays untouched, and sticky state follows the instance.

codex_secondary → reasoningEffort: low
codex           → unchanged
activeProvider  → codex_secondary
Scale

Works for two Codex providers. Also works for ten.

The code is keyed by arbitrary ProviderInstanceId. There is no special case for the second provider. Additional Codex accounts use the same path.

NCodex instances
1Generic code path
0Default bucket writes
Legacy fallback preserved
Verification

Checks run for the review branch.

Formatting is limited to the touched files. Existing unrelated lint warnings remain outside this change.

  • bun run test composerDraftStore.test.ts from apps/web
  • Changed-file formatting check
  • bun lint, with existing unrelated warnings
  • bun typecheck