diff --git a/apps/server/src/services/agent-service.ts b/apps/server/src/services/agent-service.ts index 3fdbd6a6..5e29d0db 100644 --- a/apps/server/src/services/agent-service.ts +++ b/apps/server/src/services/agent-service.ts @@ -6,7 +6,7 @@ import path from 'path'; import * as secureFs from '../lib/secure-fs.js'; import type { EventEmitter } from '../lib/events.js'; -import type { ExecuteOptions, ThinkingLevel } from '@automaker/types'; +import type { ExecuteOptions, ThinkingLevel, ReasoningEffort } from '@automaker/types'; import { readImageAsBase64, buildPromptWithImages, @@ -56,6 +56,7 @@ interface Session { workingDirectory: string; model?: string; thinkingLevel?: ThinkingLevel; // Thinking level for Claude models + reasoningEffort?: ReasoningEffort; // Reasoning effort for Codex models sdkSessionId?: string; // Claude SDK session ID for conversation continuity promptQueue: QueuedPrompt[]; // Queue of prompts to auto-run after current task } @@ -145,6 +146,7 @@ export class AgentService { imagePaths, model, thinkingLevel, + reasoningEffort, }: { sessionId: string; message: string; @@ -152,6 +154,7 @@ export class AgentService { imagePaths?: string[]; model?: string; thinkingLevel?: ThinkingLevel; + reasoningEffort?: ReasoningEffort; }) { const session = this.sessions.get(sessionId); if (!session) { @@ -164,7 +167,7 @@ export class AgentService { throw new Error('Agent is already processing a message'); } - // Update session model and thinking level if provided + // Update session model, thinking level, and reasoning effort if provided if (model) { session.model = model; await this.updateSession(sessionId, { model }); @@ -172,6 +175,9 @@ export class AgentService { if (thinkingLevel !== undefined) { session.thinkingLevel = thinkingLevel; } + if (reasoningEffort !== undefined) { + session.reasoningEffort = reasoningEffort; + } // Validate vision support before processing images const effectiveModel = model || session.model; @@ -265,8 +271,9 @@ export class AgentService { : baseSystemPrompt; // Build SDK options using centralized configuration - // Use thinking level from request, or fall back to session's stored thinking level + // Use thinking level and reasoning effort from request, or fall back to session's stored values const effectiveThinkingLevel = thinkingLevel ?? session.thinkingLevel; + const effectiveReasoningEffort = reasoningEffort ?? session.reasoningEffort; const sdkOptions = createChatOptions({ cwd: effectiveWorkDir, model: model, @@ -299,6 +306,8 @@ export class AgentService { settingSources: sdkOptions.settingSources, sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration + thinkingLevel: effectiveThinkingLevel, // Pass thinking level for Claude models + reasoningEffort: effectiveReasoningEffort, // Pass reasoning effort for Codex models }; // Build prompt content with images diff --git a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx index c2279c46..5439b675 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx @@ -1,6 +1,8 @@ // @ts-nocheck import { useEffect, useState } from 'react'; import { Feature, ThinkingLevel, useAppStore } from '@/store/app-store'; +import type { ReasoningEffort } from '@automaker/types'; +import { getProviderFromModel } from '@/lib/utils'; import { AgentTaskInfo, parseAgentContext, @@ -37,6 +39,22 @@ function formatThinkingLevel(level: ThinkingLevel | undefined): string { return labels[level]; } +/** + * Formats reasoning effort for compact display + */ +function formatReasoningEffort(effort: ReasoningEffort | undefined): string { + if (!effort || effort === 'none') return ''; + const labels: Record = { + none: '', + minimal: 'Min', + low: 'Low', + medium: 'Med', + high: 'High', + xhigh: 'XHigh', + }; + return labels[effort]; +} + interface AgentInfoPanelProps { feature: Feature; contextContent?: string; @@ -106,6 +124,10 @@ export function AgentInfoPanel({ }, [feature.id, feature.status, contextContent, isCurrentAutoTask]); // Model/Preset Info for Backlog Cards if (showAgentInfo && feature.status === 'backlog') { + const provider = getProviderFromModel(feature.model); + const isCodex = provider === 'codex'; + const isClaude = provider === 'claude'; + return (
@@ -116,7 +138,7 @@ export function AgentInfoPanel({ })()} {formatModelName(feature.model ?? DEFAULT_MODEL)}
- {feature.thinkingLevel && feature.thinkingLevel !== 'none' ? ( + {isClaude && feature.thinkingLevel && feature.thinkingLevel !== 'none' ? (
@@ -124,6 +146,14 @@ export function AgentInfoPanel({
) : null} + {isCodex && feature.reasoningEffort && feature.reasoningEffort !== 'none' ? ( +
+ + + {formatReasoningEffort(feature.reasoningEffort as ReasoningEffort)} + +
+ ) : null}
); diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index e64fd979..5c93f4e2 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -41,9 +41,12 @@ import { PlanningMode, Feature, } from '@/store/app-store'; +import type { ReasoningEffort } from '@automaker/types'; +import { codexModelHasThinking, supportsReasoningEffort } from '@automaker/types'; import { ModelSelector, ThinkingLevelSelector, + ReasoningEffortSelector, ProfileQuickSelect, TestingTabContent, PrioritySelector, @@ -78,6 +81,7 @@ type FeatureData = { skipTests: boolean; model: AgentModel; thinkingLevel: ThinkingLevel; + reasoningEffort: ReasoningEffort; branchName: string; // Can be empty string to use current branch priority: number; planningMode: PlanningMode; @@ -134,6 +138,7 @@ export function AddFeatureDialog({ skipTests: false, model: 'opus' as ModelAlias, thinkingLevel: 'none' as ThinkingLevel, + reasoningEffort: 'none' as ReasoningEffort, branchName: '', priority: 2 as number, // Default to medium priority }); @@ -220,6 +225,9 @@ export function AddFeatureDialog({ const normalizedThinking = modelSupportsThinking(selectedModel) ? newFeature.thinkingLevel : 'none'; + const normalizedReasoning = supportsReasoningEffort(selectedModel) + ? newFeature.reasoningEffort + : 'none'; // Use current branch if toggle is on // If currentBranch is provided (non-primary worktree), use it @@ -260,6 +268,7 @@ export function AddFeatureDialog({ skipTests: newFeature.skipTests, model: selectedModel, thinkingLevel: normalizedThinking, + reasoningEffort: normalizedReasoning, branchName: finalBranchName, priority: newFeature.priority, planningMode, @@ -281,6 +290,7 @@ export function AddFeatureDialog({ model: 'opus', priority: 2, thinkingLevel: 'none', + reasoningEffort: 'none', branchName: '', }); setUseCurrentBranch(true); @@ -394,6 +404,9 @@ export function AddFeatureDialog({ const newModelAllowsThinking = !isCurrentModelCursor && modelSupportsThinking(newFeature.model || 'sonnet'); + // Codex models that support reasoning effort - show reasoning selector + const newModelAllowsReasoning = supportsReasoningEffort(newFeature.model || ''); + return ( )} + {newModelAllowsReasoning && ( + + setNewFeature({ ...newFeature, reasoningEffort: effort }) + } + /> + )} )} diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 816150e3..c7bb38fb 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -41,9 +41,11 @@ import { useAppStore, PlanningMode, } from '@/store/app-store'; +import type { ReasoningEffort } from '@automaker/types'; import { ModelSelector, ThinkingLevelSelector, + ReasoningEffortSelector, ProfileQuickSelect, TestingTabContent, PrioritySelector, @@ -60,7 +62,7 @@ import { import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import type { DescriptionHistoryEntry } from '@automaker/types'; import { DependencyTreeDialog } from './dependency-tree-dialog'; -import { isCursorModel, PROVIDER_PREFIXES } from '@automaker/types'; +import { isCursorModel, PROVIDER_PREFIXES, supportsReasoningEffort } from '@automaker/types'; const logger = createLogger('EditFeatureDialog'); @@ -76,6 +78,7 @@ interface EditFeatureDialogProps { skipTests: boolean; model: ModelAlias; thinkingLevel: ThinkingLevel; + reasoningEffort: ReasoningEffort; imagePaths: DescriptionImagePath[]; textFilePaths: DescriptionTextFilePath[]; branchName: string; // Can be empty string to use current branch @@ -180,6 +183,9 @@ export function EditFeatureDialog({ const normalizedThinking: ThinkingLevel = modelSupportsThinking(selectedModel) ? (editingFeature.thinkingLevel ?? 'none') : 'none'; + const normalizedReasoning: ReasoningEffort = supportsReasoningEffort(selectedModel) + ? (editingFeature.reasoningEffort ?? 'none') + : 'none'; // Use current branch if toggle is on // If currentBranch is provided (non-primary worktree), use it @@ -195,6 +201,7 @@ export function EditFeatureDialog({ skipTests: editingFeature.skipTests ?? false, model: selectedModel, thinkingLevel: normalizedThinking, + reasoningEffort: normalizedReasoning, imagePaths: editingFeature.imagePaths ?? [], textFilePaths: editingFeature.textFilePaths ?? [], branchName: finalBranchName, @@ -233,15 +240,17 @@ export function EditFeatureDialog({ if (!editingFeature) return; // For Cursor models, thinking is handled by the model itself // For Claude models, check if it supports extended thinking + // For Codex models, use reasoning effort instead const isCursor = isCursorModel(model); + const supportsThinking = modelSupportsThinking(model); + const supportsReasoning = supportsReasoningEffort(model); + setEditingFeature({ ...editingFeature, model: model as ModelAlias, - thinkingLevel: isCursor - ? 'none' - : modelSupportsThinking(model) - ? editingFeature.thinkingLevel - : 'none', + thinkingLevel: + isCursor || !supportsThinking ? 'none' : (editingFeature.thinkingLevel ?? 'none'), + reasoningEffort: !supportsReasoning ? 'none' : (editingFeature.reasoningEffort ?? 'none'), }); }; @@ -312,6 +321,9 @@ export function EditFeatureDialog({ const editModelAllowsThinking = !isCurrentModelCursor && modelSupportsThinking(editingFeature?.model); + // Codex models that support reasoning effort - show reasoning selector + const editModelAllowsReasoning = supportsReasoningEffort(editingFeature?.model || ''); + if (!editingFeature) { return null; } @@ -634,6 +646,18 @@ export function EditFeatureDialog({ testIdPrefix="edit-thinking-level" /> )} + {editModelAllowsReasoning && ( + + setEditingFeature({ + ...editingFeature, + reasoningEffort: effort, + }) + } + testIdPrefix="edit-reasoning-effort" + /> + )} )} diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts index 30d9a93e..7e4698c9 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts @@ -8,6 +8,7 @@ import { PlanningMode, useAppStore, } from '@/store/app-store'; +import type { ReasoningEffort } from '@automaker/types'; import { FeatureImagePath as DescriptionImagePath } from '@/components/ui/description-image-dropzone'; import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; @@ -222,6 +223,7 @@ export function useBoardActions({ skipTests: boolean; model: ModelAlias; thinkingLevel: ThinkingLevel; + reasoningEffort: ReasoningEffort; imagePaths: DescriptionImagePath[]; branchName: string; priority: number; diff --git a/apps/ui/src/components/views/board-view/shared/index.ts b/apps/ui/src/components/views/board-view/shared/index.ts index 1a6c2d88..54f97c39 100644 --- a/apps/ui/src/components/views/board-view/shared/index.ts +++ b/apps/ui/src/components/views/board-view/shared/index.ts @@ -1,6 +1,7 @@ export * from './model-constants'; export * from './model-selector'; export * from './thinking-level-selector'; +export * from './reasoning-effort-selector'; export * from './profile-quick-select'; export * from './profile-select'; export * from './testing-tab-content'; diff --git a/apps/ui/src/components/views/board-view/shared/model-selector.tsx b/apps/ui/src/components/views/board-view/shared/model-selector.tsx index 2aad23dd..ddcb6f3a 100644 --- a/apps/ui/src/components/views/board-view/shared/model-selector.tsx +++ b/apps/ui/src/components/views/board-view/shared/model-selector.tsx @@ -45,8 +45,8 @@ export function ModelSelector({ // Switch to Cursor's default model (from global settings) onModelSelect(`${PROVIDER_PREFIXES.cursor}${cursorDefaultModel}`); } else if (provider === 'codex' && selectedProvider !== 'codex') { - // Switch to Codex's default model (gpt-5.2) - onModelSelect('gpt-5.2'); + // Switch to Codex's default model (gpt-5.2-codex) + onModelSelect('gpt-5.2-codex'); } else if (provider === 'claude' && selectedProvider !== 'claude') { // Switch to Claude's default model onModelSelect('sonnet'); diff --git a/apps/ui/src/components/views/board-view/shared/reasoning-effort-selector.tsx b/apps/ui/src/components/views/board-view/shared/reasoning-effort-selector.tsx new file mode 100644 index 00000000..33011cfc --- /dev/null +++ b/apps/ui/src/components/views/board-view/shared/reasoning-effort-selector.tsx @@ -0,0 +1,47 @@ +import { Label } from '@/components/ui/label'; +import { Brain } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import type { ReasoningEffort } from '@automaker/types'; +import { REASONING_EFFORT_LEVELS, REASONING_EFFORT_LABELS } from './model-constants'; + +interface ReasoningEffortSelectorProps { + selectedEffort: ReasoningEffort; + onEffortSelect: (effort: ReasoningEffort) => void; + testIdPrefix?: string; +} + +export function ReasoningEffortSelector({ + selectedEffort, + onEffortSelect, + testIdPrefix = 'reasoning-effort', +}: ReasoningEffortSelectorProps) { + return ( +
+ +
+ {REASONING_EFFORT_LEVELS.map((effort) => ( + + ))} +
+

+ Higher efforts give more reasoning tokens for complex problems. +

+
+ ); +} diff --git a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx index 6c4c74b3..e6b9c9ce 100644 --- a/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx +++ b/apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx @@ -4,9 +4,11 @@ import { useAppStore } from '@/store/app-store'; import type { ModelAlias, CursorModelId, + CodexModelId, GroupedModel, PhaseModelEntry, ThinkingLevel, + ReasoningEffort, } from '@automaker/types'; import { stripProviderPrefix, @@ -15,6 +17,7 @@ import { isGroupSelected, getSelectedVariant, isCursorModel, + codexModelHasThinking, } from '@automaker/types'; import { CLAUDE_MODELS, @@ -22,6 +25,8 @@ import { CODEX_MODELS, THINKING_LEVELS, THINKING_LEVEL_LABELS, + REASONING_EFFORT_LEVELS, + REASONING_EFFORT_LABELS, } from '@/components/views/board-view/shared/model-constants'; import { Check, ChevronsUpDown, Star, ChevronRight } from 'lucide-react'; import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon'; @@ -69,14 +74,17 @@ export function PhaseModelSelector({ const [open, setOpen] = React.useState(false); const [expandedGroup, setExpandedGroup] = React.useState(null); const [expandedClaudeModel, setExpandedClaudeModel] = React.useState(null); + const [expandedCodexModel, setExpandedCodexModel] = React.useState(null); const commandListRef = React.useRef(null); const expandedTriggerRef = React.useRef(null); const expandedClaudeTriggerRef = React.useRef(null); + const expandedCodexTriggerRef = React.useRef(null); const { enabledCursorModels, favoriteModels, toggleFavoriteModel } = useAppStore(); - // Extract model and thinking level from value + // Extract model and thinking/reasoning levels from value const selectedModel = value.model; const selectedThinkingLevel = value.thinkingLevel || 'none'; + const selectedReasoningEffort = value.reasoningEffort || 'none'; // Close expanded group when trigger scrolls out of view React.useEffect(() => { @@ -124,6 +132,29 @@ export function PhaseModelSelector({ return () => observer.disconnect(); }, [expandedClaudeModel]); + // Close expanded Codex model popover when trigger scrolls out of view + React.useEffect(() => { + const triggerElement = expandedCodexTriggerRef.current; + const listElement = commandListRef.current; + if (!triggerElement || !listElement || !expandedCodexModel) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (!entry.isIntersecting) { + setExpandedCodexModel(null); + } + }, + { + root: listElement, + threshold: 0.1, + } + ); + + observer.observe(triggerElement); + return () => observer.disconnect(); + }, [expandedCodexModel]); + // Filter Cursor models to only show enabled ones const availableCursorModels = CURSOR_MODELS.filter((model) => { const cursorId = stripProviderPrefix(model.id) as CursorModelId; @@ -241,55 +272,183 @@ export function PhaseModelSelector({ return { favorites: favs, claude: cModels, cursor: curModels, codex: codModels }; }, [favoriteModels, availableCursorModels]); - // Render Codex model item (no thinking level needed) + // Render Codex model item with secondary popover for reasoning effort (only for models that support it) const renderCodexModelItem = (model: (typeof CODEX_MODELS)[0]) => { const isSelected = selectedModel === model.id; const isFavorite = favoriteModels.includes(model.id); + const hasReasoning = codexModelHasThinking(model.id as CodexModelId); + const isExpanded = expandedCodexModel === model.id; + const currentReasoning = isSelected ? selectedReasoningEffort : 'none'; + // If model doesn't support reasoning, render as simple selector (like Cursor models) + if (!hasReasoning) { + return ( + { + onChange({ model: model.id as CodexModelId }); + setOpen(false); + }} + className="group flex items-center justify-between py-2" + > +
+ +
+ + {model.label} + + {model.description} +
+
+ +
+ + {isSelected && } +
+
+ ); + } + + // Model supports reasoning - show popover with reasoning effort options return ( { - onChange({ model: model.id }); - setOpen(false); - }} - className="group flex items-center justify-between py-2" + onSelect={() => setExpandedCodexModel(isExpanded ? null : (model.id as CodexModelId))} + className="p-0 data-[selected=true]:bg-transparent" > -
- -
- - {model.label} - - {model.description} -
-
+ { + if (!isOpen) { + setExpandedCodexModel(null); + } + }} + > + +
+
+ +
+ + {model.label} + + + {isSelected && currentReasoning !== 'none' + ? `Reasoning: ${REASONING_EFFORT_LABELS[currentReasoning]}` + : model.description} + +
+
-
- + {isSelected && } + +
+
+
+ e.preventDefault()} > - - - {isSelected && } - +
+
+ Reasoning Effort +
+ {REASONING_EFFORT_LEVELS.map((effort) => ( + + ))} +
+
+
); }; diff --git a/apps/ui/src/lib/agent-context-parser.ts b/apps/ui/src/lib/agent-context-parser.ts index 2fe66238..c8af721e 100644 --- a/apps/ui/src/lib/agent-context-parser.ts +++ b/apps/ui/src/lib/agent-context-parser.ts @@ -38,12 +38,13 @@ export function formatModelName(model: string): string { if (model.includes('sonnet')) return 'Sonnet 4.5'; if (model.includes('haiku')) return 'Haiku 4.5'; - // Codex/GPT models + // Codex/GPT models - specific formatting + if (model === 'gpt-5.2-codex') return 'GPT-5.2 Codex'; if (model === 'gpt-5.2') return 'GPT-5.2'; if (model === 'gpt-5.1-codex-max') return 'GPT-5.1 Max'; - if (model === 'gpt-5.1-codex') return 'GPT-5.1 Codex'; if (model === 'gpt-5.1-codex-mini') return 'GPT-5.1 Mini'; if (model === 'gpt-5.1') return 'GPT-5.1'; + // Generic fallbacks for other GPT models if (model.startsWith('gpt-')) return model.toUpperCase(); if (model.match(/^o\d/)) return model.toUpperCase(); // o1, o3, etc. diff --git a/apps/ui/src/lib/utils.ts b/apps/ui/src/lib/utils.ts index 542edf4a..dacfc0af 100644 --- a/apps/ui/src/lib/utils.ts +++ b/apps/ui/src/lib/utils.ts @@ -9,13 +9,14 @@ export function cn(...inputs: ClassValue[]) { /** * Determine if the current model supports extended thinking controls + * Note: This is for Claude's "thinking levels" only, not Codex's "reasoning effort" */ export function modelSupportsThinking(_model?: ModelAlias | string): boolean { if (!_model) return true; - // Check if it's a Codex model with thinking support + // Codex models don't support Claude thinking levels - they use reasoning effort instead if (_model.startsWith('gpt-') && _model in CODEX_MODEL_CONFIG_MAP) { - return codexModelHasThinking(_model as any); + return false; } // All Claude models support thinking diff --git a/libs/types/src/feature.ts b/libs/types/src/feature.ts index 598a16b9..5f44422c 100644 --- a/libs/types/src/feature.ts +++ b/libs/types/src/feature.ts @@ -3,6 +3,7 @@ */ import type { PlanningMode, ThinkingLevel } from './settings.js'; +import type { ReasoningEffort } from './provider.js'; /** * A single entry in the description history @@ -49,6 +50,7 @@ export interface Feature { branchName?: string; // Name of the feature branch (undefined = use current worktree) skipTests?: boolean; thinkingLevel?: ThinkingLevel; + reasoningEffort?: ReasoningEffort; planningMode?: PlanningMode; requirePlanApproval?: boolean; planSpec?: { diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index 29fac9a5..5845c6f0 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -11,6 +11,7 @@ import type { CursorModelId } from './cursor-models.js'; import { CURSOR_MODEL_MAP, getAllCursorModelIds } from './cursor-models.js'; import type { PromptCustomization } from './prompts.js'; import type { CodexSandboxMode, CodexApprovalPolicy } from './codex.js'; +import type { ReasoningEffort } from './provider.js'; // Re-export ModelAlias for convenience export type { ModelAlias }; @@ -108,14 +109,18 @@ const DEFAULT_CODEX_ADDITIONAL_DIRS: string[] = []; /** * PhaseModelEntry - Configuration for a single phase model * - * Encapsulates both the model selection and optional thinking level - * for Claude models. Cursor models handle thinking internally. + * Encapsulates the model selection and optional reasoning/thinking capabilities: + * - Claude models: Use thinkingLevel for extended thinking + * - Codex models: Use reasoningEffort for reasoning intensity + * - Cursor models: Handle thinking internally */ export interface PhaseModelEntry { - /** The model to use (Claude alias or Cursor model ID) */ - model: ModelAlias | CursorModelId; + /** The model to use (Claude alias, Cursor model ID, or Codex model ID) */ + model: ModelAlias | CursorModelId | CodexModelId; /** Extended thinking level (only applies to Claude models, defaults to 'none') */ thinkingLevel?: ThinkingLevel; + /** Reasoning effort level (only applies to Codex models, defaults to 'none') */ + reasoningEffort?: ReasoningEffort; } /**