Skip to content

feat(config): support 'openai-responses' as customProtocol value#173

Open
lxistired wants to merge 1 commit into
OpenCoworkAI:mainfrom
lxistired:feat/openai-responses-custom-protocol
Open

feat(config): support 'openai-responses' as customProtocol value#173
lxistired wants to merge 1 commit into
OpenCoworkAI:mainfrom
lxistired:feat/openai-responses-custom-protocol

Conversation

@lxistired
Copy link
Copy Markdown

Problem

The repo already supports the OpenAI Responses API natively in pi-coding-agent (api: 'openai-responses'), but a user with a custom relay can't opt into it. The current code force-downgrades any openai-responses model to openai-completions for custom providers, on the assumption that "most custom OpenAI-compatible relays only implement chat/completions". Reasonable default, but it leaves no escape hatch for users whose relay does implement /v1/responses (and therefore native built-in tools, store/cache, structured outputs, etc.).

Fix

Add openai-responses as a fourth customProtocol value alongside anthropic / openai / gemini:

  • pi-model-resolution.ts:

    • resolvePiRouteProtocol returns 'openai-responses' when the user picks it explicitly.
    • inferPiApi maps 'openai-responses' -> 'openai-responses'.
    • applyPiModelRuntimeOverrides only force-downgrades to openai-completions when customProtocol !== 'openai-responses'. When the user opts in explicitly, force api to 'openai-responses' (so the relay actually gets /v1/responses calls).
  • config-store.ts:

    • CustomProtocolType union adds 'openai-responses'.
    • isCustomProtocol type guard accepts it.
    • profileKeyFromProvider routes it to the same 'custom:openai' profile slot — both protocols share apiKey/baseUrl, so the user doesn't have to re-enter creds when toggling.
  • renderer/types/index.ts + shared/api-model-presets.ts: mirror the union so the renderer/shared types accept the new value.

Backward compatibility

Existing users with customProtocol: 'openai' still get the chat/completions downgrade. Only users who explicitly switch to 'openai-responses' get the new behavior.

Files

  • src/main/claude/pi-model-resolution.ts (+24/-3)
  • src/main/config/config-store.ts (+11/-3)
  • src/renderer/types/index.ts (+1/-1)
  • src/shared/api-model-presets.ts (+1/-1)

Test plan

  • tsc --noEmit passes
  • Existing customProtocol: 'openai' config still downgrades to openai-completions
  • New customProtocol: 'openai-responses' keeps api === 'openai-responses'
  • Toggling between openai and openai-responses does NOT lose stored apiKey/baseUrl (shared profile slot)
  • Persisted configs without the new value continue to validate

The repository already supports the OpenAI Responses API natively in
pi-coding-agent (api: 'openai-responses'), but there's no way for a user
to opt in to it via a custom relay — every custom-provider Responses-API
model gets force-downgraded to openai-completions, on the assumption
that 'most custom OpenAI-compatible relays only implement
chat/completions'. That's a reasonable default, but it leaves no escape
hatch for users whose relay does implement /v1/responses.

Add 'openai-responses' as a fourth customProtocol value (alongside
'anthropic' / 'openai' / 'gemini'):

- pi-model-resolution.ts:
  - resolvePiRouteProtocol returns 'openai-responses' when the user
    picks it explicitly.
  - inferPiApi maps 'openai-responses' → 'openai-responses'.
  - applyPiModelRuntimeOverrides only force-downgrades to
    openai-completions when customProtocol !== 'openai-responses'.
    When the user explicitly opted in, force the api to
    'openai-responses' (so the relay receives /v1/responses calls).

- config-store.ts:
  - CustomProtocolType union adds 'openai-responses'.
  - isCustomProtocol type guard accepts it.
  - profileKeyFromProvider routes 'openai-responses' to the same
    'custom:openai' profile slot — both protocols share apiKey/baseUrl,
    so no need to force the user to re-enter creds when toggling.

- renderer/types/index.ts + shared/api-model-presets.ts: mirror the
  type union so the renderer/shared types accept the new value.

Behavior preserved for existing users: anyone with customProtocol set
to 'openai' still gets the chat/completions downgrade. Only users who
explicitly switch to 'openai-responses' get the new behavior.
@hqhq1025 hqhq1025 added bot-rerun Temporary label for rerunning bot automation and removed bot-rerun Temporary label for rerunning bot automation labels Apr 30, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review mode: initial

Findings

  • [Major] ConfigStore.update() still normalizes openai-responses back to another protocol, so a normal save can silently drop the new setting. The diff adds the new enum value in src/main/config/config-store.ts:38, but normalizeCustomProtocol() still only accepts openai / gemini / anthropic (src/main/config/config-store.ts:372-379). update() runs that helper before writing the active set back (src/main/config/config-store.ts:1172-1242), so saving apiKey, baseUrl, or model after selecting the new mode will rewrite the set 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;
    }
  • [Major] The renderer still only supports three custom protocols, so users cannot select or preserve openai-responses from the UI. The new type is exposed in src/renderer/types/index.ts:431, but the hook still rejects it 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, and gemini (src/renderer/components/settings/SettingsAPI.tsx:185-189, src/renderer/components/ConfigModal.tsx:251-255). That makes the feature inaccessible from the settings UI and corrupts loaded configs even if the value is already persisted.
    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
  • [Major] The new runtime branch enables openai-responses, but the surrounding OpenAI-compatible helpers still exclude it, so diagnostics and env wiring take the wrong path. This PR opts custom relays into Responses mode in src/main/claude/pi-model-resolution.ts:284-289, yet isOpenAIProvider() and the OpenAI empty-key/base-url 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 fail validation/diagnostics or be treated as Anthropic in downstream 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))
      );
    }

Summary

  • 3 regressions block the new openai-responses mode from working end to end: save/update flows can coerce it away, the renderer still only supports three protocols, and OpenAI-compatible auth/diagnostic/env helpers still exclude it.
  • Not found in repo/docs: tests under src/tests/ covering these paths.

Testing

  • Not run (automation)
  • Missing coverage around ConfigStore.update() / hasUsableCredentials(), renderer bootstrap/save flows, and diagnostics/auth helper classification for customProtocol: 'openai-responses'.

Open Cowork Bot

*/
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;
}

// 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

) {
// 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))
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants