Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
44 changes: 43 additions & 1 deletion src/api/codexGateway.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { getAvailableModelIds, getThreadDetail, listDirectoryComposioConnectors, resumeThread, startThreadTurn } from './codexGateway'
import { getAvailableModelIds, getCurrentModelConfig, getThreadDetail, listDirectoryComposioConnectors, resumeThread, startThreadTurn } from './codexGateway'

function mockRpcFetch(): { requests: Array<{ method: string, params: Record<string, unknown> }> } {
const requests: Array<{ method: string, params: Record<string, unknown> }> = []
Expand Down Expand Up @@ -59,6 +59,48 @@ describe('startThreadTurn collaboration mode payloads', () => {
},
})
})

it('passes GPT-5.6 ultra reasoning through to Codex', async () => {
const { requests } = mockRpcFetch()

await startThreadTurn('thread-1', 'solve it', [], 'gpt-5.6-sol', 'ultra', undefined, [], 'default')

expect(requests[0].params.effort).toBe('ultra')
expect(requests[0].params.collaborationMode).toEqual({
mode: 'default',
settings: {
model: 'gpt-5.6-sol',
reasoning_effort: 'ultra',
developer_instructions: null,
},
})
})
})

describe('getCurrentModelConfig', () => {
afterEach(() => {
vi.unstubAllGlobals()
})

it.each(['max', 'ultra'] as const)('keeps the GPT-5.6 %s reasoning level', async (reasoningEffort) => {
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({
result: {
config: {
model: 'gpt-5.6-sol',
model_provider: 'openai',
model_reasoning_effort: reasoningEffort,
},
},
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})))

await expect(getCurrentModelConfig()).resolves.toMatchObject({
model: 'gpt-5.6-sol',
reasoningEffort,
})
})
})

describe('listDirectoryComposioConnectors', () => {
Expand Down
8 changes: 3 additions & 5 deletions src/api/codexGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import type {
ConfigReadResponse,
GetAccountRateLimitsResponse,
ModelListResponse,
ReasoningEffort,
ThreadForkResponse,
ThreadListResponse,
ThreadReadResponse,
ThreadResumeResponse,
ThreadStartResponse,
Turn,
} from './appServerDtos'
import { isReasoningEffort } from '../types/codex'
import { extractErrorMessage, normalizeCodexApiError } from './codexErrors'
import {
readActiveTurnIdFromResponse,
Expand Down Expand Up @@ -53,6 +53,7 @@ import type {
UiReviewWorkspaceView,
UiRateLimitSnapshot,
UiRateLimitWindow,
ReasoningEffort,
UiThreadAutomation,
UiThreadAutomationStatus,
} from '../types/codex'
Expand Down Expand Up @@ -700,10 +701,7 @@ async function enrichThreadMessagesWithFallback(threadId: string, messages: UiMe
}

function normalizeReasoningEffort(value: unknown): ReasoningEffort | '' {
const allowed: ReasoningEffort[] = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
return typeof value === 'string' && allowed.includes(value as ReasoningEffort)
? (value as ReasoningEffort)
: ''
return isReasoningEffort(value) ? value : ''
}

function normalizeSpeedMode(value: unknown): SpeedMode {
Expand Down
7 changes: 6 additions & 1 deletion src/components/content/ThreadComposer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@
/>

<ComposerDropdown
class="thread-composer-control"
class="thread-composer-control thread-composer-thinking-control"
:model-value="selectedReasoningEffort"
:options="reasoningOptions"
:placeholder="t('Thinking')"
Expand Down Expand Up @@ -592,6 +592,8 @@ const reasoningOptions: Array<{ value: ReasoningEffort; label: string }> = [
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
{ value: 'xhigh', label: 'Extra high' },
{ value: 'max', label: 'Max' },
{ value: 'ultra', label: 'Ultra' },
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
]
function formatModelLabel(modelId: string): string {
return modelId.trim().replace(/^gpt/i, 'GPT')
Expand Down Expand Up @@ -2214,6 +2216,9 @@ watch(
@apply truncate;
}

.thread-composer-thinking-control :deep(.composer-dropdown-options) {
@apply max-h-64;
}

.thread-composer-actions {
@apply ml-auto flex min-w-0 items-center gap-2;
Expand Down
3 changes: 2 additions & 1 deletion src/composables/useDesktopState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
} from '../api/codexGateway'
import { CodexApiError } from '../api/codexErrors'
import { normalizeFileChangeStatus, toUiFileChanges } from '../api/normalizers/v2'
import { REASONING_EFFORTS } from '../types/codex'
import type {
CollaborationModeKind,
CollaborationModeOption,
Expand Down Expand Up @@ -91,7 +92,7 @@ const TURN_START_FOLLOW_UP_SYNC_DELAY_MS = 3000
const RECENT_THREAD_MESSAGE_LOAD_REUSE_MS = 2000
const RECENT_THREAD_LIST_LOAD_REUSE_MS = 2000
const RECENT_SKILLS_LOAD_REUSE_MS = 2000
const REASONING_EFFORT_OPTIONS: ReasoningEffort[] = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
const REASONING_EFFORT_OPTIONS: readonly ReasoningEffort[] = REASONING_EFFORTS
const GLOBAL_SERVER_REQUEST_SCOPE = '__global__'
const MODEL_FALLBACK_ID = 'gpt-5.4-mini'
const OPENCODE_ZEN_DEFAULT_MODEL = 'big-pickle'
Expand Down
7 changes: 2 additions & 5 deletions src/server/codexAppServerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
resolveCodexCommand,
resolveRipgrepCommand,
} from '../commandResolution.js'
import type { CollaborationModeKind, ReasoningEffort } from '../types/codex.js'
import { isReasoningEffort, type CollaborationModeKind, type ReasoningEffort } from '../types/codex.js'
import { isAbsoluteLikePath } from '../pathUtils.js'

type JsonRpcCall = {
Expand Down Expand Up @@ -5674,10 +5674,7 @@ async function appendThreadQueuedMessage(threadId: string, message: StoredQueued
}

function normalizeReasoningEffort(value: unknown): ReasoningEffort | '' {
const allowed: ReasoningEffort[] = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
return typeof value === 'string' && allowed.includes(value as ReasoningEffort)
? (value as ReasoningEffort)
: ''
return isReasoningEffort(value) ? value : ''
}

function normalizeCollaborationModeReasoningEffort(value: ReasoningEffort | '' | null | undefined): ReasoningEffort | null {
Expand Down
17 changes: 16 additions & 1 deletion src/types/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@ export type RpcEnvelope<T> = {
result: T
}

export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
export const REASONING_EFFORTS = [
'none',
'minimal',
'low',
'medium',
'high',
'xhigh',
'max',
'ultra',
] as const

export type ReasoningEffort = (typeof REASONING_EFFORTS)[number]

export function isReasoningEffort(value: unknown): value is ReasoningEffort {
return typeof value === 'string' && REASONING_EFFORTS.some((effort) => effort === value)
}
Comment on lines +5 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Schema effort union stale 🐞 Bug ⚙ Maintainability

The PR adds 'max'/'ultra' to the runtime ReasoningEffort union, but the in-repo app-server
schema/types still define ReasoningEffort only through 'xhigh', leaving two conflicting sources of
truth. Any code that relies on the schema-generated types (re-exported via appServerDtos) will
remain unable to represent these new values without casts/workarounds.
Agent Prompt
### Issue description
The repository now has conflicting definitions of `ReasoningEffort`: runtime/UI types allow `max`/`ultra`, but the schema-generated protocol types still only allow up to `xhigh`. This mismatch will cause friction and incorrect typing anywhere schema types are used.

### Issue Context
`src/api/appServerDtos.ts` re-exports the schema-generated `ReasoningEffort`, so even though the current PR updated runtime normalization to accept new values, the protocol type artifacts remain stale.

### Fix Focus Areas
- documentation/app-server-schemas/typescript/ReasoningEffort.ts[1-8]
- documentation/app-server-schemas/json/v2/ModelListResponse.json[85-96]
- documentation/app-server-schemas/json/v2/TurnStartParams.json[1-200]
- src/api/appServerDtos.ts[1-18]

### Notes
- Regenerate these files from the upstream schema generator if applicable, or update them in-place if this repo is the source of truth.
- Ensure all schema references to `ReasoningEffort` (v1/v2, events, requests) include `max` and `ultra` where appropriate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

export type SpeedMode = 'standard' | 'fast'
export type CollaborationModeKind = 'default' | 'plan'

Expand Down
28 changes: 28 additions & 0 deletions tests/providers-models/gpt-5-6-max-and-ultra-thinking-levels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### GPT-5.6 Max and Ultra thinking levels

#### Feature/Change Name
GPT-5.6 reasoning-level selection supports the new `max` and `ultra` values.

#### Prerequisites/Setup
1. Install a Codex CLI version whose model catalog includes GPT-5.6 and its new reasoning levels.
2. Sign in with an account that can use a GPT-5.6 model.
3. Build and start the app.

#### Steps
1. Start a new chat and select `GPT-5.6-Sol` or another available GPT-5.6 model.
2. Open the Thinking selector in light theme and confirm `Max` and `Ultra` are present.
3. Select `Max`, send a prompt, and confirm the turn starts without an invalid reasoning-effort error.
4. Select `Ultra`, send a second prompt, and confirm the turn starts without an invalid reasoning-effort error.
5. Switch to dark theme and repeat the selector visibility check.
6. Reload the page while `Ultra` is configured and confirm the selector still displays `Ultra`.

#### Expected Results
- The Thinking selector includes `Max` and `Ultra` after `Extra high`.
- Selecting either value passes the exact lowercase `max` or `ultra` value to Codex.
- A configured `max` or `ultra` value survives config normalization and appears selected after refresh.
- The options remain readable in light and dark themes.

#### Rollback/Cleanup
- Restore the preferred model and thinking level.

---
1 change: 1 addition & 0 deletions tests/providers-models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ Return to the [manual test index](../../tests.md).
| [Thread-locked providers across Zen, Codex, and OpenRouter](thread-locked-providers-across-zen-codex-and-openrouter.md) |
| [Selected thread loads do not refetch provider models](selected-thread-loads-do-not-refetch-provider-models.md) |
| [Provider-backed scheduled refreshes keep model menus populated](provider-backed-scheduled-refreshes-keep-model-menus-populated.md) |
| [GPT-5.6 Max and Ultra thinking levels](gpt-5-6-max-and-ultra-thinking-levels.md) |