mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53: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:
@@ -6,7 +6,7 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as secureFs from '../lib/secure-fs.js';
|
import * as secureFs from '../lib/secure-fs.js';
|
||||||
import type { EventEmitter } from '../lib/events.js';
|
import type { EventEmitter } from '../lib/events.js';
|
||||||
import type { ExecuteOptions, ThinkingLevel } from '@automaker/types';
|
import type { ExecuteOptions, ThinkingLevel, ReasoningEffort } from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
readImageAsBase64,
|
readImageAsBase64,
|
||||||
buildPromptWithImages,
|
buildPromptWithImages,
|
||||||
@@ -56,6 +56,7 @@ interface Session {
|
|||||||
workingDirectory: string;
|
workingDirectory: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
thinkingLevel?: ThinkingLevel; // Thinking level for Claude models
|
thinkingLevel?: ThinkingLevel; // Thinking level for Claude models
|
||||||
|
reasoningEffort?: ReasoningEffort; // Reasoning effort for Codex models
|
||||||
sdkSessionId?: string; // Claude SDK session ID for conversation continuity
|
sdkSessionId?: string; // Claude SDK session ID for conversation continuity
|
||||||
promptQueue: QueuedPrompt[]; // Queue of prompts to auto-run after current task
|
promptQueue: QueuedPrompt[]; // Queue of prompts to auto-run after current task
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,7 @@ export class AgentService {
|
|||||||
imagePaths,
|
imagePaths,
|
||||||
model,
|
model,
|
||||||
thinkingLevel,
|
thinkingLevel,
|
||||||
|
reasoningEffort,
|
||||||
}: {
|
}: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -152,6 +154,7 @@ export class AgentService {
|
|||||||
imagePaths?: string[];
|
imagePaths?: string[];
|
||||||
model?: string;
|
model?: string;
|
||||||
thinkingLevel?: ThinkingLevel;
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
reasoningEffort?: ReasoningEffort;
|
||||||
}) {
|
}) {
|
||||||
const session = this.sessions.get(sessionId);
|
const session = this.sessions.get(sessionId);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -164,7 +167,7 @@ export class AgentService {
|
|||||||
throw new Error('Agent is already processing a message');
|
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) {
|
if (model) {
|
||||||
session.model = model;
|
session.model = model;
|
||||||
await this.updateSession(sessionId, { model });
|
await this.updateSession(sessionId, { model });
|
||||||
@@ -172,6 +175,9 @@ export class AgentService {
|
|||||||
if (thinkingLevel !== undefined) {
|
if (thinkingLevel !== undefined) {
|
||||||
session.thinkingLevel = thinkingLevel;
|
session.thinkingLevel = thinkingLevel;
|
||||||
}
|
}
|
||||||
|
if (reasoningEffort !== undefined) {
|
||||||
|
session.reasoningEffort = reasoningEffort;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate vision support before processing images
|
// Validate vision support before processing images
|
||||||
const effectiveModel = model || session.model;
|
const effectiveModel = model || session.model;
|
||||||
@@ -265,8 +271,9 @@ export class AgentService {
|
|||||||
: baseSystemPrompt;
|
: baseSystemPrompt;
|
||||||
|
|
||||||
// Build SDK options using centralized configuration
|
// 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 effectiveThinkingLevel = thinkingLevel ?? session.thinkingLevel;
|
||||||
|
const effectiveReasoningEffort = reasoningEffort ?? session.reasoningEffort;
|
||||||
const sdkOptions = createChatOptions({
|
const sdkOptions = createChatOptions({
|
||||||
cwd: effectiveWorkDir,
|
cwd: effectiveWorkDir,
|
||||||
model: model,
|
model: model,
|
||||||
@@ -299,6 +306,8 @@ export class AgentService {
|
|||||||
settingSources: sdkOptions.settingSources,
|
settingSources: sdkOptions.settingSources,
|
||||||
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
|
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
|
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
|
// Build prompt content with images
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Feature, ThinkingLevel, useAppStore } from '@/store/app-store';
|
import { Feature, ThinkingLevel, useAppStore } from '@/store/app-store';
|
||||||
|
import type { ReasoningEffort } from '@automaker/types';
|
||||||
|
import { getProviderFromModel } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
AgentTaskInfo,
|
AgentTaskInfo,
|
||||||
parseAgentContext,
|
parseAgentContext,
|
||||||
@@ -37,6 +39,22 @@ function formatThinkingLevel(level: ThinkingLevel | undefined): string {
|
|||||||
return labels[level];
|
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 {
|
interface AgentInfoPanelProps {
|
||||||
feature: Feature;
|
feature: Feature;
|
||||||
contextContent?: string;
|
contextContent?: string;
|
||||||
@@ -106,6 +124,10 @@ export function AgentInfoPanel({
|
|||||||
}, [feature.id, feature.status, contextContent, isCurrentAutoTask]);
|
}, [feature.id, feature.status, contextContent, isCurrentAutoTask]);
|
||||||
// Model/Preset Info for Backlog Cards
|
// Model/Preset Info for Backlog Cards
|
||||||
if (showAgentInfo && feature.status === 'backlog') {
|
if (showAgentInfo && feature.status === 'backlog') {
|
||||||
|
const provider = getProviderFromModel(feature.model);
|
||||||
|
const isCodex = provider === 'codex';
|
||||||
|
const isClaude = provider === 'claude';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 space-y-2 overflow-hidden">
|
<div className="mb-3 space-y-2 overflow-hidden">
|
||||||
<div className="flex items-center gap-2 text-[11px] flex-wrap">
|
<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>
|
<span className="font-medium">{formatModelName(feature.model ?? DEFAULT_MODEL)}</span>
|
||||||
</div>
|
</div>
|
||||||
{feature.thinkingLevel && feature.thinkingLevel !== 'none' ? (
|
{isClaude && feature.thinkingLevel && feature.thinkingLevel !== 'none' ? (
|
||||||
<div className="flex items-center gap-1 text-purple-400">
|
<div className="flex items-center gap-1 text-purple-400">
|
||||||
<Brain className="w-3 h-3" />
|
<Brain className="w-3 h-3" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
@@ -124,6 +146,14 @@ export function AgentInfoPanel({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ import {
|
|||||||
PlanningMode,
|
PlanningMode,
|
||||||
Feature,
|
Feature,
|
||||||
} from '@/store/app-store';
|
} from '@/store/app-store';
|
||||||
|
import type { ReasoningEffort } from '@automaker/types';
|
||||||
|
import { codexModelHasThinking, supportsReasoningEffort } from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
ModelSelector,
|
ModelSelector,
|
||||||
ThinkingLevelSelector,
|
ThinkingLevelSelector,
|
||||||
|
ReasoningEffortSelector,
|
||||||
ProfileQuickSelect,
|
ProfileQuickSelect,
|
||||||
TestingTabContent,
|
TestingTabContent,
|
||||||
PrioritySelector,
|
PrioritySelector,
|
||||||
@@ -78,6 +81,7 @@ type FeatureData = {
|
|||||||
skipTests: boolean;
|
skipTests: boolean;
|
||||||
model: AgentModel;
|
model: AgentModel;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
|
reasoningEffort: ReasoningEffort;
|
||||||
branchName: string; // Can be empty string to use current branch
|
branchName: string; // Can be empty string to use current branch
|
||||||
priority: number;
|
priority: number;
|
||||||
planningMode: PlanningMode;
|
planningMode: PlanningMode;
|
||||||
@@ -134,6 +138,7 @@ export function AddFeatureDialog({
|
|||||||
skipTests: false,
|
skipTests: false,
|
||||||
model: 'opus' as ModelAlias,
|
model: 'opus' as ModelAlias,
|
||||||
thinkingLevel: 'none' as ThinkingLevel,
|
thinkingLevel: 'none' as ThinkingLevel,
|
||||||
|
reasoningEffort: 'none' as ReasoningEffort,
|
||||||
branchName: '',
|
branchName: '',
|
||||||
priority: 2 as number, // Default to medium priority
|
priority: 2 as number, // Default to medium priority
|
||||||
});
|
});
|
||||||
@@ -220,6 +225,9 @@ export function AddFeatureDialog({
|
|||||||
const normalizedThinking = modelSupportsThinking(selectedModel)
|
const normalizedThinking = modelSupportsThinking(selectedModel)
|
||||||
? newFeature.thinkingLevel
|
? newFeature.thinkingLevel
|
||||||
: 'none';
|
: 'none';
|
||||||
|
const normalizedReasoning = supportsReasoningEffort(selectedModel)
|
||||||
|
? newFeature.reasoningEffort
|
||||||
|
: 'none';
|
||||||
|
|
||||||
// Use current branch if toggle is on
|
// Use current branch if toggle is on
|
||||||
// If currentBranch is provided (non-primary worktree), use it
|
// If currentBranch is provided (non-primary worktree), use it
|
||||||
@@ -260,6 +268,7 @@ export function AddFeatureDialog({
|
|||||||
skipTests: newFeature.skipTests,
|
skipTests: newFeature.skipTests,
|
||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
thinkingLevel: normalizedThinking,
|
thinkingLevel: normalizedThinking,
|
||||||
|
reasoningEffort: normalizedReasoning,
|
||||||
branchName: finalBranchName,
|
branchName: finalBranchName,
|
||||||
priority: newFeature.priority,
|
priority: newFeature.priority,
|
||||||
planningMode,
|
planningMode,
|
||||||
@@ -281,6 +290,7 @@ export function AddFeatureDialog({
|
|||||||
model: 'opus',
|
model: 'opus',
|
||||||
priority: 2,
|
priority: 2,
|
||||||
thinkingLevel: 'none',
|
thinkingLevel: 'none',
|
||||||
|
reasoningEffort: 'none',
|
||||||
branchName: '',
|
branchName: '',
|
||||||
});
|
});
|
||||||
setUseCurrentBranch(true);
|
setUseCurrentBranch(true);
|
||||||
@@ -394,6 +404,9 @@ export function AddFeatureDialog({
|
|||||||
const newModelAllowsThinking =
|
const newModelAllowsThinking =
|
||||||
!isCurrentModelCursor && modelSupportsThinking(newFeature.model || 'sonnet');
|
!isCurrentModelCursor && modelSupportsThinking(newFeature.model || 'sonnet');
|
||||||
|
|
||||||
|
// Codex models that support reasoning effort - show reasoning selector
|
||||||
|
const newModelAllowsReasoning = supportsReasoningEffort(newFeature.model || '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleDialogClose}>
|
<Dialog open={open} onOpenChange={handleDialogClose}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
@@ -619,6 +632,14 @@ export function AddFeatureDialog({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{newModelAllowsReasoning && (
|
||||||
|
<ReasoningEffortSelector
|
||||||
|
selectedEffort={newFeature.reasoningEffort}
|
||||||
|
onEffortSelect={(effort) =>
|
||||||
|
setNewFeature({ ...newFeature, reasoningEffort: effort })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -41,9 +41,11 @@ import {
|
|||||||
useAppStore,
|
useAppStore,
|
||||||
PlanningMode,
|
PlanningMode,
|
||||||
} from '@/store/app-store';
|
} from '@/store/app-store';
|
||||||
|
import type { ReasoningEffort } from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
ModelSelector,
|
ModelSelector,
|
||||||
ThinkingLevelSelector,
|
ThinkingLevelSelector,
|
||||||
|
ReasoningEffortSelector,
|
||||||
ProfileQuickSelect,
|
ProfileQuickSelect,
|
||||||
TestingTabContent,
|
TestingTabContent,
|
||||||
PrioritySelector,
|
PrioritySelector,
|
||||||
@@ -60,7 +62,7 @@ import {
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import type { DescriptionHistoryEntry } from '@automaker/types';
|
import type { DescriptionHistoryEntry } from '@automaker/types';
|
||||||
import { DependencyTreeDialog } from './dependency-tree-dialog';
|
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');
|
const logger = createLogger('EditFeatureDialog');
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ interface EditFeatureDialogProps {
|
|||||||
skipTests: boolean;
|
skipTests: boolean;
|
||||||
model: ModelAlias;
|
model: ModelAlias;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
|
reasoningEffort: ReasoningEffort;
|
||||||
imagePaths: DescriptionImagePath[];
|
imagePaths: DescriptionImagePath[];
|
||||||
textFilePaths: DescriptionTextFilePath[];
|
textFilePaths: DescriptionTextFilePath[];
|
||||||
branchName: string; // Can be empty string to use current branch
|
branchName: string; // Can be empty string to use current branch
|
||||||
@@ -180,6 +183,9 @@ export function EditFeatureDialog({
|
|||||||
const normalizedThinking: ThinkingLevel = modelSupportsThinking(selectedModel)
|
const normalizedThinking: ThinkingLevel = modelSupportsThinking(selectedModel)
|
||||||
? (editingFeature.thinkingLevel ?? 'none')
|
? (editingFeature.thinkingLevel ?? 'none')
|
||||||
: 'none';
|
: 'none';
|
||||||
|
const normalizedReasoning: ReasoningEffort = supportsReasoningEffort(selectedModel)
|
||||||
|
? (editingFeature.reasoningEffort ?? 'none')
|
||||||
|
: 'none';
|
||||||
|
|
||||||
// Use current branch if toggle is on
|
// Use current branch if toggle is on
|
||||||
// If currentBranch is provided (non-primary worktree), use it
|
// If currentBranch is provided (non-primary worktree), use it
|
||||||
@@ -195,6 +201,7 @@ export function EditFeatureDialog({
|
|||||||
skipTests: editingFeature.skipTests ?? false,
|
skipTests: editingFeature.skipTests ?? false,
|
||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
thinkingLevel: normalizedThinking,
|
thinkingLevel: normalizedThinking,
|
||||||
|
reasoningEffort: normalizedReasoning,
|
||||||
imagePaths: editingFeature.imagePaths ?? [],
|
imagePaths: editingFeature.imagePaths ?? [],
|
||||||
textFilePaths: editingFeature.textFilePaths ?? [],
|
textFilePaths: editingFeature.textFilePaths ?? [],
|
||||||
branchName: finalBranchName,
|
branchName: finalBranchName,
|
||||||
@@ -233,15 +240,17 @@ export function EditFeatureDialog({
|
|||||||
if (!editingFeature) return;
|
if (!editingFeature) return;
|
||||||
// For Cursor models, thinking is handled by the model itself
|
// For Cursor models, thinking is handled by the model itself
|
||||||
// For Claude models, check if it supports extended thinking
|
// For Claude models, check if it supports extended thinking
|
||||||
|
// For Codex models, use reasoning effort instead
|
||||||
const isCursor = isCursorModel(model);
|
const isCursor = isCursorModel(model);
|
||||||
|
const supportsThinking = modelSupportsThinking(model);
|
||||||
|
const supportsReasoning = supportsReasoningEffort(model);
|
||||||
|
|
||||||
setEditingFeature({
|
setEditingFeature({
|
||||||
...editingFeature,
|
...editingFeature,
|
||||||
model: model as ModelAlias,
|
model: model as ModelAlias,
|
||||||
thinkingLevel: isCursor
|
thinkingLevel:
|
||||||
? 'none'
|
isCursor || !supportsThinking ? 'none' : (editingFeature.thinkingLevel ?? 'none'),
|
||||||
: modelSupportsThinking(model)
|
reasoningEffort: !supportsReasoning ? 'none' : (editingFeature.reasoningEffort ?? 'none'),
|
||||||
? editingFeature.thinkingLevel
|
|
||||||
: 'none',
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -312,6 +321,9 @@ export function EditFeatureDialog({
|
|||||||
const editModelAllowsThinking =
|
const editModelAllowsThinking =
|
||||||
!isCurrentModelCursor && modelSupportsThinking(editingFeature?.model);
|
!isCurrentModelCursor && modelSupportsThinking(editingFeature?.model);
|
||||||
|
|
||||||
|
// Codex models that support reasoning effort - show reasoning selector
|
||||||
|
const editModelAllowsReasoning = supportsReasoningEffort(editingFeature?.model || '');
|
||||||
|
|
||||||
if (!editingFeature) {
|
if (!editingFeature) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -634,6 +646,18 @@ export function EditFeatureDialog({
|
|||||||
testIdPrefix="edit-thinking-level"
|
testIdPrefix="edit-thinking-level"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{editModelAllowsReasoning && (
|
||||||
|
<ReasoningEffortSelector
|
||||||
|
selectedEffort={editingFeature.reasoningEffort ?? 'none'}
|
||||||
|
onEffortSelect={(effort) =>
|
||||||
|
setEditingFeature({
|
||||||
|
...editingFeature,
|
||||||
|
reasoningEffort: effort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
testIdPrefix="edit-reasoning-effort"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
PlanningMode,
|
PlanningMode,
|
||||||
useAppStore,
|
useAppStore,
|
||||||
} from '@/store/app-store';
|
} from '@/store/app-store';
|
||||||
|
import type { ReasoningEffort } from '@automaker/types';
|
||||||
import { FeatureImagePath as DescriptionImagePath } from '@/components/ui/description-image-dropzone';
|
import { FeatureImagePath as DescriptionImagePath } from '@/components/ui/description-image-dropzone';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -222,6 +223,7 @@ export function useBoardActions({
|
|||||||
skipTests: boolean;
|
skipTests: boolean;
|
||||||
model: ModelAlias;
|
model: ModelAlias;
|
||||||
thinkingLevel: ThinkingLevel;
|
thinkingLevel: ThinkingLevel;
|
||||||
|
reasoningEffort: ReasoningEffort;
|
||||||
imagePaths: DescriptionImagePath[];
|
imagePaths: DescriptionImagePath[];
|
||||||
branchName: string;
|
branchName: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './model-constants';
|
export * from './model-constants';
|
||||||
export * from './model-selector';
|
export * from './model-selector';
|
||||||
export * from './thinking-level-selector';
|
export * from './thinking-level-selector';
|
||||||
|
export * from './reasoning-effort-selector';
|
||||||
export * from './profile-quick-select';
|
export * from './profile-quick-select';
|
||||||
export * from './profile-select';
|
export * from './profile-select';
|
||||||
export * from './testing-tab-content';
|
export * from './testing-tab-content';
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export function ModelSelector({
|
|||||||
// Switch to Cursor's default model (from global settings)
|
// Switch to Cursor's default model (from global settings)
|
||||||
onModelSelect(`${PROVIDER_PREFIXES.cursor}${cursorDefaultModel}`);
|
onModelSelect(`${PROVIDER_PREFIXES.cursor}${cursorDefaultModel}`);
|
||||||
} else if (provider === 'codex' && selectedProvider !== 'codex') {
|
} else if (provider === 'codex' && selectedProvider !== 'codex') {
|
||||||
// Switch to Codex's default model (gpt-5.2)
|
// Switch to Codex's default model (gpt-5.2-codex)
|
||||||
onModelSelect('gpt-5.2');
|
onModelSelect('gpt-5.2-codex');
|
||||||
} else if (provider === 'claude' && selectedProvider !== 'claude') {
|
} else if (provider === 'claude' && selectedProvider !== 'claude') {
|
||||||
// Switch to Claude's default model
|
// Switch to Claude's default model
|
||||||
onModelSelect('sonnet');
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ import { useAppStore } from '@/store/app-store';
|
|||||||
import type {
|
import type {
|
||||||
ModelAlias,
|
ModelAlias,
|
||||||
CursorModelId,
|
CursorModelId,
|
||||||
|
CodexModelId,
|
||||||
GroupedModel,
|
GroupedModel,
|
||||||
PhaseModelEntry,
|
PhaseModelEntry,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
|
ReasoningEffort,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
stripProviderPrefix,
|
stripProviderPrefix,
|
||||||
@@ -15,6 +17,7 @@ import {
|
|||||||
isGroupSelected,
|
isGroupSelected,
|
||||||
getSelectedVariant,
|
getSelectedVariant,
|
||||||
isCursorModel,
|
isCursorModel,
|
||||||
|
codexModelHasThinking,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import {
|
import {
|
||||||
CLAUDE_MODELS,
|
CLAUDE_MODELS,
|
||||||
@@ -22,6 +25,8 @@ import {
|
|||||||
CODEX_MODELS,
|
CODEX_MODELS,
|
||||||
THINKING_LEVELS,
|
THINKING_LEVELS,
|
||||||
THINKING_LEVEL_LABELS,
|
THINKING_LEVEL_LABELS,
|
||||||
|
REASONING_EFFORT_LEVELS,
|
||||||
|
REASONING_EFFORT_LABELS,
|
||||||
} from '@/components/views/board-view/shared/model-constants';
|
} from '@/components/views/board-view/shared/model-constants';
|
||||||
import { Check, ChevronsUpDown, Star, ChevronRight } from 'lucide-react';
|
import { Check, ChevronsUpDown, Star, ChevronRight } from 'lucide-react';
|
||||||
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
|
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
|
||||||
@@ -69,14 +74,17 @@ export function PhaseModelSelector({
|
|||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [expandedGroup, setExpandedGroup] = React.useState<string | null>(null);
|
const [expandedGroup, setExpandedGroup] = React.useState<string | null>(null);
|
||||||
const [expandedClaudeModel, setExpandedClaudeModel] = React.useState<ModelAlias | null>(null);
|
const [expandedClaudeModel, setExpandedClaudeModel] = React.useState<ModelAlias | null>(null);
|
||||||
|
const [expandedCodexModel, setExpandedCodexModel] = React.useState<CodexModelId | null>(null);
|
||||||
const commandListRef = React.useRef<HTMLDivElement>(null);
|
const commandListRef = React.useRef<HTMLDivElement>(null);
|
||||||
const expandedTriggerRef = React.useRef<HTMLDivElement>(null);
|
const expandedTriggerRef = React.useRef<HTMLDivElement>(null);
|
||||||
const expandedClaudeTriggerRef = React.useRef<HTMLDivElement>(null);
|
const expandedClaudeTriggerRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const expandedCodexTriggerRef = React.useRef<HTMLDivElement>(null);
|
||||||
const { enabledCursorModels, favoriteModels, toggleFavoriteModel } = useAppStore();
|
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 selectedModel = value.model;
|
||||||
const selectedThinkingLevel = value.thinkingLevel || 'none';
|
const selectedThinkingLevel = value.thinkingLevel || 'none';
|
||||||
|
const selectedReasoningEffort = value.reasoningEffort || 'none';
|
||||||
|
|
||||||
// Close expanded group when trigger scrolls out of view
|
// Close expanded group when trigger scrolls out of view
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -124,6 +132,29 @@ export function PhaseModelSelector({
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [expandedClaudeModel]);
|
}, [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
|
// Filter Cursor models to only show enabled ones
|
||||||
const availableCursorModels = CURSOR_MODELS.filter((model) => {
|
const availableCursorModels = CURSOR_MODELS.filter((model) => {
|
||||||
const cursorId = stripProviderPrefix(model.id) as CursorModelId;
|
const cursorId = stripProviderPrefix(model.id) as CursorModelId;
|
||||||
@@ -241,55 +272,183 @@ export function PhaseModelSelector({
|
|||||||
return { favorites: favs, claude: cModels, cursor: curModels, codex: codModels };
|
return { favorites: favs, claude: cModels, cursor: curModels, codex: codModels };
|
||||||
}, [favoriteModels, availableCursorModels]);
|
}, [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 renderCodexModelItem = (model: (typeof CODEX_MODELS)[0]) => {
|
||||||
const isSelected = selectedModel === model.id;
|
const isSelected = selectedModel === model.id;
|
||||||
const isFavorite = favoriteModels.includes(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 (
|
||||||
|
<CommandItem
|
||||||
|
key={model.id}
|
||||||
|
value={model.label}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange({ model: model.id as CodexModelId });
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
className="group flex items-center justify-between py-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 overflow-hidden">
|
||||||
|
<OpenAIIcon
|
||||||
|
className={cn(
|
||||||
|
'h-4 w-4 shrink-0',
|
||||||
|
isSelected ? 'text-primary' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col truncate">
|
||||||
|
<span className={cn('truncate font-medium', isSelected && 'text-primary')}>
|
||||||
|
{model.label}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">{model.description}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 ml-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className={cn(
|
||||||
|
'h-6 w-6 hover:bg-transparent hover:text-yellow-500 focus:ring-0',
|
||||||
|
isFavorite
|
||||||
|
? 'text-yellow-500 opacity-100'
|
||||||
|
: 'opacity-0 group-hover:opacity-100 text-muted-foreground'
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleFavoriteModel(model.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Star className={cn('h-3.5 w-3.5', isFavorite && 'fill-current')} />
|
||||||
|
</Button>
|
||||||
|
{isSelected && <Check className="h-4 w-4 text-primary shrink-0" />}
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model supports reasoning - show popover with reasoning effort options
|
||||||
return (
|
return (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={model.id}
|
key={model.id}
|
||||||
value={model.label}
|
value={model.label}
|
||||||
onSelect={() => {
|
onSelect={() => setExpandedCodexModel(isExpanded ? null : (model.id as CodexModelId))}
|
||||||
onChange({ model: model.id });
|
className="p-0 data-[selected=true]:bg-transparent"
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
className="group flex items-center justify-between py-2"
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 overflow-hidden">
|
<Popover
|
||||||
<OpenAIIcon
|
open={isExpanded}
|
||||||
className={cn(
|
onOpenChange={(isOpen) => {
|
||||||
'h-4 w-4 shrink-0',
|
if (!isOpen) {
|
||||||
isSelected ? 'text-primary' : 'text-muted-foreground'
|
setExpandedCodexModel(null);
|
||||||
)}
|
}
|
||||||
/>
|
}}
|
||||||
<div className="flex flex-col truncate">
|
>
|
||||||
<span className={cn('truncate font-medium', isSelected && 'text-primary')}>
|
<PopoverTrigger asChild>
|
||||||
{model.label}
|
<div
|
||||||
</span>
|
ref={isExpanded ? expandedCodexTriggerRef : undefined}
|
||||||
<span className="truncate text-xs text-muted-foreground">{model.description}</span>
|
className={cn(
|
||||||
</div>
|
'w-full group flex items-center justify-between py-2 px-2 rounded-sm cursor-pointer',
|
||||||
</div>
|
'hover:bg-accent',
|
||||||
|
isExpanded && 'bg-accent'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 overflow-hidden">
|
||||||
|
<OpenAIIcon
|
||||||
|
className={cn(
|
||||||
|
'h-4 w-4 shrink-0',
|
||||||
|
isSelected ? 'text-primary' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col truncate">
|
||||||
|
<span className={cn('truncate font-medium', isSelected && 'text-primary')}>
|
||||||
|
{model.label}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
{isSelected && currentReasoning !== 'none'
|
||||||
|
? `Reasoning: ${REASONING_EFFORT_LABELS[currentReasoning]}`
|
||||||
|
: model.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1 ml-2">
|
<div className="flex items-center gap-1 ml-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-6 w-6 hover:bg-transparent hover:text-yellow-500 focus:ring-0',
|
'h-6 w-6 hover:bg-transparent hover:text-yellow-500 focus:ring-0',
|
||||||
isFavorite
|
isFavorite
|
||||||
? 'text-yellow-500 opacity-100'
|
? 'text-yellow-500 opacity-100'
|
||||||
: 'opacity-0 group-hover:opacity-100 text-muted-foreground'
|
: 'opacity-0 group-hover:opacity-100 text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleFavoriteModel(model.id);
|
toggleFavoriteModel(model.id);
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<Star className={cn('h-3.5 w-3.5', isFavorite && 'fill-current')} />
|
||||||
|
</Button>
|
||||||
|
{isSelected && <Check className="h-4 w-4 text-primary shrink-0" />}
|
||||||
|
<ChevronRight
|
||||||
|
className={cn(
|
||||||
|
'h-4 w-4 text-muted-foreground transition-transform',
|
||||||
|
isExpanded && 'rotate-90'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
className="w-[220px] p-1"
|
||||||
|
sideOffset={8}
|
||||||
|
collisionPadding={16}
|
||||||
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<Star className={cn('h-3.5 w-3.5', isFavorite && 'fill-current')} />
|
<div className="space-y-1">
|
||||||
</Button>
|
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground border-b border-border/50 mb-1">
|
||||||
{isSelected && <Check className="h-4 w-4 text-primary shrink-0" />}
|
Reasoning Effort
|
||||||
</div>
|
</div>
|
||||||
|
{REASONING_EFFORT_LEVELS.map((effort) => (
|
||||||
|
<button
|
||||||
|
key={effort}
|
||||||
|
onClick={() => {
|
||||||
|
onChange({
|
||||||
|
model: model.id as CodexModelId,
|
||||||
|
reasoningEffort: effort,
|
||||||
|
});
|
||||||
|
setExpandedCodexModel(null);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'w-full flex items-center justify-between px-2 py-2 rounded-sm text-sm',
|
||||||
|
'hover:bg-accent cursor-pointer transition-colors',
|
||||||
|
isSelected && currentReasoning === effort && 'bg-accent text-accent-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<span className="font-medium">{REASONING_EFFORT_LABELS[effort]}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{effort === 'none' && 'No reasoning capability'}
|
||||||
|
{effort === 'minimal' && 'Minimal reasoning'}
|
||||||
|
{effort === 'low' && 'Light reasoning'}
|
||||||
|
{effort === 'medium' && 'Moderate reasoning'}
|
||||||
|
{effort === 'high' && 'Deep reasoning'}
|
||||||
|
{effort === 'xhigh' && 'Maximum reasoning'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{isSelected && currentReasoning === effort && (
|
||||||
|
<Check className="h-3.5 w-3.5 text-primary" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,12 +38,13 @@ export function formatModelName(model: string): string {
|
|||||||
if (model.includes('sonnet')) return 'Sonnet 4.5';
|
if (model.includes('sonnet')) return 'Sonnet 4.5';
|
||||||
if (model.includes('haiku')) return 'Haiku 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.2') return 'GPT-5.2';
|
||||||
if (model === 'gpt-5.1-codex-max') return 'GPT-5.1 Max';
|
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-codex-mini') return 'GPT-5.1 Mini';
|
||||||
if (model === 'gpt-5.1') return 'GPT-5.1';
|
if (model === 'gpt-5.1') return 'GPT-5.1';
|
||||||
|
// Generic fallbacks for other GPT models
|
||||||
if (model.startsWith('gpt-')) return model.toUpperCase();
|
if (model.startsWith('gpt-')) return model.toUpperCase();
|
||||||
if (model.match(/^o\d/)) return model.toUpperCase(); // o1, o3, etc.
|
if (model.match(/^o\d/)) return model.toUpperCase(); // o1, o3, etc.
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the current model supports extended thinking controls
|
* 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 {
|
export function modelSupportsThinking(_model?: ModelAlias | string): boolean {
|
||||||
if (!_model) return true;
|
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) {
|
if (_model.startsWith('gpt-') && _model in CODEX_MODEL_CONFIG_MAP) {
|
||||||
return codexModelHasThinking(_model as any);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All Claude models support thinking
|
// All Claude models support thinking
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PlanningMode, ThinkingLevel } from './settings.js';
|
import type { PlanningMode, ThinkingLevel } from './settings.js';
|
||||||
|
import type { ReasoningEffort } from './provider.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single entry in the description history
|
* 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)
|
branchName?: string; // Name of the feature branch (undefined = use current worktree)
|
||||||
skipTests?: boolean;
|
skipTests?: boolean;
|
||||||
thinkingLevel?: ThinkingLevel;
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
reasoningEffort?: ReasoningEffort;
|
||||||
planningMode?: PlanningMode;
|
planningMode?: PlanningMode;
|
||||||
requirePlanApproval?: boolean;
|
requirePlanApproval?: boolean;
|
||||||
planSpec?: {
|
planSpec?: {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { CursorModelId } from './cursor-models.js';
|
|||||||
import { CURSOR_MODEL_MAP, getAllCursorModelIds } from './cursor-models.js';
|
import { CURSOR_MODEL_MAP, getAllCursorModelIds } from './cursor-models.js';
|
||||||
import type { PromptCustomization } from './prompts.js';
|
import type { PromptCustomization } from './prompts.js';
|
||||||
import type { CodexSandboxMode, CodexApprovalPolicy } from './codex.js';
|
import type { CodexSandboxMode, CodexApprovalPolicy } from './codex.js';
|
||||||
|
import type { ReasoningEffort } from './provider.js';
|
||||||
|
|
||||||
// Re-export ModelAlias for convenience
|
// Re-export ModelAlias for convenience
|
||||||
export type { ModelAlias };
|
export type { ModelAlias };
|
||||||
@@ -108,14 +109,18 @@ const DEFAULT_CODEX_ADDITIONAL_DIRS: string[] = [];
|
|||||||
/**
|
/**
|
||||||
* PhaseModelEntry - Configuration for a single phase model
|
* PhaseModelEntry - Configuration for a single phase model
|
||||||
*
|
*
|
||||||
* Encapsulates both the model selection and optional thinking level
|
* Encapsulates the model selection and optional reasoning/thinking capabilities:
|
||||||
* for Claude models. Cursor models handle thinking internally.
|
* - Claude models: Use thinkingLevel for extended thinking
|
||||||
|
* - Codex models: Use reasoningEffort for reasoning intensity
|
||||||
|
* - Cursor models: Handle thinking internally
|
||||||
*/
|
*/
|
||||||
export interface PhaseModelEntry {
|
export interface PhaseModelEntry {
|
||||||
/** The model to use (Claude alias or Cursor model ID) */
|
/** The model to use (Claude alias, Cursor model ID, or Codex model ID) */
|
||||||
model: ModelAlias | CursorModelId;
|
model: ModelAlias | CursorModelId | CodexModelId;
|
||||||
/** Extended thinking level (only applies to Claude models, defaults to 'none') */
|
/** Extended thinking level (only applies to Claude models, defaults to 'none') */
|
||||||
thinkingLevel?: ThinkingLevel;
|
thinkingLevel?: ThinkingLevel;
|
||||||
|
/** Reasoning effort level (only applies to Codex models, defaults to 'none') */
|
||||||
|
reasoningEffort?: ReasoningEffort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user