Skip to content

Add provider model options to the model picker, starting with Codex Daybreak - #5212

Open
iipanda wants to merge 3 commits into
get-bb:mainfrom
iipanda:codex-daybreak-model-options
Open

iipanda wants to merge 3 commits into
get-bb:mainfrom
iipanda:codex-daybreak-model-options

Conversation

@iipanda

@iipanda iipanda commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Human comments

What was wrong

Codex's Daybreak, OpenAI's cyber access program, could only be used in bb by picking the opaque "Daybreak Blue" alias model. It was impossible to use Daybreak with a chosen model such as 6-Sol, because bb never sent cyberAccessProgram. More generally, a provider had no way to add its own choice to bb's model picker; Fast mode is the only such control, and it is hard-coded as service tiers. Per the maintainer's suggestion on #4802, this adds a generic, provider-declared picker option API and uses it for Daybreak.

What changed

  • Provider model options (generic, experimental).
    • Declaration: a provider declares experimental_modelOptions: [{ id, label, description?, values, defaultValue }]. Each value carries an optional modelUnavailableReason.
    • Per-model support: a model/list entry narrows the values it accepts with experimental_supportedModelOptions; an entry without the option id accepts every value. The design mirrors serviceTiers / supportedServiceTiers.
    • Validation: in the SDK host policy (unique ids, at least two values, the default must be one of the values).
  • Picker.
    • An option is shown only when some model lists a non-default value. Two values render as a switch, more as a segmented control.
    • Models that reject the selected value are greyed out with the reason, and ignore clicks and Enter.
    • Changing a value moves to a compatible model: the current one, then the default, then the first compatible.
    • The trigger names active options. Hidden during handoff and provider preview, like service tiers.
  • Server.
    • Each declared option resolves from the request, then the thread's last turn, then project defaults, then the declared default.
    • An explicit unknown id or value returns a 400; client preferences fall back silently.
    • Values are stored with turn requests, queued rows (migration 0142 adds model_options_json) and project defaults.
    • Values reach deriveProviderOptions as ctx.experimental_modelOptions. They cross the daemon wire only as the provider's derived providerOptions.
  • Composers.
    • New-thread composers remember the choice per provider.
    • Thread composers send values only after the user changes them, so follow-ups inherit server-side.
    • NewThreadRequest.experimental_modelOptions carries the values for plugins.
  • Codex.
    • Declares a daybreak off/on option, mapped from each model's availableAccessPrograms.cyber (missing metadata means off only).
    • With Daybreak on, turn/start sends the model's program (Daybreak Blue before Red). A model without one is refused before reaching Codex: "Daybreak isn't available for . Turn off Daybreak or choose another model."
    • The gpt-daybreak-*-latest aliases are hidden once another model offers the switch, as Codex Desktop does.
  • CLI and SDK.
    • bb thread spawn and bb thread tell take a repeatable --model-option id=value.
    • bb provider models gains a Model options column, and the SDK forwards experimental_modelOptions.
  • Docs and versions.
    • Updated: docs/api_to_audit.md, docs/provider-plugin-api.md, the Plugin Guide card, the CLI guide, the JSON shapes, and the Codex skill.
    • Plugin SDK 0.6.32, HOST_DAEMON_PROTOCOL_VERSION 231 (model catalog field), and the Codex plugin now requires SDK >= 0.6.32.
  • Fixture churn. The resolved execution type now requires modelOptions, per the no-meaningless-optional rule, so about 150 test fixtures gain a modelOptions: {} line.

How you verified

  • New tests:
    • Domain helpers.
    • SDK declaration validation.
    • Server resolution: inheritance, explicit 400s, client-preference fallback.
    • DB round trip.
    • Codex catalog mapping, alias hiding, and a bridge test against the fake app-server: Blue, Red, no field when off, refusal before turn/start.
    • The picker (switch, greyed rows, automatic switch, trigger label, hidden when unsupported).
    • The selection hook, follow-up request building, and CLI parsing and forwarding.
  • Suites: pnpm exec turbo run typecheck passes for every package. The test suites of all touched packages pass (domain, plugin-sdk, server-contract, db, server, cli, sdk, client-core, app, provider-codex, host-daemon-contract, plugin-api-docs, templates). One unrelated FilePreview test failed under load and passes alone.
  • Live, against a real Codex account (codex-cli 0.160.0), before the rebase onto current main:
    • --model-option daybreak=on on 6-Sol completed, and Codex's own log shows cyber_access_program: Some(DaybreakBlue).
    • A follow-up without the flag inherited it.
    • Astra with Daybreak on was refused with the message above.
    • daybreak=maybe returned 400.
    • In the app, the switch, the greyed 6.1-Sol and Astra rows, and the automatic switch from 6.1-Sol to 6-Sol all behaved as described.
  • Not yet re-run live after the rebase, so this is a draft until it has been.

Out of scope, listed in docs/api_to_audit.md:

  • Server-side check against the selected model: the picker and the bridge enforce it today.
  • Options during provider handoff.
  • Editing a queued row's options.
  • experimental_ProviderModelPicker support.

Fixes #4802

AGENT GENERATED

Providers can declare experimental_modelOptions: picker choices with
values, a default and a per-value reason shown on models that reject it.
Model catalog entries narrow the values they accept through
experimental_supportedModelOptions.

The server resolves each declared option from the request, the thread's
last turn, then the project defaults, falling back to the declared
default. Explicit unknown ids or values are rejected with a 400. The
resolved values are stored with turn requests, queued messages and
project defaults, and reach deriveProviderOptions as
experimental_modelOptions.

Codex declares a daybreak option. Its catalog maps each model's
availableAccessPrograms to off/on, and the Daybreak alias models move to
selected-only once another model offers the switch. With Daybreak on, the
bridge sends the model's cyberAccessProgram on turn/start (Daybreak Blue
before Red) and refuses the turn before reaching Codex when the model has
no Daybreak program.

The CLI accepts --model-option id=value on thread spawn and tell, and the
SDK forwards experimental_modelOptions on send.
The model picker renders each provider-declared option the selected
provider's models can use: two values as a switch, more as a segmented
control. Models that reject the selected value are greyed out with the
value's reason and ignore clicks and Enter, changing a value moves the
selection to a compatible model (current, then default, then first), and
the trigger names active options.

New-thread composers remember the choice per provider and send it as a
client preference. Thread composers show the thread's resolved values and
send them explicitly only after the user changes them, so ordinary
follow-ups inherit the previous turn's values on the server. Queued-row
editing keeps its options read-only. NewThreadRequest carries the values
as experimental_modelOptions.

bb provider models gains a Model options column, and the provider plugin
API doc, Plugin Guide, CLI guide, JSON shapes, Codex skill and
api_to_audit.md describe the option, the Daybreak consumer and
--model-option. The plugin SDK moves to 0.6.27 and the host-daemon
protocol to 230 for the model catalog field; the Codex plugin now requires
SDK 0.6.27.
Codex lists gpt-daybreak-blue-latest and gpt-daybreak-red-latest as their
own models. With the Daybreak switch they only duplicate it, and as
selected-only entries they still showed under More models, greyed out
while Daybreak was off. Drop them from the catalog when another model
offers Daybreak, as Codex Desktop does. Codex accepts unlisted model ids,
so a thread already on an alias keeps running.
@iipanda
iipanda marked this pull request as ready for review October 8, 2026 13:58

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex model picker: Daybreak as a switch for any supported model

1 participant