diff --git a/agents/codelayer/src/agent.ts b/agents/codelayer/src/agent.ts index 2697270..4aaa4e7 100644 --- a/agents/codelayer/src/agent.ts +++ b/agents/codelayer/src/agent.ts @@ -135,6 +135,12 @@ function resolveAnthropicThinking(model: LanguageModel, effort?: string): Record effort: resolvedEffort, } } + if (modelId.includes('opus') && (modelId.includes('5-5') || modelId.includes('5.5'))) { + return { + thinking: { type: 'adaptive', display: 'summarized' }, + effort: resolvedEffort, + } + } if (modelId.includes('opus') && (modelId.includes('4-8') || modelId.includes('4.8'))) { return { thinking: { type: 'adaptive', display: 'summarized' }, @@ -200,8 +206,13 @@ export function buildProviderOptions( model: LanguageModel, overrides: CodelayerProviderOptionOverrides = {}, ): CodelayerProviderOptions { + const modelId = ((model as { modelId?: string }).modelId ?? '').toLowerCase() + const requiresAdaptiveThinking = + modelId.includes('opus') && (modelId.includes('5-5') || modelId.includes('5.5')) const anthropicThinking = - overrides.anthropic?.thinking === 'off' + requiresAdaptiveThinking + ? resolveAnthropicThinking(model, overrides.anthropic?.effort) + : overrides.anthropic?.thinking === 'off' ? {} : overrides.anthropic?.thinking === 'adaptive' ? { @@ -292,6 +303,7 @@ const EFFORT_RANK: Record = { medium: 1, high: 2, xhigh: 3, + max: 4, } /** @@ -302,7 +314,7 @@ const EFFORT_RANK: Record = { * - codex / firepass / copilot → `reasoningEffort = level` * - anthropic `4-5`/`4.5` (extended thinking, no adaptive support) → * `{ thinking: 'enabled', budgetTokens: LOW_ANTHROPIC_BUDGET }` - * - anthropic adaptive (`4.6`+/`4.7`/`4.8`, and any other model) → + * - anthropic adaptive (`4.6`+/`4.7`/`4.8`/`5.5`, and any other model) → * `effort = level` * * Guards (respect an explicitly-throttled parent — sub-agents never think diff --git a/agents/codelayer/src/command.ts b/agents/codelayer/src/command.ts index 751df72..8198aa4 100644 --- a/agents/codelayer/src/command.ts +++ b/agents/codelayer/src/command.ts @@ -74,7 +74,7 @@ function assertThinkingValue(args: { provider: ProviderType; modelId: string; th } } - if (args.provider === 'codex' && (modelId.includes('gpt-6-astra') || modelId.includes('gpt-5.6'))) { + if (args.provider === 'codex' && (modelId.includes('gpt-6-') || modelId.includes('gpt-5.6'))) { // These models also advertise ultra, but it assumes Codex CLI spawn-agent tools we do not expose yet. supported(['low', 'medium', 'high', 'xhigh', 'max']) return @@ -89,6 +89,8 @@ function assertThinkingValue(args: { provider: ProviderType; modelId: string; th if (modelId.includes('fable-5')) { supported(['low', 'medium', 'high', 'xhigh', 'max']) + } else if (modelId.includes('opus') && (modelId.includes('5-5') || modelId.includes('5.5'))) { + supported(['low', 'medium', 'high', 'xhigh', 'max']) } else if (modelId.includes('opus') && (modelId.includes('4-8') || modelId.includes('4.8'))) { supported(['low', 'medium', 'high', 'xhigh', 'max']) } else if (modelId.includes('opus') && (modelId.includes('4-7') || modelId.includes('4.7'))) { diff --git a/agents/codelayer/test/agent.test.ts b/agents/codelayer/test/agent.test.ts index e6d9354..a8511ed 100644 --- a/agents/codelayer/test/agent.test.ts +++ b/agents/codelayer/test/agent.test.ts @@ -525,6 +525,18 @@ describe('createCodelayerAgent', () => { expect(buildProviderOptions(model, overrides).openai.reasoningEffort).toBe('max') }) + test.each(['gpt-6-sol', 'gpt-6-luna'])('%s accepts every exposed CLI effort', (modelId) => { + const model = createMockModel(modelId) + + for (const thinking of ['low', 'medium', 'high', 'xhigh', 'max']) { + const overrides = applyCliThinkingOverride({ provider: 'codex', modelId, thinking, overrides: {} }) + expect(buildProviderOptions(model, overrides).openai.reasoningEffort).toBe(thinking) + } + expect(() => + applyCliThinkingOverride({ provider: 'codex', modelId, thinking: 'extreme', overrides: {} }), + ).toThrow('Unsupported --thinking value "extreme"') + }) + test('applies explicit CLI thinking for firepass kimi models', () => { const model = createMockModel(DEFAULT_MODELS.firepass) const overrides = applyCliThinkingOverride({ @@ -580,6 +592,43 @@ describe('createCodelayerAgent', () => { }) }) + test('uses summarized adaptive thinking and all exposed efforts for opus 5.5', () => { + const modelId = 'claude-opus-5-5' + const model = createMockModel(modelId) + + expect(buildProviderOptions(model).anthropic).toEqual({ + thinking: { type: 'adaptive', display: 'summarized' }, + effort: 'medium', + cacheControl: { type: 'ephemeral' }, + }) + for (const thinking of ['low', 'medium', 'high', 'xhigh', 'max']) { + const overrides = applyCliThinkingOverride({ provider: 'anthropic', modelId, thinking, overrides: {} }) + expect(buildProviderOptions(model, overrides).anthropic).toMatchObject({ + thinking: { type: 'adaptive', display: 'summarized' }, + effort: thinking, + }) + } + expect(() => + applyCliThinkingOverride({ provider: 'anthropic', modelId, thinking: 'extreme', overrides: {} }), + ).toThrow('Unsupported --thinking value "extreme"') + }) + + test('does not disable or downgrade opus 5.5 adaptive thinking through overrides', () => { + const model = createMockModel('claude-opus-5-5') + + for (const thinking of ['off', 'enabled'] as const) { + expect( + buildProviderOptions(model, { + anthropic: { thinking, budgetTokens: 10_000, effort: 'high' }, + }).anthropic, + ).toEqual({ + thinking: { type: 'adaptive', display: 'summarized' }, + effort: 'high', + cacheControl: { type: 'ephemeral' }, + }) + } + }) + test('uses summarized adaptive thinking with explicit xhigh CLI effort for opus 4.8', () => { const model = createMockModel('claude-opus-4-8') const overrides = applyCliThinkingOverride({ @@ -1132,6 +1181,47 @@ describe('subagentThinkingOverrides', () => { } }) + test.each(['gpt-6-sol', 'gpt-6-luna'])('%s preserves the root, child, research, and outline matrix', async (modelId) => { + const rootModel = createMockModel(modelId, 'codex') + const researchModel = createMockModel('gpt-5.6-terra', 'codex') + const agent = await createCodelayerAgent({ + model: rootModel, + researchModel, + cwd: '/tmp', + context7ApiKey: 'context7-test-key', + providerOptionOverrides: { codex: { reasoningEffort: 'max', fastMode: true } }, + }) + const rootConfig = getAgentConfig(agent) + const rootOptions = (rootConfig.providerOptions as unknown as (ctx: { runId: string }) => Record)({ + runId: 'root', + }) + const researchNames = new Set([ + 'rpi:codebase-locator', + 'rpi:codebase-analyzer', + 'rpi:codebase-pattern-finder', + 'web-search-researcher', + ]) + + expect(rootConfig.model).toBe(rootModel) + expect(rootOptions.openai).toMatchObject({ reasoningEffort: 'max', fastMode: true }) + for (const subagent of getSubagents(rootConfig.tools?.agent)) { + const config = getAgentConfig(subagent.agent) + const options = (config.providerOptions as unknown as (ctx: { runId: string }) => Record)({ + runId: subagent.name, + }) + if (researchNames.has(subagent.name)) { + expect(config.model, subagent.name).toBe(researchModel) + expect(options.openai.reasoningEffort, subagent.name).toBe('xhigh') + } else { + expect(config.model, subagent.name).toBe(rootModel) + expect(options.openai.reasoningEffort, subagent.name).toBe( + subagent.name === 'rpi:outline-implementer-agent' ? 'max' : 'low', + ) + } + expect(options.openai.fastMode, subagent.name).toBe(true) + } + }) + test('lets the outline implementer use parent anthropic effort while other sub-agents stay throttled', async () => { const agent = await createCodelayerAgent({ model: createMockModel('claude-opus-4-8'), @@ -1157,6 +1247,35 @@ describe('subagentThinkingOverrides', () => { expect(generalProviderOptions({ runId: 'general' }).anthropic.effort).toBe('low') expect(outlineProviderOptions({ runId: 'outline' }).anthropic.effort).toBe('max') }) + + test('keeps opus 5.5 adaptive across root and child roles while reducing ordinary children', async () => { + const model = createMockModel('claude-opus-5-5', 'anthropic') + const agent = await createCodelayerAgent({ + model, + cwd: '/tmp', + providerOptionOverrides: { anthropic: { effort: 'max' } }, + }) + const rootConfig = getAgentConfig(agent) + const rootOptions = (rootConfig.providerOptions as unknown as (ctx: { runId: string }) => Record)({ + runId: 'root', + }) + + expect(rootOptions.anthropic).toMatchObject({ + thinking: { type: 'adaptive', display: 'summarized' }, + effort: 'max', + }) + for (const subagent of getSubagents(rootConfig.tools?.agent)) { + const config = getAgentConfig(subagent.agent) + const options = (config.providerOptions as unknown as (ctx: { runId: string }) => Record)({ + runId: subagent.name, + }) + expect(config.model, subagent.name).toBe(model) + expect(options.anthropic.thinking, subagent.name).toEqual({ type: 'adaptive', display: 'summarized' }) + expect(options.anthropic.effort, subagent.name).toBe( + subagent.name === 'rpi:outline-implementer-agent' ? 'max' : 'low', + ) + } + }) }) describe('--subagent-thinking CLI knob', () => { diff --git a/packages/agentlayer-core/models.json b/packages/agentlayer-core/models.json index 0d9132e..f81552f 100644 --- a/packages/agentlayer-core/models.json +++ b/packages/agentlayer-core/models.json @@ -54912,6 +54912,192 @@ "name": "OpenAI", "doc": "https://platform.openai.com/docs/models", "models": { + "gpt-6-sol": { + "id": "gpt-6-sol", + "name": "GPT-6 Sol", + "description": "OpenAI model for complex coding and agentic workflows", + "family": "gpt-sol", + "attachment": true, + "reasoning": true, + "reasoning_options": [ + { + "type": "effort", + "values": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "tool_call": true, + "structured_output": true, + "temperature": false, + "knowledge": "2026-04-20", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "open_weights": false, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + }, + "experimental": { + "modes": { + "fast": { + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.4, + "cache_write": 5 + }, + "provider": { + "body": { + "service_tier": "priority" + } + } + }, + "pro": { + "provider": { + "body": { + "reasoning": { + "mode": "pro" + } + } + } + } + } + }, + "cost": { + "input": 2, + "output": 10, + "cache_read": 0.2, + "cache_write": 2.5, + "tiers": [ + { + "input": 4, + "output": 15, + "cache_read": 0.4, + "cache_write": 5, + "tier": { + "type": "context", + "size": 272000 + } + } + ], + "context_over_200k": { + "input": 4, + "output": 15, + "cache_read": 0.4, + "cache_write": 5 + } + } + }, + "gpt-6-luna": { + "id": "gpt-6-luna", + "name": "GPT-6 Luna", + "description": "OpenAI's most efficient model for focused, high-volume tasks", + "family": "gpt-luna", + "attachment": true, + "reasoning": true, + "reasoning_options": [ + { + "type": "effort", + "values": [ + "none", + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "tool_call": true, + "structured_output": true, + "temperature": false, + "knowledge": "2026-05-18", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "open_weights": false, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + }, + "experimental": { + "modes": { + "fast": { + "cost": { + "input": 0.2, + "output": 1, + "cache_read": 0.02, + "cache_write": 0.25 + }, + "provider": { + "body": { + "service_tier": "priority" + } + } + }, + "pro": { + "provider": { + "body": { + "reasoning": { + "mode": "pro" + } + } + } + } + } + }, + "cost": { + "input": 0.1, + "output": 0.5, + "cache_read": 0.01, + "cache_write": 0.125, + "tiers": [ + { + "input": 0.2, + "output": 0.75, + "cache_read": 0.02, + "cache_write": 0.25, + "tier": { + "type": "context", + "size": 272000 + } + } + ], + "context_over_200k": { + "input": 0.2, + "output": 0.75, + "cache_read": 0.02, + "cache_write": 0.25 + } + } + }, "o3": { "id": "o3", "name": "o3", @@ -100591,6 +100777,73 @@ "name": "Anthropic", "doc": "https://docs.anthropic.com/en/docs/about-claude/models", "models": { + "claude-opus-5-5": { + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "description": "Claude model for long-running agentic coding and knowledge work", + "family": "claude-opus", + "attachment": true, + "reasoning": true, + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "tool_call": true, + "structured_output": true, + "temperature": false, + "knowledge": "2026-06", + "release_date": "2026-09-22", + "last_updated": "2026-09-22", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "open_weights": false, + "limit": { + "context": 1000000, + "output": 128000 + }, + "experimental": { + "modes": { + "fast": { + "cost": { + "input": 8, + "output": 40, + "cache_read": 0.4, + "cache_write": 10 + }, + "provider": { + "body": { + "speed": "fast" + }, + "headers": { + "anthropic-beta": "fast-mode-2026-02-01" + } + } + } + } + }, + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + } + }, "claude-opus-4-5": { "id": "claude-opus-4-5", "name": "Claude Opus 4.5 (latest)", diff --git a/packages/agentlayer-core/src/models.ts b/packages/agentlayer-core/src/models.ts index 8a2c82b..8cb40b9 100644 --- a/packages/agentlayer-core/src/models.ts +++ b/packages/agentlayer-core/src/models.ts @@ -37,10 +37,12 @@ export type ModelKey = `${string}/${string}` * Effective Codex context window, per model. * * Codex reserves 5% of the raw backend window for system prompt, tool overhead, and output: - * gpt-5.4/5.5 and GPT-6 Astra use 272,000 raw tokens; gpt-5.6 uses 372,000. + * gpt-5.4/5.5 and GPT-6 Astra/Sol/Luna use 272,000 raw tokens; gpt-5.6 uses 372,000. */ export const CODEX_CONTEXT_WINDOWS = { 'gpt-6-astra': 258_400, + 'gpt-6-sol': 258_400, + 'gpt-6-luna': 258_400, 'gpt-5.6-sol': 353_400, 'gpt-5.6-terra': 353_400, 'gpt-5.6-luna': 353_400, diff --git a/packages/agentlayer-core/test/compaction-engine.test.ts b/packages/agentlayer-core/test/compaction-engine.test.ts index 333672d..338cfc2 100644 --- a/packages/agentlayer-core/test/compaction-engine.test.ts +++ b/packages/agentlayer-core/test/compaction-engine.test.ts @@ -13,6 +13,7 @@ import { serializeConversation, } from '../src/compaction' import { assistantMessage, toolCall, toolResult, userMessage } from '../src/messages' +import { getCodexContextWindow } from '../src/models' describe('compaction policy', () => { test('uses Fold-compatible budget arithmetic', () => { @@ -21,6 +22,13 @@ describe('compaction policy', () => { expect(compactionUsableTokens({ contextWindow: 1 })).toBe(1) }) + test.each(['gpt-6-sol', 'gpt-6-luna'])('%s reserves output and summary headroom', (modelId) => { + const contextWindow = getCodexContextWindow(modelId) + + expect(contextWindow).toBe(258_400) + expect(compactionUsableTokens({ contextWindow })).toBe(210_016) + }) + test('caps history and turn-prefix summary output against reserved and model budgets', () => { expect(resolveCompactionMaxOutputTokens({ reserveTokens: 20_000 })).toBe(16_000) expect(resolveCompactionMaxOutputTokens({ reserveTokens: 20_000, turnPrefix: true })).toBe(10_000) diff --git a/packages/agentlayer-core/test/models.test.ts b/packages/agentlayer-core/test/models.test.ts index a45756e..16c9f7f 100644 --- a/packages/agentlayer-core/test/models.test.ts +++ b/packages/agentlayer-core/test/models.test.ts @@ -35,6 +35,16 @@ describe('ModelProvider.getModelLimits', () => { expect(limits?.output).toBe(128_000) }) + test.each(['gpt-6-sol', 'gpt-6-luna'] as const)( + 'codex/%s uses the effective Codex window while retaining public output metadata', + (modelId) => { + const limits = provider.getModelLimits(`codex/${modelId}`) + + expect(limits?.context).toBe(258_400) + expect(limits?.output).toBe(128_000) + }, + ) + test('openai/gpt-5.5 keeps the public OpenAI API context window', () => { const limits = provider.getModelLimits('openai/gpt-5.5') @@ -72,4 +82,24 @@ describe('ModelProvider.getModelLimits', () => { expect(limits?.output).toBe(128_000) expect(provider.getModelPricing('openai/gpt-6-astra')).toMatchObject({ input: 10, output: 50 }) }) + + test.each([ + ['gpt-6-sol', 2, 10], + ['gpt-6-luna', 0.1, 0.5], + ] as const)('%s keeps its public OpenAI limits and pricing', (modelId, input, output) => { + expect(provider.getModelLimits(`openai/${modelId}`)).toMatchObject({ + context: 1_050_000, + input: 922_000, + output: 128_000, + }) + expect(provider.getModelPricing(`openai/${modelId}`)).toMatchObject({ input, output }) + }) + + test('claude-opus-5-5 exposes public Anthropic limits and pricing', () => { + expect(provider.getModelLimits('anthropic/claude-opus-5-5')).toMatchObject({ + context: 1_000_000, + output: 128_000, + }) + expect(provider.getModelPricing('anthropic/claude-opus-5-5')).toMatchObject({ input: 4, output: 20 }) + }) }) diff --git a/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts b/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts index 1bee7b7..1fee2ff 100644 --- a/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts +++ b/packages/agentlayer-provider-openai-codex/test/codex-ws-adapter.test.ts @@ -33,8 +33,10 @@ function makeOptions(overrides?: Partial): LanguageM } } -describe('GPT-5.6 max reasoning', () => { - test.each(['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'])('%s accepts max effort', (modelId) => { +describe('model-specific max reasoning', () => { + const maxReasoningModels = ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-6-sol', 'gpt-6-luna'] + + test.each(maxReasoningModels)('%s accepts max effort', (modelId) => { expect(isReasoningEffortForModel(modelId, 'max')).toBe(true) }) @@ -42,45 +44,42 @@ describe('GPT-5.6 max reasoning', () => { expect(isReasoningEffortForModel('gpt-5.4', 'max')).toBe(false) }) - test.each(['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'])( - '%s uses the regular Responses request shape', - async (modelId) => { - const request = convertCallOptionsToLLMRequest( - modelId, - makeOptions({ - prompt: [ - { role: 'system', content: 'Use the repository tools.' }, - { role: 'user', content: [{ type: 'text', text: 'Locate the implementation.' }] }, - ], - tools: [ - { - type: 'function', - name: 'search', - description: 'Search the repository', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, - }, - ], - toolChoice: { type: 'auto' }, - providerOptions: { - openai: { reasoningEffort: 'max', promptCacheKey: 'cache-key' }, + test.each(maxReasoningModels)('%s uses the regular Responses request shape', async (modelId) => { + const request = convertCallOptionsToLLMRequest( + modelId, + makeOptions({ + prompt: [ + { role: 'system', content: 'Use the repository tools.' }, + { role: 'user', content: [{ type: 'text', text: 'Locate the implementation.' }] }, + ], + tools: [ + { + type: 'function', + name: 'search', + description: 'Search the repository', + inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, }, - }), - makeConfig(), - ) - - const body = await Effect.runPromise(webSocketRoute.body.from(request)) - - expect(body.instructions).toBe('Use the repository tools.') - expect(body.tools).toHaveLength(1) - expect(body.tool_choice).toBe('auto') - expect(body.prompt_cache_key).toBe('cache-key') - expect(body.reasoning).toEqual({ effort: 'max', summary: 'detailed' }) - expect(body.parallel_tool_calls).toBeUndefined() - expect(body.input).toEqual([ - { role: 'user', content: [{ type: 'input_text', text: 'Locate the implementation.' }] }, - ]) - }, - ) + ], + toolChoice: { type: 'auto' }, + providerOptions: { + openai: { reasoningEffort: 'max', promptCacheKey: 'cache-key' }, + }, + }), + makeConfig(), + ) + + const body = await Effect.runPromise(webSocketRoute.body.from(request)) + + expect(body.instructions).toBe('Use the repository tools.') + expect(body.tools).toHaveLength(1) + expect(body.tool_choice).toBe('auto') + expect(body.prompt_cache_key).toBe('cache-key') + expect(body.reasoning).toEqual({ effort: 'max', summary: 'detailed' }) + expect(body.parallel_tool_calls).toBeUndefined() + expect(body.input).toEqual([ + { role: 'user', content: [{ type: 'input_text', text: 'Locate the implementation.' }] }, + ]) + }) }) // --------------------------------------------------------------------------- diff --git a/packages/opencode-llm-vendor/src/protocols/openai-responses.ts b/packages/opencode-llm-vendor/src/protocols/openai-responses.ts index c874ae3..9ce9da3 100644 --- a/packages/opencode-llm-vendor/src/protocols/openai-responses.ts +++ b/packages/opencode-llm-vendor/src/protocols/openai-responses.ts @@ -437,10 +437,10 @@ const lowerOptions = Effect.fn('OpenAIResponses.lowerOptions')(function* (reques } }) -const GPT_5_6_MODELS = new Set(['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) +const MAX_REASONING_MODELS = new Set(['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-6-sol', 'gpt-6-luna']) export const isReasoningEffortForModel = (modelId: string, effort: ReasoningEffort): boolean => - OpenAIOptions.isReasoningEffort(effort) || (effort === 'max' && GPT_5_6_MODELS.has(modelId)) + OpenAIOptions.isReasoningEffort(effort) || (effort === 'max' && MAX_REASONING_MODELS.has(modelId)) const fromRequest = Effect.fn('OpenAIResponses.fromRequest')(function* (request: LLMRequest) { const generation = request.generation