Skip to content

feat(desktop): give sandboxes the gateway's whole model list - #253

Merged
quickbeard merged 1 commit into
mainfrom
feat/sandbox-model-list
Aug 17, 2026
Merged

quickbeard merged 1 commit into
mainfrom
feat/sandbox-model-list

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Follow-up to #238. A sandbox guest's provider block was built from the auth file alone, and that file records only the one model chosen at login — so Work offered a single model while Code offered the whole catalogue, since the SSO flow passes fetchModels' result into the same buildGatewayConfig for the local server.

resolveSandboxEgress now asks the gateway for the list at sandbox start and passes it through, so both sides come from the same source.

Details

  • Same network path as everything else. The lookup goes through Chromium's stack, so enterprise proxies, PAC files, and trust roots apply — the reason that wrapper existed for the broker in the first place. It moves out of the returned egress object to module scope now that the broker isn't its only caller.
  • Cached, keyed by the credentials themselves (gateway-models.ts). Sandboxes start together at launch and would otherwise each ask; a machine that can't reach the gateway would pay the 10s fetch timeout once per sandbox. A rotated token or a different gateway misses the cache by construction, so a stale catalogue can't outlive the login it came from.
  • Failure degrades, never blocks. A failed lookup resolves to no models instead of rejecting — the guest falls back to the recorded model, exactly what it had before this existed — and drops the cache entry so the next start retries.
  • The failure is logged. fetchModels throws rather than returning an empty list, so an empty result is the failure signal. Without a line in the log, a short picker looks identical to a gateway that genuinely offers one model, which is the first question when Work looks thinner than Code.

Notes

  • Everything here is fork-only (packages/desktop/src/main/sandbox/), so DIVERGENCES.md needs no new row. The package README's egress.ts bullet gains the model catalogue.
  • Worst case on the start path is one gateway round trip, bounded by fetchModels' existing 10s timeout, once per credential set per app run.

Testing

  • packages/desktop: 108 tests pass, bun typecheck clean. New gateway-models.test.ts covers the three behaviours worth pinning: concurrent starts share one lookup, new credentials miss the cache, a failure yields no models and is retried rather than cached.
  • Driven in the real desktop app against the live test1 sandbox: the guest config is now built from a live lookup — (sandbox) › resolved the gateway model list { models: 1 }, ~300ms, sandbox ready 1.6s later. This account's gateway exposes exactly one model (MiniMax/MiniMax-M3), confirmed by hitting /v1/models directly, and the local server's codev.jsonc lists the same one — so Work and Code now match, which is the point. An account with a wider key is where the difference shows.

🤖 Generated with Claude Code

A sandbox guest's provider block was built from the auth file alone, which
records only the one model chosen at login, so Work offered a single model
while Code offered the whole catalogue -- the SSO flow passes fetchModels'
result into the same buildGatewayConfig for the local server.

resolveSandboxEgress now asks the gateway for the list at start and passes
it through, so both sides come from the same source. The lookup goes
through Chromium's stack like every other host-side call made on a guest's
behalf, so enterprise proxies, PAC files and trust roots apply to it too;
that wrapper moves out of the returned egress object to module scope now
that the broker is not its only caller.

The answer is cached, keyed by the credentials themselves: sandboxes start
together at launch and would otherwise each ask, and a machine that cannot
reach the gateway would pay the fetch timeout once per sandbox. A rotated
token or a different gateway misses the cache by construction, so a stale
catalogue cannot outlive the login it came from.

A failed lookup resolves to no models rather than rejecting -- the guest
falls back to the recorded model, which is exactly what it had before --
and drops the cache entry so the next start retries. Since fetchModels
throws rather than returning an empty list, an empty result is exactly the
failure signal, and it is logged: a short picker otherwise looks the same
as a gateway that really does offer one model.

Verified in the app against the real sandbox: the guest config is built
from a live lookup ("resolved the gateway model list { models: 1 }"), and
this account's gateway does expose exactly one model, matching the local
server's config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@quickbeard
quickbeard force-pushed the feat/sandbox-model-list branch from 32c9eef to 8818fe5 Compare August 17, 2026 06:54
@quickbeard
quickbeard merged commit de914b0 into main Aug 17, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the feat/sandbox-model-list branch August 17, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant