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
4 changes: 2 additions & 2 deletions docs/features/command-palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ This is usually faster than opening Settings and browsing manually.
Default bindings:

- `Alt+1` → `Claude Opus 4.8`
- `Alt+2` → `GPT-5.5`
- `Alt+3` → `GPT-5.4`
- `Alt+2` → `GPT-5.6 Terra`
- `Alt+3` → `GPT-5.6 Sol`

### Work Mostly From The Keyboard

Expand Down
2 changes: 1 addition & 1 deletion docs/features/workspace-latest-turn-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This rendered example shows the Information panel with the latest-turn summary p

```json
{
"workspaceTurnSummaryPrimaryModel": "gpt-5.4-mini",
"workspaceTurnSummaryPrimaryModel": "gpt-5.6-luna",
"workspaceTurnSummaryFallbackModel": "claude-haiku-4-5"
}
```
Expand Down
13 changes: 8 additions & 5 deletions docs/providers/provider-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,15 @@ If you want the user-facing setup workflow instead of the runtime internals, use
- `untrusted`: App Server-aligned low-friction default; pause only for actions treated as untrusted.
- `on-request`: ask when approval is needed.
- `reasoning effort`
- `minimal` / `low`: fastest.
- `low`: fastest.
- `medium`: balanced default.
- `high` / `xhigh`: slower, more deliberate.
- Codex App Server turns normalize `minimal` to `low` because the current
upstream model API rejects built-in tools such as `image_gen` and
`web_search` with `reasoning.effort = minimal`.
- `max` / `ultra`: deepest reasoning tiers introduced with the GPT-5.6
Codex CLI scale.
- `minimal` is a legacy persisted value that is no longer selectable in the
UI; Codex App Server turns normalize it to `low` because the upstream
model API rejects built-in tools such as `image_gen` and `web_search`
with `reasoning.effort = minimal`.
- `reasoning summary`
- `auto`: let Codex decide.
- `concise`: short summary.
Expand All @@ -283,7 +286,7 @@ When a task switches from one Codex model to another, Stave does not attempt to

- Codex App Server transport: local `codex app-server` from Codex CLI `0.142.0`
- Codex CLI baseline: `0.142.0`
- Current Stave-supported Codex model IDs: `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex-spark`
- Current Stave-supported Codex model IDs: `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5` (default: `gpt-5.6-terra`)

Stave requires a user-installed Codex CLI (`codex` ≥ 0.142.0). Users must have Codex CLI available in their PATH or configured via `runtimeOptions.codexBinaryPath` / `STAVE_CODEX_CLI_PATH`. A user-configured binary path still takes precedence over auto-discovery.

Expand Down
2 changes: 2 additions & 0 deletions electron/main/ipc/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ export const RuntimeOptionsObjectSchema = z
z.literal("medium"),
z.literal("high"),
z.literal("xhigh"),
z.literal("max"),
z.literal("ultra"),
])
.optional(),
codexWebSearch: z
Expand Down
2 changes: 1 addition & 1 deletion src/components/ai-elements/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ function getPromptToolbarAccentClass(
}

function isHighestEffortValue(value?: string) {
return value === "max" || value === "xhigh";
return value === "ultra" || value === "max" || value === "xhigh";
}

function getEffortIconToneClass(value?: string) {
Expand Down
5 changes: 4 additions & 1 deletion src/components/session/chat-input.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,15 @@ type CommandCatalogRuntimeArgs = Pick<
const CLAUDE_EFFORT_CYCLE_ORDER = CLAUDE_EFFORT_OPTIONS.map(
(option) => option.value,
);
// Legacy "minimal" is intentionally absent — cycling from a persisted
// "minimal" value falls back to the first entry ("low").
const CODEX_EFFORT_CYCLE_ORDER = [
"low",
"medium",
"high",
"xhigh",
"minimal",
"max",
"ultra",
] as const satisfies readonly AppSettings["codexReasoningEffort"][];

function cycleOptionValue<T extends string>(args: {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/providers/codex-runtime-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export function resolveCodexAppServerReasoningEffort(args: {
args.reasoningEffort !== "low" &&
args.reasoningEffort !== "medium" &&
args.reasoningEffort !== "high" &&
args.reasoningEffort !== "xhigh"
args.reasoningEffort !== "xhigh" &&
args.reasoningEffort !== "max" &&
args.reasoningEffort !== "ultra"
) {
return undefined;
}
Expand Down
58 changes: 31 additions & 27 deletions src/lib/providers/model-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ export const CLAUDE_SDK_MODEL_OPTIONS = [

// Source:
// - local `codex app-server` / CLI baseline support
// - https://developers.openai.com/codex/models
// - https://developers.openai.com/codex/models (GPT-5.6 family, 2026-07-09)
// GPT-5.6 ships as Sol (flagship), Terra (balanced), and Luna (fast/cheap).
// gpt-5.5 is kept as the previous-generation fallback; gpt-5.4 variants and
// codex-spark moved to legacy display names only.
export const CODEX_MODEL_OPTIONS = [
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.3-codex-spark",
] as const;

export interface ProviderDescriptor {
Expand Down Expand Up @@ -87,7 +90,7 @@ export const PROVIDER_DESCRIPTORS = [
iconUrl: CODEX_COLOR_ICON_URL,
fallbackLabel: "O",
models: CODEX_MODEL_OPTIONS,
defaultModel: "gpt-5.5",
defaultModel: "gpt-5.6-terra",
sessionLabel: "Codex thread ID",
capabilities: {
nativeCommandCatalog: true,
Expand Down Expand Up @@ -227,14 +230,6 @@ export const MODEL_TIER_ORDER = [
"frontier",
] as const satisfies readonly ModelTier[];

const GENERAL_CODING_TASK_TYPES = [
"quick_edit",
"implementation",
"debug",
"review",
"general",
] as const satisfies readonly TaskType[];

export const MODEL_CAPABILITIES: Record<string, ModelCapability> = {
[CLAUDE_FABLE_MODEL]: {
providerId: "claude-code",
Expand Down Expand Up @@ -285,32 +280,32 @@ export const MODEL_CAPABILITIES: Record<string, ModelCapability> = {
taskTypes: ["quick_edit", "general"],
defaultClaudeEffort: "medium",
},
"gpt-5.5": {
"gpt-5.6-sol": {
providerId: "codex",
model: "gpt-5.5",
model: "gpt-5.6-sol",
tier: "frontier",
taskTypes: ["plan", "implementation", "debug", "review", "safety"],
defaultCodexReasoningEffort: "high",
},
"gpt-5.4": {
"gpt-5.6-terra": {
providerId: "codex",
model: "gpt-5.4",
tier: "standard",
taskTypes: GENERAL_CODING_TASK_TYPES,
model: "gpt-5.6-terra",
tier: "heavy",
taskTypes: ["plan", "implementation", "debug", "review", "safety"],
defaultCodexReasoningEffort: "medium",
},
"gpt-5.4-mini": {
"gpt-5.6-luna": {
providerId: "codex",
model: "gpt-5.4-mini",
model: "gpt-5.6-luna",
tier: "light",
taskTypes: ["quick_edit", "general"],
defaultCodexReasoningEffort: "low",
},
"gpt-5.3-codex-spark": {
"gpt-5.5": {
providerId: "codex",
model: "gpt-5.3-codex-spark",
tier: "heavy",
taskTypes: ["implementation", "debug", "review"],
model: "gpt-5.5",
tier: "frontier",
taskTypes: ["plan", "implementation", "debug", "review", "safety"],
defaultCodexReasoningEffort: "high",
},
};
Expand Down Expand Up @@ -419,10 +414,14 @@ export function resolveDefaultCodexEffortForModel(args: {
}

const normalizedModel = args.model.trim().toLowerCase();
if (normalizedModel.includes("mini")) {
if (normalizedModel.includes("mini") || normalizedModel.includes("luna")) {
return "low";
}
if (normalizedModel.includes("spark") || normalizedModel.includes("5.5")) {
if (
normalizedModel.includes("spark") ||
normalizedModel.includes("sol") ||
normalizedModel.includes("5.5")
) {
return "high";
}
return "medium";
Expand Down Expand Up @@ -492,7 +491,12 @@ export function toHumanModelName(args: { model: string }) {
"claude-sonnet-4-6": "Claude Sonnet 4.6",
"claude-sonnet-4-6[1m]": "Claude Sonnet 4.6 (1M)",
"claude-haiku-4-5": "Claude Haiku 4.5",
"gpt-5.6-sol": "GPT-5.6 Sol",
"gpt-5.6-terra": "GPT-5.6 Terra",
"gpt-5.6-luna": "GPT-5.6 Luna",
"gpt-5.5": "GPT-5.5",
// Legacy Codex labels kept so historical chat/turn records still render
// recognizable names after the picker lineup moved to GPT-5.6.
"gpt-5.4": "GPT-5.4",
"gpt-5.4-mini": "GPT-5.4 Mini",
"gpt-5-codex": "GPT-5-Codex",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/providers/model-shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export const MODEL_SHORTCUT_SLOT_LABELS = [

export const DEFAULT_MODEL_SHORTCUT_KEYS = [
"claude-code:claude-opus-4-8",
"codex:gpt-5.5",
"codex:gpt-5.4",
"codex:gpt-5.6-terra",
"codex:gpt-5.6-sol",
"",
"",
"",
Expand Down
11 changes: 10 additions & 1 deletion src/lib/providers/provider.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,16 @@ export interface ProviderRuntimeOptions {
codexNetworkAccess?: boolean;
codexApprovalPolicy?: "never" | "on-request" | "on-failure" | "untrusted";
codexBinaryPath?: string;
codexReasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
// "minimal" is a legacy value kept for persisted settings; the runtime maps
// it to "low". "max" and "ultra" arrived with the GPT-5.6 Codex CLI scale.
codexReasoningEffort?:
| "minimal"
| "low"
| "medium"
| "high"
| "xhigh"
| "max"
| "ultra";
codexWebSearch?: "disabled" | "cached" | "live";
codexShowRawReasoning?: boolean;
codexReasoningSummary?: "auto" | "concise" | "detailed" | "none";
Expand Down
6 changes: 5 additions & 1 deletion src/lib/providers/runtime-option-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ export const CODEX_SANDBOX_MODE_OPTIONS = [
{ value: "danger-full-access", label: "danger-full-access" },
] as const satisfies readonly SelectOption<NonNullable<ProviderRuntimeOptions["codexFileAccess"]>>[];

// "minimal" was dropped from the Codex CLI effort scale with GPT-5.6 and is
// no longer selectable; legacy persisted values still validate and map to
// "low" at runtime (see resolveCodexAppServerReasoningEffort).
export const CODEX_EFFORT_OPTIONS = [
{ value: "minimal", label: "Minimal" },
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
{ value: "xhigh", label: "X-High" },
{ value: "max", label: "Max" },
{ value: "ultra", label: "Ultra" },
] as const satisfies readonly SelectOption<NonNullable<ProviderRuntimeOptions["codexReasoningEffort"]>>[];

export const CODEX_WEB_SEARCH_OPTIONS = [
Expand Down
12 changes: 7 additions & 5 deletions src/lib/task-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export interface TaskPreset {
/**
* Reasoning effort applied to `task` presets. Claude presets accept
* `low | medium | high | xhigh | max`; Codex presets accept
* `minimal | low | medium | high | xhigh`. When omitted, the model's
* `low | medium | high | xhigh | max | ultra` (legacy persisted `minimal`
* still validates and maps to `low` at runtime). When omitted, the model's
* default effort is used at launch. Ignored for CLI sessions.
*/
effort?: TaskPresetEffort;
Expand All @@ -46,7 +47,8 @@ export type TaskPresetEffort =
| "medium"
| "high"
| "xhigh"
| "max";
| "max"
| "ultra";

/**
* Effort options selectable for a `task` preset, scoped to the provider's
Expand Down Expand Up @@ -91,11 +93,11 @@ export const DEFAULT_TASK_PRESETS: readonly TaskPreset[] = [
model: DEFAULT_CLAUDE_OPUS_MODEL,
},
{
id: "default-gpt-5-5-task",
label: "GPT-5.5",
id: "default-gpt-5-6-task",
label: "GPT-5.6",
kind: "task",
provider: "codex",
model: "gpt-5.5",
model: "gpt-5.6-terra",
},
{
id: "default-claude-cli-session",
Expand Down
11 changes: 9 additions & 2 deletions src/store/app.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1470,7 +1470,14 @@ export interface AppSettings {
codexNetworkAccess: boolean;
codexApprovalPolicy: "never" | "on-request" | "on-failure" | "untrusted";
codexBinaryPath: string;
codexReasoningEffort: "minimal" | "low" | "medium" | "high" | "xhigh";
codexReasoningEffort:
| "minimal"
| "low"
| "medium"
| "high"
| "xhigh"
| "max"
| "ultra";
codexWebSearch: "disabled" | "cached" | "live";
codexShowRawReasoning: boolean;
codexReasoningSummary: "auto" | "concise" | "detailed" | "none";
Expand Down Expand Up @@ -2675,7 +2682,7 @@ const defaultSettings: AppSettings = {
promptResponseStyle: DEFAULT_PROMPT_RESPONSE_STYLE,
promptPrDescription: DEFAULT_PROMPT_PR_DESCRIPTION,
promptInlineCompletion: DEFAULT_PROMPT_INLINE_COMPLETION,
workspaceTurnSummaryPrimaryModel: "gpt-5.4-mini",
workspaceTurnSummaryPrimaryModel: "gpt-5.6-luna",
workspaceTurnSummaryFallbackModel: "claude-haiku-4-5",
workspaceTurnSummaryPrompt: DEFAULT_PROMPT_WORKSPACE_TURN_SUMMARY,

Expand Down
8 changes: 6 additions & 2 deletions tests/chat-input.runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ describe("chat-input runtime helpers", () => {
expect(cycleClaudeEffortValue("max")).toBe("low");
});

test("cycles Codex effort with minimal at the end of the loop", () => {
test("cycles Codex effort through max and ultra, mapping legacy minimal to low", () => {
expect(cycleCodexEffortValue("low")).toBe("medium");
expect(cycleCodexEffortValue("medium")).toBe("high");
expect(cycleCodexEffortValue("high")).toBe("xhigh");
expect(cycleCodexEffortValue("xhigh")).toBe("minimal");
expect(cycleCodexEffortValue("xhigh")).toBe("max");
expect(cycleCodexEffortValue("max")).toBe("ultra");
expect(cycleCodexEffortValue("ultra")).toBe("low");
// Legacy persisted "minimal" is no longer in the cycle; it falls back to
// the first entry.
expect(cycleCodexEffortValue("minimal")).toBe("low");
});

Expand Down
24 changes: 24 additions & 0 deletions tests/codex-runtime-options.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import {
resolveCodexAppServerReasoningEffort,
resolveEffectiveCodexApprovalPolicy,
resolveEffectiveCodexFileAccessMode,
} from "@/lib/providers/codex-runtime-options";
Expand Down Expand Up @@ -42,3 +43,26 @@ describe("resolveEffectiveCodexApprovalPolicy", () => {
})).toBe("untrusted");
});
});

describe("resolveCodexAppServerReasoningEffort", () => {
test("passes through the GPT-5.6 max and ultra effort tiers", () => {
expect(
resolveCodexAppServerReasoningEffort({ reasoningEffort: "max" }),
).toBe("max");
expect(
resolveCodexAppServerReasoningEffort({ reasoningEffort: "ultra" }),
).toBe("ultra");
});

test("normalizes legacy minimal to low", () => {
expect(
resolveCodexAppServerReasoningEffort({ reasoningEffort: "minimal" }),
).toBe("low");
});

test("returns undefined for unknown or missing efforts", () => {
expect(
resolveCodexAppServerReasoningEffort({ reasoningEffort: undefined }),
).toBeUndefined();
});
});
14 changes: 14 additions & 0 deletions tests/ipc-schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ describe("provider IPC schemas", () => {
expect(parsed.success).toBe(true);
});

test("accepts Codex max/ultra efforts and legacy minimal in runtime options", () => {
for (const effort of ["max", "ultra", "minimal"] as const) {
const parsed = StreamTurnArgsSchema.safeParse({
providerId: "codex",
prompt: "continue",
runtimeOptions: {
codexReasoningEffort: effort,
},
});

expect(parsed.success).toBe(true);
}
});

test("accepts provider timeout windows up to 24 hours", () => {
expect(StreamTurnArgsSchema.safeParse({
providerId: "codex",
Expand Down
Loading
Loading