Skip to content

Codex API/OAuth 切换后 relay WebSocket 与会话可见性 repair 仍可能漂移 #754

@sciman-top

Description

@sciman-top

背景

我在 Windows + Codex App/CLI + Cockpit Tools 切换 OAuth / API Key / API relay 的场景里复现到两个关联问题:

  1. 切到 API relay 后 Codex App 可能重新尝试 responses_websocket,表现为持续 Reconnecting
  2. 切到 API 后 Codex App 和 Codex CLI 的 picker 会话历史为空,即使本地 sessions / state_5.sqlite 里实际还有历史。

本次基于最新 main 源码审查,HEAD 为:70942f79df8c847d4ffb5f76ad400adba870c472

现象与根因判断

1. API Key/custom relay provider 写入的连接语义仍不适合 relay

源码位置:src-tauri/src/modules/codex_account.rs

  • write_api_provider_to_config_toml() 的 custom provider 分支会写:
    • model_provider = <provider_id>
    • [model_providers.<provider_id>]
    • wire_api = "responses"
    • requires_openai_auth = true
  • 但没有写 supports_websockets = false
  • write_api_key_provider_to_config_toml() 还会把 API Key provider 固定折叠为 codex_local_access,并同样写 requires_openai_auth = true,也没有 supports_websockets = false

对 OpenAI 官方端点这可能还能工作,但对常见 API relay / compatible endpoint 来说,实际需要的是稳定的 custom provider bucket,并显式禁用 WebSocket。否则 Codex/App 会把该 provider 当成可走 responses_websocket 的 OpenAI 风格 provider,导致 relay 不支持 WebSocket 时反复重连。

本地实测可工作的投影形态是:

model_provider = "<stable_custom_provider_id>"

[model_providers.<stable_custom_provider_id>]
name = "..."
base_url = "https://relay.example.com/v1"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false

也就是说,不应把 relay 强行塞回 built-in openai bucket,也不应在 API Key relay 场景统一折叠到一个不表达真实 provider 身份的 runtime bucket。

2. session visibility repair 只修 provider bucket,漏掉 picker 可见性字段

源码位置:src-tauri/src/modules/codex_session_visibility.rs

当前 repair 的 SQLite 逻辑只统计/更新:

SELECT COUNT(*) FROM threads WHERE COALESCE(model_provider, '') <> ?1
UPDATE threads SET model_provider = ?1 WHERE COALESCE(model_provider, '') <> ?1

这能修复 threads.model_provider 不匹配的问题,但不能覆盖另一个实际会让 picker 空白的状态:历史 thread 已经在正确 provider bucket 下,但 has_user_event = 0,同时 first_user_message 非空。此时:

  • repair 会认为 model_provider 已匹配,所以 rows_to_update = 0
  • 但 App/CLI picker 仍可能按 has_user_event / user-message 元数据过滤,结果历史为空。

所以现在的 repair 会出现“报告已修/无需修,但 picker 仍空”的假阴性。

3. 自动/手动 repair 的边界与文案存在漂移风险

src/pages/CodexAccountsPage.tsxopenApiSwitchVisibilityNotice() 会调用 runApiSwitchVisibilityRepair(),而 changelog 同时出现过“自动修复”和“切号后端不再自动执行历史会话可见性修复 / explicit repair action”的描述。无论最终交互是自动还是显式按钮,关键问题是当前 backend repair 只覆盖 provider,没覆盖 picker visibility metadata,因此 UI 层很容易给用户一个“repair 已运行”的错觉。

建议修法

建议把 Codex 切换后的 on-disk projection 当成一个原子结果维护:

  1. API Key / API relay 模式

    • auth.json 的 API Key 语义。
    • config.toml
      • model_provider = <account.api_provider_id 或稳定 custom provider id>
      • [model_providers.<id>] base_url / wire_api = "responses"
      • requires_openai_auth = false
      • relay/compatible endpoint 默认或可配置写 supports_websockets = false
    • 不要把 relay 塞进 built-in openai bucket;否则 shared openai bucket 下无法禁用 WebSocket。
    • 对多 provider/API 账号,避免统一折叠到 codex_local_access 导致历史 bucket 与真实连接 provider 不一致。
  2. OAuth / ChatGPT 模式

    • 切回 built-in openai bucket。
    • 清理 API relay 的 runtime provider 投影,避免残留 custom provider 影响 OAuth。
    • 只保留 OAuth 所需的 auth.json token 投影。
  3. session visibility repair 增强

    • 在修 threads.model_provider 的同时,schema-aware 地检测 threads 表是否存在 has_user_eventfirst_user_messagethread_source 等列。
    • first_user_message 非空但 has_user_event != 1 时,也应计入 repair。
    • 可考虑:
UPDATE threads
SET
  model_provider = ?1,
  has_user_event = CASE
    WHEN COALESCE(first_user_message, '') <> '' THEN 1
    ELSE has_user_event
  END,
  thread_source = CASE
    WHEN COALESCE(thread_source, '') = '' AND COALESCE(first_user_message, '') <> '' THEN 'user'
    ELSE thread_source
  END
WHERE
  COALESCE(model_provider, '') <> ?1
  OR (COALESCE(first_user_message, '') <> '' AND COALESCE(has_user_event, 0) <> 1)

实际实现需要先 PRAGMA table_info(threads),兼容旧 schema,缺列时降级为只修 provider。

  1. 增加诊断指标,避免 repair 假阴性

    • active_provider_thread_count
    • user_message_thread_count
    • visible_user_event_thread_count
    • thread_source 分布
    • user_message_thread_count > 0visible_user_event_thread_count == 0 时,应提示“历史存在但 picker 元数据不可见”,而不是显示“无需修复”。
  2. 增加回归测试

    • API Key custom provider 写入应保留 stable provider id,并写入 requires_openai_auth = false / supports_websockets = false
    • OAuth 切回 openai 时应清理 API relay provider 投影。
    • state_5.sqlite 中 provider 已匹配但 has_user_event = 0 的 thread,repair 后应变为 picker 可见。
    • repair summary 应能区分 provider rows updated 与 visibility metadata rows updated。

期望结果

  • API relay 模式下 Codex App 不再走不支持的 responses_websocket,避免 Reconnecting
  • API/OAuth 来回切换后,App 和 CLI picker 看到的 provider bucket 与真实连接 provider 一致。
  • 历史会话不仅在文件/SQLite 中存在,而且在 picker 中可见。
  • repair 不再出现“执行成功但历史仍为空”的假阴性。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions