Skip to content

feat(fx): choose a backend per thread - #29388

Open
jakeleventhal wants to merge 5 commits into
pingdotgg:mainfrom
jakeleventhal:fx-backend-picker
Open

jakeleventhal wants to merge 5 commits into
pingdotgg:mainfrom
jakeleventhal:fx-backend-picker

Conversation

@jakeleventhal

@jakeleventhal jakeleventhal commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Replacement for #26994, preserving its full discussion and evidence links. GitHub rejected reopening the original through GraphQL and REST (state cannot be changed. The fx-backend-picker branch was force-pushed or recreated.); restoring the closing head also did not allow reopening. The validated rebased head remains on the original fork branch.

fx can run on Vercel AI Gateway, a Codex or Grok subscription, or a configured OpenAI-compatible endpoint, but T3 Code only offers the machine's active backend. This adds per-thread backend selection and restores Default as a valid backend/model pair, using the thread's folder configuration.

Latest review follow-up

31616ad restores the original backend/model pair when selection fails after changing backends. Restoring the backend can itself select a saved model, so rollback also restores the original model when needed. Missing-Default and rejected-model errors retain their original failure. If fx rejects rollback, that error is logged and selection still fails before a turn starts.

The two state-based regressions failed before the fix with the session left on Gateway, then passed with the original Codex custom-model pair restored. All eight focused cases and vp run check:changed HEAD --concurrency 2 passed: nine adjacent tests, 10 affected-package typechecks, formatting, lint, both Knip passes and GraphQL checks. The loaded-session protocol case now expects the rollback provider request. git diff --check passed. Rollback validation receipt.

The preceding 0e58bfc head passed CI, but rejecting selection alone did not restore the native session pair; this follow-up supersedes that limitation. 31616ad awaits fresh CI and bot review. Maintainer approval of Default’s folder-aware/backend-switching behavior and the missing fresh before/native UI evidence remain pending. No real fx or integrated UI run was performed for this follow-up.

Scope and reconsideration

Reconsidering the closure explanation and closed event 34691805908. Upstream still exposes only the machine-default fx backend; per-backend catalogs, grouping, switching, and folder-aware Default remain absent.

Backend selection and resetting Default share a concrete invariant: restore a backend and a model that belong together. After a thread switches from Gateway to Codex, selecting Default must restore the default backend before its model; writing the Gateway model on Codex can fail or leave the previous model running. The adapter replay test covers both switching backends in one session and restoring a loaded session to the default backend, with the provider write before the model write.

Folder-aware lookup is independently separable. It is retained here deliberately because the same Default resolution path returns that backend/model pair in the thread's folder; it is not a required technical dependency of backend grouping. This is a request to reconsider the combined scope, not a claim that maintainers approved it. The closure's missing before/native visual evidence remains a limitation, explicitly recorded below.

The rebase preserves upstream's compact picker, trailing Show more/less control, effort column and IME handling (#28057/#28076/#28409), mobile policy and reduced-motion behavior, and the longer fx version-probe timeout (#28400). It also retains the final auth fallback correction: unknown Gateway sign-in cannot hide a signed-in Codex backend.

Fix

  • Probing: the fx snapshot runs fx status --json (sign-in) and fx models --json (catalog) once per backend with FX_PROVIDER. Custom endpoints come from the providers keys in ~/.fx/settings.json. Nothing opens an ACP session, which fx would save. Models are listed as <backend>:<model> (fx backend ids never contain :).
  • Contracts: a new optional ServerProvider.subProviders (name plus optional unavailableReason) tells clients how to group a provider's models by subProvider. Only fx sets it, so other providers are unchanged.
  • Web picker: the fx tab shows Default, then one collapsible heading per backend. The thread's backend opens by default. A signed-out backend is a disabled heading with the fx login <backend> hint. Search and Favorites stay flat, retaining upstream's compact rows and inline backend labels.
  • Mobile thread settings: the same grouping, reusing the collapsible provider header (nested, no icon). The grouping helper in client-runtime is shared with web.
  • Per thread, not per machine: the adapter writes fx's session-only provider option before model, so each thread keeps its own backend and fx's global default is never changed. Logins stay machine-wide, which is why signed-out backends show as unavailable everywhere. A model id with no backend prefix (existing threads) still runs on the session's current backend.
  • Default: it now resolves with fx status --json run in the thread's folder, so a per-project model in fx's settings is used there. On a session that is on another backend, Default now moves it to the default backend rather than staying put.
  • Docs: docs/user/providers-fx.md is updated.

Prior visual evidence (2026-10-29)

These are historical captures of the original implementation, before upstream's later compact picker changes; they are not refreshed screenshots of this rebased head. Web, against a scripted fake fx (Codex and Gateway signed in, Grok signed out).

Before: the fx tab was a flat list of Default plus the models of the machine's active backend, with no way to switch backends.

Picker open, Grok signed out:

Switching backend: open Vercel AI Gateway from a thread on Codex:

The same thread ran one turn on Codex and the next on the gateway (the fake echoes the backend and model the session ran on):

Search across backends:

There is no before screenshot or interaction recording in the prior evidence. Mobile has no device capture: the remote iOS simulators were either owned by other sessions or stuck on the dev client's "Open in" prompt. The mobile grouping is covered by a unit test and typecheck only.

Prior validation

Original implementation attribution: Claude Opus 5.5 in Claude Code. This revival and rebase used GPT-6.1 Sol through Codex.

  • Original final head: 5a4461a033b56a3b866e6a6d9bfb14e593ad3e6a. CI run 40894823238 completed successfully on that head; this is prior CI, not a result for the rebased head.
  • The original implementation reported real fx 0.0.15 probes in a throwaway HOME for FX_PROVIDER/FX_MODEL, a custom endpoint, and a per-folder model. Those probes were not rerun during revival.
  • The original local check:changed report noted unrelated macOS /var versus /private/var failures in ServerEnvironment.test.ts and build-npm-launcher.test.ts. Current results are listed separately below.

Current validation

  • Rebased onto upstream ac8dce0cc3abd8a337f2d806b5082a6834b9b81a.
  • vp test run packages/provider-fx/src/server/status.test.ts apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts apps/mobile/src/lib/modelOptions.test.ts: 43 tests passed across three files. Covers per-backend catalogs/auth, unreadable-sign-in fallback, folder-aware defaults, provider-before-model writes, same-session backend switching, loaded-session Default reset, and grouping with a signed-out backend. The upstream six-second/hung-version probe cases are preserved using TestClock.
  • vp run check:changed upstream/main --concurrency 2: passed. Format, lint, unused patches, both knip passes, GraphQL generation, 39 selected package typechecks, 54 tests in seven adjacent files, and nine selected React Compiler cases for four changed client files passed. CI still owns the full suites, builds, Rust, and release smoke.
  • git diff --check: passed.
  • No browser, desktop, native-device, or real-provider turn was run during revival. No current visual/CLI runtime validation is claimed. The shared web picker covers web and desktop; mobile uses the same grouping helper and separate native rendering. The optional contract carries backend metadata across local, remote, and tunnel connections; other adapters do not set it.

Follow-up a4dbade6c95f913fad03c45c281672dbbe2ae453 fixes Default targeting an unavailable backend. Default retains the current backend/model pair when its target backend is neither current nor selectable. The existing adapter regression now covers selectable, unlisted and missing backend selectors; it failed before the fix and passes afterward. vp run check:changed HEAD --concurrency 2 passed before committing, including both Knip passes, GraphQL checks, 10 affected-package typechecks and 7 adjacent tests. CI and bot reviews are rerunning for this follow-up.

Follow-up 0e58bfcad4095b955759df19d70d19e89ae18d63 addresses a partial Default backend/model switch: if the backend is selectable but cannot provide the requested Default model, selection fails before a turn can run. An unselectable backend still retains the previous pair. The expanded adapter case failed before the fix and now covers successful restoration and unavailable-model rejection. All five fx adapter cases and vp run check:changed HEAD --concurrency 2 passed before committing (format, lint, both Knip passes, GraphQL checks, 10 affected-package typechecks and 7 adjacent tests). That head passed CI; the rollback correction and its current validation above supersede this earlier status.

GPT-6.1 Sol through Codex.

jakeleventhal and others added 2 commits November 2, 2026 09:58
fx can run on Vercel AI Gateway, a Codex or Grok subscription, or a
configured OpenAI-compatible endpoint, but T3 Code only offered the
backend `fx provider` had set, so switching meant a terminal.

The fx snapshot now probes every backend with `FX_PROVIDER` (`fx status`
for sign-in, `fx models` for its catalog) and lists models as
`<backend>:<model>`. A new `subProviders` field on the provider snapshot
lets the web picker and the mobile thread settings group them under
collapsible backend headings; signed-out backends stay listed as
unavailable with the `fx login` command to run. The adapter writes fx's
per-session `provider` option before `model`, so each thread keeps its
own backend without touching fx's global default.

"Default" now resolves with `fx status` in the thread's folder, so a model
fx's settings give one project is picked up there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Nov 2, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The PR adds per-thread fx backend selection and grouped model pickers. It also changes Default to honor project-folder backend/model settings, which needs human confirmation because existing Default selections may run on a different backend.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 0e58bfc and 31616ad.

Files selected for processing: 3
  • apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts
  • packages/provider-fx/src/server/acpSupport.test.ts
  • packages/provider-fx/src/server/acpSupport.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough

The change adds sub-provider metadata, probes fx backends for authentication and models, applies backend-qualified model choices, and adds collapsible backend sections to the web and mobile model pickers.

Changes: fx Backend Model Flow

Layer / File(s) Summary
Sub-provider data and grouping
packages/contracts/src/server.ts, packages/client-runtime/src/providerSubProviders.ts, packages/client-runtime/package.json, apps/mobile/src/lib/modelOptions.ts, apps/mobile/src/lib/modelOptions.test.ts
Provider contracts and model options now carry sub-provider descriptors. The shared grouping helper retains descriptor order and empty groups. The mobile test covers grouped, ungrouped, and empty-group cases.
Backend discovery and status
packages/provider-fx/src/server/status.ts, packages/provider-fx/src/server/status.test.ts
The status check discovers configured backends and probes authentication and model lists per backend. The resulting snapshot includes backend-specific models and availability details. Tests cover probe results, status cases, and default choices read for a working directory.
Backend-qualified model selection
packages/provider-fx/src/server/acpSupport.ts, packages/provider-fx/src/server/adapter.ts, packages/provider-fx/src/server/driver.ts, packages/provider-fx/src/server/acpSupport.test.ts, apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts
Model choices can specify a backend and model. The adapter resolves default choices using each runtime’s working directory and applies backend changes before model changes. Tests cover backend changes and restoration after a failed default selection.
Backend sections in model pickers
apps/web/src/components/chat/ModelPickerContent.tsx, apps/web/src/components/chat/modelPickerKeys.ts, apps/mobile/src/features/threads/ThreadSettingsSheet.tsx, docs/user/providers-fx.md
The web and mobile pickers display provider models in backend sections with availability details and expansion controls. Search and Favorites remain flat. The fx documentation describes backend selection and default behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Picker as Model picker
  participant Adapter as FxAdapterV2
  participant Choice as readFxDefaultChoice
  participant CLI as fx CLI
  participant Session as ACP session config
  Picker->>Adapter: Send backend-qualified model selection
  Adapter->>Choice: Resolve default choice for runtime cwd
  Choice->>CLI: Run fx status --json in runtime cwd
  CLI-->>Choice: Return model and model_source
  Choice-->>Adapter: Return backend and model
  Adapter->>Session: Set provider before model when backend changes
Loading

Suggested reviewers: agadzik, juliusmarminge

Merge Risk: Minimal · up to 31616

No unresolved issue is established for the changed Default-selection behavior; the PR is mergeable after normal checks.

Pre-merge checks: Passed 3 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Description check Inconclusive The description follows the template's sections (Problem, Change, Scope and approval, Verification) with substantial content. It explains the problem (T3 Code only offers the machine's active fx backe… Add a concise Problem statement with expected behavior and reproduction steps. In Scope and approval, link the triaged issue or the maintainer discussion that approved this scope, including the approval comment. The Default folder-aware and…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title "feat(fx): choose a backend per thread" matches the main change: per-thread fx backend selection, with grouping of models by backend and a folder-aware Default. It is concise, follows the co…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "feat(fx): choose a backend per thread" matches the main change: per-thread fx backend selection, with grouping of models by backend and a folder-aware Default. It is concise, follows the conventional commit style used in the template, and names the primary feature.

Full details: Description check

Explanation

The description follows the template's sections (Problem, Change, Scope and approval, Verification) with substantial content. It explains the problem (T3 Code only offers the machine's active fx backend) and the change (per-thread backend selection, grouping in web and mobile, folder-aware Default, docs). Verification lists focused tests, the check run, and states what was not checked. Gaps remain: the Scope and approval section cites a closed PR and asks for reconsideration instead of linking a triaged issue or maintainer approval, and the template's requirement for before/after UI screenshots is only partly met by historical captures that predate the rebased head. The description is detailed but long, and its Problem section is not a clear one- or two-sentence statement with reproduction steps.

Resolution

Add a concise Problem statement with expected behavior and reproduction steps. In Scope and approval, link the triaged issue or the maintainer discussion that approved this scope, including the approval comment. The Default folder-aware and backend-switching behavior needs explicit maintainer approval, or a justification under the template's exemption rules. Provide before/after UI screenshots or a recording for the current rebased head, or state clearly that they are pending. Trim the Verification and history sections so the current validation is easy to find.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai coderabbitai Bot left a comment •

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.

  • Fix comments: Fix CodeRabbit comments on this PR

@coderabbitai coderabbitai Bot left a comment •

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.

  • Fix comments: Fix CodeRabbit comments on this PR

@maria-rcks

maria-rcks commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified head 31616ad4bdb1f6459c4eaf386534648689449dc1 on linux with repository playwright and /usr/bin/google-chrome. no source changes were needed.

  • reproduced on github/main (9bcf75335f9b): fx only offered default and the active backend model. the second configured backend could not be selected.
  • created the project and threads through the app. real codex gpt-6.1-sol turns completed on main and the pr. real fx 0.0.16 turns used two configured openai-compatible backends, proxy and alternate, pointing at the live local provider proxy. no mocked provider or seeded conversation data.
  • switched the same saved fx session proxy → alternate → proxy → alternate, completing a real turn after each switch. default restored proxy. a second thread verified folder-aware default: explicit proxy → default restored the folder's alternate backend, while the global default remained proxy. saved fx session preferences confirm the backend/model pairs independently of reply text.
  • search returned both backend model choices in a flat list. signed-out gateway, codex subscription and grok subscription headings were disabled and showed login hints. existing unqualified fx selections and the separate codex thread continued to run.
  • blacksmith: all 46 focused existing tests passed. vp run check:changed ac8dce0cc3abd8a337f2d806b5082a6834b9b81a --concurrency 2 passed, including 39 package typechecks, 56 adjacent tests and 9 selected react compiler checks. github ci has no failing checks at this head.

command transcript and saved-session transitions

before, same thread and model:

main fx picker only offers the active backend

pr, matching picker state:

pr fx picker adds backend headings and signed-out hints

folder-aware default selection and a completed real turn. the session log in the transcript confirms proxy → alternate:

selecting folder-aware default and completing a real fx turn

Not verified

native ios/android rendering, electron shell behavior, macos/windows, and relay/tunnel connections were not exercised on this linux web run. fx built-in subscription and gateway turns require credentials unavailable here; real backend-switching checks used configured endpoints with the same gpt-6.1-sol model. rejected-selection rollback was covered by focused existing tests, but was not induced against real fx. no performance claim is made.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 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.

2 participants