Merge pull request #1069 from sgaluza/feat/opus-4-7-adaptive-thinking

fix(cliproxy): use adaptive thinking for Opus 4.7 + add 'max' level
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-04-22 22:03:21 -04:00
commit 142381078e
20 files changed
+220 -22

No files matched your search

@@ -538,6 +538,20 @@ describe('applyThinkingConfig - composite variant integration', () => {
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('claude-opus-4-6-thinking(32768)');
});
it('rewrites legacy budget suffixes for claude level-based models', () => {
const envVars: NodeJS.ProcessEnv = {
ANTHROPIC_MODEL: 'claude-opus-4-7(32768)',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-7(32768)',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001',
};
const result = applyThinkingConfig(envVars, 'claude' as CLIProxyProvider, 'max');
expect(result.ANTHROPIC_MODEL).toBe('claude-opus-4-7(max)');
expect(result.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('claude-opus-4-7(max)');
});
it('should use codex effort suffix style when provider is codex', () => {
const envVars: NodeJS.ProcessEnv = {
ANTHROPIC_MODEL: 'gpt-5.3-codex',
@@ -58,4 +58,24 @@ describe('model-catalog compatibility lookups', () => {
expect(catalog?.models.map((model) => model.id)).toEqual(['gemini-2.5-pro']);
});
it('preserves static maxLevel when live thinking metadata omits it', () => {
const catalog = mergeCatalog('claude', [
{
id: 'claude-opus-4-7',
display_name: 'Claude Opus 4.7',
thinking: {
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
dynamic_allowed: true,
},
},
]);
expect(catalog?.models[0]?.thinking).toMatchObject({
type: 'levels',
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
maxLevel: 'max',
dynamicAllowed: true,
});
});
});
+5 -3
View File
@@ -147,9 +147,11 @@ describe('Model Catalog', () => {
const opus47 = MODEL_CATALOG.claude.models.find((m) => m.id === 'claude-opus-4-7');
assert(opus47, 'Should include Claude Opus 4.7');
assert.strictEqual(opus47.name, 'Claude Opus 4.7');
// Claude provider differs from AGY: zero thinking budget is disallowed,
// and extended (1M) context is available on the Anthropic API.
assert.strictEqual(opus47.thinking.zeroAllowed, false);
// Opus 4.7 requires adaptive thinking (type: 'levels'); manual budget_tokens
// is rejected by the Anthropic API with 400. Extended (1M) context is available.
assert.strictEqual(opus47.thinking.type, 'levels');
assert.deepStrictEqual(opus47.thinking.levels, ['low', 'medium', 'high', 'xhigh', 'max']);
assert.strictEqual(opus47.thinking.maxLevel, 'max');
assert.strictEqual(opus47.extendedContext, true);
});
@@ -29,6 +29,7 @@ describe('Thinking Validator', () => {
expect(VALID_THINKING_LEVELS).toContain('medium');
expect(VALID_THINKING_LEVELS).toContain('high');
expect(VALID_THINKING_LEVELS).toContain('xhigh');
expect(VALID_THINKING_LEVELS).toContain('max');
expect(VALID_THINKING_LEVELS).toContain('auto');
});
@@ -38,6 +39,24 @@ describe('Thinking Validator', () => {
expect(THINKING_LEVEL_BUDGETS.medium).toBe(8192);
expect(THINKING_LEVEL_BUDGETS.high).toBe(24576);
expect(THINKING_LEVEL_BUDGETS.xhigh).toBe(32768);
// `max` sits above xhigh — unconstrained thinking on Opus 4.7 / Mythos
expect(THINKING_LEVEL_BUDGETS.max).toBeGreaterThan(THINKING_LEVEL_BUDGETS.xhigh);
});
it('should treat max as a distinct top tier on models that list it (Opus 4.7)', () => {
// Opus 4.7 exposes both xhigh and max; max must not collapse into xhigh.
const result = validateThinking('claude', 'claude-opus-4-7', 'max');
expect(result.valid).toBe(true);
expect(result.value).toBe('max');
expect(result.warning).toBeUndefined();
});
it('should still alias max -> xhigh for models without a max level (backcompat)', () => {
// Codex catalog uses ['low','medium','high','xhigh'] with maxLevel 'xhigh'.
// User input "max" should map down to xhigh rather than be rejected.
const result = validateThinking('codex', 'gpt-5.4', 'max');
expect(result.valid).toBe(true);
expect(result.value).toBe('xhigh');
});
it('should export off values', () => {
@@ -35,6 +35,7 @@ describe('config thinking override normalization', () => {
it('accepts valid levels', () => {
expect(parseThinkingOverrideInput('High')).toEqual({ value: 'high' });
expect(parseThinkingOverrideInput('Max')).toEqual({ value: 'max' });
});
it('validates numeric bounds', () => {
@@ -90,6 +90,16 @@ describe('translateAnthropicRequest', () => {
]);
});
it('maps adaptive anthropic thinking into Cursor reasoning effort', () => {
const translated = translateAnthropicRequest({
thinking: { type: 'adaptive' },
output_config: { effort: 'xhigh' },
messages: [{ role: 'user', content: 'hello' }],
});
expect(translated.reasoning_effort).toBe('high');
});
it('preserves mixed user text around tool_result blocks in order', () => {
const translated = translateAnthropicRequest({
messages: [
@@ -30,6 +30,17 @@ describe('ProxyRequestTransformer regressions', () => {
expect(result.reasoning).toEqual({ enabled: true, effort: 'high' });
});
it('explicitly normalizes anthropic xhigh adaptive effort for OpenAI-compatible upstreams', () => {
const result = new ProxyRequestTransformer().transform({
messages: [{ role: 'user', content: 'hello' }],
thinking: { type: 'adaptive' },
output_config: { effort: 'xhigh' },
});
expect(result.reasoning_effort).toBe('high');
expect(result.reasoning).toEqual({ enabled: true, effort: 'high' });
});
it('rejects unsupported thinking types instead of silently dropping them', () => {
expect(() =>
new ProxyRequestTransformer().transform({
@@ -143,6 +143,23 @@ describe('droid-config-manager', () => {
expect(settings.customModels[0].extraArgs?.thinking?.budget_tokens).toBe(40960);
});
it('writes adaptive anthropic thinking for claude-opus-4-7 overrides', async () => {
await upsertCcsModel('claude', {
model: 'claude-opus-4-7',
displayName: 'CCS claude',
baseUrl: 'https://api.anthropic.com',
apiKey: 'anthropic-key',
provider: 'anthropic',
reasoningOverride: 'max',
});
const settingsPath = path.join(tmpDir, '.factory', 'settings.json');
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
expect(settings.customModels[0].extraArgs?.thinking?.type).toBe('adaptive');
expect(settings.customModels[0].extraArgs?.thinking?.budget_tokens).toBeUndefined();
expect(settings.customModels[0].extraArgs?.output_config?.effort).toBe('max');
});
it('should clear prior reasoning config when override disables thinking', async () => {
await upsertCcsModel('glm', {
model: 'glm-4.7',