fix(providers): complete custom openai-responses support (#436)

This commit is contained in:
Felix
2026-03-12 16:19:05 +08:00
committed by GitHub
Unverified
parent 272432783a
commit c0c8701cc3
13 changed files with 414 additions and 67 deletions

View File

@@ -114,6 +114,15 @@ import clawxIcon from '@/assets/logo.svg';
// Use the shared provider registry for setup providers
const providers = SETUP_PROVIDERS;
function getProtocolBaseUrlPlaceholder(
apiProtocol: ProviderAccount['apiProtocol'],
): string {
if (apiProtocol === 'anthropic-messages') {
return 'https://api.example.com/anthropic';
}
return 'https://api.example.com/v1';
}
// NOTE: Channel types moved to Settings > Channels page
// NOTE: Skill bundles moved to Settings > Skills page - auto-install essential skills during setup
@@ -712,6 +721,7 @@ function ProviderContent({
const [selectedAccountId, setSelectedAccountId] = useState<string | null>(null);
const [baseUrl, setBaseUrl] = useState('');
const [modelId, setModelId] = useState('');
const [apiProtocol, setApiProtocol] = useState<ProviderAccount['apiProtocol']>('openai-completions');
const [providerMenuOpen, setProviderMenuOpen] = useState(false);
const providerMenuRef = useRef<HTMLDivElement | null>(null);
@@ -905,6 +915,7 @@ function ProviderContent({
let cancelled = false;
(async () => {
if (!selectedProvider) return;
setApiProtocol('openai-completions');
try {
const snapshot = await fetchProviderSnapshot();
const statusMap = new Map(snapshot.statuses.map((status) => [status.id, status]));
@@ -917,7 +928,7 @@ function ProviderContent({
const accountIdForLoad = preferredAccount?.id || selectedProvider;
setSelectedAccountId(preferredAccount?.id || null);
const savedProvider = await hostApiFetch<{ baseUrl?: string; model?: string } | null>(
const savedProvider = await hostApiFetch<{ baseUrl?: string; model?: string; apiProtocol?: ProviderAccount['apiProtocol'] } | null>(
`/api/providers/${encodeURIComponent(accountIdForLoad)}`,
);
const storedKey = (await hostApiFetch<{ apiKey: string | null }>(
@@ -929,6 +940,7 @@ function ProviderContent({
const info = providers.find((p) => p.id === selectedProvider);
setBaseUrl(savedProvider?.baseUrl || info?.defaultBaseUrl || '');
setModelId(savedProvider?.model || info?.defaultModelId || '');
setApiProtocol(savedProvider?.apiProtocol || 'openai-completions');
}
} catch (error) {
if (!cancelled) {
@@ -1002,7 +1014,12 @@ function ProviderContent({
'provider:validateKey',
selectedAccountId || selectedProvider,
apiKey,
{ baseUrl: baseUrl.trim() || undefined }
{
baseUrl: baseUrl.trim() || undefined,
apiProtocol: (selectedProvider === 'custom' || selectedProvider === 'ollama')
? apiProtocol
: undefined,
}
) as { valid: boolean; error?: string };
setKeyValid(result.valid);
@@ -1039,6 +1056,9 @@ function ProviderContent({
? 'local'
: 'api_key',
baseUrl: baseUrl.trim() || undefined,
apiProtocol: (selectedProvider === 'custom' || selectedProvider === 'ollama')
? apiProtocol
: undefined,
model: effectiveModelId,
enabled: true,
isDefault: false,
@@ -1056,6 +1076,7 @@ function ProviderContent({
label: accountPayload.label,
authMode: accountPayload.authMode,
baseUrl: accountPayload.baseUrl,
apiProtocol: accountPayload.apiProtocol,
model: accountPayload.model,
enabled: accountPayload.enabled,
},
@@ -1212,7 +1233,7 @@ function ProviderContent({
<Input
id="baseUrl"
type="text"
placeholder="https://api.example.com/v1"
placeholder={getProtocolBaseUrlPlaceholder(apiProtocol)}
value={baseUrl}
onChange={(e) => {
setBaseUrl(e.target.value);
@@ -1246,6 +1267,59 @@ function ProviderContent({
</div>
)}
{selectedProvider === 'custom' && (
<div className="space-y-2">
<Label>{t('provider.protocol')}</Label>
<div className="flex gap-2 text-sm">
<button
type="button"
onClick={() => {
setApiProtocol('openai-completions');
onConfiguredChange(false);
}}
className={cn(
'flex-1 py-2 px-3 rounded-lg border transition-colors',
apiProtocol === 'openai-completions'
? 'bg-primary/10 border-primary/30 font-medium'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted'
)}
>
{t('provider.protocols.openaiCompletions')}
</button>
<button
type="button"
onClick={() => {
setApiProtocol('openai-responses');
onConfiguredChange(false);
}}
className={cn(
'flex-1 py-2 px-3 rounded-lg border transition-colors',
apiProtocol === 'openai-responses'
? 'bg-primary/10 border-primary/30 font-medium'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted'
)}
>
{t('provider.protocols.openaiResponses')}
</button>
<button
type="button"
onClick={() => {
setApiProtocol('anthropic-messages');
onConfiguredChange(false);
}}
className={cn(
'flex-1 py-2 px-3 rounded-lg border transition-colors',
apiProtocol === 'anthropic-messages'
? 'bg-primary/10 border-primary/30 font-medium'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted'
)}
>
{t('provider.protocols.anthropic')}
</button>
</div>
</div>
)}
{/* Auth mode toggle for providers supporting both */}
{isOAuth && supportsApiKey && (
<div className="flex rounded-lg border overflow-hidden text-sm">