feat(config): support 'openai-responses' as customProtocol value#173
feat(config): support 'openai-responses' as customProtocol value#173lxistired wants to merge 1 commit into
Conversation
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.
There was a problem hiding this comment.
Review mode: initial
Findings
-
[Major]
ConfigStore.update()still normalizesopenai-responsesback to another protocol, so a normal save can silently drop the new setting. The diff adds the new enum value insrc/main/config/config-store.ts:38, butnormalizeCustomProtocol()still only acceptsopenai/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 savingapiKey,baseUrl, ormodelafter selecting the new mode will rewrite the set away fromopenai-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-responsesfrom the UI. The new type is exposed insrc/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 onlyanthropic,openai, andgemini(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 insrc/main/claude/pi-model-resolution.ts:284-289, yetisOpenAIProvider()and the OpenAI empty-key/base-url helpers only recognizecustomProtocol === '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-diagnosticsuses the same gate (src/main/config/api-diagnostics.ts:99-105), andapplyToEnv()only exportsOPENAI_*forcustomProtocol === 'openai'(src/main/config/config-store.ts:1416-1429). A custom/v1/responsesrelay 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-responsesmode 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 forcustomProtocol: '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'; |
There was a problem hiding this comment.
[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'; |
There was a problem hiding this comment.
[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 |
There was a problem hiding this comment.
[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))
);
}
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 anyopenai-responsesmodel toopenai-completionsfor 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-responsesas a fourthcustomProtocolvalue alongsideanthropic/openai/gemini:pi-model-resolution.ts:resolvePiRouteProtocolreturns'openai-responses'when the user picks it explicitly.inferPiApimaps'openai-responses'->'openai-responses'.applyPiModelRuntimeOverridesonly force-downgrades toopenai-completionswhencustomProtocol !== 'openai-responses'. When the user opts in explicitly, forceapito'openai-responses'(so the relay actually gets/v1/responsescalls).config-store.ts:CustomProtocolTypeunion adds'openai-responses'.isCustomProtocoltype guard accepts it.profileKeyFromProviderroutes it to the same'custom:openai'profile slot — both protocols shareapiKey/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 --noEmitpassescustomProtocol: 'openai'config still downgrades toopenai-completionscustomProtocol: 'openai-responses'keepsapi === 'openai-responses'openaiandopenai-responsesdoes NOT lose stored apiKey/baseUrl (shared profile slot)