diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs index efcd1c5d..6e076a31 100644 --- a/src-tauri/src/provider.rs +++ b/src-tauri/src/provider.rs @@ -262,6 +262,9 @@ pub struct ProviderMeta { /// conversions fall back to provider ID. #[serde(rename = "promptCacheKey", skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option, + /// Codex OAuth FAST mode: inject `service_tier = "priority"` for ChatGPT Codex requests. + #[serde(rename = "codexFastMode", skip_serializing_if = "Option::is_none")] + pub codex_fast_mode: Option, /// 累加模式应用中,该 provider 是否已写入 live config。 /// `None` 表示旧数据/未知状态,`Some(false)` 表示明确仅存在于数据库中。 #[serde(rename = "liveConfigManaged", skip_serializing_if = "Option::is_none")] @@ -277,6 +280,12 @@ pub struct ProviderMeta { } impl ProviderMeta { + /// Codex OAuth FAST mode 是否启用。默认关闭,因为 `service_tier="priority"` + /// 会按更高速率消耗 ChatGPT 订阅配额,用户需显式开启以换取更低延迟。 + pub fn codex_fast_mode_enabled(&self) -> bool { + self.codex_fast_mode.unwrap_or(false) + } + /// 解析指定托管认证供应商绑定的账号 ID。 /// /// 新版优先读取 authBinding,旧版继续兼容 githubAccountId。 diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index be454c65..585a8399 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -151,10 +151,16 @@ pub fn transform_claude_request_for_api_format( "openai_responses" => { // Codex OAuth (ChatGPT Plus/Pro 反代) 需要在请求体里强制 store: false // + include: ["reasoning.encrypted_content"],由 transform 层统一处理。 + let codex_fast_mode = provider + .meta + .as_ref() + .map(|m| m.codex_fast_mode_enabled()) + .unwrap_or(false); super::transform_responses::anthropic_to_responses( body, Some(cache_key), is_codex_oauth, + codex_fast_mode, ) } "openai_chat" => { @@ -1330,6 +1336,43 @@ mod tests { assert_eq!(transformed["prompt_cache_key"], "explicit-cache-key"); } + #[test] + fn test_transform_claude_request_for_api_format_codex_oauth_fast_mode_off() { + let provider = create_provider_with_meta( + json!({ + "env": { + "ANTHROPIC_BASE_URL": "https://chatgpt.com/backend-api/codex" + } + }), + ProviderMeta { + provider_type: Some("codex_oauth".to_string()), + codex_fast_mode: Some(false), + ..ProviderMeta::default() + }, + ); + let body = json!({ + "model": "gpt-5.4", + "messages": [{ "role": "user", "content": "hello" }], + "max_tokens": 128 + }); + + let transformed = transform_claude_request_for_api_format( + body, + &provider, + "openai_responses", + None, + None, + ) + .unwrap(); + + assert_eq!(transformed["store"], json!(false)); + assert!(transformed.get("service_tier").is_none()); + assert_eq!( + transformed["include"], + json!(["reasoning.encrypted_content"]) + ); + } + #[test] fn test_transform_claude_request_for_api_format_gemini_native() { let provider = create_provider_with_meta( diff --git a/src-tauri/src/proxy/providers/transform_responses.rs b/src-tauri/src/proxy/providers/transform_responses.rs index 64d9a649..9ed4209c 100644 --- a/src-tauri/src/proxy/providers/transform_responses.rs +++ b/src-tauri/src/proxy/providers/transform_responses.rs @@ -17,10 +17,13 @@ use serde_json::{json, Value}; /// `is_codex_oauth`: 当目标后端是 ChatGPT Plus/Pro 反代 (`chatgpt.com/backend-api/codex`) 时为 true。 /// 该后端强制要求 `store: false`,并要求 `include` 包含 `reasoning.encrypted_content` /// 以便在无服务端状态下保持多轮 reasoning 上下文。 +/// `codex_fast_mode`: 仅在 `is_codex_oauth` 为 true 时生效,控制是否注入 +/// `service_tier = "priority"`。 pub fn anthropic_to_responses( body: Value, cache_key: Option<&str>, is_codex_oauth: bool, + codex_fast_mode: bool, ) -> Result { let mut result = json!({}); @@ -125,10 +128,15 @@ pub fn anthropic_to_responses( // (codex-rs 结构体根本没有这三个字段,OpenAI 自己的客户端不发它们) // - instructions / tools / parallel_tool_calls: 必填字段,缺则兜底默认值 // (cc-switch 的 transform 当前是"条件写入",可能产生缺失) + // - service_tier: 仅在 FAST mode 开启时写入 "priority" + // (与 OpenAI 官方 codex-rs 当前请求结构保持一致) // - stream: 必须永远 true(codex-rs 硬编码 true,且 cc-switch 的 // SSE 解析层只处理流式响应,强制覆盖避免客户端误传 false) if is_codex_oauth { result["store"] = json!(false); + if codex_fast_mode { + result["service_tier"] = json!("priority"); + } const REASONING_MARKER: &str = "reasoning.encrypted_content"; let mut includes: Vec = body @@ -520,7 +528,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["model"], "gpt-4o"); assert_eq!(result["max_output_tokens"], 1024); assert_eq!(result["input"][0]["role"], "user"); @@ -539,7 +547,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["instructions"], "You are a helpful assistant."); // system should not appear in input assert_eq!(result["input"].as_array().unwrap().len(), 1); @@ -557,7 +565,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["instructions"], "Part 1\n\nPart 2"); } @@ -574,7 +582,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["tools"][0]["type"], "function"); assert_eq!(result["tools"][0]["name"], "get_weather"); assert!(result["tools"][0].get("parameters").is_some()); @@ -591,7 +599,7 @@ mod tests { "tool_choice": {"type": "any"} }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["tool_choice"], "required"); } @@ -604,7 +612,7 @@ mod tests { "tool_choice": {"type": "tool", "name": "get_weather"} }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["tool_choice"]["type"], "function"); assert_eq!(result["tool_choice"]["name"], "get_weather"); } @@ -623,7 +631,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); let input_arr = result["input"].as_array().unwrap(); // Should produce: assistant message (text) + function_call item @@ -653,7 +661,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); let input_arr = result["input"].as_array().unwrap(); // Should produce: function_call_output item (lifted) @@ -677,7 +685,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); let input_arr = result["input"].as_array().unwrap(); // thinking should be discarded, only text remains @@ -700,7 +708,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); let content = result["input"][0]["content"].as_array().unwrap(); assert_eq!(content[0]["type"], "input_text"); @@ -858,7 +866,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["model"], "o3-mini"); } @@ -870,7 +878,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, Some("my-provider-id"), false).unwrap(); + let result = anthropic_to_responses(input, Some("my-provider-id"), false, true).unwrap(); assert_eq!(result["prompt_cache_key"], "my-provider-id"); } @@ -888,7 +896,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert!(result["tools"][0].get("cache_control").is_none()); } @@ -905,7 +913,7 @@ mod tests { }] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert!(result["input"][0]["content"][0] .get("cache_control") .is_none()); @@ -967,7 +975,7 @@ mod tests { "max_tokens": 4096, "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["max_output_tokens"], 4096); assert!(result.get("max_completion_tokens").is_none()); } @@ -981,7 +989,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "xhigh"); } @@ -995,7 +1003,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "low"); } @@ -1008,7 +1016,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "low"); } @@ -1021,7 +1029,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "medium"); } @@ -1034,7 +1042,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "high"); } @@ -1047,7 +1055,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["reasoning"]["effort"], "xhigh"); } @@ -1060,7 +1068,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert!(result.get("reasoning").is_none()); } @@ -1074,10 +1082,11 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); // store 必须显式为 false(ChatGPT 后端拒绝 true) assert_eq!(result["store"], json!(false)); + assert_eq!(result["service_tier"], json!("priority")); // include 必须包含 reasoning.encrypted_content(无服务端状态下保持多轮 reasoning) assert_eq!(result["include"], json!(["reasoning.encrypted_content"])); @@ -1093,9 +1102,10 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert!(result.get("store").is_none()); + assert!(result.get("service_tier").is_none()); assert!(result.get("include").is_none()); } @@ -1109,7 +1119,7 @@ mod tests { "include": ["something.else", "reasoning.encrypted_content"] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); let includes = result["include"] .as_array() .expect("include should be array"); @@ -1130,6 +1140,21 @@ mod tests { assert_eq!(marker_count, 1, "marker 不应被重复添加(idempotent 失败)"); } + #[test] + fn test_anthropic_to_responses_codex_oauth_fast_mode_can_be_disabled() { + let input = json!({ + "model": "gpt-5-codex", + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Hello"}] + }); + + let result = anthropic_to_responses(input, None, true, false).unwrap(); + + assert_eq!(result["store"], json!(false)); + assert!(result.get("service_tier").is_none()); + assert_eq!(result["include"], json!(["reasoning.encrypted_content"])); + } + #[test] fn test_anthropic_to_responses_codex_oauth_strips_max_output_tokens() { // ChatGPT Plus/Pro 反代不接受 max_output_tokens(OpenAI 官方 codex-rs 的 @@ -1141,7 +1166,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert!( result.get("max_output_tokens").is_none(), @@ -1159,7 +1184,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["max_output_tokens"], json!(1024)); } @@ -1177,7 +1202,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert!( result.get("temperature").is_none(), @@ -1195,7 +1220,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert!( result.get("top_p").is_none(), @@ -1212,7 +1237,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert_eq!( result["instructions"], @@ -1246,7 +1271,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert_eq!( result["instructions"], @@ -1270,7 +1295,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, true).unwrap(); + let result = anthropic_to_responses(input, None, true, true).unwrap(); assert_eq!( result["stream"], @@ -1291,7 +1316,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert_eq!(result["temperature"], json!(0.7)); assert_eq!(result["top_p"], json!(0.9)); @@ -1307,7 +1332,7 @@ mod tests { "messages": [{"role": "user", "content": "Hello"}] }); - let result = anthropic_to_responses(input, None, false).unwrap(); + let result = anthropic_to_responses(input, None, false, true).unwrap(); assert!( result.get("parallel_tool_calls").is_none(), diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index b8ce157e..c4f7fea7 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -360,10 +360,20 @@ impl StreamCheckService { .as_ref() .and_then(|m| m.provider_type.as_deref()) == Some("codex_oauth"); + let codex_fast_mode = provider + .meta + .as_ref() + .map(|m| m.codex_fast_mode_enabled()) + .unwrap_or(false); let body = if is_openai_responses { - anthropic_to_responses(anthropic_body, Some(&provider.id), is_codex_oauth) - .map_err(|e| AppError::Message(format!("Failed to build test request: {e}")))? + anthropic_to_responses( + anthropic_body, + Some(&provider.id), + is_codex_oauth, + codex_fast_mode, + ) + .map_err(|e| AppError::Message(format!("Failed to build test request: {e}")))? } else if is_gemini_native { anthropic_to_gemini(anthropic_body) .map_err(|e| AppError::Message(format!("Failed to build test request: {e}")))? diff --git a/src/components/providers/forms/ClaudeFormFields.tsx b/src/components/providers/forms/ClaudeFormFields.tsx index aafb2e27..08fcbc87 100644 --- a/src/components/providers/forms/ClaudeFormFields.tsx +++ b/src/components/providers/forms/ClaudeFormFields.tsx @@ -82,6 +82,8 @@ interface ClaudeFormFieldsProps { isCodexOauthAuthenticated?: boolean; selectedCodexAccountId?: string | null; onCodexAccountSelect?: (accountId: string | null) => void; + codexFastMode?: boolean; + onCodexFastModeChange?: (enabled: boolean) => void; // Template Values templateValueEntries: Array<[string, TemplateValueConfig]>; @@ -148,6 +150,8 @@ export function ClaudeFormFields({ isCodexOauthPreset, selectedCodexAccountId, onCodexAccountSelect, + codexFastMode, + onCodexFastModeChange, templateValueEntries, templateValues, templatePresetName, @@ -374,6 +378,8 @@ export function ClaudeFormFields({ )} diff --git a/src/components/providers/forms/CodexOAuthSection.tsx b/src/components/providers/forms/CodexOAuthSection.tsx index 8f66b4dc..603939ad 100644 --- a/src/components/providers/forms/CodexOAuthSection.tsx +++ b/src/components/providers/forms/CodexOAuthSection.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, @@ -30,6 +31,10 @@ interface CodexOAuthSectionProps { selectedAccountId?: string | null; /** 账号选择回调 */ onAccountSelect?: (accountId: string | null) => void; + /** 是否开启 Codex FAST mode */ + fastModeEnabled?: boolean; + /** FAST mode 切换回调 */ + onFastModeChange?: (enabled: boolean) => void; } /** @@ -42,6 +47,8 @@ export const CodexOAuthSection: React.FC = ({ className, selectedAccountId, onAccountSelect, + fastModeEnabled = false, + onFastModeChange, }) => { const { t } = useTranslation(); const [copied, setCopied] = React.useState(false); @@ -140,6 +147,27 @@ export const CodexOAuthSection: React.FC = ({ )} + {onFastModeChange && ( +
+
+ +

+ {t("codexOauth.fastModeDescription", { + defaultValue: + 'Send service_tier="priority" for lower latency. Turn it off if the ChatGPT Codex backend rejects the parameter.', + })} +

+
+ +
+ )} + {/* 已登录账号列表 */} {hasAnyAccount && (
diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index 2cb445c8..87f4b79c 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -382,6 +382,9 @@ export function ProviderForm({ const [selectedCodexAccountId, setSelectedCodexAccountId] = useState< string | null >(() => resolveManagedAccountId(initialData?.meta, "codex_oauth")); + const [codexFastMode, setCodexFastMode] = useState( + () => initialData?.meta?.codexFastMode ?? false, + ); const { codexAuth, @@ -1115,7 +1118,7 @@ export function ProviderForm({ const providerType = templatePreset?.providerType || initialData?.meta?.providerType; - payload.meta = { + const nextMeta: ProviderMeta = { ...(baseMeta ?? {}), commonConfigEnabled: appId === "claude" @@ -1146,6 +1149,7 @@ export function ProviderForm({ isCopilotProvider && selectedGitHubAccountId ? selectedGitHubAccountId : undefined, + codexFastMode: isCodexOauthProvider ? codexFastMode : undefined, testConfig: testConfig.enabled ? testConfig : undefined, costMultiplier: pricingConfig.enabled ? pricingConfig.costMultiplier @@ -1170,6 +1174,12 @@ export function ProviderForm({ : undefined, }; + if (!isCodexOauthProvider && "codexFastMode" in nextMeta) { + delete nextMeta.codexFastMode; + } + + payload.meta = nextMeta; + await onSubmit(payload); }; @@ -1735,6 +1745,8 @@ export function ProviderForm({ isCodexOauthAuthenticated={isCodexOauthAuthenticated} selectedCodexAccountId={selectedCodexAccountId} onCodexAccountSelect={setSelectedCodexAccountId} + codexFastMode={codexFastMode} + onCodexFastModeChange={setCodexFastMode} templateValueEntries={templateValueEntries} templateValues={templateValues} templatePresetName={templatePreset?.name || ""} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index eae16cdc..79a167f1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -929,7 +929,9 @@ "addAnotherAccount": "Add another account", "logoutAll": "Logout all accounts", "retry": "Retry", - "copyCode": "Copy code" + "copyCode": "Copy code", + "fastMode": "FAST mode", + "fastModeDescription": "Send service_tier=\"priority\" for lower latency. Off by default — enabling it consumes your ChatGPT quota at a higher rate." }, "endpointTest": { "title": "API Endpoint Management", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 3336108c..405d627a 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -929,7 +929,9 @@ "addAnotherAccount": "別のアカウントを追加", "logoutAll": "すべてのアカウントをログアウト", "retry": "再試行", - "copyCode": "コードをコピー" + "copyCode": "コードをコピー", + "fastMode": "FAST モード", + "fastModeDescription": "低遅延のため service_tier=\"priority\" を送信します。既定ではオフ——オンにすると ChatGPT のクォータがより速く消費されます。" }, "endpointTest": { "title": "API エンドポイント管理", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 59caf241..6a1abe87 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -929,7 +929,9 @@ "addAnotherAccount": "添加其他账号", "logoutAll": "注销所有账号", "retry": "重试", - "copyCode": "复制代码" + "copyCode": "复制代码", + "fastMode": "FAST 模式", + "fastModeDescription": "发送 service_tier=\"priority\" 换取更低延迟。默认关闭——开启后会按更高速率消耗 ChatGPT 配额。" }, "endpointTest": { "title": "请求地址管理", diff --git a/src/types.ts b/src/types.ts index 9a183d14..68b93884 100644 --- a/src/types.ts +++ b/src/types.ts @@ -155,6 +155,8 @@ export interface ProviderMeta { isFullUrl?: boolean; // Prompt cache key for OpenAI Responses-compatible endpoints (improves cache hit rate) promptCacheKey?: string; + // Codex OAuth FAST mode: injects service_tier="priority" on ChatGPT Codex requests + codexFastMode?: boolean; // 供应商类型(用于识别 Copilot 等特殊供应商) providerType?: string; // GitHub Copilot 关联账号 ID(旧字段,保留兼容读取)