diff --git a/src/main/claude/pi-model-resolution.ts b/src/main/claude/pi-model-resolution.ts index 15fba88e..c283ef45 100644 --- a/src/main/claude/pi-model-resolution.ts +++ b/src/main/claude/pi-model-resolution.ts @@ -41,7 +41,11 @@ export interface SyntheticPiModelFallback { export function resolvePiRouteProtocol(provider?: string, customProtocol?: string): string { if (provider === 'custom') { - if (customProtocol === 'openai' || customProtocol === 'gemini') { + if ( + customProtocol === 'openai' || + customProtocol === 'openai-responses' || + customProtocol === 'gemini' + ) { return customProtocol; } return 'anthropic'; @@ -76,6 +80,8 @@ export function inferPiApi(protocol: string): string { case 'gemini': case 'google': return 'google-generative-ai'; + case 'openai-responses': + return 'openai-responses'; case 'openai': default: return 'openai-completions'; @@ -267,10 +273,26 @@ export function applyPiModelRuntimeOverrides( } const effectiveProvider = options.rawProvider || options.configProvider; - if (options.customBaseUrl && isCustomProvider && nextModel.api === 'openai-responses') { - // Most custom OpenAI-compatible relays only implement chat/completions. + if ( + options.customBaseUrl && + isCustomProvider && + nextModel.api === 'openai-responses' && + options.customProtocol !== 'openai-responses' + ) { + // Most custom OpenAI-compatible relays only implement chat/completions, so + // downgrade by default. Skip the downgrade when the user has explicitly + // chosen 'openai-responses' as the customProtocol — that's an opt-in signal + // that their relay implements the Responses API. nextModel = { ...nextModel, api: 'openai-completions' } as typeof nextModel; } + if ( + isCustomProvider && + options.customProtocol === 'openai-responses' && + nextModel.api !== 'openai-responses' + ) { + // User explicitly opted in to Responses API for this custom relay. + nextModel = { ...nextModel, api: 'openai-responses' } as typeof nextModel; + } if (effectiveProvider === 'openrouter' && nextModel.api !== 'openai-completions') { nextModel = { ...nextModel, api: 'openai-completions' } as typeof nextModel; } diff --git a/src/main/config/config-store.ts b/src/main/config/config-store.ts index 6308fa44..f7983636 100644 --- a/src/main/config/config-store.ts +++ b/src/main/config/config-store.ts @@ -35,7 +35,7 @@ import { API_PROVIDER_PRESETS, PI_AI_CURATED_PRESETS } from '../../shared/api-mo * Application configuration schema */ export type ProviderType = 'openrouter' | 'anthropic' | 'custom' | 'openai' | 'gemini' | 'ollama'; -export type CustomProtocolType = 'anthropic' | 'openai' | 'gemini'; +export type CustomProtocolType = 'anthropic' | 'openai' | 'openai-responses' | 'gemini'; export type AppTheme = 'dark' | 'light' | 'system'; export type ProviderProfileKey = | 'openrouter' @@ -293,7 +293,12 @@ function isProviderType(value: unknown): value is ProviderType { } function isCustomProtocol(value: unknown): value is CustomProtocolType { - return value === 'anthropic' || value === 'openai' || value === 'gemini'; + return ( + value === 'anthropic' || + value === 'openai' || + value === 'openai-responses' || + value === 'gemini' + ); } function isProfileKey(value: unknown): value is ProviderProfileKey { @@ -311,7 +316,10 @@ function profileKeyFromProvider( if (provider !== 'custom') { return provider; } - if (customProtocol === 'openai') { + if (customProtocol === 'openai' || customProtocol === 'openai-responses') { + // openai-responses shares the profile slot with openai (same baseUrl/key, + // different request shape). Avoids forcing the user to re-enter creds when + // toggling between the two protocols on the same relay. return 'custom:openai'; } if (customProtocol === 'gemini') { diff --git a/src/renderer/types/index.ts b/src/renderer/types/index.ts index 06f5cadb..04ec9e76 100644 --- a/src/renderer/types/index.ts +++ b/src/renderer/types/index.ts @@ -428,7 +428,7 @@ export interface ExecutionContext { // App Config types export type ProviderType = 'openrouter' | 'anthropic' | 'custom' | 'openai' | 'gemini' | 'ollama'; -export type CustomProtocolType = 'anthropic' | 'openai' | 'gemini'; +export type CustomProtocolType = 'anthropic' | 'openai' | 'openai-responses' | 'gemini'; export type AppTheme = 'dark' | 'light' | 'system'; export type ProviderProfileKey = | 'openrouter' diff --git a/src/shared/api-model-presets.ts b/src/shared/api-model-presets.ts index 2ac15700..733b5bfa 100644 --- a/src/shared/api-model-presets.ts +++ b/src/shared/api-model-presets.ts @@ -6,7 +6,7 @@ export type SharedProviderType = | 'gemini' | 'ollama'; -export type SharedCustomProtocolType = 'anthropic' | 'openai' | 'gemini'; +export type SharedCustomProtocolType = 'anthropic' | 'openai' | 'openai-responses' | 'gemini'; export interface SharedProviderPreset { name: string;