mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge branch 'v0.9.0rc' into feat/subagents-skills
This commit is contained in:
@@ -12,5 +12,6 @@ export {
|
||||
getAncestors,
|
||||
formatAncestorContextForPrompt,
|
||||
type DependencyResolutionResult,
|
||||
type DependencySatisfactionOptions,
|
||||
type AncestorContext,
|
||||
} from './resolver.js';
|
||||
|
||||
@@ -174,21 +174,40 @@ function detectCycles(features: Feature[], featureMap: Map<string, Feature>): st
|
||||
return cycles;
|
||||
}
|
||||
|
||||
export interface DependencySatisfactionOptions {
|
||||
/** If true, only require dependencies to not be 'running' (ignore verification requirement) */
|
||||
skipVerification?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a feature's dependencies are satisfied (all complete or verified)
|
||||
*
|
||||
* @param feature - Feature to check
|
||||
* @param allFeatures - All features in the project
|
||||
* @param options - Optional configuration for dependency checking
|
||||
* @returns true if all dependencies are satisfied, false otherwise
|
||||
*/
|
||||
export function areDependenciesSatisfied(feature: Feature, allFeatures: Feature[]): boolean {
|
||||
export function areDependenciesSatisfied(
|
||||
feature: Feature,
|
||||
allFeatures: Feature[],
|
||||
options?: DependencySatisfactionOptions
|
||||
): boolean {
|
||||
if (!feature.dependencies || feature.dependencies.length === 0) {
|
||||
return true; // No dependencies = always ready
|
||||
}
|
||||
|
||||
const skipVerification = options?.skipVerification ?? false;
|
||||
|
||||
return feature.dependencies.every((depId: string) => {
|
||||
const dep = allFeatures.find((f) => f.id === depId);
|
||||
return dep && (dep.status === 'completed' || dep.status === 'verified');
|
||||
if (!dep) return false;
|
||||
|
||||
if (skipVerification) {
|
||||
// When skipping verification, only block if dependency is currently running
|
||||
return dep.status !== 'running';
|
||||
}
|
||||
// Default: require 'completed' or 'verified'
|
||||
return dep.status === 'completed' || dep.status === 'verified';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import {
|
||||
CLAUDE_MODEL_MAP,
|
||||
CURSOR_MODEL_MAP,
|
||||
CODEX_MODEL_MAP,
|
||||
DEFAULT_MODELS,
|
||||
PROVIDER_PREFIXES,
|
||||
isCursorModel,
|
||||
@@ -19,6 +20,11 @@ import {
|
||||
type ThinkingLevel,
|
||||
} from '@automaker/types';
|
||||
|
||||
// Pattern definitions for Codex/OpenAI models
|
||||
const CODEX_MODEL_PREFIXES = ['gpt-'];
|
||||
const OPENAI_O_SERIES_PATTERN = /^o\d/;
|
||||
const OPENAI_O_SERIES_ALLOWED_MODELS = new Set<string>();
|
||||
|
||||
/**
|
||||
* Resolve a model key/alias to a full model string
|
||||
*
|
||||
@@ -56,16 +62,6 @@ export function resolveModelString(
|
||||
return modelKey;
|
||||
}
|
||||
|
||||
// Check if it's a bare Cursor model ID (e.g., "composer-1", "auto", "gpt-4o")
|
||||
if (modelKey in CURSOR_MODEL_MAP) {
|
||||
// Return with cursor- prefix so provider routing works correctly
|
||||
const prefixedModel = `${PROVIDER_PREFIXES.cursor}${modelKey}`;
|
||||
console.log(
|
||||
`[ModelResolver] Detected bare Cursor model ID: "${modelKey}" -> "${prefixedModel}"`
|
||||
);
|
||||
return prefixedModel;
|
||||
}
|
||||
|
||||
// Full Claude model string - pass through unchanged
|
||||
if (modelKey.includes('claude-')) {
|
||||
console.log(`[ModelResolver] Using full Claude model string: ${modelKey}`);
|
||||
@@ -79,6 +75,27 @@ export function resolveModelString(
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// OpenAI/Codex models - check BEFORE bare Cursor models since they overlap
|
||||
// (Cursor supports gpt models, but bare "gpt-*" should route to Codex)
|
||||
if (
|
||||
CODEX_MODEL_PREFIXES.some((prefix) => modelKey.startsWith(prefix)) ||
|
||||
(OPENAI_O_SERIES_PATTERN.test(modelKey) && OPENAI_O_SERIES_ALLOWED_MODELS.has(modelKey))
|
||||
) {
|
||||
console.log(`[ModelResolver] Using OpenAI/Codex model: ${modelKey}`);
|
||||
return modelKey;
|
||||
}
|
||||
|
||||
// Check if it's a bare Cursor model ID (e.g., "composer-1", "auto", "gpt-4o")
|
||||
// Note: This is checked AFTER Codex check to prioritize Codex for bare gpt-* models
|
||||
if (modelKey in CURSOR_MODEL_MAP) {
|
||||
// Return with cursor- prefix so provider routing works correctly
|
||||
const prefixedModel = `${PROVIDER_PREFIXES.cursor}${modelKey}`;
|
||||
console.log(
|
||||
`[ModelResolver] Detected bare Cursor model ID: "${modelKey}" -> "${prefixedModel}"`
|
||||
);
|
||||
return prefixedModel;
|
||||
}
|
||||
|
||||
// Unknown model key - use default
|
||||
console.warn(`[ModelResolver] Unknown model key "${modelKey}", using default: "${defaultModel}"`);
|
||||
return defaultModel;
|
||||
|
||||
@@ -180,7 +180,7 @@ describe('model-resolver', () => {
|
||||
|
||||
it('should use custom default for unknown model key', () => {
|
||||
const customDefault = 'claude-opus-4-20241113';
|
||||
const result = resolveModelString('gpt-4', customDefault);
|
||||
const result = resolveModelString('truly-unknown-model', customDefault);
|
||||
|
||||
expect(result).toBe(customDefault);
|
||||
});
|
||||
|
||||
@@ -93,6 +93,9 @@ export {
|
||||
getClaudeSettingsPath,
|
||||
getClaudeStatsCachePath,
|
||||
getClaudeProjectsDir,
|
||||
getCodexCliPaths,
|
||||
getCodexConfigDir,
|
||||
getCodexAuthPath,
|
||||
getShellPaths,
|
||||
getExtendedPath,
|
||||
// Node.js paths
|
||||
@@ -120,6 +123,9 @@ export {
|
||||
findClaudeCliPath,
|
||||
getClaudeAuthIndicators,
|
||||
type ClaudeAuthIndicators,
|
||||
findCodexCliPath,
|
||||
getCodexAuthIndicators,
|
||||
type CodexAuthIndicators,
|
||||
// Electron userData operations
|
||||
setElectronUserDataPath,
|
||||
getElectronUserDataPath,
|
||||
|
||||
@@ -44,11 +44,15 @@ export async function* spawnJSONLProcess(options: SubprocessOptions): AsyncGener
|
||||
console.log(`[SubprocessManager] Passing ${stdinData.length} bytes via stdin`);
|
||||
}
|
||||
|
||||
// On Windows, .cmd files must be run through shell (cmd.exe)
|
||||
const needsShell = process.platform === 'win32' && command.toLowerCase().endsWith('.cmd');
|
||||
|
||||
const childProcess: ChildProcess = spawn(command, args, {
|
||||
cwd,
|
||||
env: processEnv,
|
||||
// Use 'pipe' for stdin when we need to write data, otherwise 'ignore'
|
||||
stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
||||
shell: needsShell,
|
||||
});
|
||||
|
||||
// Write stdin data if provided
|
||||
@@ -194,10 +198,14 @@ export async function spawnProcess(options: SubprocessOptions): Promise<Subproce
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// On Windows, .cmd files must be run through shell (cmd.exe)
|
||||
const needsShell = process.platform === 'win32' && command.toLowerCase().endsWith('.cmd');
|
||||
|
||||
const childProcess = spawn(command, args, {
|
||||
cwd,
|
||||
env: processEnv,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
shell: needsShell,
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
|
||||
@@ -71,6 +71,131 @@ export function getClaudeCliPaths(): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get NVM-installed Node.js bin paths for CLI tools
|
||||
*/
|
||||
function getNvmBinPaths(): string[] {
|
||||
const nvmDir = process.env.NVM_DIR || path.join(os.homedir(), '.nvm');
|
||||
const versionsDir = path.join(nvmDir, 'versions', 'node');
|
||||
|
||||
try {
|
||||
if (!fsSync.existsSync(versionsDir)) {
|
||||
return [];
|
||||
}
|
||||
const versions = fsSync.readdirSync(versionsDir);
|
||||
return versions.map((version) => path.join(versionsDir, version, 'bin'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fnm (Fast Node Manager) installed Node.js bin paths
|
||||
*/
|
||||
function getFnmBinPaths(): string[] {
|
||||
const homeDir = os.homedir();
|
||||
const possibleFnmDirs = [
|
||||
path.join(homeDir, '.local', 'share', 'fnm', 'node-versions'),
|
||||
path.join(homeDir, '.fnm', 'node-versions'),
|
||||
// macOS
|
||||
path.join(homeDir, 'Library', 'Application Support', 'fnm', 'node-versions'),
|
||||
];
|
||||
|
||||
const binPaths: string[] = [];
|
||||
|
||||
for (const fnmDir of possibleFnmDirs) {
|
||||
try {
|
||||
if (!fsSync.existsSync(fnmDir)) {
|
||||
continue;
|
||||
}
|
||||
const versions = fsSync.readdirSync(fnmDir);
|
||||
for (const version of versions) {
|
||||
binPaths.push(path.join(fnmDir, version, 'installation', 'bin'));
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors for this directory
|
||||
}
|
||||
}
|
||||
|
||||
return binPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common paths where Codex CLI might be installed
|
||||
*/
|
||||
export function getCodexCliPaths(): string[] {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const homeDir = os.homedir();
|
||||
|
||||
if (isWindows) {
|
||||
const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
|
||||
const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
|
||||
return [
|
||||
path.join(homeDir, '.local', 'bin', 'codex.exe'),
|
||||
path.join(appData, 'npm', 'codex.cmd'),
|
||||
path.join(appData, 'npm', 'codex'),
|
||||
path.join(appData, '.npm-global', 'bin', 'codex.cmd'),
|
||||
path.join(appData, '.npm-global', 'bin', 'codex'),
|
||||
// Volta on Windows
|
||||
path.join(homeDir, '.volta', 'bin', 'codex.exe'),
|
||||
// pnpm on Windows
|
||||
path.join(localAppData, 'pnpm', 'codex.cmd'),
|
||||
path.join(localAppData, 'pnpm', 'codex'),
|
||||
];
|
||||
}
|
||||
|
||||
// Include NVM bin paths for codex installed via npm global under NVM
|
||||
const nvmBinPaths = getNvmBinPaths().map((binPath) => path.join(binPath, 'codex'));
|
||||
|
||||
// Include fnm bin paths
|
||||
const fnmBinPaths = getFnmBinPaths().map((binPath) => path.join(binPath, 'codex'));
|
||||
|
||||
// pnpm global bin path
|
||||
const pnpmHome = process.env.PNPM_HOME || path.join(homeDir, '.local', 'share', 'pnpm');
|
||||
|
||||
return [
|
||||
// Standard locations
|
||||
path.join(homeDir, '.local', 'bin', 'codex'),
|
||||
'/opt/homebrew/bin/codex',
|
||||
'/usr/local/bin/codex',
|
||||
'/usr/bin/codex',
|
||||
path.join(homeDir, '.npm-global', 'bin', 'codex'),
|
||||
// Linuxbrew
|
||||
'/home/linuxbrew/.linuxbrew/bin/codex',
|
||||
// Volta
|
||||
path.join(homeDir, '.volta', 'bin', 'codex'),
|
||||
// pnpm global
|
||||
path.join(pnpmHome, 'codex'),
|
||||
// Yarn global
|
||||
path.join(homeDir, '.yarn', 'bin', 'codex'),
|
||||
path.join(homeDir, '.config', 'yarn', 'global', 'node_modules', '.bin', 'codex'),
|
||||
// Snap packages
|
||||
'/snap/bin/codex',
|
||||
// NVM paths
|
||||
...nvmBinPaths,
|
||||
// fnm paths
|
||||
...fnmBinPaths,
|
||||
];
|
||||
}
|
||||
|
||||
const CODEX_CONFIG_DIR_NAME = '.codex';
|
||||
const CODEX_AUTH_FILENAME = 'auth.json';
|
||||
const CODEX_TOKENS_KEY = 'tokens';
|
||||
|
||||
/**
|
||||
* Get the Codex configuration directory path
|
||||
*/
|
||||
export function getCodexConfigDir(): string {
|
||||
return path.join(os.homedir(), CODEX_CONFIG_DIR_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to Codex auth file
|
||||
*/
|
||||
export function getCodexAuthPath(): string {
|
||||
return path.join(getCodexConfigDir(), CODEX_AUTH_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Claude configuration directory path
|
||||
*/
|
||||
@@ -413,6 +538,11 @@ function getAllAllowedSystemPaths(): string[] {
|
||||
getClaudeSettingsPath(),
|
||||
getClaudeStatsCachePath(),
|
||||
getClaudeProjectsDir(),
|
||||
// Codex CLI paths
|
||||
...getCodexCliPaths(),
|
||||
// Codex config directory and files
|
||||
getCodexConfigDir(),
|
||||
getCodexAuthPath(),
|
||||
// Shell paths
|
||||
...getShellPaths(),
|
||||
// Node.js system paths
|
||||
@@ -432,6 +562,8 @@ function getAllAllowedSystemDirs(): string[] {
|
||||
// Claude config
|
||||
getClaudeConfigDir(),
|
||||
getClaudeProjectsDir(),
|
||||
// Codex config
|
||||
getCodexConfigDir(),
|
||||
// Version managers (need recursive access for version directories)
|
||||
...getNvmPaths(),
|
||||
...getFnmPaths(),
|
||||
@@ -740,6 +872,10 @@ export async function findClaudeCliPath(): Promise<string | null> {
|
||||
return findFirstExistingPath(getClaudeCliPaths());
|
||||
}
|
||||
|
||||
export async function findCodexCliPath(): Promise<string | null> {
|
||||
return findFirstExistingPath(getCodexCliPaths());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Claude authentication status by checking various indicators
|
||||
*/
|
||||
@@ -800,8 +936,14 @@ export async function getClaudeAuthIndicators(): Promise<ClaudeAuthIndicators> {
|
||||
const content = await systemPathReadFile(credPath);
|
||||
const credentials = JSON.parse(content);
|
||||
result.hasCredentialsFile = true;
|
||||
// Support multiple credential formats:
|
||||
// 1. Claude Code CLI format: { claudeAiOauth: { accessToken, refreshToken } }
|
||||
// 2. Legacy format: { oauth_token } or { access_token }
|
||||
// 3. API key format: { api_key }
|
||||
const hasClaudeOauth = !!credentials.claudeAiOauth?.accessToken;
|
||||
const hasLegacyOauth = !!(credentials.oauth_token || credentials.access_token);
|
||||
result.credentials = {
|
||||
hasOAuthToken: !!(credentials.oauth_token || credentials.access_token),
|
||||
hasOAuthToken: hasClaudeOauth || hasLegacyOauth,
|
||||
hasApiKey: !!credentials.api_key,
|
||||
};
|
||||
break;
|
||||
@@ -812,3 +954,56 @@ export async function getClaudeAuthIndicators(): Promise<ClaudeAuthIndicators> {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface CodexAuthIndicators {
|
||||
hasAuthFile: boolean;
|
||||
hasOAuthToken: boolean;
|
||||
hasApiKey: boolean;
|
||||
}
|
||||
|
||||
const CODEX_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
|
||||
const CODEX_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY'] as const;
|
||||
|
||||
function hasNonEmptyStringField(record: Record<string, unknown>, keys: readonly string[]): boolean {
|
||||
return keys.some((key) => typeof record[key] === 'string' && record[key]);
|
||||
}
|
||||
|
||||
function getNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const tokens = record[CODEX_TOKENS_KEY];
|
||||
if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
|
||||
return tokens as Record<string, unknown>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getCodexAuthIndicators(): Promise<CodexAuthIndicators> {
|
||||
const result: CodexAuthIndicators = {
|
||||
hasAuthFile: false,
|
||||
hasOAuthToken: false,
|
||||
hasApiKey: false,
|
||||
};
|
||||
|
||||
try {
|
||||
const authContent = await systemPathReadFile(getCodexAuthPath());
|
||||
result.hasAuthFile = true;
|
||||
|
||||
try {
|
||||
const authJson = JSON.parse(authContent) as Record<string, unknown>;
|
||||
result.hasOAuthToken = hasNonEmptyStringField(authJson, CODEX_OAUTH_KEYS);
|
||||
result.hasApiKey = hasNonEmptyStringField(authJson, CODEX_API_KEY_KEYS);
|
||||
const nestedTokens = getNestedTokens(authJson);
|
||||
if (nestedTokens) {
|
||||
result.hasOAuthToken =
|
||||
result.hasOAuthToken || hasNonEmptyStringField(nestedTokens, CODEX_OAUTH_KEYS);
|
||||
result.hasApiKey =
|
||||
result.hasApiKey || hasNonEmptyStringField(nestedTokens, CODEX_API_KEY_KEYS);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors; file exists but contents are unreadable
|
||||
}
|
||||
} catch {
|
||||
// Auth file not found or inaccessible
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -284,11 +284,15 @@ describe('subprocess.ts', () => {
|
||||
const generator = spawnJSONLProcess(options);
|
||||
await collectAsyncGenerator(generator);
|
||||
|
||||
expect(cp.spawn).toHaveBeenCalledWith('my-command', ['--flag', 'value'], {
|
||||
cwd: '/work/dir',
|
||||
env: expect.objectContaining({ CUSTOM_VAR: 'test' }),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
expect(cp.spawn).toHaveBeenCalledWith(
|
||||
'my-command',
|
||||
['--flag', 'value'],
|
||||
expect.objectContaining({
|
||||
cwd: '/work/dir',
|
||||
env: expect.objectContaining({ CUSTOM_VAR: 'test' }),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge env with process.env', async () => {
|
||||
@@ -473,11 +477,15 @@ describe('subprocess.ts', () => {
|
||||
|
||||
await spawnProcess(options);
|
||||
|
||||
expect(cp.spawn).toHaveBeenCalledWith('my-cmd', ['--verbose'], {
|
||||
cwd: '/my/dir',
|
||||
env: expect.objectContaining({ MY_VAR: 'value' }),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
expect(cp.spawn).toHaveBeenCalledWith(
|
||||
'my-cmd',
|
||||
['--verbose'],
|
||||
expect.objectContaining({
|
||||
cwd: '/my/dir',
|
||||
env: expect.objectContaining({ MY_VAR: 'value' }),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty stdout and stderr', async () => {
|
||||
|
||||
100
libs/types/src/codex-models.ts
Normal file
100
libs/types/src/codex-models.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Codex CLI Model IDs
|
||||
* Based on OpenAI Codex CLI official models
|
||||
* Reference: https://developers.openai.com/codex/models/
|
||||
*/
|
||||
export type CodexModelId =
|
||||
| 'gpt-5.2-codex' // Most advanced agentic coding model for complex software engineering
|
||||
| 'gpt-5-codex' // Purpose-built for Codex CLI with versatile tool use
|
||||
| 'gpt-5-codex-mini' // Faster workflows optimized for low-latency code Q&A and editing
|
||||
| 'codex-1' // Version of o3 optimized for software engineering
|
||||
| 'codex-mini-latest' // Version of o4-mini for Codex, optimized for faster workflows
|
||||
| 'gpt-5'; // GPT-5 base flagship model
|
||||
|
||||
/**
|
||||
* Codex model metadata
|
||||
*/
|
||||
export interface CodexModelConfig {
|
||||
id: CodexModelId;
|
||||
label: string;
|
||||
description: string;
|
||||
hasThinking: boolean;
|
||||
/** Whether the model supports vision/image inputs */
|
||||
supportsVision: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete model map for Codex CLI
|
||||
*/
|
||||
export const CODEX_MODEL_CONFIG_MAP: Record<CodexModelId, CodexModelConfig> = {
|
||||
'gpt-5.2-codex': {
|
||||
id: 'gpt-5.2-codex',
|
||||
label: 'GPT-5.2-Codex',
|
||||
description: 'Most advanced agentic coding model for complex software engineering',
|
||||
hasThinking: true,
|
||||
supportsVision: true, // GPT-5 supports vision
|
||||
},
|
||||
'gpt-5-codex': {
|
||||
id: 'gpt-5-codex',
|
||||
label: 'GPT-5-Codex',
|
||||
description: 'Purpose-built for Codex CLI with versatile tool use',
|
||||
hasThinking: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
'gpt-5-codex-mini': {
|
||||
id: 'gpt-5-codex-mini',
|
||||
label: 'GPT-5-Codex-Mini',
|
||||
description: 'Faster workflows optimized for low-latency code Q&A and editing',
|
||||
hasThinking: false,
|
||||
supportsVision: true,
|
||||
},
|
||||
'codex-1': {
|
||||
id: 'codex-1',
|
||||
label: 'Codex-1',
|
||||
description: 'Version of o3 optimized for software engineering',
|
||||
hasThinking: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
'codex-mini-latest': {
|
||||
id: 'codex-mini-latest',
|
||||
label: 'Codex-Mini-Latest',
|
||||
description: 'Version of o4-mini for Codex, optimized for faster workflows',
|
||||
hasThinking: false,
|
||||
supportsVision: true,
|
||||
},
|
||||
'gpt-5': {
|
||||
id: 'gpt-5',
|
||||
label: 'GPT-5',
|
||||
description: 'GPT-5 base flagship model',
|
||||
hasThinking: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper: Check if model has thinking capability
|
||||
*/
|
||||
export function codexModelHasThinking(modelId: CodexModelId): boolean {
|
||||
return CODEX_MODEL_CONFIG_MAP[modelId]?.hasThinking ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get display name for model
|
||||
*/
|
||||
export function getCodexModelLabel(modelId: CodexModelId): string {
|
||||
return CODEX_MODEL_CONFIG_MAP[modelId]?.label ?? modelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get all Codex model IDs
|
||||
*/
|
||||
export function getAllCodexModelIds(): CodexModelId[] {
|
||||
return Object.keys(CODEX_MODEL_CONFIG_MAP) as CodexModelId[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Check if Codex model supports vision
|
||||
*/
|
||||
export function codexModelSupportsVision(modelId: CodexModelId): boolean {
|
||||
return CODEX_MODEL_CONFIG_MAP[modelId]?.supportsVision ?? true;
|
||||
}
|
||||
52
libs/types/src/codex.ts
Normal file
52
libs/types/src/codex.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/** Sandbox modes for Codex CLI command execution */
|
||||
export type CodexSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
|
||||
|
||||
/** Approval policies for Codex CLI tool execution */
|
||||
export type CodexApprovalPolicy = 'untrusted' | 'on-failure' | 'on-request' | 'never';
|
||||
|
||||
/** Codex event types emitted by CLI */
|
||||
export type CodexEventType =
|
||||
| 'thread.started'
|
||||
| 'turn.started'
|
||||
| 'turn.completed'
|
||||
| 'turn.failed'
|
||||
| 'item.completed'
|
||||
| 'error';
|
||||
|
||||
/** Codex item types in CLI events */
|
||||
export type CodexItemType =
|
||||
| 'agent_message'
|
||||
| 'reasoning'
|
||||
| 'command_execution'
|
||||
| 'file_change'
|
||||
| 'mcp_tool_call'
|
||||
| 'web_search'
|
||||
| 'plan_update';
|
||||
|
||||
/** Codex CLI event structure */
|
||||
export interface CodexEvent {
|
||||
type: CodexEventType;
|
||||
thread_id?: string;
|
||||
item?: {
|
||||
type: CodexItemType;
|
||||
content?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** Codex CLI configuration (stored in .automaker/codex-config.json) */
|
||||
export interface CodexCliConfig {
|
||||
/** Default model to use when not specified */
|
||||
defaultModel?: string;
|
||||
/** List of enabled models */
|
||||
models?: string[];
|
||||
}
|
||||
|
||||
/** Codex authentication status */
|
||||
export interface CodexAuthStatus {
|
||||
authenticated: boolean;
|
||||
method: 'oauth' | 'api_key' | 'none';
|
||||
hasCredentialsFile?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
@@ -217,6 +217,7 @@ export interface CursorAuthStatus {
|
||||
authenticated: boolean;
|
||||
method: 'login' | 'api_key' | 'none';
|
||||
hasCredentialsFile?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
|
||||
import type { PlanningMode, ThinkingLevel } from './settings.js';
|
||||
|
||||
/**
|
||||
* A single entry in the description history
|
||||
*/
|
||||
export interface DescriptionHistoryEntry {
|
||||
description: string;
|
||||
timestamp: string; // ISO date string
|
||||
source: 'initial' | 'enhance' | 'edit'; // What triggered this version
|
||||
enhancementMode?: 'improve' | 'technical' | 'simplify' | 'acceptance'; // Only for 'enhance' source
|
||||
}
|
||||
|
||||
export interface FeatureImagePath {
|
||||
id: string;
|
||||
path: string;
|
||||
@@ -54,6 +64,7 @@ export interface Feature {
|
||||
error?: string;
|
||||
summary?: string;
|
||||
startedAt?: string;
|
||||
descriptionHistory?: DescriptionHistoryEntry[]; // History of description changes
|
||||
[key: string]: unknown; // Keep catch-all for extensibility
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,26 @@ export type {
|
||||
McpSSEServerConfig,
|
||||
McpHttpServerConfig,
|
||||
AgentDefinition,
|
||||
ReasoningEffort,
|
||||
} from './provider.js';
|
||||
|
||||
// Codex CLI types
|
||||
export type {
|
||||
CodexSandboxMode,
|
||||
CodexApprovalPolicy,
|
||||
CodexCliConfig,
|
||||
CodexAuthStatus,
|
||||
} from './codex.js';
|
||||
export * from './codex-models.js';
|
||||
|
||||
// Feature types
|
||||
export type { Feature, FeatureImagePath, FeatureTextFilePath, FeatureStatus } from './feature.js';
|
||||
export type {
|
||||
Feature,
|
||||
FeatureImagePath,
|
||||
FeatureTextFilePath,
|
||||
FeatureStatus,
|
||||
DescriptionHistoryEntry,
|
||||
} from './feature.js';
|
||||
|
||||
// Session types
|
||||
export type {
|
||||
@@ -38,7 +54,18 @@ export type { ErrorType, ErrorInfo } from './error.js';
|
||||
export type { ImageData, ImageContentBlock } from './image.js';
|
||||
|
||||
// Model types and constants
|
||||
export { CLAUDE_MODEL_MAP, DEFAULT_MODELS, type ModelAlias } from './model.js';
|
||||
export {
|
||||
CLAUDE_MODEL_MAP,
|
||||
CODEX_MODEL_MAP,
|
||||
CODEX_MODEL_IDS,
|
||||
REASONING_CAPABLE_MODELS,
|
||||
supportsReasoningEffort,
|
||||
getAllCodexModelIds,
|
||||
DEFAULT_MODELS,
|
||||
type ModelAlias,
|
||||
type CodexModelId,
|
||||
type AgentModel,
|
||||
} from './model.js';
|
||||
|
||||
// Event types
|
||||
export type { EventType, EventCallback } from './event.js';
|
||||
@@ -104,11 +131,13 @@ export {
|
||||
} from './settings.js';
|
||||
|
||||
// Model display constants
|
||||
export type { ModelOption, ThinkingLevelOption } from './model-display.js';
|
||||
export type { ModelOption, ThinkingLevelOption, ReasoningEffortOption } from './model-display.js';
|
||||
export {
|
||||
CLAUDE_MODELS,
|
||||
THINKING_LEVELS,
|
||||
THINKING_LEVEL_LABELS,
|
||||
REASONING_EFFORT_LEVELS,
|
||||
REASONING_EFFORT_LABELS,
|
||||
getModelDisplayName,
|
||||
} from './model-display.js';
|
||||
|
||||
@@ -151,6 +180,7 @@ export {
|
||||
PROVIDER_PREFIXES,
|
||||
isCursorModel,
|
||||
isClaudeModel,
|
||||
isCodexModel,
|
||||
getModelProvider,
|
||||
stripProviderPrefix,
|
||||
addProviderPrefix,
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
*/
|
||||
|
||||
import type { ModelAlias, ThinkingLevel, ModelProvider } from './settings.js';
|
||||
import type { ReasoningEffort } from './provider.js';
|
||||
import type { CursorModelId } from './cursor-models.js';
|
||||
import type { AgentModel, CodexModelId } from './model.js';
|
||||
import { CODEX_MODEL_MAP } from './model.js';
|
||||
|
||||
/**
|
||||
* ModelOption - Display metadata for a model option in the UI
|
||||
@@ -63,6 +66,61 @@ export const CLAUDE_MODELS: ModelOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Codex model options with full metadata for UI display
|
||||
* Official models from https://developers.openai.com/codex/models/
|
||||
*/
|
||||
export const CODEX_MODELS: (ModelOption & { hasReasoning?: boolean })[] = [
|
||||
{
|
||||
id: CODEX_MODEL_MAP.gpt52Codex,
|
||||
label: 'GPT-5.2-Codex',
|
||||
description: 'Most advanced agentic coding model (default for ChatGPT users).',
|
||||
badge: 'Premium',
|
||||
provider: 'codex',
|
||||
hasReasoning: true,
|
||||
},
|
||||
{
|
||||
id: CODEX_MODEL_MAP.gpt5Codex,
|
||||
label: 'GPT-5-Codex',
|
||||
description: 'Purpose-built for Codex CLI (default for CLI users).',
|
||||
badge: 'Balanced',
|
||||
provider: 'codex',
|
||||
hasReasoning: true,
|
||||
},
|
||||
{
|
||||
id: CODEX_MODEL_MAP.gpt5CodexMini,
|
||||
label: 'GPT-5-Codex-Mini',
|
||||
description: 'Faster workflows for code Q&A and editing.',
|
||||
badge: 'Speed',
|
||||
provider: 'codex',
|
||||
hasReasoning: false,
|
||||
},
|
||||
{
|
||||
id: CODEX_MODEL_MAP.codex1,
|
||||
label: 'Codex-1',
|
||||
description: 'o3-based model optimized for software engineering.',
|
||||
badge: 'Premium',
|
||||
provider: 'codex',
|
||||
hasReasoning: true,
|
||||
},
|
||||
{
|
||||
id: CODEX_MODEL_MAP.codexMiniLatest,
|
||||
label: 'Codex-Mini-Latest',
|
||||
description: 'o4-mini-based model for faster workflows.',
|
||||
badge: 'Balanced',
|
||||
provider: 'codex',
|
||||
hasReasoning: false,
|
||||
},
|
||||
{
|
||||
id: CODEX_MODEL_MAP.gpt5,
|
||||
label: 'GPT-5',
|
||||
description: 'GPT-5 base flagship model.',
|
||||
badge: 'Balanced',
|
||||
provider: 'codex',
|
||||
hasReasoning: true,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Thinking level options with display labels
|
||||
*
|
||||
@@ -89,6 +147,43 @@ export const THINKING_LEVEL_LABELS: Record<ThinkingLevel, string> = {
|
||||
ultrathink: 'Ultra',
|
||||
};
|
||||
|
||||
/**
|
||||
* ReasoningEffortOption - Display metadata for reasoning effort selection (Codex/OpenAI)
|
||||
*/
|
||||
export interface ReasoningEffortOption {
|
||||
/** Reasoning effort identifier */
|
||||
id: ReasoningEffort;
|
||||
/** Display label */
|
||||
label: string;
|
||||
/** Description of what this level does */
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reasoning effort options for Codex/OpenAI models
|
||||
* All models support reasoning effort levels
|
||||
*/
|
||||
export const REASONING_EFFORT_LEVELS: ReasoningEffortOption[] = [
|
||||
{ id: 'none', label: 'None', description: 'No reasoning tokens (GPT-5.1 models only)' },
|
||||
{ id: 'minimal', label: 'Minimal', description: 'Very quick reasoning' },
|
||||
{ id: 'low', label: 'Low', description: 'Quick responses for simpler queries' },
|
||||
{ id: 'medium', label: 'Medium', description: 'Balance between depth and speed (default)' },
|
||||
{ id: 'high', label: 'High', description: 'Maximizes reasoning depth for critical tasks' },
|
||||
{ id: 'xhigh', label: 'XHigh', description: 'Highest level for gpt-5.1-codex-max and newer' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Map of reasoning effort levels to short display labels
|
||||
*/
|
||||
export const REASONING_EFFORT_LABELS: Record<ReasoningEffort, string> = {
|
||||
none: 'None',
|
||||
minimal: 'Min',
|
||||
low: 'Low',
|
||||
medium: 'Med',
|
||||
high: 'High',
|
||||
xhigh: 'XHigh',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get display name for a model
|
||||
*
|
||||
@@ -107,6 +202,12 @@ export function getModelDisplayName(model: ModelAlias | string): string {
|
||||
haiku: 'Claude Haiku',
|
||||
sonnet: 'Claude Sonnet',
|
||||
opus: 'Claude Opus',
|
||||
[CODEX_MODEL_MAP.gpt52Codex]: 'GPT-5.2-Codex',
|
||||
[CODEX_MODEL_MAP.gpt5Codex]: 'GPT-5-Codex',
|
||||
[CODEX_MODEL_MAP.gpt5CodexMini]: 'GPT-5-Codex-Mini',
|
||||
[CODEX_MODEL_MAP.codex1]: 'Codex-1',
|
||||
[CODEX_MODEL_MAP.codexMiniLatest]: 'Codex-Mini-Latest',
|
||||
[CODEX_MODEL_MAP.gpt5]: 'GPT-5',
|
||||
};
|
||||
return displayNames[model] || model;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,70 @@ export const CLAUDE_MODEL_MAP: Record<string, string> = {
|
||||
opus: 'claude-opus-4-5-20251101',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Codex/OpenAI model identifiers
|
||||
* Based on OpenAI Codex CLI official models
|
||||
* See: https://developers.openai.com/codex/models/
|
||||
*/
|
||||
export const CODEX_MODEL_MAP = {
|
||||
// Codex-specific models
|
||||
/** Most advanced agentic coding model for complex software engineering (default for ChatGPT users) */
|
||||
gpt52Codex: 'gpt-5.2-codex',
|
||||
/** Purpose-built for Codex CLI with versatile tool use (default for CLI users) */
|
||||
gpt5Codex: 'gpt-5-codex',
|
||||
/** Faster workflows optimized for low-latency code Q&A and editing */
|
||||
gpt5CodexMini: 'gpt-5-codex-mini',
|
||||
/** Version of o3 optimized for software engineering */
|
||||
codex1: 'codex-1',
|
||||
/** Version of o4-mini for Codex, optimized for faster workflows */
|
||||
codexMiniLatest: 'codex-mini-latest',
|
||||
|
||||
// Base GPT-5 model (also available in Codex)
|
||||
/** GPT-5 base flagship model */
|
||||
gpt5: 'gpt-5',
|
||||
} as const;
|
||||
|
||||
export const CODEX_MODEL_IDS = Object.values(CODEX_MODEL_MAP);
|
||||
|
||||
/**
|
||||
* Models that support reasoning effort configuration
|
||||
* These models can use reasoning.effort parameter
|
||||
*/
|
||||
export const REASONING_CAPABLE_MODELS = new Set([
|
||||
CODEX_MODEL_MAP.gpt52Codex,
|
||||
CODEX_MODEL_MAP.gpt5Codex,
|
||||
CODEX_MODEL_MAP.gpt5,
|
||||
CODEX_MODEL_MAP.codex1, // o3-based model
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if a model supports reasoning effort configuration
|
||||
*/
|
||||
export function supportsReasoningEffort(modelId: string): boolean {
|
||||
return REASONING_CAPABLE_MODELS.has(modelId as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Codex model IDs as an array
|
||||
*/
|
||||
export function getAllCodexModelIds(): CodexModelId[] {
|
||||
return CODEX_MODEL_IDS as CodexModelId[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default models per provider
|
||||
*/
|
||||
export const DEFAULT_MODELS = {
|
||||
claude: 'claude-opus-4-5-20251101',
|
||||
cursor: 'auto', // Cursor's recommended default
|
||||
codex: CODEX_MODEL_MAP.gpt52Codex, // GPT-5.2-Codex is the most advanced agentic coding model
|
||||
} as const;
|
||||
|
||||
export type ModelAlias = keyof typeof CLAUDE_MODEL_MAP;
|
||||
export type CodexModelId = (typeof CODEX_MODEL_MAP)[keyof typeof CODEX_MODEL_MAP];
|
||||
|
||||
/**
|
||||
* AgentModel - Alias for ModelAlias for backward compatibility
|
||||
* Represents available models across providers
|
||||
*/
|
||||
export type AgentModel = ModelAlias | CodexModelId;
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
|
||||
import type { ModelProvider } from './settings.js';
|
||||
import { CURSOR_MODEL_MAP, type CursorModelId } from './cursor-models.js';
|
||||
import { CLAUDE_MODEL_MAP } from './model.js';
|
||||
import { CLAUDE_MODEL_MAP, CODEX_MODEL_MAP, type CodexModelId } from './model.js';
|
||||
|
||||
/** Provider prefix constants */
|
||||
export const PROVIDER_PREFIXES = {
|
||||
cursor: 'cursor-',
|
||||
codex: 'codex-',
|
||||
// Add new provider prefixes here
|
||||
} as const;
|
||||
|
||||
@@ -52,6 +53,35 @@ export function isClaudeModel(model: string | undefined | null): boolean {
|
||||
return model.includes('claude-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model string represents a Codex/OpenAI model
|
||||
*
|
||||
* @param model - Model string to check (e.g., "gpt-5.2", "o1", "codex-gpt-5.2")
|
||||
* @returns true if the model is a Codex model
|
||||
*/
|
||||
export function isCodexModel(model: string | undefined | null): boolean {
|
||||
if (!model || typeof model !== 'string') return false;
|
||||
|
||||
// Check for explicit codex- prefix
|
||||
if (model.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a gpt- model
|
||||
if (model.startsWith('gpt-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's an o-series model (o1, o3, etc.)
|
||||
if (/^o\d/.test(model)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's in the CODEX_MODEL_MAP
|
||||
const modelValues = Object.values(CODEX_MODEL_MAP);
|
||||
return modelValues.includes(model as CodexModelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the provider for a model string
|
||||
*
|
||||
@@ -59,6 +89,11 @@ export function isClaudeModel(model: string | undefined | null): boolean {
|
||||
* @returns The provider type, defaults to 'claude' for unknown models
|
||||
*/
|
||||
export function getModelProvider(model: string | undefined | null): ModelProvider {
|
||||
// Check Codex first before Cursor, since Cursor also supports gpt models
|
||||
// but bare gpt-* should route to Codex
|
||||
if (isCodexModel(model)) {
|
||||
return 'codex';
|
||||
}
|
||||
if (isCursorModel(model)) {
|
||||
return 'cursor';
|
||||
}
|
||||
@@ -96,6 +131,7 @@ export function stripProviderPrefix(model: string): string {
|
||||
* @example
|
||||
* addProviderPrefix('composer-1', 'cursor') // 'cursor-composer-1'
|
||||
* addProviderPrefix('cursor-composer-1', 'cursor') // 'cursor-composer-1' (no change)
|
||||
* addProviderPrefix('gpt-5.2', 'codex') // 'codex-gpt-5.2'
|
||||
* addProviderPrefix('sonnet', 'claude') // 'sonnet' (Claude doesn't use prefix)
|
||||
*/
|
||||
export function addProviderPrefix(model: string, provider: ModelProvider): string {
|
||||
@@ -105,6 +141,10 @@ export function addProviderPrefix(model: string, provider: ModelProvider): strin
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.cursor)) {
|
||||
return `${PROVIDER_PREFIXES.cursor}${model}`;
|
||||
}
|
||||
} else if (provider === 'codex') {
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
return `${PROVIDER_PREFIXES.codex}${model}`;
|
||||
}
|
||||
}
|
||||
// Claude models don't use prefixes
|
||||
return model;
|
||||
@@ -123,6 +163,7 @@ export function getBareModelId(model: string): string {
|
||||
/**
|
||||
* Normalize a model string to its canonical form
|
||||
* - For Cursor: adds cursor- prefix if missing
|
||||
* - For Codex: can add codex- prefix (but bare gpt-* is also valid)
|
||||
* - For Claude: returns as-is
|
||||
*
|
||||
* @param model - Model string to normalize
|
||||
@@ -136,5 +177,19 @@ export function normalizeModelString(model: string | undefined | null): string {
|
||||
return `${PROVIDER_PREFIXES.cursor}${model}`;
|
||||
}
|
||||
|
||||
// For Codex, bare gpt-* and o-series models are valid canonical forms
|
||||
// Only add prefix if it's in CODEX_MODEL_MAP but doesn't have gpt-/o prefix
|
||||
const codexModelValues = Object.values(CODEX_MODEL_MAP);
|
||||
if (codexModelValues.includes(model as CodexModelId)) {
|
||||
// If it already starts with gpt- or o, it's canonical
|
||||
if (model.startsWith('gpt-') || /^o\d/.test(model)) {
|
||||
return model;
|
||||
}
|
||||
// Otherwise, it might need a prefix (though this is unlikely)
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.codex)) {
|
||||
return `${PROVIDER_PREFIXES.codex}${model}`;
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
*/
|
||||
|
||||
import type { ThinkingLevel } from './settings.js';
|
||||
import type { CodexSandboxMode, CodexApprovalPolicy } from './codex.js';
|
||||
|
||||
/**
|
||||
* Reasoning effort levels for Codex/OpenAI models
|
||||
* Controls the computational intensity and reasoning tokens used.
|
||||
* Based on OpenAI API documentation:
|
||||
* - 'none': No reasoning (GPT-5.1 models only)
|
||||
* - 'minimal': Very quick reasoning
|
||||
* - 'low': Quick responses for simpler queries
|
||||
* - 'medium': Balance between depth and speed (default)
|
||||
* - 'high': Maximizes reasoning depth for critical tasks
|
||||
* - 'xhigh': Highest level, supported by gpt-5.1-codex-max and newer
|
||||
*/
|
||||
export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
||||
|
||||
/**
|
||||
* Configuration for a provider instance
|
||||
@@ -87,11 +101,14 @@ export interface ExecuteOptions {
|
||||
maxTurns?: number;
|
||||
allowedTools?: string[];
|
||||
mcpServers?: Record<string, McpServerConfig>;
|
||||
/** If true, allows all MCP tools unrestricted (no approval needed). Default: false */
|
||||
mcpUnrestrictedTools?: boolean;
|
||||
/** If true, automatically approves all MCP tool calls. Default: undefined (uses approval policy) */
|
||||
mcpAutoApproveTools?: boolean;
|
||||
abortController?: AbortController;
|
||||
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
||||
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
||||
settingSources?: Array<'user' | 'project' | 'local'>; // Sources for CLAUDE.md loading
|
||||
sandbox?: { enabled: boolean; autoAllowBashIfSandboxed?: boolean }; // Sandbox configuration
|
||||
/**
|
||||
* If true, the provider should run in read-only mode (no file modifications).
|
||||
* For Cursor CLI, this omits the --force flag, making it suggest-only.
|
||||
@@ -109,6 +126,31 @@ export interface ExecuteOptions {
|
||||
* Key: agent name, Value: agent definition
|
||||
*/
|
||||
agents?: Record<string, AgentDefinition>;
|
||||
/**
|
||||
* Reasoning effort for Codex/OpenAI models with reasoning capabilities.
|
||||
* Controls how many reasoning tokens the model generates before responding.
|
||||
* Supported values: 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
|
||||
* - none: No reasoning tokens (fastest)
|
||||
* - minimal/low: Quick reasoning for simple tasks
|
||||
* - medium: Balanced reasoning (default)
|
||||
* - high: Extended reasoning for complex tasks
|
||||
* - xhigh: Maximum reasoning for quality-critical tasks
|
||||
* Only applies to models that support reasoning (gpt-5.1-codex-max+, o3-mini, o4-mini)
|
||||
*/
|
||||
reasoningEffort?: ReasoningEffort;
|
||||
codexSettings?: {
|
||||
autoLoadAgents?: boolean;
|
||||
sandboxMode?: CodexSandboxMode;
|
||||
approvalPolicy?: CodexApprovalPolicy;
|
||||
enableWebSearch?: boolean;
|
||||
enableImages?: boolean;
|
||||
additionalDirs?: string[];
|
||||
threadId?: string;
|
||||
};
|
||||
outputFormat?: {
|
||||
type: 'json_schema';
|
||||
schema: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,4 +227,5 @@ export interface ModelDefinition {
|
||||
supportsTools?: boolean;
|
||||
tier?: 'basic' | 'standard' | 'premium';
|
||||
default?: boolean;
|
||||
hasReasoning?: boolean;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
* (for file I/O via SettingsService) and the UI (for state management and sync).
|
||||
*/
|
||||
|
||||
import type { ModelAlias } from './model.js';
|
||||
import type { ModelAlias, AgentModel, CodexModelId } from './model.js';
|
||||
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';
|
||||
|
||||
// Re-export ModelAlias for convenience
|
||||
export type { ModelAlias };
|
||||
@@ -95,7 +96,14 @@ export function getThinkingTokenBudget(level: ThinkingLevel | undefined): number
|
||||
}
|
||||
|
||||
/** ModelProvider - AI model provider for credentials and API key management */
|
||||
export type ModelProvider = 'claude' | 'cursor';
|
||||
export type ModelProvider = 'claude' | 'cursor' | 'codex';
|
||||
|
||||
const DEFAULT_CODEX_AUTO_LOAD_AGENTS = false;
|
||||
const DEFAULT_CODEX_SANDBOX_MODE: CodexSandboxMode = 'workspace-write';
|
||||
const DEFAULT_CODEX_APPROVAL_POLICY: CodexApprovalPolicy = 'on-request';
|
||||
const DEFAULT_CODEX_ENABLE_WEB_SEARCH = false;
|
||||
const DEFAULT_CODEX_ENABLE_IMAGES = true;
|
||||
const DEFAULT_CODEX_ADDITIONAL_DIRS: string[] = [];
|
||||
|
||||
/**
|
||||
* PhaseModelEntry - Configuration for a single phase model
|
||||
@@ -227,7 +235,7 @@ export interface AIProfile {
|
||||
name: string;
|
||||
/** User-friendly description */
|
||||
description: string;
|
||||
/** Provider selection: 'claude' or 'cursor' */
|
||||
/** Provider selection: 'claude', 'cursor', or 'codex' */
|
||||
provider: ModelProvider;
|
||||
/** Whether this is a built-in default profile */
|
||||
isBuiltIn: boolean;
|
||||
@@ -245,6 +253,10 @@ export interface AIProfile {
|
||||
* Note: For Cursor, thinking is embedded in the model ID (e.g., 'claude-sonnet-4-thinking')
|
||||
*/
|
||||
cursorModel?: CursorModelId;
|
||||
|
||||
// Codex-specific settings
|
||||
/** Which Codex/GPT model to use - only for Codex provider */
|
||||
codexModel?: CodexModelId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,6 +274,12 @@ export function profileHasThinking(profile: AIProfile): boolean {
|
||||
return modelConfig?.hasThinking ?? false;
|
||||
}
|
||||
|
||||
if (profile.provider === 'codex') {
|
||||
// Codex models handle thinking internally (o-series models)
|
||||
const model = profile.codexModel || 'gpt-5.2';
|
||||
return model.startsWith('o');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -273,6 +291,10 @@ export function getProfileModelString(profile: AIProfile): string {
|
||||
return `cursor:${profile.cursorModel || 'auto'}`;
|
||||
}
|
||||
|
||||
if (profile.provider === 'codex') {
|
||||
return `codex:${profile.codexModel || 'gpt-5.2'}`;
|
||||
}
|
||||
|
||||
// Claude
|
||||
return profile.model || 'sonnet';
|
||||
}
|
||||
@@ -387,6 +409,18 @@ export interface GlobalSettings {
|
||||
/** Version number for schema migration */
|
||||
version: number;
|
||||
|
||||
// Migration Tracking
|
||||
/** Whether localStorage settings have been migrated to API storage (prevents re-migration) */
|
||||
localStorageMigrated?: boolean;
|
||||
|
||||
// Onboarding / Setup Wizard
|
||||
/** Whether the initial setup wizard has been completed */
|
||||
setupComplete: boolean;
|
||||
/** Whether this is the first run experience (used by UI onboarding) */
|
||||
isFirstRun: boolean;
|
||||
/** Whether Claude setup was skipped during onboarding */
|
||||
skipClaudeSetup: boolean;
|
||||
|
||||
// Theme Configuration
|
||||
/** Currently selected theme */
|
||||
theme: ThemeMode;
|
||||
@@ -406,6 +440,8 @@ export interface GlobalSettings {
|
||||
defaultSkipTests: boolean;
|
||||
/** Default: enable dependency blocking */
|
||||
enableDependencyBlocking: boolean;
|
||||
/** Skip verification requirement in auto-mode (treat 'completed' same as 'verified') */
|
||||
skipVerificationInAutoMode: boolean;
|
||||
/** Default: use git worktrees for feature branches */
|
||||
useWorktrees: boolean;
|
||||
/** Default: only show AI profiles (hide other settings) */
|
||||
@@ -450,6 +486,8 @@ export interface GlobalSettings {
|
||||
projects: ProjectRef[];
|
||||
/** Projects in trash/recycle bin */
|
||||
trashedProjects: TrashedProjectRef[];
|
||||
/** ID of the currently open project (null if none) */
|
||||
currentProjectId: string | null;
|
||||
/** History of recently opened project IDs */
|
||||
projectHistory: string[];
|
||||
/** Current position in project history for navigation */
|
||||
@@ -474,11 +512,25 @@ export interface GlobalSettings {
|
||||
// Claude Agent SDK Settings
|
||||
/** Auto-load CLAUDE.md files using SDK's settingSources option */
|
||||
autoLoadClaudeMd?: boolean;
|
||||
/** Enable sandbox mode for bash commands (default: false, enable for additional security) */
|
||||
enableSandboxMode?: boolean;
|
||||
/** Skip showing the sandbox risk warning dialog */
|
||||
/** Skip the sandbox environment warning dialog on startup */
|
||||
skipSandboxWarning?: boolean;
|
||||
|
||||
// Codex CLI Settings
|
||||
/** Auto-load .codex/AGENTS.md instructions into Codex prompts */
|
||||
codexAutoLoadAgents?: boolean;
|
||||
/** Sandbox mode for Codex CLI command execution */
|
||||
codexSandboxMode?: CodexSandboxMode;
|
||||
/** Approval policy for Codex CLI tool execution */
|
||||
codexApprovalPolicy?: CodexApprovalPolicy;
|
||||
/** Enable web search capability for Codex CLI (--search flag) */
|
||||
codexEnableWebSearch?: boolean;
|
||||
/** Enable image attachment support for Codex CLI (-i flag) */
|
||||
codexEnableImages?: boolean;
|
||||
/** Additional directories with write access (--add-dir flags) */
|
||||
codexAdditionalDirs?: string[];
|
||||
/** Last thread ID for session resumption */
|
||||
codexThreadId?: string;
|
||||
|
||||
// MCP Server Configuration
|
||||
/** List of configured MCP servers for agent use */
|
||||
mcpServers: MCPServerConfig[];
|
||||
@@ -656,7 +708,7 @@ export const DEFAULT_PHASE_MODELS: PhaseModelConfig = {
|
||||
};
|
||||
|
||||
/** Current version of the global settings schema */
|
||||
export const SETTINGS_VERSION = 3;
|
||||
export const SETTINGS_VERSION = 4;
|
||||
/** Current version of the credentials schema */
|
||||
export const CREDENTIALS_VERSION = 1;
|
||||
/** Current version of the project settings schema */
|
||||
@@ -689,6 +741,9 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
|
||||
/** Default global settings used when no settings file exists */
|
||||
export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
version: SETTINGS_VERSION,
|
||||
setupComplete: false,
|
||||
isFirstRun: true,
|
||||
skipClaudeSetup: false,
|
||||
theme: 'dark',
|
||||
sidebarOpen: true,
|
||||
chatHistoryOpen: false,
|
||||
@@ -696,6 +751,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
maxConcurrency: 3,
|
||||
defaultSkipTests: true,
|
||||
enableDependencyBlocking: true,
|
||||
skipVerificationInAutoMode: false,
|
||||
useWorktrees: false,
|
||||
showProfilesOnly: false,
|
||||
defaultPlanningMode: 'skip',
|
||||
@@ -711,6 +767,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
aiProfiles: [],
|
||||
projects: [],
|
||||
trashedProjects: [],
|
||||
currentProjectId: null,
|
||||
projectHistory: [],
|
||||
projectHistoryIndex: -1,
|
||||
lastProjectDir: undefined,
|
||||
@@ -718,8 +775,14 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
worktreePanelCollapsed: false,
|
||||
lastSelectedSessionByProject: {},
|
||||
autoLoadClaudeMd: false,
|
||||
enableSandboxMode: false,
|
||||
skipSandboxWarning: false,
|
||||
codexAutoLoadAgents: DEFAULT_CODEX_AUTO_LOAD_AGENTS,
|
||||
codexSandboxMode: DEFAULT_CODEX_SANDBOX_MODE,
|
||||
codexApprovalPolicy: DEFAULT_CODEX_APPROVAL_POLICY,
|
||||
codexEnableWebSearch: DEFAULT_CODEX_ENABLE_WEB_SEARCH,
|
||||
codexEnableImages: DEFAULT_CODEX_ENABLE_IMAGES,
|
||||
codexAdditionalDirs: DEFAULT_CODEX_ADDITIONAL_DIRS,
|
||||
codexThreadId: undefined,
|
||||
mcpServers: [],
|
||||
enableSkills: true,
|
||||
skillsSources: ['user', 'project'],
|
||||
|
||||
Reference in New Issue
Block a user