diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts index 77d69e918..54507258e 100644 --- a/src/app/settings/settings.page.ts +++ b/src/app/settings/settings.page.ts @@ -583,6 +583,7 @@ export class SettingsPage { this.settings.cloud_ai_api_key = ''; this.settings.cloud_ai_model = ''; this.settings.cloud_ai_base_url = ''; + this.settings.cloud_ai_model_supports_temperature = null; this.saveSettings(); } @@ -619,6 +620,11 @@ export class SettingsPage { const { data } = await modal.onWillDismiss(); if (data?.modelId) { this.settings.cloud_ai_model = data.modelId; + // Capability advertised by the provider (OpenRouter); null when unknown. + this.settings.cloud_ai_model_supports_temperature = + typeof data.supportsTemperature === 'boolean' + ? data.supportsTemperature + : null; this.saveSettings(); } } diff --git a/src/classes/settings/settings.ts b/src/classes/settings/settings.ts index f5e7142eb..24c11b8e7 100755 --- a/src/classes/settings/settings.ts +++ b/src/classes/settings/settings.ts @@ -258,6 +258,7 @@ export class Settings implements ISettings { public cloud_ai_api_key: string; public cloud_ai_model: string; public cloud_ai_base_url: string; + public cloud_ai_model_supports_temperature: boolean | null; public show_backup_issues: boolean; @@ -612,6 +613,7 @@ export class Settings implements ISettings { this.cloud_ai_api_key = ''; this.cloud_ai_model = ''; this.cloud_ai_base_url = ''; + this.cloud_ai_model_supports_temperature = null; this.show_backup_issues = true; @@ -714,6 +716,12 @@ export class Settings implements ISettings { } else { this.cloud_ai_base_url = settingsObj.cloud_ai_base_url; } + if (typeof settingsObj.cloud_ai_model_supports_temperature === 'boolean') { + this.cloud_ai_model_supports_temperature = + settingsObj.cloud_ai_model_supports_temperature; + } else { + this.cloud_ai_model_supports_temperature = null; + } } public resetBeanFilter() { diff --git a/src/components/cloud-model-picker/cloud-model-picker.component.ts b/src/components/cloud-model-picker/cloud-model-picker.component.ts index b85b8161b..c7bb020c2 100644 --- a/src/components/cloud-model-picker/cloud-model-picker.component.ts +++ b/src/components/cloud-model-picker/cloud-model-picker.component.ts @@ -96,7 +96,7 @@ export class CloudModelPickerComponent implements OnInit { selectModel(model: CloudModel): void { void this.modalCtrl.dismiss( - { modelId: model.id }, + { modelId: model.id, supportsTemperature: model.supportsTemperature }, 'confirm', CloudModelPickerComponent.COMPONENT_ID, ); diff --git a/src/interfaces/settings/iSettings.ts b/src/interfaces/settings/iSettings.ts index 113688f54..65f2db3e9 100755 --- a/src/interfaces/settings/iSettings.ts +++ b/src/interfaces/settings/iSettings.ts @@ -236,6 +236,9 @@ export interface ISettings { cloud_ai_api_key: string; cloud_ai_model: string; cloud_ai_base_url: string; + // Whether the selected model accepts an explicit temperature, when the + // provider advertises it (OpenRouter). null = unknown, decided at runtime. + cloud_ai_model_supports_temperature: boolean | null; show_backup_issues: boolean; diff --git a/src/services/aiBeanImport/__tests__/cloud-model-list.service.spec.ts b/src/services/aiBeanImport/__tests__/cloud-model-list.service.spec.ts index 0b04e92bb..b75cd3a5c 100644 --- a/src/services/aiBeanImport/__tests__/cloud-model-list.service.spec.ts +++ b/src/services/aiBeanImport/__tests__/cloud-model-list.service.spec.ts @@ -252,6 +252,51 @@ describe('cloud-model-list.service', () => { expect(gpt4o.contextLength).toBe(128000); expect(claude.contextLength).toBe(200000); }); + + it('should derive supportsTemperature from supported_parameters', async () => { + // Arrange + fetchSpy.and.returnValue( + Promise.resolve( + mockFetchResponse({ + data: [ + { + id: 'openai/gpt-4o', + name: 'GPT-4o', + supported_parameters: ['temperature', 'top_p', 'tools'], + }, + { + id: 'openai/gpt-5.6-terra', + name: 'GPT-5.6 Terra', + supported_parameters: ['top_p', 'tools'], + }, + { + id: 'some/model-without-metadata', + name: 'No Metadata', + }, + ], + }), + ), + ); + + // Act + const models = await fetchAvailableModels( + AI_PROVIDER_ENUM.OPENROUTER, + '', + ); + + // Assert + expect( + models.find((m) => m.id === 'openai/gpt-4o').supportsTemperature, + ).toBe(true); + expect( + models.find((m) => m.id === 'openai/gpt-5.6-terra').supportsTemperature, + ).toBe(false); + // No supported_parameters advertised → unknown, left undefined. + expect( + models.find((m) => m.id === 'some/model-without-metadata') + .supportsTemperature, + ).toBeUndefined(); + }); }); describe('OpenAI', () => { diff --git a/src/services/aiBeanImport/cloud-field-extraction.service.ts b/src/services/aiBeanImport/cloud-field-extraction.service.ts index e051b4371..3c2f0bb38 100644 --- a/src/services/aiBeanImport/cloud-field-extraction.service.ts +++ b/src/services/aiBeanImport/cloud-field-extraction.service.ts @@ -59,6 +59,10 @@ export class CloudFieldExtractionService { apiKey: settings.cloud_ai_api_key, model: settings.cloud_ai_model, baseUrl: settings.cloud_ai_base_url || undefined, + // Skip the first (doomed) attempt when the provider already told us the + // model rejects an explicit temperature. + supportsTemperature: + settings.cloud_ai_model_supports_temperature ?? undefined, }; } diff --git a/src/services/aiBeanImport/cloud-model-list.service.ts b/src/services/aiBeanImport/cloud-model-list.service.ts index 6318eecf8..ae32af03a 100644 --- a/src/services/aiBeanImport/cloud-model-list.service.ts +++ b/src/services/aiBeanImport/cloud-model-list.service.ts @@ -4,6 +4,9 @@ export interface CloudModel { id: string; name: string; contextLength?: number; + // Whether the model accepts an explicit `temperature`, when the provider + // advertises it (OpenRouter's supported_parameters). undefined = unknown. + supportsTemperature?: boolean; } const TIMEOUT_MS = 15000; @@ -42,6 +45,7 @@ interface RawOpenRouterModel { id: string; name?: string; context_length?: number; + supported_parameters?: string[]; } interface RawCustomModel { @@ -167,6 +171,9 @@ function buildProviderConfig( id: m.id, name: m.name ?? m.id, contextLength: m.context_length, + supportsTemperature: Array.isArray(m.supported_parameters) + ? m.supported_parameters.includes('temperature') + : undefined, }; }, };