mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
feat(codex): add unified session history toggle for official providers
Codex buckets resume history by the model_provider id recorded in each session: official runs (no key, built-in "openai") and cc-switch third-party runs (shared "custom") are mutually invisible in the resume picker. Add an opt-in setting that runs official providers under the shared "custom" id so future official sessions land in the same history bucket as third-party ones. Forward-only by design: existing sessions are not migrated. When enabled, official live config.toml gets model_provider = "custom" plus a [model_providers.custom] entry that mirrors the built-in openai provider (requires_openai_auth routes auth to the ChatGPT login in auth.json, name "OpenAI" keeps is_openai() feature gates, explicit supports_websockets/wire_api restore built-in defaults). auth.json is untouched. Key invariants: - Injection lives only in the live config: switch-away backfill strips the exact injected shape, so stored provider configs stay clean and turning the toggle off fully reverts on the next write. - Toggle changes apply immediately via a takeover-aware reapply: when the proxy owns the live config (backup/placeholder present), only the live backup is updated, mirroring the provider-switch path. - The takeover backup path runs the same injection so a takeover release restores a config that still carries the unified routing. - Injection refuses to activate a foreign [model_providers.custom] table (e.g. stale entry with a third-party base_url) to avoid routing ChatGPT OAuth traffic to an unknown backend. The toggle lives under Settings → Codex App Enhancements; the description warns that resuming old sessions across providers may fail because encrypted_content reasoning only decrypts on the backend that created it (upstream treats cross-provider resume as unsupported).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { KeyRound } from "lucide-react";
|
||||
import { History, KeyRound } from "lucide-react";
|
||||
import type { SettingsFormState } from "@/hooks/useSettings";
|
||||
import { ToggleRow } from "@/components/ui/toggle-row";
|
||||
|
||||
@@ -30,6 +30,16 @@ export function CodexAuthSettings({
|
||||
onChange({ preserveCodexOfficialAuthOnSwitch: value })
|
||||
}
|
||||
/>
|
||||
|
||||
<ToggleRow
|
||||
icon={<History className="h-4 w-4 text-sky-500" />}
|
||||
title={t("settings.unifyCodexSessionHistory")}
|
||||
description={t("settings.unifyCodexSessionHistoryDescription")}
|
||||
checked={settings.unifyCodexSessionHistory ?? false}
|
||||
onCheckedChange={(value) =>
|
||||
onChange({ unifyCodexSessionHistory: value })
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ export function useSettingsForm(): UseSettingsFormResult {
|
||||
skipClaudeOnboarding: data.skipClaudeOnboarding ?? false,
|
||||
preserveCodexOfficialAuthOnSwitch:
|
||||
data.preserveCodexOfficialAuthOnSwitch ?? false,
|
||||
unifyCodexSessionHistory: data.unifyCodexSessionHistory ?? false,
|
||||
claudeConfigDir: sanitizeDir(data.claudeConfigDir),
|
||||
codexConfigDir: sanitizeDir(data.codexConfigDir),
|
||||
geminiConfigDir: sanitizeDir(data.geminiConfigDir),
|
||||
@@ -143,6 +144,7 @@ export function useSettingsForm(): UseSettingsFormResult {
|
||||
enableClaudePluginIntegration: false,
|
||||
skipClaudeOnboarding: false,
|
||||
preserveCodexOfficialAuthOnSwitch: false,
|
||||
unifyCodexSessionHistory: false,
|
||||
language: readPersistedLanguage(),
|
||||
} as SettingsFormState);
|
||||
|
||||
@@ -182,6 +184,7 @@ export function useSettingsForm(): UseSettingsFormResult {
|
||||
skipClaudeOnboarding: serverData.skipClaudeOnboarding ?? false,
|
||||
preserveCodexOfficialAuthOnSwitch:
|
||||
serverData.preserveCodexOfficialAuthOnSwitch ?? false,
|
||||
unifyCodexSessionHistory: serverData.unifyCodexSessionHistory ?? false,
|
||||
claudeConfigDir: sanitizeDir(serverData.claudeConfigDir),
|
||||
codexConfigDir: sanitizeDir(serverData.codexConfigDir),
|
||||
geminiConfigDir: sanitizeDir(serverData.geminiConfigDir),
|
||||
|
||||
@@ -670,6 +670,8 @@
|
||||
"codexAuth": "Codex App Enhancements",
|
||||
"preserveCodexOfficialAuthOnSwitch": "Keep official login when switching third-party providers",
|
||||
"preserveCodexOfficialAuthOnSwitchDescription": "When enabled, you can use the Codex app's official plugins, mobile remote control, and other features while using third-party APIs.",
|
||||
"unifyCodexSessionHistory": "Unified Codex session history",
|
||||
"unifyCodexSessionHistoryDescription": "When enabled, the official subscription runs under the shared \"custom\" provider id, so future official sessions appear in the same history list as third-party sessions (existing sessions are not migrated). Note: resuming an old session across providers may fail because its encrypted_content reasoning can only be decrypted by the backend that created it.",
|
||||
"appVisibility": {
|
||||
"title": "Homepage Display",
|
||||
"description": "Choose which apps to show on the homepage",
|
||||
|
||||
@@ -670,6 +670,8 @@
|
||||
"codexAuth": "Codex アプリ拡張",
|
||||
"preserveCodexOfficialAuthOnSwitch": "サードパーティ切替時に公式ログインを保持",
|
||||
"preserveCodexOfficialAuthOnSwitchDescription": "オンにすると、サードパーティ API を使用する際に Codex アプリの公式プラグインやスマホからのリモート操作などの機能を利用できます。",
|
||||
"unifyCodexSessionHistory": "Codex セッション履歴を統一",
|
||||
"unifyCodexSessionHistoryDescription": "オンにすると、公式サブスクリプションも共有の custom プロバイダー ID で動作し、今後の公式セッションはサードパーティのセッションと同じ履歴リストに表示されます(既存セッションは移行しません)。注意:プロバイダーをまたいで古いセッションを再開すると、encrypted_content の推論内容を相手のバックエンドが復号できず、再開に失敗する場合があります。",
|
||||
"appVisibility": {
|
||||
"title": "ホームページ表示",
|
||||
"description": "ホームページに表示するアプリを選択",
|
||||
|
||||
@@ -670,6 +670,8 @@
|
||||
"codexAuth": "Codex 應用增強",
|
||||
"preserveCodexOfficialAuthOnSwitch": "切換第三方時保留官方登入",
|
||||
"preserveCodexOfficialAuthOnSwitchDescription": "開啟後可以在使用第三方 API 的時候使用 Codex 應用的官方外掛、手機遠端操作等功能",
|
||||
"unifyCodexSessionHistory": "統一 Codex 會話歷史",
|
||||
"unifyCodexSessionHistoryDescription": "開啟後,官方訂閱將以共享的 custom 供應商標識執行,未來的官方會話與第三方會話出現在同一歷史清單中(不遷移既有會話)。注意:跨供應商繼續舊會話時,對方後端可能無法解密會話中的 encrypted_content 推理內容,導致繼續失敗",
|
||||
"appVisibility": {
|
||||
"title": "主頁面顯示",
|
||||
"description": "選擇在主頁面顯示的應用程式",
|
||||
|
||||
@@ -670,6 +670,8 @@
|
||||
"codexAuth": "Codex 应用增强",
|
||||
"preserveCodexOfficialAuthOnSwitch": "切换第三方时保留官方登录",
|
||||
"preserveCodexOfficialAuthOnSwitchDescription": "开启后可以在使用第三方 API 的时候使用 Codex 应用的官方插件、手机远程操作等功能",
|
||||
"unifyCodexSessionHistory": "统一 Codex 会话历史",
|
||||
"unifyCodexSessionHistoryDescription": "开启后,官方订阅将以共享的 custom 供应商标识运行,未来的官方会话与第三方会话出现在同一历史列表中(不迁移既有会话)。注意:跨供应商继续旧会话时,对方后端可能无法解密会话中的 encrypted_content 推理内容,导致继续失败",
|
||||
"appVisibility": {
|
||||
"title": "主页面显示",
|
||||
"description": "选择在主页面显示的应用",
|
||||
|
||||
@@ -16,6 +16,7 @@ export const settingsSchema = z.object({
|
||||
launchOnStartup: z.boolean().optional(),
|
||||
enableLocalProxy: z.boolean().optional(),
|
||||
preserveCodexOfficialAuthOnSwitch: z.boolean().optional(),
|
||||
unifyCodexSessionHistory: z.boolean().optional(),
|
||||
language: z.enum(["en", "zh", "zh-TW", "ja"]).optional(),
|
||||
|
||||
// 设备级目录覆盖
|
||||
|
||||
@@ -351,6 +351,9 @@ export interface Settings {
|
||||
enableFailoverToggle?: boolean;
|
||||
// Preserve Codex ChatGPT login in auth.json when switching third-party providers
|
||||
preserveCodexOfficialAuthOnSwitch?: boolean;
|
||||
// Run official Codex under the shared "custom" provider id so future
|
||||
// sessions share one resume-history bucket with third-party providers
|
||||
unifyCodexSessionHistory?: boolean;
|
||||
// User has confirmed the failover toggle first-run notice
|
||||
failoverConfirmed?: boolean;
|
||||
// User has confirmed the first-run welcome notice
|
||||
|
||||
Reference in New Issue
Block a user