diff --git a/src/agents/plugins/__tests__/codemie-code-reasoning.test.ts b/src/agents/plugins/__tests__/codemie-code-reasoning.test.ts index f64c30ff3..c66b9d809 100644 --- a/src/agents/plugins/__tests__/codemie-code-reasoning.test.ts +++ b/src/agents/plugins/__tests__/codemie-code-reasoning.test.ts @@ -134,6 +134,14 @@ describe('CodeMie Code Plugin — Reasoning Sanitization Integration', () => { const beforeRun = CodeMieCodePluginMetadata.lifecycle!.beforeRun!; const onSessionEnd = CodeMieCodePluginMetadata.lifecycle!.onSessionEnd!; + it('declares the complete reasoning-effort range for OpenCode forwarding', () => { + expect(CodeMieCodePluginMetadata.reasoningEffort).toMatchObject({ + strategy: 'cli-flag', + flag: '--variant', + supportedLevels: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], + }); + }); + beforeEach(() => { vi.clearAllMocks(); mockDiscoverSessions.mockResolvedValue([]); diff --git a/src/agents/plugins/__tests__/opencode-gpt55-routing.test.ts b/src/agents/plugins/__tests__/opencode-gpt55-routing.test.ts index b415963df..bd70eed7f 100644 --- a/src/agents/plugins/__tests__/opencode-gpt55-routing.test.ts +++ b/src/agents/plugins/__tests__/opencode-gpt55-routing.test.ts @@ -150,6 +150,28 @@ describe('GPT-5.6 → Responses API routing', () => { expect(config.limit.context).toBe(1050000); }); + it('dynamic GPT-5.6 config preserves the complete effort-level map', () => { + const config = convertApiModelToOpenCodeConfig(makeLlmModel('openai.gpt-5.6-luna')); + expect(Object.keys(config.variants ?? {})).toEqual(['minimal', 'none', 'low', 'medium', 'high', 'xhigh', 'max']); + expect(config.variants?.minimal?.reasoningEffort).toBe('none'); + expect(config.variants?.none?.reasoningEffort).toBe('none'); + expect(config.variants?.xhigh?.reasoningEffort).toBe('xhigh'); + expect(config.variants?.max?.reasoningEffort).toBe('max'); + }); + + it.each([ + ['gpt-5.6-luna', 'none'], + ['gpt-5.6-terra', 'none'], + ['gpt-6-sol', 'none'], + ['gpt-6-luna', 'none'], + ['gpt-6-astra', 'low'], + ['gpt-6.1-sol', 'low'], + ])('%s maps CodeMie minimal to native %s', (modelId, nativeMinimal) => { + const config = convertApiModelToOpenCodeConfig(makeLlmModel(modelId)); + expect(config.use_responses_api).toBe(true); + expect(config.variants?.minimal?.reasoningEffort).toBe(nativeMinimal); + }); + // ── Static fallback path (OPENCODE_MODEL_CONFIGS) ────────────────────────── it('static config has gpt-5.6-sol-2026-07-09 with use_responses_api: true', () => { @@ -164,6 +186,13 @@ describe('GPT-5.6 → Responses API routing', () => { it('static config gpt-5.6-sol-2026-07-09 reports context limit of 1050000', () => { expect(OPENCODE_MODEL_CONFIGS['gpt-5.6-sol-2026-07-09']!.limit.context).toBe(1050000); }); + + it('static GPT-5.6 config preserves xhigh and max', () => { + const variants = OPENCODE_MODEL_CONFIGS['gpt-5.6-sol-2026-07-09']!.variants; + expect(variants?.minimal?.reasoningEffort).toBe('none'); + expect(variants?.xhigh?.reasoningEffort).toBe('xhigh'); + expect(variants?.max?.reasoningEffort).toBe('max'); + }); }); describe('GPT-6 → Responses API routing', () => { diff --git a/src/agents/plugins/codemie-code.plugin.ts b/src/agents/plugins/codemie-code.plugin.ts index 4f31e0f44..7db9baeb8 100644 --- a/src/agents/plugins/codemie-code.plugin.ts +++ b/src/agents/plugins/codemie-code.plugin.ts @@ -223,6 +223,14 @@ export const CodeMieCodePluginMetadata: AgentMetadata = { supportedProviders: ['litellm', 'ai-run-sso', 'ollama', 'bedrock', 'bearer-auth'], + reasoningEffort: { + strategy: 'cli-flag', + flag: '--variant', + placement: 'append', + supportedLevels: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], + userOverrideFlags: ['--variant'], + }, + ssoConfig: { enabled: true, clientType: 'codemie-code' }, lifecycle: { diff --git a/src/agents/plugins/opencode/opencode-dynamic-models.ts b/src/agents/plugins/opencode/opencode-dynamic-models.ts index 0b2b4aaa4..e23afb924 100644 --- a/src/agents/plugins/opencode/opencode-dynamic-models.ts +++ b/src/agents/plugins/opencode/opencode-dynamic-models.ts @@ -16,7 +16,7 @@ import type { LlmModel } from '../../../providers/plugins/sso/sso.http-client.js'; import { fetchCodeMieLlmModels, isRouterModel } from '../../../providers/plugins/sso/sso.http-client.js'; import type { OpenCodeModelConfig } from './opencode-model-configs.js'; -import { OPENCODE_MODEL_CONFIGS } from './opencode-model-configs.js'; +import { getExtendedReasoningVariants, OPENCODE_MODEL_CONFIGS } from './opencode-model-configs.js'; import { CodeMieSSO } from '../../../providers/plugins/sso/sso.auth.js'; import { logger } from '../../../utils/logger.js'; @@ -109,6 +109,7 @@ export function convertApiModelToOpenCodeConfig(model: LlmModel, isSelected = fa const family = detectFamily(id); const limit = detectLimits(id, family); const responsesApi = isResponsesApiModel(id); + const extendedVariants = responsesApi ? getExtendedReasoningVariants(id) : undefined; const toPerMillion = (v: number | undefined) => (v ?? 0) * 1_000_000; @@ -131,6 +132,7 @@ export function convertApiModelToOpenCodeConfig(model: LlmModel, isSelected = fa temperature: model.features?.temperature ?? true, structured_output: model.features?.tools ? true : undefined, ...(responsesApi && { use_responses_api: true }), + ...(extendedVariants && { variants: extendedVariants }), modalities: { input: model.multimodal ? ['text', 'image'] : ['text'], output: ['text'], diff --git a/src/agents/plugins/opencode/opencode-model-configs.ts b/src/agents/plugins/opencode/opencode-model-configs.ts index af5992bc6..ef6f3f3cd 100644 --- a/src/agents/plugins/opencode/opencode-model-configs.ts +++ b/src/agents/plugins/opencode/opencode-model-configs.ts @@ -52,6 +52,70 @@ export interface OpenCodeModelConfig { headers?: Record; timeout?: number; }; + /** Named model variants used by OpenCode's --variant flag. */ + variants?: Record>; +} + +const GPT_5_6_REASONING_LEVELS = ['none', 'low', 'medium', 'high', 'xhigh', 'max'] as const; +const GPT_6_REASONING_LEVELS_FROM_LOW = ['low', 'medium', 'high', 'xhigh', 'max'] as const; + +function getNativeReasoningLevels(modelId: string): readonly string[] | undefined { + if (/gpt-5[.-]6(?:[.-]|$)/.test(modelId)) { + return GPT_5_6_REASONING_LEVELS; + } + + // GPT-6 Astra and GPT-6.1 Sol do not expose either `none` or `minimal`. + if ( + /gpt-6[.-]astra(?:[.-]|$)/.test(modelId) || + /gpt-6[.-]1(?:[.-]|$)/.test(modelId) + ) { + return GPT_6_REASONING_LEVELS_FROM_LOW; + } + + // GPT-6 Sol and Luna expose `none`, but not `minimal`. + if (/gpt-6(?:[.-]|$)/.test(modelId)) { + return GPT_5_6_REASONING_LEVELS; + } + + return undefined; +} + +/** + * Return explicit OpenAI Responses variants for models with model-specific + * reasoning vocabularies. CodeMie's `minimal` is an alias for the model's + * lowest native setting when OpenAI does not expose a native `minimal` value. + * OpenCode's provider defaults do not define `max`, so it must be present in + * the injected model configuration. + */ +export function getExtendedReasoningVariants( + modelId: string, +): Record> | undefined { + const nativeLevels = getNativeReasoningLevels(modelId); + if (!nativeLevels) return undefined; + + const minimalTarget = nativeLevels.includes('none') ? 'none' : 'low'; + const nativeVariants = nativeLevels.map(level => [ + level, + { + reasoningEffort: level, + reasoningSummary: 'auto', + include: ['reasoning.encrypted_content'], + }, + ] as const); + + return Object.fromEntries( + [ + ['minimal', { + reasoningEffort: minimalTarget, + reasoningSummary: 'auto', + include: ['reasoning.encrypted_content'], + }], + ...nativeVariants, + ].map(([level, options]) => [ + level, + options, + ]), + ); } export const OPENCODE_MODEL_CONFIGS: Record = { @@ -269,6 +333,7 @@ export const OPENCODE_MODEL_CONFIGS: Record = { temperature: false, structured_output: true, use_responses_api: true, + variants: getExtendedReasoningVariants('gpt-5.6-sol-2026-07-09'), modalities: { input: ['text', 'image'], output: ['text'] @@ -816,6 +881,7 @@ export function getModelConfig(modelId: string): OpenCodeModelConfig { prefix => modelId.startsWith(prefix) ); const familyDefaults = familyPrefix ? MODEL_FAMILY_DEFAULTS[familyPrefix] : {}; + const extendedVariants = getExtendedReasoningVariants(modelId); // Extract family from model ID (e.g., "gpt-4o" -> "gpt-4", "claude-4-5-sonnet" -> "claude-4") const family = familyDefaults.family @@ -839,6 +905,7 @@ export function getModelConfig(modelId: string): OpenCodeModelConfig { release_date: today, last_updated: today, open_weights: false, + ...(extendedVariants && { use_responses_api: true, variants: extendedVariants }), cost: { input: 0, output: 0 }, limit: familyDefaults.limit ?? { context: 128000, output: 4096 } };