From 3f3c9e1a45507bbca93aa2d3f9f8b9b3b1bf416d Mon Sep 17 00:00:00 2001 From: hex2077 Date: Sun, 8 Mar 2026 20:20:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B9=B6=E6=89=A9=E5=B1=95=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 Gemini 3.1 Flash Lite 模型到 provider-models 和别名映射 - 扩展 Codex 转换器以支持 reasoning 配置和服务层级传递 - 为 Codex 模型添加 -fast 后缀变体,自动设置优先级服务层级和高推理强度 - 扩展 Grok 模型以支持 -nsfw 后缀变体,并添加账户级 NSFW 设置流程 - 放宽 Gemini 模型思考功能检测条件以包含所有 gemini-3 系列模型 --- VERSION | 2 +- src/converters/strategies/CodexConverter.js | 14 +-- src/providers/gemini/antigravity-core.js | 6 +- src/providers/grok/grok-core.js | 99 +++++++++++++++++++-- src/providers/openai/codex-core.js | 19 +++- src/providers/provider-models.js | 2 + 6 files changed, 124 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index b630701..c63c9a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.10.7 +2.10.8 diff --git a/src/converters/strategies/CodexConverter.js b/src/converters/strategies/CodexConverter.js index 5d341e3..6f6b167 100644 --- a/src/converters/strategies/CodexConverter.js +++ b/src/converters/strategies/CodexConverter.js @@ -103,20 +103,19 @@ export class CodexConverter extends BaseConverter { codexRequest.store = false; codexRequest.parallel_tool_calls = true; codexRequest.include = ['reasoning.encrypted_content']; + codexRequest.service_tier = responsesRequest.service_tier || 'default'; // 删除Codex不支持的字段 delete codexRequest.max_output_tokens; delete codexRequest.max_completion_tokens; delete codexRequest.temperature; delete codexRequest.top_p; - delete codexRequest.service_tier; delete codexRequest.user; - delete codexRequest.reasoning; // 添加 reasoning 配置 codexRequest.reasoning = { - "effort": "medium", - "summary": "auto" + "effort": responsesRequest.reasoning_effort || responsesRequest.reasoning?.effort || "medium", + "summary": responsesRequest.reasoning?.summary || "auto" }; @@ -155,11 +154,12 @@ export class CodexConverter extends BaseConverter { store: false, metadata: data.metadata || {}, reasoning: { - effort: data.reasoning_effort || 'medium', - summary: 'auto' + effort: data.reasoning_effort || data.reasoning?.effort || 'medium', + summary: data.reasoning?.summary || 'auto' }, parallel_tool_calls: true, - include: ['reasoning.encrypted_content'] + include: ['reasoning.encrypted_content'], + service_tier: data.service_tier || 'default' }; // 处理 OpenAI Responses 特有的 instructions 和 input 字段(如果存在) diff --git a/src/providers/gemini/antigravity-core.js b/src/providers/gemini/antigravity-core.js index 69c5c70..83aaadb 100644 --- a/src/providers/gemini/antigravity-core.js +++ b/src/providers/gemini/antigravity-core.js @@ -63,6 +63,7 @@ const MODEL_ALIAS_MAP = { 'gemini-3-pro-image-preview': 'gemini-3-pro-image', 'gemini-3-pro-preview': 'gemini-3-pro-high', 'gemini-3.1-pro-preview': 'gemini-3.1-pro-high', + 'gemini-3.1-flash-lite-preview': 'gemini-3.1-flash-lite-preview', 'gemini-3-flash-preview': 'gemini-3-flash', 'gemini-2.5-flash-preview': 'gemini-2.5-flash', 'gemini-claude-sonnet-4-5': 'claude-sonnet-4-5', @@ -77,6 +78,7 @@ const MODEL_NAME_MAP = { 'gemini-3-pro-image': 'gemini-3-pro-image-preview', 'gemini-3-pro-high': 'gemini-3-pro-preview', 'gemini-3.1-pro-high': 'gemini-3.1-pro-preview', + 'gemini-3.1-flash-lite-preview': 'gemini-3.1-flash-lite-preview', 'gemini-3-flash': 'gemini-3-flash-preview', 'gemini-2.5-flash': 'gemini-2.5-flash-preview', 'claude-sonnet-4-5': 'gemini-claude-sonnet-4-5', @@ -129,8 +131,8 @@ function isImageModel(modelName) { function modelSupportsThinking(modelName) { if (!modelName) return false; const name = modelName.toLowerCase(); - // 支持 thinking 的模型:gemini-3-*, gemini-2.5-*, claude-*-thinking - return name.startsWith('gemini-3-') || + // 支持 thinking 的模型:gemini-3*, gemini-2.5-*, claude-*-thinking + return name.startsWith('gemini-3') || name.startsWith('gemini-2.5-') || name.includes('-thinking'); } diff --git a/src/providers/grok/grok-core.js b/src/providers/grok/grok-core.js index 1065fb1..1462045 100644 --- a/src/providers/grok/grok-core.js +++ b/src/providers/grok/grok-core.js @@ -34,8 +34,7 @@ const httpsAgent = new https.Agent({ ALPNProtocols: ['http/1.1'], ecdhCurve: 'X25519:P-256:P-384', honorCipherOrder: false, sessionTimeout: 300, }); -const GROK_MODELS = getProviderModels(MODEL_PROVIDER.GROK_CUSTOM); -const MODEL_MAPPING = { +const CORE_MODEL_MAPPING = { 'grok-3': { name: 'grok-3', mode: 'MODEL_MODE_GROK_3' }, 'grok-3-mini': { name: 'grok-3', mode: 'MODEL_MODE_GROK_3_MINI_THINKING' }, 'grok-3-thinking': { name: 'grok-3', mode: 'MODEL_MODE_GROK_3_THINKING' }, @@ -53,6 +52,24 @@ const MODEL_MAPPING = { 'grok-imagine-1.0-video': { name: 'grok-3', mode: 'MODEL_MODE_FAST' } }; +const MODEL_MAPPING = { ...CORE_MODEL_MAPPING }; +Object.keys(CORE_MODEL_MAPPING).forEach(key => { + if (!key.endsWith('-nsfw')) { + MODEL_MAPPING[`${key}-nsfw`] = CORE_MODEL_MAPPING[key]; + } +}); + +const GROK_MODELS = Object.keys(MODEL_MAPPING); + +function isGrokNsfwModel(modelId) { + return typeof modelId === 'string' && modelId.toLowerCase().endsWith('-nsfw'); +} + +function normalizeGrokModelId(modelId) { + if (typeof modelId !== 'string') return modelId; + return isGrokNsfwModel(modelId) ? modelId.slice(0, -5) : modelId; +} + export class GrokApiService { constructor(config) { this.config = config; @@ -63,11 +80,70 @@ export class GrokApiService { this.baseUrl = config.GROK_BASE_URL || 'https://grok.com'; this.chatApi = `${this.baseUrl}/rest/app-chat/conversations/new`; this.isInitialized = false; + this.nsfwSetupDone = false; this.converter = ConverterFactory.getConverter(MODEL_PROTOCOL_PREFIX.GROK); if (this.converter && this.uuid) this.converter.setUuid(this.uuid); this.lastSyncAt = null; } + async setupNsfw() { + if (this.nsfwSetupDone) return; + try { + await this.acceptTos(); + await this.setBirthDate(); + await this.enableNsfwAccount(); + this.nsfwSetupDone = true; + logger.info(`[Grok NSFW] Account-level NSFW setup completed for ${this.uuid}`); + } catch (error) { + logger.warn(`[Grok NSFW] Failed to setup account-level NSFW: ${error.message}`); + } + } + + async acceptTos() { + const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/app-chat/accept-tos`, headers: this.buildHeaders(), data: {}, httpAgent, httpsAgent, timeout: 15000 }; + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); + this._applySidecar(axiosConfig); + try { await axios(axiosConfig); } catch (e) { logger.debug(`[Grok TOS] ${e.message}`); } + } + + async setBirthDate() { + const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/app-chat/set-birth-date`, headers: this.buildHeaders(), data: { "birthDate": "1990-01-01" }, httpAgent, httpsAgent, timeout: 15000 }; + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); + this._applySidecar(axiosConfig); + try { await axios(axiosConfig); } catch (e) { logger.debug(`[Grok Birth] ${e.message}`); } + } + + async enableNsfwAccount() { + const name = Buffer.from("always_show_nsfw_content"); + const inner = Buffer.concat([Buffer.from([0x0a, name.length]), name]); + const protobuf = Buffer.concat([Buffer.from([0x0a, 0x02, 0x10, 0x01, 0x12, inner.length]), inner]); + + const header = Buffer.alloc(5); + header.writeUInt8(0, 0); + header.writeUInt32BE(protobuf.length, 1); + const payload = Buffer.concat([header, protobuf]); + + const headers = this.buildHeaders(); + headers['content-type'] = 'application/grpc-web+proto'; + headers['x-grpc-web'] = '1'; + headers['x-user-agent'] = 'connect-es/2.1.1'; + headers['referer'] = `${this.baseUrl}/?_s=data`; + + const axiosConfig = { + method: 'post', + url: `${this.baseUrl}/auth_mgmt.AuthManagement/UpdateUserFeatureControls`, + headers, + data: payload, + httpAgent, + httpsAgent, + timeout: 15000, + responseType: 'arraybuffer' + }; + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); + this._applySidecar(axiosConfig); + try { await axios(axiosConfig); } catch (e) { throw e; } + } + _applySidecar(axiosConfig) { const sidecar = getTLSSidecar(); if (sidecar.isReady()) { @@ -243,7 +319,9 @@ export class GrokApiService { } buildPayload(modelId, requestBody) { - const mapping = MODEL_MAPPING[modelId] || MODEL_MAPPING['grok-3']; + const rawModelId = typeof modelId === 'string' ? modelId : ''; + const normalizedModelId = normalizeGrokModelId(rawModelId); + const mapping = MODEL_MAPPING[normalizedModelId] || MODEL_MAPPING['grok-3']; let message = requestBody.message || ""; let toolOverrides = requestBody.toolOverrides || {}; let fileAttachments = requestBody.fileAttachments || []; @@ -296,12 +374,13 @@ export class GrokApiService { if (requestBody.videoGenPrompt) message = requestBody.videoGenPrompt; } - const modelLower = modelId.toLowerCase(); + const modelLower = normalizedModelId.toLowerCase(); const isMediaModel = modelLower.includes('imagine') || modelLower.includes('video') || modelLower.includes('edit'); + const isNsfw = isGrokNsfwModel(rawModelId) || requestBody.nsfw === true || requestBody.disableNsfwFilter === true; return { "deviceEnvInfo": { "darkModeEnabled": false, "devicePixelRatio": 2, "screenWidth": 2056, "screenHeight": 1329, "viewportWidth": 2056, "viewportHeight": 1083 }, - "disableMemory": false, "disableSearch": false, "disableSelfHarmShortCircuit": false, "disableTextFollowUps": false, + "disableMemory": false, "disableNsfwFilter": isNsfw, "disableSearch": false, "disableSelfHarmShortCircuit": false, "disableTextFollowUps": false, "enableImageGeneration": isMediaModel, "enableImageStreaming": isMediaModel, "enableSideBySide": true, "fileAttachments": fileAttachments, "forceConcise": false, "forceSideBySide": false, "imageAttachments": [], "imageGenerationCount": 2, "isAsyncChat": false, "isReasoning": false, "message": message, "modelMode": mapping.mode, "modelName": mapping.name, @@ -406,9 +485,14 @@ export class GrokApiService { getProviderPoolManager().markProviderNeedRefresh(MODEL_PROVIDER.GROK_CUSTOM, { uuid: this.uuid }); } + const rawModel = typeof model === 'string' ? model : ''; + const normalizedModel = normalizeGrokModelId(rawModel); + const modelLower = normalizedModel.toLowerCase(); + const isNsfw = isGrokNsfwModel(rawModel) || requestBody.nsfw === true || requestBody.disableNsfwFilter === true; + if (isNsfw) await this.setupNsfw(); + this.buildPayload(model, requestBody); - const modelLower = model.toLowerCase(); const isVideoModel = modelLower.includes('video'); const isImageModel = modelLower.includes('imagine') && !isVideoModel && !modelLower.includes('edit'); const isImageEditModel = modelLower.includes('edit'); @@ -461,7 +545,8 @@ export class GrokApiService { requestBody.toolOverrides = { ...requestBody.toolOverrides, videoGen: true }; } } else if (isImageModel || isImageEditModel) { - requestBody.toolOverrides = { ...requestBody.toolOverrides, imageGen: true }; + const isNsfw = isGrokNsfwModel(rawModel) || requestBody.nsfw === true || requestBody.disableNsfwFilter === true; + requestBody.toolOverrides = { ...requestBody.toolOverrides, imageGen: isNsfw ? { disableNsfwFilter: true } : true }; } let fileAttachments = requestBody.fileAttachments || []; diff --git a/src/providers/openai/codex-core.js b/src/providers/openai/codex-core.js index 26dbeb9..bdf9c88 100644 --- a/src/providers/openai/codex-core.js +++ b/src/providers/openai/codex-core.js @@ -10,7 +10,9 @@ import { MODEL_PROVIDER, formatExpiryLog } from '../../utils/common.js'; import { getProxyConfigForProvider } from '../../utils/proxy-utils.js'; import { getProviderModels } from '../provider-models.js'; -const CODEX_MODELS = getProviderModels(MODEL_PROVIDER.CODEX_API); +const baseModels = getProviderModels(MODEL_PROVIDER.CODEX_API); +const fastModels = baseModels.map(m => `${m}-fast`); +const CODEX_MODELS = [...new Set([...baseModels, ...fastModels])]; const CODEX_VERSION = '0.111.0'; /** @@ -339,9 +341,19 @@ export class CodexApiService { // 明确会话维度:优先使用 session_id 或 conversation_id,其次 user_id const sessionId = metadata.session_id || metadata.conversation_id || metadata.user_id || 'default'; + // 判断是否为 fast 模型并确定默认值 + const isFastModel = model.endsWith('-fast'); + const defaultServiceTier = isFastModel ? 'priority' : 'default'; + const defaultReasoningEffort = isFastModel ? 'xhigh' : 'medium'; + const cleanedBody = { ...requestBody }; delete cleanedBody.metadata; + // 如果是 fast 模型,移除后缀再传给上游 + if (isFastModel) { + cleanedBody.model = model.replace('-fast', ''); + } + // 生成会话缓存键 // 弱化 model 依赖,以提升同会话跨模型的缓存命中率 // 仅当 sessionId 为 'default' 时加上 model 前缀,提供基础隔离 @@ -363,6 +375,11 @@ export class CodexApiService { // 注意:requestBody 已经去除了 metadata return { ...cleanedBody, + service_tier: cleanedBody.service_tier || defaultServiceTier, + reasoning: { + ...cleanedBody.reasoning, + effort: cleanedBody.reasoning?.effort || defaultReasoningEffort + }, stream, prompt_cache_key: cache.id }; diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 8d49736..b0d9606 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -13,11 +13,13 @@ export const PROVIDER_MODELS = { 'gemini-3-pro-preview', 'gemini-3-flash-preview', 'gemini-3.1-pro-preview', + 'gemini-3.1-flash-lite-preview', ], 'gemini-antigravity': [ 'gemini-2.5-computer-use-preview-10-2025', 'gemini-3-pro-image-preview', 'gemini-3.1-pro-preview', + 'gemini-3.1-flash-lite-preview', 'gemini-3-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash-preview',