From 2b41eab8df07573699741215187bd7c85977533e Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Sun, 13 Sep 2026 20:59:40 +0800 Subject: [PATCH] feat(api-access): manage DeepSeek through Codex API --- src/i18n/ja.ts | 4 -- src/i18n/locales/en.ts | 4 -- src/i18n/locales/zh-CN.ts | 4 -- src/pages/ApiAccessPage.tsx | 95 +++++++++++++---------------- src/pages/EasyModePage.tsx | 10 ++- src/services/modelService.ts | 16 ++--- src/services/providerHealthCheck.ts | 7 ++- src/styles.css | 34 ----------- tests/modelService.test.ts | 14 ----- tests/providerConfig.test.ts | 25 ++++---- tests/providerHealthCheck.test.ts | 20 ++++++ 11 files changed, 88 insertions(+), 145 deletions(-) diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 7e3913e..4ec7e9f 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -1205,10 +1205,6 @@ export const jaOverrides = { 'apiAccess.remarkPlaceholder': '例:本番環境、チームのメインアカウント', 'apiAccess.baseRequiredPlaceholder': '必須、例:https://api.example.com', 'apiAccess.baseOptionalPlaceholder': '任意。空欄の場合は既定のアドレスを使用', - 'apiAccess.preset.title': 'OpenAI 互換プリセット', - 'apiAccess.preset.description': 'DeepSeek 公式アドレスを入力済みです。モデル一覧を取得し、公開するモデルを選択してください', - 'apiAccess.thinking.builtIn': '組み込み推論レベル', - 'apiAccess.thinking.builtInDescription': '現在公開中のすべてのモデルに自動適用します', 'apiAccess.thinking.title': '推論レベル', 'apiAccess.thinking.description': '上流の対応状況に合わせて追加してください', 'apiAccess.thinking.placeholder': '例:low、medium、またはカスタムレベル', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 2028842..84d7a78 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -1205,10 +1205,6 @@ export const en: Record = { 'apiAccess.remarkPlaceholder': 'For example, Production or Team primary account', 'apiAccess.baseRequiredPlaceholder': 'Required, for example https://api.example.com', 'apiAccess.baseOptionalPlaceholder': 'Optional; leave blank to use the default address', - 'apiAccess.preset.title': 'OpenAI-Compatible Preset', - 'apiAccess.preset.description': 'The official DeepSeek address is prefilled; fetch the list and select the models to open', - 'apiAccess.thinking.builtIn': 'Built-in Reasoning Levels', - 'apiAccess.thinking.builtInDescription': 'Applied automatically to all currently open models', 'apiAccess.thinking.title': 'Reasoning Levels', 'apiAccess.thinking.description': 'Add levels supported by the upstream provider', 'apiAccess.thinking.placeholder': 'For example: low, medium, or a custom level', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 101be27..fe32d83 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -1204,10 +1204,6 @@ export const zhCN = { 'apiAccess.remarkPlaceholder': '例如:生产环境、团队主账号', 'apiAccess.baseRequiredPlaceholder': '必填,例如 https://api.example.com', 'apiAccess.baseOptionalPlaceholder': '可选,留空使用默认地址', - 'apiAccess.preset.title': 'OpenAI 兼容预设', - 'apiAccess.preset.description': '已预填 DeepSeek 官方地址;请获取模型列表并选择需要开放的模型', - 'apiAccess.thinking.builtIn': '内置思考等级', - 'apiAccess.thinking.builtInDescription': '自动应用到当前开放的全部模型', 'apiAccess.thinking.title': '思考等级', 'apiAccess.thinking.description': '按上游支持情况自行添加', 'apiAccess.thinking.placeholder': '例如 low、medium 或自定义等级', diff --git a/src/pages/ApiAccessPage.tsx b/src/pages/ApiAccessPage.tsx index 451223f..d718d52 100644 --- a/src/pages/ApiAccessPage.tsx +++ b/src/pages/ApiAccessPage.tsx @@ -54,9 +54,7 @@ import { responseList, } from '../services/managementApi'; import { - applyDeepSeekModelPreset, DEEPSEEK_BASE_URL, - DEEPSEEK_THINKING_LEVELS, fetchModels, mergeModelOptions, modelsFromRecord, @@ -87,7 +85,7 @@ export type ProviderSection = export type ProviderCategory = ProviderSection | 'deepseek'; export const OPENAI_THINKING_LEVELS = ['low', 'medium', 'high', 'xhigh'] as const; -export { DEEPSEEK_BASE_URL, DEEPSEEK_THINKING_LEVELS }; +export { DEEPSEEK_BASE_URL }; type ProviderDefinition = { id: ProviderCategory; @@ -229,11 +227,11 @@ const providerDefinitions: ProviderDefinition[] = [ }, { id: 'deepseek', - section: 'openai-compatibility', - responseKey: 'openai-compatibility', + section: 'codex-api-key', + responseKey: 'codex-api-key', labelKey: 'apiAccess.provider.deepseek', icon: deepseekIcon, - openAi: true, + openAi: false, }, { id: 'claude-api-key', section: 'claude-api-key', responseKey: 'claude-api-key', labelKey: 'apiAccess.provider.claude', icon: claudeIcon, openAi: false }, { id: 'gemini-api-key', section: 'gemini-api-key', responseKey: 'gemini-api-key', labelKey: 'apiAccess.provider.gemini', icon: geminiIcon, openAi: false }, @@ -265,9 +263,17 @@ const isDeepSeekRecord = (record: Record) => { export const providerCategoryMatchesRecord = ( category: ProviderCategory, record: Record, + section: ProviderSection = definitionFor(category).section, ) => { - if (category === 'deepseek') return isDeepSeekRecord(record); - if (category === 'openai-compatibility') return !isDeepSeekRecord(record); + if (category === 'deepseek') { + return section === 'codex-api-key' && isDeepSeekRecord(record); + } + if (category === 'codex-api-key') { + return section === 'codex-api-key' && !isDeepSeekRecord(record); + } + if (category === 'openai-compatibility') { + return section === 'openai-compatibility'; + } return true; }; @@ -328,9 +334,13 @@ const providerHealthIdentity = (row: ProviderRow) => [ row.models.map((model) => model.name).join('\u0000'), ].join('\u0001'); -const providerModelType = (section: ProviderSection): ModelProvider => { +const providerModelType = ( + section: ProviderSection, + record?: Record, +): ModelProvider => { if (section === 'gemini-api-key') return 'gemini'; if (section === 'claude-api-key') return 'claude'; + if (section === 'codex-api-key' && record && isDeepSeekRecord(record)) return 'deepseek'; if (section === 'codex-api-key') return 'codex'; return 'openai'; }; @@ -474,9 +484,9 @@ const thinkingLevelsFromModels = (models: ModelOption[]): string[] => { const draftFromRow = (row: ProviderRow): ProviderDraft => { const definition = definitionFor(row.section); - const isDeepSeek = row.section === 'openai-compatibility' && isDeepSeekRecord(row.record); + const isDeepSeek = row.section === 'codex-api-key' && isDeepSeekRecord(row.record); return { - name: row.name, + name: isDeepSeek ? 'DeepSeek' : row.name, apiKey: definition.openAi ? row.apiKeys.join('\n') : row.apiKey, remark: row.remark || (definition.openAi && !isDeepSeek ? row.name : ''), baseUrl: row.baseUrl, @@ -541,7 +551,6 @@ export const createProviderDraft = (category: ProviderCategory): ProviderDraft = name: 'DeepSeek', remark: '', baseUrl: DEEPSEEK_BASE_URL, - thinkingLevels: [...DEEPSEEK_THINKING_LEVELS], }; }; @@ -558,13 +567,6 @@ export const applyProviderPreset = ( category: ProviderCategory, draft: ProviderDraft, ): ProviderDraft => { - if (category === 'deepseek') { - return { - ...draft, - thinkingLevels: [...DEEPSEEK_THINKING_LEVELS], - models: applyDeepSeekModelPreset(draft.models), - }; - } if (!definitionFor(category).openAi || draft.thinkingLevels === undefined) return draft; const levels = draft.thinkingLevels; return { @@ -702,6 +704,9 @@ export const buildProviderRecord = ( 'api-key': draft.apiKey.trim(), models, }; + if (section === 'codex-api-key' && draft.name.trim().toLowerCase() === 'deepseek') { + next.name = 'DeepSeek'; + } if (draft.baseUrl.trim()) next['base-url'] = draft.baseUrl.trim(); else delete next['base-url']; if (priority !== null && Number.isFinite(priority)) next.priority = priority; @@ -898,9 +903,12 @@ export function ApiAccessPage() { .map((record, index) => rowFromRecord(activeSection, record, index)) .map((row) => ({ ...row, + name: activeCategory === 'deepseek' + ? t('apiAccess.provider.deepseek') + : row.name, remark: apiAccessRemarks[providerRemarkIdentity(row.section, row.apiKeys)] ?? '', })) - .filter((row) => providerCategoryMatchesRecord(activeCategory, row.record)) + .filter((row) => providerCategoryMatchesRecord(activeCategory, row.record, activeSection)) .filter((row) => { const query = filter.trim().toLowerCase(); if (!query) return true; @@ -909,7 +917,7 @@ export function ApiAccessPage() { .toLowerCase() .includes(query); }), - [activeCategory, activeSection, apiAccessRemarks, filter, records], + [activeCategory, activeSection, apiAccessRemarks, filter, records, t], ); const openCreate = () => { @@ -925,9 +933,7 @@ export function ApiAccessPage() { setError(''); setEditingRow(row); const draft = draftFromRow(row); - setDialogDraft(activeCategory === 'deepseek' - ? { ...draft, thinkingLevels: [...DEEPSEEK_THINKING_LEVELS] } - : draft); + setDialogDraft(draft); setDialogOpen(true); }; @@ -1189,7 +1195,7 @@ export function ApiAccessPage() { const countForDefinition = (definition: ProviderDefinition) => records[definition.section].filter((record) => - providerCategoryMatchesRecord(definition.id, record) + providerCategoryMatchesRecord(definition.id, record, definition.section) ).length; return ( @@ -1390,7 +1396,7 @@ function ProviderHealthDialog({ row, onClose }: ProviderHealthDialogProps) { }, []); const healthOptions = useMemo(() => ({ - provider: providerModelType(row.section), + provider: providerModelType(row.section, row.record), baseUrl: row.baseUrl, apiKeys: row.apiKeys, authIndex: row.authIndex, @@ -1713,13 +1719,15 @@ function ApiProviderDialog({ setModelLoading(true); setModelError(''); try { - const provider: ModelProvider = definition.section === 'gemini-api-key' - ? 'gemini' - : definition.section === 'claude-api-key' - ? 'claude' - : definition.section === 'codex-api-key' - ? 'codex' - : 'openai'; + const provider: ModelProvider = activeCategory === 'deepseek' + ? 'deepseek' + : definition.section === 'gemini-api-key' + ? 'gemini' + : definition.section === 'claude-api-key' + ? 'claude' + : definition.section === 'codex-api-key' + ? 'codex' + : 'openai'; const modelApiKey = draft.apiKey.split(/\r?\n/).map((value) => value.trim()).find(Boolean) ?? ''; const fetchedModels = await fetchModels( provider, @@ -1867,26 +1875,7 @@ function ApiProviderDialog({ /> - {activeCategory === 'deepseek' ? ( -
- -
- {t('apiAccess.preset.title')} - {t('apiAccess.preset.description')} -
-
- ) : null} - {activeCategory === 'deepseek' ? ( -
-
- {t('apiAccess.thinking.builtIn')} - {t('apiAccess.thinking.builtInDescription')} -
-
- {DEEPSEEK_THINKING_LEVELS.map((level) => {level})} -
-
- ) : activeCategory === 'openai-compatibility' ? ( + {activeCategory === 'openai-compatibility' ? (
{t('apiAccess.thinking.title')} diff --git a/src/pages/EasyModePage.tsx b/src/pages/EasyModePage.tsx index a975809..17dd271 100644 --- a/src/pages/EasyModePage.tsx +++ b/src/pages/EasyModePage.tsx @@ -31,7 +31,6 @@ import { responseList, } from "../services/managementApi"; import { - applyDeepSeekModelPreset, DEEPSEEK_BASE_URL, fetchModels, mergeModelOptions, @@ -89,7 +88,7 @@ const apiSectionOptions: ApiSectionOption[] = [ { id: "claude", managementSection: "claude-api-key", nameKey: "easyMode.api.platformName.claude", provider: "claude", defaultBaseUrl: "", icon: claudeIcon }, { id: "codex", managementSection: "codex-api-key", nameKey: "easyMode.api.platformName.codex", provider: "codex", defaultBaseUrl: "", icon: codexIcon }, { id: "gemini", managementSection: "gemini-api-key", nameKey: "easyMode.api.platformName.gemini", provider: "gemini", defaultBaseUrl: "", icon: geminiIcon }, - { id: "deepseek", managementSection: "openai-compatibility", nameKey: "easyMode.api.platformName.deepseek", provider: "openai", defaultBaseUrl: DEEPSEEK_BASE_URL, icon: deepseekIcon }, + { id: "deepseek", managementSection: "codex-api-key", nameKey: "easyMode.api.platformName.deepseek", provider: "deepseek", defaultBaseUrl: DEEPSEEK_BASE_URL, icon: deepseekIcon }, ]; const isDeepSeekRecord = (record: Record) => { @@ -196,7 +195,7 @@ export function EasyModePage({ const sourceList = recordsBySection[section.managementSection]; const list = section.id === "deepseek" ? sourceList.filter(isDeepSeekRecord) - : section.id === "openai-compatibility" + : section.id === "codex" ? sourceList.filter((record) => !isDeepSeekRecord(record)) : sourceList; counts[section.id] = list.length; @@ -427,9 +426,7 @@ export function EasyModePage({ const configPayload = await managementApi.get("/config"); const list = responseList(configPayload, managementSection); const selectedModels = apiSelectedModels.map((model) => ({ name: model.name.trim() })); - const models = selectedApiSection === "deepseek" - ? applyDeepSeekModelPreset(selectedModels) - : selectedModels; + const models = selectedModels; const newEntry = managementSection === "openai-compatibility" ? { name: apiRemark.trim() || `${selectedApiSection} (${list.length + 1})`, @@ -440,6 +437,7 @@ export function EasyModePage({ models, } : { + ...(selectedApiSection === "deepseek" ? { name: "DeepSeek" } : {}), "api-key": apiKey.trim(), "base-url": normalizeBaseUrl(apiBaseUrl.trim()), models, diff --git a/src/services/modelService.ts b/src/services/modelService.ts index bb66494..7b9d710 100644 --- a/src/services/modelService.ts +++ b/src/services/modelService.ts @@ -11,14 +11,13 @@ export type ModelOption = { inputModalities?: Array<'text' | 'image'>; thinking?: Record; }; -export type ModelProvider = 'gemini' | 'codex' | 'claude' | 'openai'; +export type ModelProvider = 'gemini' | 'codex' | 'deepseek' | 'claude' | 'openai'; export type ModelSelectionMode = 'initial' | 'refresh'; const DEFAULT_GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com'; const DEFAULT_CLAUDE_BASE_URL = 'https://api.anthropic.com'; export const DEEPSEEK_BASE_URL = 'https://api.deepseek.com'; -export const DEEPSEEK_THINKING_LEVELS = ['low', 'high', 'max'] as const; const modelKey = (name: string) => name.trim().toLowerCase(); @@ -56,16 +55,6 @@ export function reconcileModelSelection( return new Set(Array.from(requestedSelection).filter((name) => availableNames.has(name))); } -export function applyDeepSeekModelPreset(models: ModelOption[]): ModelOption[] { - return models.map((model) => ({ - ...model, - thinking: { - ...model.thinking, - levels: [...DEEPSEEK_THINKING_LEVELS], - }, - })); -} - export function normalizeBaseUrl(value: string): string { let raw = value.trim(); if (!raw) return ''; @@ -97,6 +86,8 @@ export const modelEndpointCandidates = (provider: ModelProvider, baseUrl: string ? DEFAULT_GEMINI_BASE_URL : provider === 'claude' ? DEFAULT_CLAUDE_BASE_URL + : provider === 'deepseek' + ? DEEPSEEK_BASE_URL : ''); const normalized = normalizeBaseUrl(resolvedBaseUrl); if (!normalized) return []; @@ -107,6 +98,7 @@ export const modelEndpointCandidates = (provider: ModelProvider, baseUrl: string const withoutVersion = base.replace(/\/(?:v1beta|v1)$/i, ''); if (provider === 'gemini') return [`${withoutVersion}/v1beta/models`]; if (provider === 'claude') return [`${withoutVersion}/v1/models`]; + if (provider === 'deepseek') return [`${base}/models`]; return [/\/v1$/i.test(base) ? `${base}/models` : `${base}/v1/models`]; }; diff --git a/src/services/providerHealthCheck.ts b/src/services/providerHealthCheck.ts index 1d48d07..10964ce 100644 --- a/src/services/providerHealthCheck.ts +++ b/src/services/providerHealthCheck.ts @@ -40,6 +40,7 @@ export type ProviderHealthCheckOptions = { const defaultBaseUrl = (provider: ModelProvider) => { if (provider === 'claude') return 'https://api.anthropic.com'; if (provider === 'gemini') return 'https://generativelanguage.googleapis.com'; + if (provider === 'deepseek') return 'https://api.deepseek.com'; return ''; }; @@ -153,10 +154,12 @@ export function buildProviderHealthProbe( if (key) setHeaderIfMissing(headers, 'Authorization', `Bearer ${key}`); else if (authIndex) setHeaderIfMissing(headers, 'Authorization', 'Bearer $TOKEN$'); - if (provider === 'codex') { + if (provider === 'codex' || provider === 'deepseek') { return { ...metadata, - url: `${root}/v1/responses`, + url: provider === 'deepseek' + ? `${normalizeBaseUrl(baseUrl.trim() || defaultBaseUrl(provider))}/responses` + : `${root}/v1/responses`, header: headers, protocol: 'openai-responses', data: JSON.stringify({ diff --git a/src/styles.css b/src/styles.css index c203b04..0ea1429 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4557,40 +4557,6 @@ textarea:focus-visible { min-height: 42px; } -.provider-preset-summary { - display: grid; - grid-template-columns: 34px minmax(0, 1fr); - align-items: center; - gap: 10px; - min-height: 52px; - padding: 9px 11px; - border: 1px solid var(--theme-dce2fb); - border-radius: 7px; - background: var(--theme-f5f7ff); -} - -.provider-preset-summary .provider-logo { - width: 30px; - height: 30px; -} - -.provider-preset-summary > div { - display: grid; - min-width: 0; - gap: 3px; -} - -.provider-preset-summary strong { - color: var(--theme-344ca1); - font-size: var(--font-size-label); -} - -.provider-preset-summary span { - color: var(--theme-66709a); - font-size: var(--font-size-meta); - overflow-wrap: anywhere; -} - .thinking-level-config { display: grid; gap: 9px; diff --git a/tests/modelService.test.ts b/tests/modelService.test.ts index 1ae82f3..0e70ffc 100644 --- a/tests/modelService.test.ts +++ b/tests/modelService.test.ts @@ -1,7 +1,5 @@ import { describe, expect, it } from 'bun:test'; import { - applyDeepSeekModelPreset, - DEEPSEEK_THINKING_LEVELS, mergeModelOptions, reconcileModelSelection, } from '../src/services/modelService'; @@ -46,15 +44,3 @@ describe('model discovery selection', () => { )).toEqual([{ name: 'DEEPSEEK-CHAT', alias: 'Chat' }]); }); }); - -describe('DeepSeek model preset', () => { - it('adds built-in thinking levels while preserving existing metadata', () => { - expect(applyDeepSeekModelPreset([{ - name: 'deepseek-reasoner', - thinking: { custom: true, levels: ['legacy'] }, - }])).toEqual([{ - name: 'deepseek-reasoner', - thinking: { custom: true, levels: [...DEEPSEEK_THINKING_LEVELS] }, - }]); - }); -}); diff --git a/tests/providerConfig.test.ts b/tests/providerConfig.test.ts index 9202c9f..abec0e2 100644 --- a/tests/providerConfig.test.ts +++ b/tests/providerConfig.test.ts @@ -5,7 +5,6 @@ import { buildProviderRecord, createProviderDraft, DEEPSEEK_BASE_URL, - DEEPSEEK_THINKING_LEVELS, exclusionsForModelSelection, modelSelectionForDiscovery, parseProviderHeaders, @@ -50,8 +49,7 @@ describe('API 接入配置合并', () => { ]); }); - it('DeepSeek 对选定模型应用内置思考等级', () => { - expect(DEEPSEEK_THINKING_LEVELS).toEqual(['low', 'high', 'max']); + it('DeepSeek 使用 Codex API 记录且不写入内置思考等级', () => { const draft = createProviderDraft('deepseek'); const discovered = [ { name: 'deepseek-chat' }, @@ -64,7 +62,7 @@ describe('API 接入配置合并', () => { models: discovered, }); const identified = applyProviderRemarkIdentity('deepseek', prepared); - const result = buildProviderRecord('openai-compatibility', identified); + const result = buildProviderRecord('codex-api-key', identified); expect(draft.name).toBe('DeepSeek'); expect(draft.remark).toBe(''); @@ -74,19 +72,16 @@ describe('API 接入配置合并', () => { expect(result).toMatchObject({ name: 'DeepSeek', 'base-url': 'https://api.deepseek.com', - 'api-key-entries': [{ 'api-key': 'deepseek-key' }], + 'api-key': 'deepseek-key', models: [ { name: 'deepseek-chat', - thinking: { levels: [...DEEPSEEK_THINKING_LEVELS] }, }, { name: 'deepseek-reasoner', - thinking: { levels: [...DEEPSEEK_THINKING_LEVELS] }, }, { name: 'deepseek-new-model', - thinking: { levels: [...DEEPSEEK_THINKING_LEVELS] }, }, ], }); @@ -103,14 +98,20 @@ describe('API 接入配置合并', () => { expect(draft.name).toBe('生产环境'); }); - it('DeepSeek 接入单独归类,不在 OpenAI 兼容列表重复显示', () => { - const record = { + it('DeepSeek 只从 Codex API 分类识别,旧 OpenAI 兼容记录保持原样', () => { + const codexRecord = { + name: 'custom-deepseek', + 'base-url': 'https://api.deepseek.com/v1', + }; + const legacyRecord = { name: 'custom-deepseek', 'base-url': 'https://api.deepseek.com/v1', }; - expect(providerCategoryMatchesRecord('deepseek', record)).toBe(true); - expect(providerCategoryMatchesRecord('openai-compatibility', record)).toBe(false); + expect(providerCategoryMatchesRecord('deepseek', codexRecord, 'codex-api-key')).toBe(true); + expect(providerCategoryMatchesRecord('codex-api-key', codexRecord, 'codex-api-key')).toBe(false); + expect(providerCategoryMatchesRecord('deepseek', legacyRecord, 'openai-compatibility')).toBe(false); + expect(providerCategoryMatchesRecord('openai-compatibility', legacyRecord, 'openai-compatibility')).toBe(true); }); it('OpenAI 兼容接入把选定思考等级写入全部开放模型', () => { diff --git a/tests/providerHealthCheck.test.ts b/tests/providerHealthCheck.test.ts index a05b9e3..160b38e 100644 --- a/tests/providerHealthCheck.test.ts +++ b/tests/providerHealthCheck.test.ts @@ -178,6 +178,26 @@ describe('API 接入健康检测', () => { expect(probe.protocol).toBe('openai-responses'); }); + it('为 DeepSeek 使用 Codex Responses 协议并保留自定义路径前缀', () => { + const probe = buildProviderHealthProbe( + 'deepseek', + 'https://api.deepseek.com', + 'deepseek-chat', + 'deepseek-key', + ); + + expect(probe.url).toBe('https://api.deepseek.com/responses'); + expect(probe.protocol).toBe('openai-responses'); + expect(JSON.parse(probe.data)).toEqual({ + model: 'deepseek-chat', + input: 'hi', + stream: true, + }); + expect(modelEndpointCandidates('deepseek', 'https://api.deepseek.com')).toEqual([ + 'https://api.deepseek.com/models', + ]); + }); + it('为 Gemini 使用默认地址并移除 models/ 前缀', () => { const probe = buildProviderHealthProbe( 'gemini',