mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
feat: add reasoning effort support for Codex models
- Add ReasoningEffortSelector component for UI selection - Integrate reasoning effort in feature creation/editing dialogs - Add reasoning effort support to phase model selector - Update agent service and board actions to handle reasoning effort - Add reasoning effort fields to feature and settings types - Update model selector and agent info panel with reasoning effort display - Enhance agent context parser for reasoning effort processing Reasoning effort allows fine-tuned control over Codex model reasoning capabilities, providing options from 'none' to 'xhigh' for different task complexity requirements.
This commit is contained in:
@@ -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<ReasoningEffort, string> = {
|
||||
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 (
|
||||
<div className="mb-3 space-y-2 overflow-hidden">
|
||||
<div className="flex items-center gap-2 text-[11px] flex-wrap">
|
||||
@@ -116,7 +138,7 @@ export function AgentInfoPanel({
|
||||
})()}
|
||||
<span className="font-medium">{formatModelName(feature.model ?? DEFAULT_MODEL)}</span>
|
||||
</div>
|
||||
{feature.thinkingLevel && feature.thinkingLevel !== 'none' ? (
|
||||
{isClaude && feature.thinkingLevel && feature.thinkingLevel !== 'none' ? (
|
||||
<div className="flex items-center gap-1 text-purple-400">
|
||||
<Brain className="w-3 h-3" />
|
||||
<span className="font-medium">
|
||||
@@ -124,6 +146,14 @@ export function AgentInfoPanel({
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{isCodex && feature.reasoningEffort && feature.reasoningEffort !== 'none' ? (
|
||||
<div className="flex items-center gap-1 text-purple-400">
|
||||
<Brain className="w-3 h-3" />
|
||||
<span className="font-medium">
|
||||
{formatReasoningEffort(feature.reasoningEffort as ReasoningEffort)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<Dialog open={open} onOpenChange={handleDialogClose}>
|
||||
<DialogContent
|
||||
@@ -619,6 +632,14 @@ export function AddFeatureDialog({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{newModelAllowsReasoning && (
|
||||
<ReasoningEffortSelector
|
||||
selectedEffort={newFeature.reasoningEffort}
|
||||
onEffortSelect={(effort) =>
|
||||
setNewFeature({ ...newFeature, reasoningEffort: effort })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
@@ -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 && (
|
||||
<ReasoningEffortSelector
|
||||
selectedEffort={editingFeature.reasoningEffort ?? 'none'}
|
||||
onEffortSelect={(effort) =>
|
||||
setEditingFeature({
|
||||
...editingFeature,
|
||||
reasoningEffort: effort,
|
||||
})
|
||||
}
|
||||
testIdPrefix="edit-reasoning-effort"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-2 pt-2 border-t border-border">
|
||||
<Label className="flex items-center gap-2 text-sm">
|
||||
<Brain className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
Reasoning Effort
|
||||
</Label>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{REASONING_EFFORT_LEVELS.map((effort) => (
|
||||
<button
|
||||
key={effort}
|
||||
type="button"
|
||||
onClick={() => onEffortSelect(effort)}
|
||||
className={cn(
|
||||
'flex-1 px-3 py-2 rounded-md border text-sm font-medium transition-colors min-w-[60px]',
|
||||
selectedEffort === effort
|
||||
? 'bg-primary text-primary-foreground border-primary'
|
||||
: 'bg-background hover:bg-accent border-input'
|
||||
)}
|
||||
data-testid={`${testIdPrefix}-${effort}`}
|
||||
>
|
||||
{REASONING_EFFORT_LABELS[effort]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Higher efforts give more reasoning tokens for complex problems.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user