mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(proxy): session-based prompt_cache_key routing for Codex Chat bridge
Codex always sends prompt_cache_key in its Responses requests, but the Responses -> Chat Completions conversion dropped it, breaking session cache affinity on upstreams that route by key (e.g. Kimi Coding). - Re-inject prompt_cache_key after conversion in the forwarder: an explicit client key wins, otherwise a client-provided session ID; generated per-request UUIDs are never sent upstream. - Provider-aware gating: "auto" enables only known-compatible upstreams (api.openai.com, api.kimi.com/coding) because strict gateways reject unknown fields with HTTP 400 (e.g. Fireworks); an advanced Auto/Enabled/Disabled override is available on the Codex form in all four locales. - Kimi For Coding preset opts in explicitly.
This commit is contained in:
@@ -40,6 +40,7 @@ import type {
|
||||
CodexApiFormat,
|
||||
CodexCatalogModel,
|
||||
CodexChatReasoning,
|
||||
PromptCacheRoutingMode,
|
||||
ProviderCategory,
|
||||
} from "@/types";
|
||||
|
||||
@@ -89,6 +90,8 @@ interface CodexFormFieldsProps {
|
||||
onMaxOutputTokensChange: (value: string) => void;
|
||||
codexChatReasoning?: CodexChatReasoning;
|
||||
onCodexChatReasoningChange?: (value: CodexChatReasoning) => void;
|
||||
promptCacheRouting: PromptCacheRoutingMode;
|
||||
onPromptCacheRoutingChange: (value: PromptCacheRoutingMode) => void;
|
||||
|
||||
// Model Catalog
|
||||
catalogModels?: CodexCatalogModel[];
|
||||
@@ -182,6 +185,8 @@ export function CodexFormFields({
|
||||
onMaxOutputTokensChange,
|
||||
codexChatReasoning = {},
|
||||
onCodexChatReasoningChange,
|
||||
promptCacheRouting,
|
||||
onPromptCacheRoutingChange,
|
||||
catalogModels = [],
|
||||
onCatalogModelsChange,
|
||||
speedTestEndpoints,
|
||||
@@ -229,6 +234,7 @@ export function CodexFormFields({
|
||||
isAnthropicFormat ||
|
||||
supportsThinking ||
|
||||
supportsEffort ||
|
||||
promptCacheRouting !== "auto" ||
|
||||
!!maxOutputTokens;
|
||||
const [advancedExpanded, setAdvancedExpanded] = useState(hasAnyAdvancedValue);
|
||||
|
||||
@@ -716,6 +722,49 @@ export function CodexFormFields({
|
||||
shouldShowSpeedTest && "border-t border-border-default pt-3",
|
||||
)}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<FormLabel>
|
||||
{t("codexConfig.promptCacheRoutingLabel", {
|
||||
defaultValue: "提示词缓存路由",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={promptCacheRouting}
|
||||
onValueChange={(value) =>
|
||||
onPromptCacheRoutingChange(
|
||||
value as PromptCacheRoutingMode,
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">
|
||||
{t("codexConfig.promptCacheRoutingAuto", {
|
||||
defaultValue: "自动(推荐)",
|
||||
})}
|
||||
</SelectItem>
|
||||
<SelectItem value="enabled">
|
||||
{t("codexConfig.promptCacheRoutingEnabled", {
|
||||
defaultValue: "开启",
|
||||
})}
|
||||
</SelectItem>
|
||||
<SelectItem value="disabled">
|
||||
{t("codexConfig.promptCacheRoutingDisabled", {
|
||||
defaultValue: "关闭",
|
||||
})}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{t("codexConfig.promptCacheRoutingHint", {
|
||||
defaultValue:
|
||||
"自动模式仅对已确认兼容的上游发送 prompt_cache_key;开启可用于其他兼容网关,关闭可避免严格网关因未知字段返回 400。只使用客户端提供的稳定会话 ID。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<FormLabel>
|
||||
{t("codexConfig.reasoningGroupTitle", {
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
CodexApiFormat,
|
||||
CodexCatalogModel,
|
||||
CodexChatReasoning,
|
||||
PromptCacheRoutingMode,
|
||||
ClaudeApiKeyField,
|
||||
} from "@/types";
|
||||
import {
|
||||
@@ -357,6 +358,7 @@ function ProviderFormFull({
|
||||
),
|
||||
});
|
||||
setCodexChatReasoning(initialData?.meta?.codexChatReasoning ?? {});
|
||||
setPromptCacheRouting(initialData?.meta?.promptCacheRouting ?? "auto");
|
||||
setCustomUserAgent(initialData?.meta?.customUserAgent ?? "");
|
||||
setLocalProxyHeadersOverride(
|
||||
formatRequestOverrideObject(
|
||||
@@ -529,6 +531,10 @@ function ProviderFormFull({
|
||||
useState<CodexChatReasoning>(
|
||||
() => initialData?.meta?.codexChatReasoning ?? {},
|
||||
);
|
||||
const [promptCacheRouting, setPromptCacheRouting] =
|
||||
useState<PromptCacheRoutingMode>(
|
||||
() => initialData?.meta?.promptCacheRouting ?? "auto",
|
||||
);
|
||||
const [customUserAgent, setCustomUserAgent] = useState<string>(
|
||||
() => initialData?.meta?.customUserAgent ?? "",
|
||||
);
|
||||
@@ -632,6 +638,7 @@ function ProviderFormFull({
|
||||
const template = getCodexCustomTemplate();
|
||||
resetCodexConfig(template.auth, template.config);
|
||||
setCodexChatReasoning({});
|
||||
setPromptCacheRouting("auto");
|
||||
}
|
||||
}, [appId, initialData, selectedPresetId, resetCodexConfig]);
|
||||
|
||||
@@ -1485,6 +1492,13 @@ function ProviderFormFull({
|
||||
localCodexApiFormat === "openai_chat"
|
||||
? normalizeCodexChatReasoningForSave(codexChatReasoning)
|
||||
: undefined,
|
||||
promptCacheRouting:
|
||||
appId === "codex" &&
|
||||
category !== "official" &&
|
||||
localCodexApiFormat === "openai_chat" &&
|
||||
promptCacheRouting !== "auto"
|
||||
? promptCacheRouting
|
||||
: undefined,
|
||||
customUserAgent:
|
||||
(appId === "claude" || appId === "codex") && category !== "official"
|
||||
? customUserAgent.trim() || undefined
|
||||
@@ -1651,6 +1665,7 @@ function ProviderFormFull({
|
||||
const template = getCodexCustomTemplate();
|
||||
resetCodexConfig(template.auth, template.config);
|
||||
setCodexChatReasoning({});
|
||||
setPromptCacheRouting("auto");
|
||||
setLocalCodexApiFormat(
|
||||
codexApiFormatFromWireApi(extractCodexWireApi(template.config)) ??
|
||||
"openai_responses",
|
||||
@@ -1692,6 +1707,7 @@ function ProviderFormFull({
|
||||
|
||||
resetCodexConfig(auth, config, preset.modelCatalog ?? []);
|
||||
setCodexChatReasoning(preset.codexChatReasoning ?? {});
|
||||
setPromptCacheRouting(preset.promptCacheRouting ?? "auto");
|
||||
setLocalCodexApiFormat(
|
||||
preset.apiFormat ??
|
||||
codexApiFormatFromWireApi(extractCodexWireApi(config)) ??
|
||||
@@ -2182,6 +2198,8 @@ function ProviderFormFull({
|
||||
onMaxOutputTokensChange={setLocalCodexMaxOutputTokens}
|
||||
codexChatReasoning={codexChatReasoning}
|
||||
onCodexChatReasoningChange={setCodexChatReasoning}
|
||||
promptCacheRouting={promptCacheRouting}
|
||||
onPromptCacheRoutingChange={setPromptCacheRouting}
|
||||
catalogModels={codexCatalogModels}
|
||||
onCatalogModelsChange={setCodexCatalogModels}
|
||||
speedTestEndpoints={speedTestEndpoints}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
CodexApiFormat,
|
||||
CodexCatalogModel,
|
||||
CodexChatReasoning,
|
||||
PromptCacheRoutingMode,
|
||||
} from "../types";
|
||||
import type { PresetTheme } from "./claudeProviderPresets";
|
||||
|
||||
@@ -36,6 +37,8 @@ export interface CodexProviderPreset {
|
||||
modelCatalog?: CodexCatalogModel[];
|
||||
// Codex Responses -> Chat Completions reasoning capability defaults
|
||||
codexChatReasoning?: CodexChatReasoning;
|
||||
// Session-based prompt-cache routing override for Chat Completions upstreams
|
||||
promptCacheRouting?: PromptCacheRoutingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -604,6 +607,7 @@ requires_openai_auth = true`,
|
||||
),
|
||||
endpointCandidates: ["https://api.kimi.com/coding/v1"],
|
||||
apiFormat: "openai_chat",
|
||||
promptCacheRouting: "enabled",
|
||||
modelCatalog: modelCatalog([
|
||||
{
|
||||
model: "kimi-for-coding",
|
||||
|
||||
@@ -1284,6 +1284,11 @@
|
||||
"maxOutputTokensLabel": "Max output tokens",
|
||||
"maxOutputTokensPlaceholder": "Leave empty to use the default 8192",
|
||||
"maxOutputTokensHint": "Codex does not forward model_max_output_tokens in the request body, so the default 8192 ceiling can truncate long or thinking-heavy responses (stop_reason=max_tokens). This value overrides the request's max_tokens on the Anthropic path. Do not exceed the real output ceiling of the model/gateway or it may 400. Leave empty to use the default 8192.",
|
||||
"promptCacheRoutingLabel": "Prompt cache routing",
|
||||
"promptCacheRoutingAuto": "Auto (recommended)",
|
||||
"promptCacheRoutingEnabled": "Enabled",
|
||||
"promptCacheRoutingDisabled": "Disabled",
|
||||
"promptCacheRoutingHint": "Auto sends prompt_cache_key only to known-compatible upstreams. Enable it for other compatible gateways, or disable it if a strict gateway rejects unknown fields with HTTP 400. Only a stable client-provided session ID is used.",
|
||||
"upstreamModelName": "Upstream Model Name",
|
||||
"upstreamModelNameHint": "For Chat format, enter the real upstream model here; routing converts Codex Responses requests to Chat Completions while keeping this model.",
|
||||
"modelNamePlaceholder": "e.g., gpt-5-codex",
|
||||
|
||||
@@ -1284,6 +1284,11 @@
|
||||
"maxOutputTokensLabel": "最大出力トークン",
|
||||
"maxOutputTokensPlaceholder": "空欄の場合はデフォルトの 8192 を使用",
|
||||
"maxOutputTokensHint": "Codex は model_max_output_tokens をリクエストボディに含めないため、デフォルト上限 8192 では長い回答や深い思考時に切り詰められることがあります(stop_reason=max_tokens)。この値は Anthropic 経路で max_tokens を上書きします。モデル/ゲートウェイの実際の出力上限を超えると 400 になる場合があります。空欄でデフォルトの 8192 を使用します。",
|
||||
"promptCacheRoutingLabel": "プロンプトキャッシュルーティング",
|
||||
"promptCacheRoutingAuto": "自動(推奨)",
|
||||
"promptCacheRoutingEnabled": "有効",
|
||||
"promptCacheRoutingDisabled": "無効",
|
||||
"promptCacheRoutingHint": "自動モードでは、互換性を確認済みの上流にのみ prompt_cache_key を送信します。他の互換ゲートウェイでは有効にし、未知のフィールドを 400 で拒否する厳格なゲートウェイでは無効にしてください。クライアントが提供した安定したセッション ID のみを使用します。",
|
||||
"upstreamModelName": "上流モデル名",
|
||||
"upstreamModelNameHint": "Chat 形式ではここに実際の上流モデルを入力します。ルーティングで Codex の Responses リクエストを Chat Completions に変換し、このモデルを維持します。",
|
||||
"modelNamePlaceholder": "例: gpt-5-codex",
|
||||
|
||||
@@ -1261,6 +1261,11 @@
|
||||
"maxOutputTokensLabel": "最大輸出 tokens",
|
||||
"maxOutputTokensPlaceholder": "留空則使用預設 8192",
|
||||
"maxOutputTokensHint": "Codex 不會把 model_max_output_tokens 寫進請求體,預設上限 8192 容易在長回答或深度思考時被截斷(stop_reason=max_tokens)。此處設定會作為 Anthropic 的 max_tokens 覆蓋請求值。請勿超過該模型/閘道的真實輸出上限,否則可能 400。留空使用預設 8192。",
|
||||
"promptCacheRoutingLabel": "提示詞快取路由",
|
||||
"promptCacheRoutingAuto": "自動(建議)",
|
||||
"promptCacheRoutingEnabled": "開啟",
|
||||
"promptCacheRoutingDisabled": "關閉",
|
||||
"promptCacheRoutingHint": "自動模式僅對已確認相容的上游傳送 prompt_cache_key;開啟可用於其他相容閘道,關閉可避免嚴格閘道因未知欄位回傳 400。只使用客戶端提供的穩定工作階段 ID。",
|
||||
"defaultModelLabel": "預設模型",
|
||||
"defaultModelPlaceholder": "例如: gpt-5.6",
|
||||
"defaultModelHint": "Codex 預設請求的模型,隨時可改,無需等待軟體更新供應商範本。留空且設定了模型映射時,預設使用映射第一行。",
|
||||
|
||||
@@ -1284,6 +1284,11 @@
|
||||
"maxOutputTokensLabel": "最大输出 tokens",
|
||||
"maxOutputTokensPlaceholder": "留空则使用默认 8192",
|
||||
"maxOutputTokensHint": "Codex 不会把 model_max_output_tokens 写进请求体,默认上限 8192 容易在长回答或深度思考时被截断(stop_reason=max_tokens)。此处设置会作为 Anthropic 的 max_tokens 覆盖请求值。请勿超过该模型/网关的真实输出上限,否则可能 400。留空使用默认 8192。",
|
||||
"promptCacheRoutingLabel": "提示词缓存路由",
|
||||
"promptCacheRoutingAuto": "自动(推荐)",
|
||||
"promptCacheRoutingEnabled": "开启",
|
||||
"promptCacheRoutingDisabled": "关闭",
|
||||
"promptCacheRoutingHint": "自动模式仅对已确认兼容的上游发送 prompt_cache_key;开启可用于其他兼容网关,关闭可避免严格网关因未知字段返回 400。只使用客户端提供的稳定会话 ID。",
|
||||
"upstreamModelName": "上游模型名称",
|
||||
"upstreamModelNameHint": "Chat 格式下这里填写真实上游模型;路由会把 Codex 的 Responses 请求转换为 Chat Completions 并保持该模型。",
|
||||
"modelNamePlaceholder": "例如: gpt-5-codex",
|
||||
|
||||
@@ -161,6 +161,8 @@ export interface CodexChatReasoning {
|
||||
outputFormat?: CodexChatReasoningOutputFormat;
|
||||
}
|
||||
|
||||
export type PromptCacheRoutingMode = "auto" | "enabled" | "disabled";
|
||||
|
||||
export interface LocalProxyRequestOverrides {
|
||||
headers?: Record<string, string>;
|
||||
body?: Record<string, unknown>;
|
||||
@@ -206,6 +208,9 @@ export interface ProviderMeta {
|
||||
isFullUrl?: boolean;
|
||||
// Prompt cache key for OpenAI Responses-compatible endpoints (improves cache hit rate)
|
||||
promptCacheKey?: string;
|
||||
// Session-based prompt-cache routing for Codex Responses -> Chat conversions.
|
||||
// auto enables only for known-compatible upstreams; enabled/disabled are user overrides.
|
||||
promptCacheRouting?: PromptCacheRoutingMode;
|
||||
// Codex OAuth FAST mode: injects service_tier="priority" on ChatGPT Codex requests
|
||||
codexFastMode?: boolean;
|
||||
// Codex Responses -> Chat Completions reasoning capability metadata
|
||||
|
||||
Reference in New Issue
Block a user