diff --git a/packages/core/src/models/modelConfigResolver.test.ts b/packages/core/src/models/modelConfigResolver.test.ts index 4aeb8fbee..7efdb04cf 100644 --- a/packages/core/src/models/modelConfigResolver.test.ts +++ b/packages/core/src/models/modelConfigResolver.test.ts @@ -854,4 +854,38 @@ describe('modelConfigResolver', () => { } }); }); + + describe('[Regression] issue-4219 — env-var-only path must call defaultModalities()', () => { + it('[Regression] env-var-only path: modalities auto-detected for qwen3.6-35b-a3b', () => { + // REPRODUCES issue-4219: + // When the model is supplied only via OPENAI_MODEL (no modelProviders entry), + // resolveGenerationConfig() iterates MODEL_GENERATION_CONFIG_FIELDS but never + // calls defaultModalities(). This leaves config.modalities undefined, causing + // image attachments to be silently dropped with an "Unsupported " + // message even though the model supports images. + // + // The modelRegistry path (resolveModelConfig -> resolveModelConfig in modelRegistry.ts) + // and the modelsConfig path (applyResolvedModelDefaults) both call defaultModalities() + // when generationConfig.modalities is undefined. The env-var-only path does not. + const result = resolveModelConfig({ + authType: AuthType.USE_OPENAI, + cli: {}, + settings: {}, + env: { + OPENAI_API_KEY: 'test-key', + OPENAI_BASE_URL: 'http://localhost:8000/v1', + OPENAI_MODEL: 'qwen3.6-35b-a3b', + }, + // No modelProvider — this is the env-var-only path + }); + + expect(result.config.model).toBe('qwen3.6-35b-a3b'); + + // The qwen3.6-35b pattern in modalityDefaults.ts maps to { image: true, video: true }. + // The env-var-only path must auto-detect this — just as the modelProviders path does + // in modelsConfig.ts applyResolvedModelDefaults() lines 791-797. + expect(result.config.modalities).toBeDefined(); + expect(result.config.modalities?.image).toBe(true); + }); + }); }); diff --git a/packages/core/src/models/modelConfigResolver.ts b/packages/core/src/models/modelConfigResolver.ts index 81c916523..72a513f68 100644 --- a/packages/core/src/models/modelConfigResolver.ts +++ b/packages/core/src/models/modelConfigResolver.ts @@ -21,6 +21,7 @@ import { AuthType } from '../core/contentGenerator.js'; import type { ContentGeneratorConfig } from '../core/contentGenerator.js'; import { DEFAULT_QWEN_MODEL } from '../config/models.js'; +import { defaultModalities } from '../core/modalityDefaults.js'; import { resolveField, resolveOptionalField, @@ -268,7 +269,7 @@ export function resolveModelConfig( settings?.generationConfig, modelProvider?.generationConfig, authType, - modelProvider?.id, + modelProvider?.id ?? modelResult.value, sources, ); @@ -396,5 +397,15 @@ function resolveGenerationConfig( } } + // modalities fallback: auto-detect from model when neither modelProvider nor + // settings supplied it. Mirrors modelRegistry.resolveModelConfig and + // modelsConfig.applyResolvedModelDefaults so all paths agree on which models + // are multimodal — without this, env-var-only setups silently drop @image + // attachments for image-capable models (issue #4219). + if (result.modalities === undefined && modelId) { + result.modalities = defaultModalities(modelId); + sources['modalities'] = computedSource('auto-detected from model'); + } + return result; }