fix(providers): remove per-key headers input for openai-compatibility

The backend OpenAICompatibilityAPIKey struct only has api-key and
proxy-url (internal/config/config.go:570-577). PUT /openai-compatibility
unmarshals into this struct (config_lists.go:439) and discards any
unknown field, so per-entry headers submitted from the UI never persist
and never reach the runtime request path. The GET response wrapper
also has no headers field (config_auth_index.go:31-34).

Drop the per-entry headers input from BaseProviderForm and the
ApiKeyEntry/ApiKeyEntryInput types. Connectivity test and model
discovery stop reading entry-level headers — they continue to apply
provider-level headers, which is the supported contract.
This commit is contained in:
LTbinglingfeng
2026-05-28 01:44:02 +08:00
Unverified
parent 7c1b89c09d
commit 97b2152a81
8 changed files with 3 additions and 99 deletions
@@ -55,15 +55,8 @@ const emptyModel = (): ModelEntryInput => ({ name: '', alias: '' });
const emptyApiKeyEntry = (): ApiKeyEntryInput => ({
apiKey: '',
proxyUrl: '',
headersText: '',
});
const headersObjectToText = (headers?: Record<string, string>): string =>
Object.entries(headers ?? {})
.filter(([k]) => k.trim())
.map(([k, v]) => `${k}: ${v}`)
.join('\n');
const stripDisableAllRule = (list?: string[]): string[] =>
(list ?? []).filter((s) => s.trim() !== '*');
@@ -124,7 +117,6 @@ function buildInitialForm(
? cfg.apiKeyEntries.map((entry) => ({
apiKey: entry.apiKey,
proxyUrl: entry.proxyUrl ?? '',
headersText: headersObjectToText(entry.headers),
authIndex: entry.authIndex,
}))
: [emptyApiKeyEntry()],
@@ -823,30 +815,6 @@ export function BaseProviderForm({
placeholder="http://127.0.0.1:7890"
/>
</div>
<div className={styles.field}>
<label className={styles.label}>
{t('providersPage.form.headers')}
<span className={styles.labelHint}>
{' '}
· {t('providersPage.form.headersHint')}
</span>
</label>
<textarea
className={styles.textarea}
value={entry.headersText}
rows={3}
onChange={(e) =>
updateField(
'apiKeyEntries',
apiKeyEntries.map((it, i) =>
i === realIdx ? { ...it, headersText: e.target.value } : it
)
)
}
disabled={mutating}
placeholder="X-Custom-Header: value"
/>
</div>
{status.state === 'error' ? (
<div className={styles.connectivityError}>
{status.message}
@@ -42,23 +42,6 @@ const pickModel = (
return '';
};
const parseHeadersText = (text: string): Record<string, string> => {
const out: Record<string, string> = {};
String(text ?? '')
.split(/\n+/)
.map((line) => line.trim())
.filter(Boolean)
.forEach((line) => {
const sep = line.indexOf(':');
if (sep <= 0) return;
const key = line.slice(0, sep).trim();
const value = line.slice(sep + 1).trim();
if (!key) return;
out[key] = value;
});
return out;
};
const resolveBearerToken = (headers: Record<string, string>): string => {
const auth = Object.entries(headers).find(
([k]) => k.toLowerCase() === 'authorization'
@@ -130,7 +113,6 @@ export function useConnectivityTest(
entry.apiKey ?? '',
entry.authIndex ?? '',
entry.proxyUrl ?? '',
entry.headersText ?? '',
].join('||')
),
[apiKeyEntries]
@@ -225,7 +207,6 @@ export function useConnectivityTest(
const headerObj: Record<string, string> = {
'Content-Type': 'application/json',
...buildHeaderObject(formHeaders),
...parseHeadersText(entry?.headersText ?? ''),
};
if (!hasHeader(headerObj, 'authorization')) {
if (entryKey) {
@@ -14,23 +14,6 @@ export const MODEL_DISCOVERY_BRANDS: ReadonlyArray<ProviderBrand> = [
export const isModelDiscoveryBrand = (brand: ProviderBrand): boolean =>
MODEL_DISCOVERY_BRANDS.includes(brand);
const parseHeadersText = (text: string): Record<string, string> => {
const out: Record<string, string> = {};
String(text ?? '')
.split(/\n+/)
.map((line) => line.trim())
.filter(Boolean)
.forEach((line) => {
const sep = line.indexOf(':');
if (sep <= 0) return;
const key = line.slice(0, sep).trim();
const value = line.slice(sep + 1).trim();
if (!key) return;
out[key] = value;
});
return out;
};
const toErrorMessage = (err: unknown): string => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
@@ -115,13 +98,11 @@ export function useModelDiscovery(
const entryKey = (firstEntry?.apiKey ?? '').trim();
const entryAuthIndex =
(firstEntry?.authIndex ?? '').trim() || resolvedAuthIndex;
const entryHeaders = parseHeadersText(firstEntry?.headersText ?? '');
const headers = { ...baseHeaders, ...entryHeaders };
try {
next = await modelsApi.fetchModelsViaApiCall(
baseUrl,
entryKey,
headers,
baseHeaders,
entryAuthIndex
);
} catch (firstErr) {
@@ -167,7 +148,7 @@ export function useModelDiscovery(
.map((h) => `${h.key}:${h.value}`)
.join('|');
const entriesSig = (apiKeyEntries ?? [])
.map((e) => `${e.apiKey ?? ''}::${e.authIndex ?? ''}::${e.headersText ?? ''}`)
.map((e) => `${e.apiKey ?? ''}::${e.authIndex ?? ''}`)
.join('|');
return [
baseUrl,
-1
View File
@@ -91,7 +91,6 @@ export interface ModelEntryInput {
export interface ApiKeyEntryInput {
apiKey: string;
proxyUrl: string;
headersText: string;
authIndex?: string;
}
@@ -74,23 +74,6 @@ const parseTextList = (text: string): string[] =>
.map((item) => item.trim())
.filter(Boolean);
const parseHeadersText = (text: string): Record<string, string> => {
const result: Record<string, string> = {};
text
.split(/\n+/)
.map((line) => line.trim())
.filter(Boolean)
.forEach((line) => {
const sep = line.indexOf(':');
if (sep <= 0) return;
const key = line.slice(0, sep).trim();
const value = line.slice(sep + 1).trim();
if (!key) return;
result[key] = value;
});
return result;
};
const headersFromEntries = (
entries: Array<{ key: string; value: string }>
): Record<string, string> => {
@@ -177,9 +160,6 @@ const buildOpenAIConfig = (
?.map((entry) => ({
apiKey: entry.apiKey.trim(),
proxyUrl: entry.proxyUrl.trim() || undefined,
headers: Object.keys(parseHeadersText(entry.headersText)).length
? parseHeadersText(entry.headersText)
: undefined,
authIndex: entry.authIndex?.trim() || undefined,
}))
.filter((entry) => entry.apiKey) ?? [];
-2
View File
@@ -265,8 +265,6 @@ const serializeModelAliases = (models?: ModelAlias[]) =>
const serializeApiKeyEntry = (entry: ApiKeyEntry) => {
const payload: Record<string, unknown> = { 'api-key': entry.apiKey };
if (entry.proxyUrl) payload['proxy-url'] = entry.proxyUrl;
const headers = serializeHeaders(entry.headers);
if (headers) payload.headers = headers;
return payload;
};
+1 -3
View File
@@ -99,13 +99,11 @@ const normalizeApiKeyEntry = (entry: unknown): ApiKeyEntry | null => {
if (!trimmed) return null;
const proxyUrl = record?.['proxy-url'];
const headers = record ? normalizeHeaders(record.headers) : undefined;
const authIndex = normalizeAuthIndex(record?.['auth-index']);
const result: ApiKeyEntry = {
apiKey: trimmed,
proxyUrl: proxyUrl ? String(proxyUrl) : undefined,
headers
proxyUrl: proxyUrl ? String(proxyUrl) : undefined
};
if (authIndex) result.authIndex = authIndex;
return result;
-1
View File
@@ -13,7 +13,6 @@ export interface ModelAlias {
export interface ApiKeyEntry {
apiKey: string;
proxyUrl?: string;
headers?: Record<string, string>;
authIndex?: string;
}