Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/providers/provider-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ If you want the user-facing setup workflow instead of the runtime internals, use
- `Guided`: `workspace-write` + `untrusted` + network off + web search cached
- `Auto`: `danger-full-access` + `never` + network on + web search live

Current Codex defaults follow the App Server-aligned baseline in Stave: `workspace-write` file access, `untrusted` approvals, `network access = off`, `web search = cached`, `reasoning effort = medium`, raw reasoning off, and reasoning summary auto-detection enabled.
Current Codex defaults follow the App Server-aligned baseline in Stave: `workspace-write` file access, `untrusted` approvals, `network access = off`, `web search = cached`, `reasoning effort = xhigh` (the codex-cli 0.144.1 server-catalog default for the GPT-5.6 family), raw reasoning off, and reasoning summary auto-detection enabled.

Stave now forwards an explicit `show_raw_agent_reasoning: false` override when the Codex UI toggle is off, so local CLI defaults or config files do not leave raw reasoning enabled unexpectedly.

Expand Down
9 changes: 8 additions & 1 deletion src/components/layout/PresetBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,14 @@ function PresetChip(props: PresetChipProps) {
<Ellipsis className="size-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuContent
align="end"
className="w-40"
// Keep focus where it is when the menu closes after "Edit…".
// Radix's default focus-return lands outside the freshly opened
// preset editor Popover and immediately dismisses it.
onCloseAutoFocus={(event) => event.preventDefault()}
>
<DropdownMenuItem onSelect={() => onRequestEdit()}>
Edit…
</DropdownMenuItem>
Expand Down
37 changes: 34 additions & 3 deletions src/components/layout/settings-dialog-providers-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
DEFAULT_CLAUDE_OPUS_MODEL,
DEFAULT_CLAUDE_SONNET_MODEL,
getDefaultModelForProvider,
listCodexReasoningEffortsForModel,
} from "@/lib/providers/model-catalog";
import { useCodexModelCatalog } from "@/lib/providers/use-codex-model-catalog";
import { UI_LAYER_CLASS } from "@/lib/ui-layers";
Expand Down Expand Up @@ -366,6 +367,22 @@ const CODEX_REASONING_EFFORT_HELP = [
example:
"Reserve this for genuinely complex work where you want Codex to think much longer.",
},
{
value: "max",
label: "Max",
description:
"Highest deliberation on the GPT-5.6 effort scale, above `xhigh`.",
example:
"Reserve this for hard multi-step implementation or debugging work where accuracy matters more than speed.",
},
{
value: "ultra",
label: "Ultra",
description:
"The deepest reasoning budget Codex offers and the highest latency cost. Not every model accepts this tier (e.g. GPT-5.6 Luna caps at `max`).",
example:
"Reserve this for the hardest frontier-model tasks where you want Codex to think as long as possible.",
},
] as const satisfies readonly ExplainedSelectOption<
NonNullable<ProviderRuntimeOptions["codexReasoningEffort"]>
>[];
Expand Down Expand Up @@ -604,6 +621,7 @@ export function ProvidersSection() {
codexNetworkAccess,
codexApprovalPolicy,
codexReasoningEffort,
modelCodex,
codexWebSearch,
codexShowRawReasoning,
codexReasoningSummary,
Expand Down Expand Up @@ -640,6 +658,7 @@ export function ProvidersSection() {
state.settings.codexNetworkAccess,
state.settings.codexApprovalPolicy,
state.settings.codexReasoningEffort,
state.settings.modelCodex,
state.settings.codexWebSearch,
state.settings.codexShowRawReasoning,
state.settings.codexReasoningSummary,
Expand Down Expand Up @@ -683,6 +702,18 @@ export function ProvidersSection() {
model: defaultClaudeAdvisorModel,
fallback: DEFAULT_CLAUDE_SONNET_MODEL,
});
// Scoped to the default Codex model so, e.g., GPT-5.6 Luna never offers
// "Ultra" here — a value only Sol/Terra accept. "Minimal" is always kept
// available since it's a legacy value Stave still maps to "low" at
// runtime, not part of the current model-reported effort scale.
const codexReasoningEffortOptions = useMemo(() => {
const supported = listCodexReasoningEffortsForModel({ model: modelCodex });
return CODEX_REASONING_EFFORT_HELP.filter(
(option) =>
option.value === "minimal" ||
(supported as readonly string[]).includes(option.value),
);
}, [modelCodex]);
const claudeAdvisorModelEnabled = claudeAdvisorModel.trim().length > 0;
const activeClaudeAdvisorSourceModel = resolveClaudeAdvisorSourceModel({
model: claudeAdvisorModel.trim() || modelClaude,
Expand Down Expand Up @@ -1237,15 +1268,15 @@ export function ProvidersSection() {
<SettingsFieldGuide
title="Codex Reasoning Effort"
summary="Higher effort gives Codex more room to reason, but it also tends to slow the turn down."
items={buildGuideItems(CODEX_REASONING_EFFORT_HELP)}
examples={buildGuideExamples(CODEX_REASONING_EFFORT_HELP)}
items={buildGuideItems(codexReasoningEffortOptions)}
examples={buildGuideExamples(codexReasoningEffortOptions)}
tooltip="Compare Codex reasoning effort levels"
/>
}
>
<DescribedSelect
value={codexReasoningEffort}
options={CODEX_REASONING_EFFORT_HELP}
options={codexReasoningEffortOptions}
onValueChange={(value) =>
updateSettings({
patch: {
Expand Down
10 changes: 8 additions & 2 deletions src/components/layout/settings-dialog-sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ import { useCodexModelCatalog } from "@/lib/providers/use-codex-model-catalog";
import {
BOOLEAN_TOGGLE_OPTIONS,
CLAUDE_EFFORT_OPTIONS,
CODEX_EFFORT_OPTIONS,
listCodexEffortOptionsForModel,
} from "@/lib/providers/runtime-option-contract";
import { cn } from "@/lib/utils";
import {
Expand Down Expand Up @@ -1789,6 +1789,12 @@ function ModelsSection() {
() => modelOptions.filter((option) => option.providerId === "codex"),
[modelOptions],
);
// Scoped to the default Codex model so, e.g., GPT-5.6 Luna never offers
// "Ultra" here — a value only Sol/Terra accept.
const codexEffortOptions = useMemo(
() => listCodexEffortOptionsForModel({ model: modelCodex }),
[modelCodex],
);
const updateEligibleModels = useCallback(
(args: { providerId: "claude-code" | "codex"; models: string[] }) => {
updateSettings({
Expand Down Expand Up @@ -1950,7 +1956,7 @@ function ModelsSection() {
<SelectValue />
</SelectTrigger>
<SelectContent>
{CODEX_EFFORT_OPTIONS.map((option) => (
{codexEffortOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
Expand Down
34 changes: 28 additions & 6 deletions src/components/layout/task-preset-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
() => listModelsForPresetProvider(provider),
[provider],
);
// Model-scoped so, e.g., GPT-5.6 Luna never offers "Ultra" — a value only
// Sol/Terra accept.
const effortOptions = useMemo(
() => listEffortsForPresetProvider(provider),
[provider],
() => listEffortsForPresetProvider(provider, model),
[provider, model],
);
const providerOptions = useMemo<
{ value: ProviderId; label: string }[]
Expand Down Expand Up @@ -91,10 +93,30 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
: "claude-code";
setProvider(providerId);
const nextModels = listModelsForPresetProvider(providerId);
if (!nextModels.includes(model)) {
setModel(getDefaultModelForProvider({ providerId }));
const nextModel = nextModels.includes(model)
? model
: getDefaultModelForProvider({ providerId });
if (nextModel !== model) {
setModel(nextModel);
}
const nextEfforts = listEffortsForPresetProvider(providerId).map(
const nextEfforts = listEffortsForPresetProvider(
providerId,
nextModel,
).map((option) => option.value);
if (
effort !== DEFAULT_EFFORT_VALUE &&
!nextEfforts.includes(effort as TaskPresetEffort)
) {
setEffort(DEFAULT_EFFORT_VALUE);
}
}

function handleModelChange(nextModel: string) {
setModel(nextModel);
// Some Codex models accept a narrower effort scale than others (e.g.
// GPT-5.6 Luna has no "Ultra"); drop back to the default when the
// currently selected effort isn't valid for the new model.
const nextEfforts = listEffortsForPresetProvider(provider, nextModel).map(
(option) => option.value,
);
if (
Expand Down Expand Up @@ -170,7 +192,7 @@ export function TaskPresetEditor(props: TaskPresetEditorProps) {
<span className="text-xs font-medium text-muted-foreground">
Model
</span>
<Select value={model} onValueChange={setModel}>
<Select value={model} onValueChange={handleModelChange}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
Expand Down
28 changes: 26 additions & 2 deletions src/components/session/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
type ProviderCommandCatalogState,
} from "@/lib/providers/provider-command-catalog";
import {
clampCodexEffortToModel,
getDefaultModelForProvider,
getProviderLabel,
listProviderIds,
Expand Down Expand Up @@ -1385,12 +1386,21 @@ function BaseChatInput() {
return () =>
updateSettings({
patch: {
codexReasoningEffort: cycleCodexEffortValue(codexReasoningEffort),
codexReasoningEffort: cycleCodexEffortValue(
codexReasoningEffort,
activeModel,
),
},
});
}
return undefined;
}, [activeProvider, claudeEffort, codexReasoningEffort, updateSettings]);
}, [
activeModel,
activeProvider,
claudeEffort,
codexReasoningEffort,
updateSettings,
]);
const goalStatus = useMemo(
() =>
buildChatInputGoalStatus({
Expand Down Expand Up @@ -1879,6 +1889,20 @@ function BaseChatInput() {
});
return;
}
if (selection.providerId === "codex") {
// Some Codex models accept a narrower effort scale than others
// (e.g. GPT-5.6 Luna has no "Ultra") — clamp so switching models
// never leaves an unsupported effort selected.
updateSettings({
patch: {
codexReasoningEffort: clampCodexEffortToModel({
model: nextModel,
effort: codexReasoningEffort,
}),
},
});
return;
}
}}
fastMode={activeProvider === "codex" ? codexFastMode : undefined}
onFastModeChange={
Expand Down
12 changes: 11 additions & 1 deletion src/components/session/chat-input.runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PromptInputRuntimeStatusItem } from "@/components/ai-elements/prompt-input-runtime-bar";
import type { PromptInputGoalStatus } from "@/components/ai-elements/prompt-input-goal-status";
import { resolveEffectiveCodexFileAccessMode } from "@/lib/providers/codex-runtime-options";
import { listCodexReasoningEffortsForModel } from "@/lib/providers/model-catalog";
import type {
ProviderGoalSnapshot,
ProviderId,
Expand Down Expand Up @@ -99,12 +100,21 @@ export function cycleClaudeEffortValue(current: AppSettings["claudeEffort"]) {
});
}

/**
* Cycles the Codex reasoning effort. When `model` is provided, the cycle is
* scoped to that model's supported efforts (e.g. GPT-5.6 Luna skips "ultra")
* so clicking through the toolbar never lands on a value the model rejects.
*/
export function cycleCodexEffortValue(
current: AppSettings["codexReasoningEffort"],
model?: string,
) {
const order = model
? listCodexReasoningEffortsForModel({ model })
: CODEX_EFFORT_CYCLE_ORDER;
return cycleOptionValue({
current,
order: CODEX_EFFORT_CYCLE_ORDER,
order,
});
}

Expand Down
Loading
Loading