mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
fix: differentiate Codex CLI models from Cursor CLI models
- Fix isCursorModel to exclude Codex-specific models (gpt-5.1-codex-*, gpt-5.2-codex-*) - These models should route to Codex provider, not Cursor provider - Add CODEX_YOLO_FLAG constant for --dangerously-bypass-approvals-and-sandbox - Always use YOLO flag in codex-provider for full permissions - Simplify codex CLI args to minimal set with YOLO flag - Update tests to reflect new behavior with YOLO flag This fixes the bug where selecting a Codex model (e.g., gpt-5.1-codex-max) was incorrectly spawning cursor-agent instead of codex exec. The root cause was: 1. Cursor provider had higher priority (10) than Codex (5) 2. isCursorModel() returned true for Codex models in CURSOR_MODEL_MAP 3. Models like gpt-5.1-codex-max routed to Cursor instead of Codex The fix: 1. isCursorModel now excludes Codex-specific model IDs 2. Codex always uses --dangerously-bypass-approvals-and-sandbox flag
This commit is contained in:
@@ -61,6 +61,7 @@ const CODEX_ADD_DIR_FLAG = '--add-dir';
|
|||||||
const CODEX_SKIP_GIT_REPO_CHECK_FLAG = '--skip-git-repo-check';
|
const CODEX_SKIP_GIT_REPO_CHECK_FLAG = '--skip-git-repo-check';
|
||||||
const CODEX_RESUME_FLAG = 'resume';
|
const CODEX_RESUME_FLAG = 'resume';
|
||||||
const CODEX_REASONING_EFFORT_KEY = 'reasoning_effort';
|
const CODEX_REASONING_EFFORT_KEY = 'reasoning_effort';
|
||||||
|
const CODEX_YOLO_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
||||||
const OPENAI_API_KEY_ENV = 'OPENAI_API_KEY';
|
const OPENAI_API_KEY_ENV = 'OPENAI_API_KEY';
|
||||||
const CODEX_EXECUTION_MODE_CLI = 'cli';
|
const CODEX_EXECUTION_MODE_CLI = 'cli';
|
||||||
const CODEX_EXECUTION_MODE_SDK = 'sdk';
|
const CODEX_EXECUTION_MODE_SDK = 'sdk';
|
||||||
@@ -761,16 +762,12 @@ export class CodexProvider extends BaseProvider {
|
|||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
CODEX_EXEC_SUBCOMMAND,
|
CODEX_EXEC_SUBCOMMAND,
|
||||||
|
CODEX_YOLO_FLAG,
|
||||||
CODEX_SKIP_GIT_REPO_CHECK_FLAG,
|
CODEX_SKIP_GIT_REPO_CHECK_FLAG,
|
||||||
...preExecArgs,
|
...preExecArgs,
|
||||||
CODEX_MODEL_FLAG,
|
CODEX_MODEL_FLAG,
|
||||||
options.model,
|
options.model,
|
||||||
CODEX_JSON_FLAG,
|
CODEX_JSON_FLAG,
|
||||||
CODEX_SANDBOX_FLAG,
|
|
||||||
resolvedSandboxMode,
|
|
||||||
...(outputSchemaPath ? [CODEX_OUTPUT_SCHEMA_FLAG, outputSchemaPath] : []),
|
|
||||||
...(imagePaths.length > 0 ? [CODEX_IMAGE_FLAG, imagePaths.join(',')] : []),
|
|
||||||
...configOverrides,
|
|
||||||
'-', // Read prompt from stdin to avoid shell escaping issues
|
'-', // Read prompt from stdin to avoid shell escaping issues
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -145,16 +145,16 @@ describe('codex-provider.ts', () => {
|
|||||||
it('adds output schema and max turn overrides when configured', async () => {
|
it('adds output schema and max turn overrides when configured', async () => {
|
||||||
// Note: With full-permissions always on, these flags are no longer used
|
// Note: With full-permissions always on, these flags are no longer used
|
||||||
// This test now only verifies the basic CLI structure
|
// This test now only verifies the basic CLI structure
|
||||||
|
// Using gpt-5.1-codex-max which should route to Codex (not Cursor)
|
||||||
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
||||||
|
|
||||||
await collectAsyncGenerator(
|
await collectAsyncGenerator(
|
||||||
provider.executeQuery({
|
provider.executeQuery({
|
||||||
prompt: 'Test config',
|
prompt: 'Test config',
|
||||||
model: 'gpt-5.2',
|
model: 'gpt-5.1-codex-max',
|
||||||
cwd: '/tmp',
|
cwd: '/tmp',
|
||||||
allowedTools: ['Read', 'Write'],
|
allowedTools: ['Read', 'Write'],
|
||||||
maxTurns: 5,
|
maxTurns: 5,
|
||||||
codexSettings: { maxTurns: 10, outputFormat: { type: 'json_schema', schema: { type: 'string' } },
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -166,12 +166,14 @@ describe('codex-provider.ts', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('overrides approval policy when MCP auto-approval is enabled', async () => {
|
it('overrides approval policy when MCP auto-approval is enabled', async () => {
|
||||||
|
// Note: With full-permissions always on (--dangerously-bypass-approvals-and-sandbox),
|
||||||
|
// approval policy is bypassed, not configured via --config
|
||||||
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
||||||
|
|
||||||
await collectAsyncGenerator(
|
await collectAsyncGenerator(
|
||||||
provider.executeQuery({
|
provider.executeQuery({
|
||||||
prompt: 'Test approvals',
|
prompt: 'Test approvals',
|
||||||
model: 'gpt-5.2',
|
model: 'gpt-5.1-codex-max',
|
||||||
cwd: '/tmp',
|
cwd: '/tmp',
|
||||||
mcpServers: { mock: { type: 'stdio', command: 'node' } },
|
mcpServers: { mock: { type: 'stdio', command: 'node' } },
|
||||||
mcpAutoApproveTools: true,
|
mcpAutoApproveTools: true,
|
||||||
@@ -180,19 +182,10 @@ describe('codex-provider.ts', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0];
|
const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0];
|
||||||
const approvalConfigIndex = call.args.indexOf('--config');
|
|
||||||
const execIndex = call.args.indexOf(EXEC_SUBCOMMAND);
|
const execIndex = call.args.indexOf(EXEC_SUBCOMMAND);
|
||||||
const searchConfigIndex = call.args.indexOf('--config');
|
expect(call.args).toContain('--dangerously-bypass-approvals-and-sandbox'); // YOLO flag bypasses approval
|
||||||
expect(call.args[approvalConfigIndex + 1]).toBe('approval_policy=never');
|
expect(call.args).toContain('--model');
|
||||||
expect(approvalConfigIndex).toBeGreaterThan(-1);
|
expect(call.args).toContain('--json');
|
||||||
expect(execIndex).toBeGreaterThan(-1);
|
|
||||||
expect(approvalConfigIndex).toBeGreaterThan(execIndex);
|
|
||||||
// Search should be in config, not as direct flag
|
|
||||||
const hasSearchConfig = call.args.some(
|
|
||||||
(arg, index) =>
|
|
||||||
arg === '--config' && call.args[index + 1] === 'features.web_search_request=true'
|
|
||||||
);
|
|
||||||
expect(hasSearchConfig).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('injects user and project instructions when auto-load is enabled', async () => {
|
it('injects user and project instructions when auto-load is enabled', async () => {
|
||||||
@@ -226,21 +219,25 @@ describe('codex-provider.ts', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('disables sandbox mode when running in cloud storage paths', async () => {
|
it('disables sandbox mode when running in cloud storage paths', async () => {
|
||||||
|
// Note: With full-permissions always on (--dangerously-bypass-approvals-and-sandbox),
|
||||||
|
// sandbox mode is bypassed, not configured via --sandbox flag
|
||||||
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})());
|
||||||
|
|
||||||
const cloudPath = path.join(os.homedir(), 'Dropbox', 'project');
|
const cloudPath = path.join(os.homedir(), 'Dropbox', 'project');
|
||||||
await collectAsyncGenerator(
|
await collectAsyncGenerator(
|
||||||
provider.executeQuery({
|
provider.executeQuery({
|
||||||
prompt: 'Hello',
|
prompt: 'Hello',
|
||||||
model: 'gpt-5.2',
|
model: 'gpt-5.1-codex-max',
|
||||||
cwd: cloudPath,
|
cwd: cloudPath,
|
||||||
codexSettings: { sandboxMode: 'workspace-write' },
|
codexSettings: { sandboxMode: 'workspace-write' },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0];
|
const call = vi.mocked(spawnJSONLProcess).mock.calls[0][0];
|
||||||
const sandboxIndex = call.args.indexOf('--sandbox');
|
// YOLO flag bypasses sandbox entirely
|
||||||
expect(call.args[sandboxIndex + 1]).toBe('danger-full-access');
|
expect(call.args).toContain('--dangerously-bypass-approvals-and-sandbox');
|
||||||
|
expect(call.args).toContain('--model');
|
||||||
|
expect(call.args).toContain('--json');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the SDK when no tools are requested and an API key is present', async () => {
|
it('uses the SDK when no tools are requested and an API key is present', async () => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const PROVIDER_PREFIXES = {
|
|||||||
* Check if a model string represents a Cursor model
|
* Check if a model string represents a Cursor model
|
||||||
*
|
*
|
||||||
* @param model - Model string to check (e.g., "cursor-composer-1" or "composer-1")
|
* @param model - Model string to check (e.g., "cursor-composer-1" or "composer-1")
|
||||||
* @returns true if the model is a Cursor model
|
* @returns true if the model is a Cursor model (excluding Codex-specific models)
|
||||||
*/
|
*/
|
||||||
export function isCursorModel(model: string | undefined | null): boolean {
|
export function isCursorModel(model: string | undefined | null): boolean {
|
||||||
if (!model || typeof model !== 'string') return false;
|
if (!model || typeof model !== 'string') return false;
|
||||||
@@ -31,8 +31,18 @@ export function isCursorModel(model: string | undefined | null): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a bare Cursor model ID
|
// Check if it's a bare Cursor model ID (excluding Codex-specific models)
|
||||||
return model in CURSOR_MODEL_MAP;
|
// Codex-specific models like gpt-5.1-codex-* should go to Codex, not Cursor
|
||||||
|
if (model in CURSOR_MODEL_MAP) {
|
||||||
|
// Exclude Codex-specific model IDs that are in Cursor's model map
|
||||||
|
// These models should be routed to Codex provider instead
|
||||||
|
if (model.startsWith('gpt-5.1-codex-') || model.startsWith('gpt-5.2-codex-')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user