fix(openai-compatibility): append chat and models paths directly to base URLs
This commit is contained in:
1 parent
7c2dab9fc2
commit
65df80091c
3 files changed
+62
-8
No files matched your search
@@ -40,21 +40,23 @@ const stripKnownSuffix = (baseUrl: string) =>
|
||||
.replace(/\/(?:v1beta|v1)\/models$/i, '')
|
||||
.replace(/\/models$/i, '');
|
||||
|
||||
const endpointCandidates = (provider: ModelProvider, baseUrl: string): string[] => {
|
||||
export const modelEndpointCandidates = (provider: ModelProvider, baseUrl: string): string[] => {
|
||||
const resolvedBaseUrl = baseUrl.trim()
|
||||
|| (provider === 'gemini'
|
||||
? DEFAULT_GEMINI_BASE_URL
|
||||
: provider === 'claude'
|
||||
? DEFAULT_CLAUDE_BASE_URL
|
||||
: '');
|
||||
const base = stripKnownSuffix(resolvedBaseUrl);
|
||||
if (!base) return [];
|
||||
const normalized = normalizeBaseUrl(resolvedBaseUrl);
|
||||
if (!normalized) return [];
|
||||
if (provider === 'openai') {
|
||||
return [/\/models$/i.test(normalized) ? normalized : `${normalized}/models`];
|
||||
}
|
||||
const base = stripKnownSuffix(normalized);
|
||||
const withoutVersion = base.replace(/\/(?:v1beta|v1)$/i, '');
|
||||
if (provider === 'gemini') return [`${withoutVersion}/v1beta/models`];
|
||||
if (provider === 'claude') return [`${withoutVersion}/v1/models`];
|
||||
const first = /\/v1$/i.test(base) ? `${base}/models` : `${base}/v1/models`;
|
||||
const fallback = `${base}/models`;
|
||||
return first === fallback ? [first] : [first, fallback];
|
||||
return [/\/v1$/i.test(base) ? `${base}/models` : `${base}/v1/models`];
|
||||
};
|
||||
|
||||
const normalizeModelList = (payload: unknown): ModelOption[] => {
|
||||
@@ -95,7 +97,7 @@ export async function fetchModels(
|
||||
timeoutMs?: number,
|
||||
): Promise<ModelOption[]> {
|
||||
const normalized = baseUrl.trim() ? normalizeBaseUrl(baseUrl) : '';
|
||||
const candidates = endpointCandidates(provider, normalized);
|
||||
const candidates = modelEndpointCandidates(provider, normalized);
|
||||
if (candidates.length === 0) throw new Error(modelText('model.error.baseUrlRequired'));
|
||||
const headers: Record<string, string> = { ...customHeaders };
|
||||
const hasHeader = (name: string) =>
|
||||
|
||||
@@ -44,6 +44,12 @@ const endpointRoot = (provider: ModelProvider, baseUrl: string) => {
|
||||
return normalized.replace(/\/(?:v1beta|v1)$/i, '');
|
||||
};
|
||||
|
||||
const openAIChatCompletionsEndpoint = (baseUrl: string) => {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
if (!normalized) return '';
|
||||
return `${normalized}/chat/completions`;
|
||||
};
|
||||
|
||||
const hasHeader = (headers: Record<string, string>, name: string) =>
|
||||
Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
||||
|
||||
@@ -148,7 +154,7 @@ export function buildProviderHealthProbe(
|
||||
}
|
||||
|
||||
return {
|
||||
url: `${root}/v1/chat/completions`,
|
||||
url: openAIChatCompletionsEndpoint(baseUrl),
|
||||
header: headers,
|
||||
protocol: 'openai-chat',
|
||||
data: JSON.stringify({
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
primaryProviderHealthCredential,
|
||||
runProviderModelHealthChecks,
|
||||
} from '../src/services/providerHealthCheck';
|
||||
import { modelEndpointCandidates } from '../src/services/modelService';
|
||||
|
||||
describe('API 接入健康检测', () => {
|
||||
it('健康检测只保留已勾选模型,并使用发现结果补充模型信息', () => {
|
||||
@@ -59,6 +60,51 @@ describe('API 接入健康检测', () => {
|
||||
expect(probe.protocol).toBe('openai-chat');
|
||||
});
|
||||
|
||||
it('为非 v1 的 OpenAI 兼容版本前缀保留原始版本号', () => {
|
||||
const probe = buildProviderHealthProbe(
|
||||
'openai',
|
||||
'https://open.bigmodel.cn/api/coding/paas/v4',
|
||||
'glm-test',
|
||||
'secret-key',
|
||||
);
|
||||
|
||||
expect(probe.url).toBe(
|
||||
'https://open.bigmodel.cn/api/coding/paas/v4/chat/completions',
|
||||
);
|
||||
expect(modelEndpointCandidates(
|
||||
'openai',
|
||||
'https://open.bigmodel.cn/api/coding/paas/v4',
|
||||
)).toEqual([
|
||||
'https://open.bigmodel.cn/api/coding/paas/v4/models',
|
||||
]);
|
||||
});
|
||||
|
||||
it('把不含版本号的中转地址视为完整 API 路径前缀', () => {
|
||||
const baseUrl = 'https://relay.example/custom/openai';
|
||||
const probe = buildProviderHealthProbe(
|
||||
'openai',
|
||||
baseUrl,
|
||||
'mapped-model',
|
||||
'secret-key',
|
||||
);
|
||||
|
||||
expect(probe.url).toBe(
|
||||
'https://relay.example/custom/openai/chat/completions',
|
||||
);
|
||||
expect(modelEndpointCandidates('openai', baseUrl)).toEqual([
|
||||
'https://relay.example/custom/openai/models',
|
||||
]);
|
||||
expect(modelEndpointCandidates(
|
||||
'openai',
|
||||
'https://relay.example/custom/openai/v1/models',
|
||||
)).toEqual([
|
||||
'https://relay.example/custom/openai/v1/models',
|
||||
]);
|
||||
expect(modelEndpointCandidates('codex', 'https://codex.example')).toEqual([
|
||||
'https://codex.example/v1/models',
|
||||
]);
|
||||
});
|
||||
|
||||
it('不会覆盖供应商已有的自定义认证头', () => {
|
||||
const probe = buildProviderHealthProbe(
|
||||
'claude',
|
||||
|
||||
Reference in new issue
Block a user