Merge pull request #515 from irisitymichaelgrundberg/feat/codex-reasoning-effort

feat(codex): start a codex session at a chosen reasoning effort
This commit is contained in:
Codeman maintainer
2026-10-04 23:24:39 +02:00
7 changed files with 93 additions and 1 deletions
+13
View File
@@ -24,6 +24,7 @@
import { describe, it, expect } from 'vitest';
import { getCli } from '../src/config/cli-registry/registry.js';
import { buildSpawnCommandFromRegistry, type SpawnBridgeOptions } from '../src/session-cli-registry-bridge.js';
import { CODEX_REASONING_EFFORTS } from '../src/types/session.js';
/** A fixed session id, so `--session-id` is stable across runs. */
const SID = '0f9c2b14-1111-2222-3333-444455556666';
@@ -131,6 +132,18 @@ describe('codex', () => {
it('resumes with a POSITIONAL subcommand, not a flag', () => {
expect(cx({ model: 'gpt-5', resumeSessionId: 'roll_42' })).toBe('codex --model gpt-5 resume roll_42');
});
it('sends reasoning effort as one model_reasoning_effort config value, for every level', () => {
for (const level of CODEX_REASONING_EFFORTS) {
expect(cx({ reasoningEffort: level })).toBe(`codex --config model_reasoning_effort=${level}`);
}
});
it('keeps reasoning effort ahead of the resume subcommand', () => {
expect(cx({ model: 'gpt-5', reasoningEffort: 'high', resumeSessionId: 'roll_42' })).toBe(
'codex --model gpt-5 --config model_reasoning_effort=high resume roll_42'
);
});
});
describe('gemini', () => {
@@ -0,0 +1,41 @@
/**
* @fileoverview `codexConfig.reasoningEffort` on the create routes.
*
* The level becomes part of a `--config model_reasoning_effort=<level>` launch token, so the
* schema admits only the words codex knows; anything else fails the request rather than
* reaching the argv.
*/
import { describe, it, expect } from 'vitest';
import { CreateSessionSchema, QuickStartSchema } from '../src/web/schemas.js';
import { CODEX_REASONING_EFFORTS } from '../src/types/session.js';
describe('codexConfig.reasoningEffort', () => {
it('accepts every level codex knows on both create routes', () => {
for (const level of CODEX_REASONING_EFFORTS) {
const created = CreateSessionSchema.parse({
workingDir: '/tmp',
mode: 'codex',
codexConfig: { reasoningEffort: level },
});
expect(created.codexConfig?.reasoningEffort).toBe(level);
const quick = QuickStartSchema.parse({
caseName: 'work',
mode: 'codex',
codexConfig: { reasoningEffort: level },
});
expect(quick.codexConfig?.reasoningEffort).toBe(level);
}
});
it('rejects a level codex does not know, and anything shaped like shell, on both create routes', () => {
for (const reasoningEffort of ['bogus', 'HIGH', 'high; rm -rf /', '']) {
expect(() =>
CreateSessionSchema.parse({ workingDir: '/tmp', mode: 'codex', codexConfig: { reasoningEffort } })
).toThrow();
expect(() =>
QuickStartSchema.parse({ caseName: 'work', mode: 'codex', codexConfig: { reasoningEffort } })
).toThrow();
}
});
});
@@ -104,6 +104,20 @@ describe('clampExternalCliBypassForOwner — multi-user mode', () => {
expect(out.grokConfig).toEqual({ alwaysApprove: false, model: 'grok-4.5' });
});
it("keeps a non-granted owner's codex reasoning effort while forcing bypass off", async () => {
// The clamp rewrites one field and must carry the rest; a clamp rebuilt from named
// fields would drop the effort here without a word.
const out = await _clampExternalCliBypassForOwner(
'peon',
{ dangerouslyBypassApprovals: true, reasoningEffort: 'xhigh' },
undefined,
undefined,
undefined,
undefined
);
expect(out.codexConfig).toEqual({ dangerouslyBypassApprovals: false, reasoningEffort: 'xhigh' });
});
it('leaves codex/antigravity/grok absent when nothing was sent (they already spawn safe)', async () => {
const out = await _clampExternalCliBypassForOwner('peon', undefined, undefined, undefined, undefined, undefined);
expect(out.codexConfig).toBeUndefined();