Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions src/main/claude/pi-model-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] This new branch opts custom relays into openai-responses, but the surrounding OpenAI-compatible helpers still exclude that protocol. isOpenAIProvider() and related helpers only recognize customProtocol === 'openai' (src/main/config/auth-utils.ts:41-46, src/main/config/auth-utils.ts:147-156, src/main/config/auth-utils.ts:220-232), api-diagnostics uses the same gate (src/main/config/api-diagnostics.ts:99-105), and applyToEnv() only exports OPENAI_* for customProtocol === 'openai' (src/main/config/config-store.ts:1416-1429). A custom /v1/responses relay will therefore be treated as non-OpenAI in validation and env-based flows.

Suggested fix:

function isCustomOpenAIProtocol(protocol: CustomProtocolType | undefined): boolean {
  return protocol === 'openai' || protocol === 'openai-responses';
}

export function isOpenAIProvider(config: Pick<AppConfig, 'provider' | 'customProtocol'>): boolean {
  return (
    config.provider === 'openai' ||
    config.provider === 'ollama' ||
    (config.provider === 'custom' && isCustomOpenAIProtocol(config.customProtocol))
  );
}

// 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;
}
Expand Down
14 changes: 11 additions & 3 deletions src/main/config/config-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] Adding openai-responses to CustomProtocolType is not enough here because normalizeCustomProtocol() still rejects it (src/main/config/config-store.ts:372-379). update() runs that normalizer before persisting the active set (src/main/config/config-store.ts:1172-1242), so a normal save of apiKey / baseUrl / model rewrites the protocol away from openai-responses.

Suggested fix:

function normalizeCustomProtocol(
  value: CustomProtocolType | undefined,
  fallback: CustomProtocolType = 'anthropic'
): CustomProtocolType {
  if (
    value === 'openai' ||
    value === 'openai-responses' ||
    value === 'gemini' ||
    value === 'anthropic'
  ) {
    return value;
  }
  return fallback;
}

export type AppTheme = 'dark' | 'light' | 'system';
export type ProviderProfileKey =
| 'openrouter'
Expand Down Expand Up @@ -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 {
Expand All @@ -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') {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] The renderer still only understands three custom protocols even though the type now exposes a fourth. useApiConfigState rejects openai-responses and falls back to Anthropic (src/renderer/hooks/useApiConfigState.ts:97-114, src/renderer/hooks/useApiConfigState.ts:285-292, src/renderer/hooks/useApiConfigState.ts:820-825), and both protocol pickers still render only Anthropic / OpenAI / Gemini (src/renderer/components/settings/SettingsAPI.tsx:185-189, src/renderer/components/ConfigModal.tsx:251-255). That makes the new mode unreachable from the UI and corrupts persisted values on load/save.

Suggested fix:

function isCustomProtocol(value: unknown): value is CustomProtocolType {
  return (
    value === 'anthropic' ||
    value === 'openai' ||
    value === 'openai-responses' ||
    value === 'gemini'
  );
}

if (customProtocol === 'openai' || customProtocol === 'openai-responses') {
  return 'custom:openai';
}

[
  { id: 'anthropic', label: 'Anthropic' },
  { id: 'openai', label: 'OpenAI' },
  { id: 'openai-responses', label: 'OpenAI Responses' },
  { id: 'gemini', label: 'Gemini' },
] as const

export type AppTheme = 'dark' | 'light' | 'system';
export type ProviderProfileKey =
| 'openrouter'
Expand Down
2 changes: 1 addition & 1 deletion src/shared/api-model-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading