Skip to content

[Codex effort format] - Step 1: Collapse Codex models to one row per model - #2766

Open
zeroliu wants to merge 1 commit into
v4-previewfrom
codex-effort-1-wire-format
Open

[Codex effort format] - Step 1: Collapse Codex models to one row per model#2766
zeroliu wants to merge 1 commit into
v4-previewfrom
codex-effort-1-wire-format

Conversation

@zeroliu

@zeroliu zeroliu commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Relates to logancyang/obsidian-copilot-preview#271

Why

Open Settings → Agent → Codex today and the model list is 33 rows: GPT-5.6-Sol (low), GPT-5.6-Sol (medium), GPT-5.6-Sol (high), GPT-5.6-Sol (xhigh), GPT-5.6-Sol (max), GPT-5.6-Sol (ultra), then the same spread for Terra, Luna, GPT-5.5, GPT-5.4, GPT-5.4-Mini and Codex-Spark. Each carries its own enable toggle, so a user can enable GPT-5.6-Sol at high and not at max — a distinction that means nothing. The chat model picker shows the same 33 rows and never offers an effort dropdown.

The cause is a wire format Copilot got wrong. codex-acp addresses a model as <base>[<effort>]gpt-5.6-sol[ultra] — and says so itself when given anything else:

Unsupported format of modelId: gpt-5.4. Expected: modelId[effort].

Copilot decoded <base>/<effort> and gated the trailing segment against a hardcoded minimal | low | medium | high | xhigh set. Splitting gpt-5.6-sol[ultra] on / yields one segment, so that set was never consulted and every advertised id decoded as an effort-less base model. Widening the set to add max and ultra would have changed nothing.

Everything downstream already collapses correctly — it was being fed 33 distinct "models."

What

Codex models collapse to one row per base model, in both the settings list and the chat picker, with effort chosen from a sibling dropdown.

Before: Settings → Agent → Codex lists 33 rows; picking GPT-5.6-Sol (max) in chat is picking a different model from GPT-5.6-Sol (low), and there is no effort control.

After: the same list shows 7 rows — one per base model. Selecting GPT-5.6-Sol in chat offers low · medium · high · xhigh · max · ultra; selecting GPT-5.5 offers low · medium · high · xhigh.

Effort levels now come only from what the installed CLI advertises, per model:

Model Effort levels offered
GPT-5.6-Sol, GPT-5.6-Terra low, medium, high, xhigh, max, ultra
GPT-5.6-Luna low, medium, high, xhigh, max
GPT-5.5, GPT-5.4, GPT-5.4-Mini, GPT-5.3-Codex-Spark low, medium, high, xhigh

Copilot no longer keeps a list of effort names, so a codex-acp release that introduces another level collapses correctly with no plugin change.

On upgrade, a settings migration folds each user's per-effort rows into one row per base model. A base model stays enabled if any of its effort variants was, and a saved default of gpt-5.6-sol[xhigh] becomes gpt-5.6-sol at xhigh. No model disappears from the picker.

Non goal

  • Codex keeps switching models through session/set_model with the bracketed id. It also advertises a category:"model" config option and a reasoning_effort selector; routing through those would cost a round-trip per model per probe for information the bracketed catalog already carries in full.
  • The blurb on a collapsed row still comes from its first effort variant, so GPT-5.6-Sol reads "…Fast responses with lighter reasoning" until the next PR in this stack.
  • opencode's own hardcoded effort list is untouched here.
  • fast-mode and collaboration_mode, which codex-acp also advertises, stay unread.

Screenshot

Not captured — the affected surfaces are Obsidian panes (Settings → Agent → Codex and the chat model picker) and this environment cannot drive the Obsidian UI to load the branch build.

In place of a rendered capture, the live catalog from codex-acp@1.1.10 was replayed through the branch's real wire.decode and translateBackendState, which produce exactly what those panes render:

reported wire ids: 33   →   collapsed rows: 7
gpt-5.6-sol         | GPT-5.6-Sol         | [low,medium,high,xhigh,max,ultra]
gpt-5.6-terra       | GPT-5.6-Terra       | [low,medium,high,xhigh,max,ultra]
gpt-5.6-luna        | GPT-5.6-Luna        | [low,medium,high,xhigh,max]
gpt-5.5             | GPT-5.5             | [low,medium,high,xhigh]
gpt-5.4             | GPT-5.4             | [low,medium,high,xhigh]
gpt-5.4-mini        | GPT-5.4-Mini        | [low,medium,high,xhigh]
gpt-5.3-codex-spark | GPT-5.3-Codex-Spark | [low,medium,high,xhigh]
current: { baseModelId: "gpt-5.6-sol", effort: "high" }

Verification step 2 reaches the real panes.

Risk

High

Criterion Status Reason
No behavior change, or a cosmetic/copy/docs/config change visible where it renders, or deterministic tests cover the changed behavior codexModelId.test.ts covers parse/format including unseen effort tokens and malformed brackets; descriptor.test.ts collapses a transcribed live catalog to 7 entries with per-model effort sets; codexModelIdMigration.test.ts covers the collapse, the enabled set, the sticky default and idempotency
A defect would fail CI or be obvious on first use A wrong split changes the settings row count on the first probe; the collapse tests run in CI
A revert fully restores prior state, including persisted data The migration rewrites configuredModels[].info.id in place and stamps settingsVersion: 9. Reverting the code leaves base-form rows that the old codec's next probe would prune, dropping the user's enabled set
No auth, permissions, secrets, or input-handling surface changes Model id parsing only
No public API, plugin API, message, or on-disk contract changes The persisted settings schema changes: codex ConfiguredModel.info.id, backends.codex.enabledModels, and agentMode.backends.codex.defaultModel are rewritten under settingsVersion 9
No core-path concurrency, async-lifecycle, or state-machine changes The codec is pure and the migration is synchronous, running before agent discovery is wired
No hot-path behavior lacks deterministic coverage Both decode branches, both encode branches, and the effort-snap in applySelection are covered
No new dependency Dependency manifests are unchanged
Human-only behavior stays in one feature area and surfaces quickly Confined to Agent Mode's Codex backend; a wrong result appears on the first settings open or model switch

Review: inspect src/settings/migrations/codexModelIdMigration.tsplanCodexModelIdCollapse and stripEffortLabel — line by line against a real pre-upgrade data.json, since it is the only irreversible write here; then src/agentMode/backends/codex/codexModelId.ts (CODEX_WIRE_ID, parseCodexModelId) and src/agentMode/backends/codex/descriptor.ts (codexWire, applySelection, firstAdvertisedEffort). Run Verification steps 1–4, including step 4's upgrade-from-backup path.

Verification

Requires codex-acp installed and logged in, on a vault whose Codex models were already enrolled by a previous Copilot version.

  1. Copy the vault's .obsidian/plugins/copilot/data.json somewhere safe, and note which Codex models are enabled and which are not.
  2. Build and load this branch, then open Settings → Agent → Codex. The list shows one row per base model — 7 with a current codex-acp — rather than one row per model-and-effort. Every base model you had enabled is still enabled; every one you had disabled is still disabled.
  3. Open a Codex chat and the model picker. Select GPT-5.6-Sol: the effort dropdown offers low through ultra. Select GPT-5.5: it offers low through xhigh, with no max or ultra. Choose ultra on Sol and send a message — the turn runs rather than erroring.
  4. Quit Obsidian, restore the backup data.json, delete its "settingsVersion" line, and relaunch. The list again shows one row per base model with the same enabled set — confirming the migration replays from a genuine pre-upgrade state, not just from already-migrated settings.
  5. Reopen the picker and confirm the effort you chose in step 3 is still selected.

Note

The issue proposed adding max and ultra to KNOWN_CODEX_EFFORTS. That would not have worked: with the delimiter wrong, that set never matched any advertised id. It is deleted here rather than extended.

The issue also asked for collapsing logic in the settings list and a matching gallery story. Neither is present, because neither is needed — translateBackendState already groups by decoded base model and agentModelDiscovery enrolls the grouped entries, so configuredModelGrouping.ts and ConfiguredModelEnableList are unchanged and no component renders differently.

@zeroliu
zeroliu marked this pull request as ready for review August 8, 2026 04:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74335a9424

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +163 to +164
const effort = selection.effort ?? firstAdvertisedEffort(session, selection.baseModelId);
await session.applyModelWireId(codexWire.encode({ ...selection, effort }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the agent-default effort when selection effort is null

AgentDefaultModelSetting.onModelChange deliberately persists { effort: null } to mean “let the agent choose,” but this fallback converts that value to the first advertised option—currently low. On the next Codex session, even if codex-acp starts the selected model at high, confirming the saved default sends model[low] and silently lowers reasoning effort. Keep the backend-reported/native effort when null is selected, or make the settings UI require an explicit Codex effort instead of treating the first option as the default.

Useful? React with 👍 / 👎.

Comment on lines +187 to +189
function firstAdvertisedEffort(session: AgentSession, baseModelId: string): string | null {
const entry = findModelEntry(session.getState()?.model, baseModelId);
return entry?.effortOptions.find((o) => o.value !== null)?.value ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expose a lone bracketed variant as an advertised effort

When codex-acp advertises exactly one entry for a model, such as gpt-6[high], translateBackendState leaves that model's effortOptions empty because suffix efforts are currently derived only for groups with at least two variants. This lookup therefore returns null, and selecting or seeding the model sends the bare gpt-6, which this change notes codex-acp rejects. The decoded effort must remain available even when the catalog contains only one variant.

Useful? React with 👍 / 👎.

* `configuredModelId`s that `backends.codex.enabledModels` references survive.
*/

import { parseCodexModelId } from "@/agentMode";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the settings migration from loading Agent Mode on mobile

This value import evaluates the entire @/agentMode barrel whenever the settings migrations load, including its top-level node:os/node:path imports and backend registry. main.ts deliberately imports that barrel only behind isDesktopRuntime() because Node is stubbed on mobile and under app.emulateMobile(true); the new unconditional migration import bypasses that gate and can crash the plugin during module initialization on those runtimes. Move this pure codec behind a Node-free module boundary that migrations can import without evaluating the Agent Mode barrel.

Useful? React with 👍 / 👎.

codex-acp addresses models as `<base>[<effort>]` — its own `set_model`
rejection names the contract ("Expected: modelId[effort]"). Copilot decoded
`<base>/<effort>`, a format codex-acp has never emitted, so every advertised
id decoded as an effort-less base model: the picker listed 34 rows with no
effort dropdown, and discovery enrolled one `ConfiguredModel` per (model ×
effort) pair, giving the Codex settings list one toggle per variant.

Widening `KNOWN_CODEX_EFFORTS` would not have helped — with the wrong
delimiter it never matched anything. The set is deleted rather than extended:
the bracket makes the effort token unambiguous, so Copilot enumerates no
effort levels at all and a CLI release that adds one (`max`, `ultra`, …)
collapses correctly with no plugin change.

Everything downstream was already correct and is untouched —
`translateBackendState` groups by decoded base model and `agentModelDiscovery`
enrolls the translated entries, so both the picker and the settings list
collapse once the codec reads the real format.

Settings v9 migration: rename the persisted per-effort rows in place (rather
than let the next probe's `syncAgentModels` prune them) so the
`configuredModelId`s that `backends.codex.enabledModels` references survive
the upgrade, and lift the bracketed level out of the sticky default's
`baseModelId` into its `effort`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zeroliu
zeroliu force-pushed the codex-effort-1-wire-format branch from 74335a9 to 2e6a779 Compare August 8, 2026 04:38

zeroliu commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

// codex names a session after the raw first prompt (which leaks the injected

P2 Badge Add a gallery story for the collapsed Codex picker

This changes the picker from separate per-effort Codex rows to a meaningful new visual state—a collapsed model row with a sibling effort dropdown—but the diff adds no story that renders this state. Without the required gallery coverage, the new dropdown and model-specific effort sets cannot be visually verified; add or update the adjacent picker story with collapsed Codex models and differing effort options.

AGENTS.md reference: AGENTS.md:L23-L23


// codex names a session after the raw first prompt (which leaks the injected

P2 Badge Document the new Codex effort selection behavior

After this upgrade, users see a collapsed Codex model list and a new per-model effort control, but docs/agent-mode-and-tools.md still says only that the picker chooses a model and never explains the effort selector or that available levels vary by model. Update that corresponding user-facing guide so users can understand the new control and persisted selection behavior.

AGENTS.md reference: AGENTS.md:L37-L47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

An error occurred while trying to automatically change base from v4-preview to master August 8, 2026 05:02
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