Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ is visible) and never forces an upstream refresh.
| Area | What it does |
| --- | --- |
| **Dashboard summary** | Multi-agent mode, online state, version, uptime, provider count, 30-day token total, active providers, and available native/routed models. |
| **Native reasoning retention** | Independently opt into reasoning ciphertext forwarding on native model or account changes. Both default off; account retention is experimental. See [the configuration scope](/reference/configuration/server/#native-reasoning-retention). |
| **Sub-agent delegation** | Choose a native or routed model and optional reasoning effort shared by OpenCodex delegation guidance and the separate native-default opt-in. This is not a proxy-side per-spawn router; see below. |
| **Sidecars** | Choose the web-search model and effort plus the vision-description model. Changes apply on the next request. |
| **Maintenance** | Resync the Codex model catalog, inspect project-local config bypass warnings, check the latest or preview release, and run an update with optional proxy restart. In the desktop shell, its update entry opens the native app update page instead of running the package updater. |
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Aliases are optional short request names. They never change the native model id

Aliases match case-insensitively. A model alias works as `or/opus` or, when globally unique, bare `opus`; an ambiguous bare alias reports its qualified candidates. Codex model pickers show the qualified alias while preserving the canonical `provider/model` routing id. A provider's `defaultAliases` value overrides `defaultModelAliases`. Built-ins are skipped when multiple models in one provider match the same pattern.

### Native reasoning retention

Native reasoning ciphertext retention on model and account changes has two opt-in settings, both off by default. Configure them on the dashboard overview or see [Native reasoning retention](/reference/configuration/server/#native-reasoning-retention) for the exact scope and experimental account behavior.

### Cursor effort rows

`cursorEffortRows` is an optional boolean and defaults to `false`. When enabled, the raw OpenAI-style
Expand Down
22 changes: 22 additions & 0 deletions docs-site/src/content/docs/reference/configuration/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ runs helper features around provider requests.
| `websockets?` | `boolean` | `false` | Advertise and admit the client-facing Responses WebSocket path. False keeps clients on HTTP/SSE; it does not disable an eligible canonical ChatGPT upstream WS optimization. Complete-input requests may reuse an upstream connection within the same selected credential, account, thread and turn; changed handshake policy or missing identity keeps requests on separate connections. This does not trim HTTP input or create previous-response IDs. |
| `codexNativeSteering?` | `boolean` | `false` | Experimental, native-only mid-turn steering on the Responses WebSocket endpoint. Requires `websockets: true`, a compatible upstream/client, and a pinned account/model/tool surface. Validated generation settings can change in explicit saved-result continuations. Does not enable translated models or HTTP fallback. See [native steering](/guides/codex-integration/#experimental-native-mid-turn-steering). |
| `codexNativeInjection?` | `boolean` | `false` | Experimental saved function-result injection on compatible native multi-agent WebSocket turns. Requires `websockets: true`, explicit `multi_agent.enabled`, and an eligible provider. Separate from steering; no automatic tool rerun or recovery create. See [native injection](/guides/codex-integration/#experimental-native-function-result-injection). |
| `nativeReasoningRetention?` | `{ modelSwitch?: boolean; accountSwitch?: boolean }` | both `false` | Permit reasoning ciphertext forwarding across a known model or account change on the same canonical ChatGPT backend. Account retention is experimental. See [Native reasoning retention](#native-reasoning-retention). |
| `corsAllowOrigins?` | `string[]` | `[]` | Additional exact origins allowed by CORS. Loopback origins are always allowed. Authority-based browser extension origins such as `chrome-extension://<extension-id>` are supported; `*` is not a wildcard. Firefox and Safari regenerate the extension UUID (per install / per browser launch), so update the entry when the origin changes. |
| `apiKeys?` | `OcxApiKey[]` | `[]` | Generated `ocx_…` data-plane admission credentials on non-loopback binds. They do not authorize management APIs; management access uses the separate credential documented in the [management reference](/reference/management-api/). Dashboard-managed. |
| `storageCleanupPolicy?` | `StorageCleanupPolicy` | disabled | Opt-in archived-session cleanup policy. Never enabled implicitly. |
Expand Down Expand Up @@ -52,6 +53,27 @@ runs helper features around provider requests.
| `visionSidecar?` | `OcxVisionSidecarConfig` | on when usable | Image-description sidecar options. |
| `images?` | `OcxImagesConfig` | automatic OpenAI selection | Standalone Images relay options for Codex `image_gen`. |

## Native reasoning retention

The dashboard overview exposes two independent opt-ins: retain reasoning when switching models, and retain it when switching accounts. Both default off. The same policy can be saved in `config.json`:

```jsonc
{
"nativeReasoningRetention": {
"modelSwitch": true,
"accountSwitch": false
}
}
```

This example retains reasoning `encrypted_content` on a known model switch within the same native ChatGPT destination and credential. Set `accountSwitch` to `true` only to opt into experimental cross-account forwarding. When both model and account change, both switches must be enabled. The policy applies to each actual native Combo target and pool retry; it does not grant retention to third-party or API-key destinations.

Retention means that OpenCodex may forward the carried ciphertext. It cannot confirm that the upstream accepts or reuses that reasoning. A detected decrypt rejection still enters the existing bounded cleanup recovery, and the rejection memo takes precedence over either switch. Response and conversation references, foreign reasoning item ids, uploaded-file account restrictions and cache isolation continue using their own ownership rules. Native compaction blocks keep their existing cleanup behavior; direct native `/v1/responses/compact` sanitization is unchanged.

Use `GET /api/native-reasoning-retention` to read the resolved booleans and `PUT` with a partial boolean object to update them. `PUT` with JSON `null` restores both defaults. Invalid fields or unknown keys are rejected; a malformed file block disables retention with a warning. Dashboard/API changes apply to subsequent requests. Local `ocx config set/unset` edits require a restart to converge the running proxy.

## Upstream transport recovery

While the canonical ChatGPT upstream WebSocket waits for the first Responses event after
sending the create frame, it watches for liveness rather than a fixed deadline. When the socket
supports protocol pings, the proxy pings it every 15 seconds. Any inbound frame — quota,
Expand Down
1 change: 1 addition & 0 deletions docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ See [Codex prompt layers](/guides/codex-prompt/) for the layer model and the key
| `GET /api/config` | Return the redacted, management-safe configuration DTO | — |
| `PUT /api/config` | Disabled full-config replacement guard | 405; use focused endpoints instead |
| `GET, PUT /api/settings` | Read runtime/startup settings or update auto-start, stream mode, app-owned memory budget, and `codexAccountPickerEnabled` | 400 invalid, non-object, or empty update |
| `GET, PUT /api/native-reasoning-retention` | Read resolved model/account reasoning-retention booleans; partial PUT preserves omitted fields, JSON `null` restores defaults. See [native reasoning retention](/reference/configuration/server/#native-reasoning-retention). | 400 unknown field, invalid type or non-object/non-null body |
| `GET /api/startup-health` | Read cached service/shim startup health | — |
| `POST /api/startup-action` | Install or repair the service or Codex shim | 400 invalid action; 500 action failure |
| `GET, POST /api/windows-tray` | Read Windows tray state or install/start/stop/uninstall it | 400 unsupported platform/action; 500 operation failure |
Expand Down
1 change: 1 addition & 0 deletions docs-site/src/content/docs/zh-cn/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ bun run dev:gui
| 区域 | 作用 |
| --- | --- |
| **Dashboard 摘要** | 显示 multi-agent 模式、在线状态、版本、运行时间、provider 数量、30 天 token 总量、活动 provider 和可用的原生/路由模型。 |
| **原生推理密文保留** | 独立开启原生模型/账号切换时的推理密文转发;默认均关闭,跨账号保留属于实验性选项。见 [配置范围](/zh-cn/reference/configuration/#原生推理密文保留)。 |
| **Sub-agent delegation** | 选择供 OpenCodex 委派指引与可选的 Codex 原生子代理默认值共用的原生/路由模型和可选 reasoning 强度。它不是逐次生成的路由器,详见下文。 |
| **Sidecar** | 选择 web-search 模型及强度,以及图像描述模型;更改从下一次请求开始生效。 |
| **Maintenance** | 重新同步 Codex 模型目录,查看项目级配置绕过警告,检查 latest/preview 版本,并可在更新后重启代理。 在桌面 shell 中,该更新入口会打开原生应用更新页面,而不是运行包更新器。 |
Expand Down
19 changes: 19 additions & 0 deletions docs-site/src/content/docs/zh-cn/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ opencodex 会把持久化配置存放在 `$OPENCODEX_HOME/config.json`,通常

路由有自己独立的顺序化解析规则;见 [Routing](/reference/configuration/routing/)。

## 原生推理密文保留

仪表盘概览提供“切换模型时保留”和“切换账号时保留”两个独立开关,默认均关闭。也可以在 `config.json` 中配置:

```jsonc
{
"nativeReasoningRetention": {
"modelSwitch": true,
"accountSwitch": false
}
}
```

上面的例子允许在同一原生 ChatGPT 目标和凭据下,发生已知模型切换时转发推理 `encrypted_content`。跨账号保留属于实验性选项;模型和账号同时变化时必须同时开启两个开关。策略按实际选中的原生 Combo 成员或账号池重试目标判断,不适用于第三方或 API-key provider。

允许转发不代表上游一定接受或复用推理。已检测到的解密拒绝仍执行现有的有限清理恢复,拒绝缓存优先于开关。旧响应和会话引用、外部推理项 ID、上传文件的账号限制及缓存隔离保持各自的归属规则。原生压缩块继续沿用现有清理行为,原生 `/v1/responses/compact` 的直接转发清理也不改变。

仪表盘和 `PUT /api/native-reasoning-retention` 的修改对后续请求生效。PUT 接受部分布尔字段,JSON `null` 恢复默认值;未知字段或类型错误会被拒绝。使用本地 `ocx config set/unset` 修改后需重启代理。完整说明见 [Native reasoning retention](/reference/configuration/server/#native-reasoning-retention)。

## 配置域

- [Providers](/reference/configuration/providers/) — provider 条目、认证、端点、目录、allowlist、上下文限制、配额和 provider 特定选项。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ Aside 配置档的变更在这种情况下仍会保存一件事:确认之后
| `GET /api/config` | 返回已脱敏、对管理安全的配置 DTO | — |
| `PUT /api/config` | 禁用的完整配置替换保护 | 405;请改用聚焦端点 |
| `GET, PUT /api/settings` | 读取运行时/启动设置,或更新自动启动、流模式、应用拥有的内存预算和 `codexAccountPickerEnabled` | 400 无效、非对象或空更新 |
| `GET, PUT /api/native-reasoning-retention` | 读取模型/账号推理保留开关;部分 PUT 保留未提交字段,JSON `null` 恢复默认值。见 [原生推理密文保留](/zh-cn/reference/configuration/#原生推理密文保留)。 | 400 未知字段、无效类型或非对象/非 null 输入 |
| `GET /api/startup-health` | 读取缓存的服务/shim 启动健康状态 | — |
| `POST /api/startup-action` | 安装或修复服务或 Codex shim | 400 无效动作;500 动作失败 |
| `GET, POST /api/windows-tray` | 读取 Windows 托盘状态,或安装、启动、停止、卸载它 | 400 不支持的平台/动作;500 操作失败 |
Expand Down
102 changes: 102 additions & 0 deletions gui/src/components/NativeReasoningRetentionPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useT } from "../i18n/shared";
import { Switch } from "../ui";
import { createBoundedFetch } from "../bounded-fetch";
import { requireJson } from "../pages/dashboard-shared";

interface Settings { modelSwitch: boolean; accountSwitch: boolean }
function readSettings(payload: unknown): Settings {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Invalid reasoning settings");
const value = payload as Record<string, unknown>;
if (typeof value.modelSwitch !== "boolean" || typeof value.accountSwitch !== "boolean") {
throw new Error("Missing reasoning settings");
}
return { modelSwitch: value.modelSwitch, accountSwitch: value.accountSwitch };
}

export default function NativeReasoningRetentionPanel({ apiBase }: { apiBase: string }) {
return <NativeReasoningRetentionControls key={apiBase} apiBase={apiBase} />;
}

function NativeReasoningRetentionControls({ apiBase }: { apiBase: string }) {
const t = useT();
const [saved, setSaved] = useState<Settings | undefined>();
const [modelSwitch, setModelSwitch] = useState(false);
const [accountSwitch, setAccountSwitch] = useState(false);
const [busy, setBusy] = useState(false);
const [loadError, setLoadError] = useState(false);
const [feedback, setFeedback] = useState<"saved" | "failed" | null>(null);
const active = useRef(false);
const pending = useRef<ReturnType<typeof createBoundedFetch> | null>(null);
const accept = useCallback((value: Settings) => {
setSaved(value); setModelSwitch(value.modelSwitch); setAccountSwitch(value.accountSwitch);
}, []);
const load = useCallback(async () => {
if (pending.current) return;
const request = createBoundedFetch(15_000);
pending.current = request; setLoadError(false);
try {
const response = await fetch(`${apiBase}/api/native-reasoning-retention`, { signal: request.signal });
const value = readSettings(await requireJson(response));
if (active.current && pending.current === request) accept(value);
} catch {
if (active.current && pending.current === request) setLoadError(true);
} finally {
request.clear();
if (pending.current === request) pending.current = null;
}
}, [apiBase, accept]);
useEffect(() => {
active.current = true;
const timer = window.setTimeout(() => { void load(); }, 0);
return () => {
window.clearTimeout(timer); active.current = false;
pending.current?.controller.abort(); pending.current?.clear(); pending.current = null;
};
}, [load]);
const save = async (reset = false) => {
if (pending.current || saved === undefined) return;
const request = createBoundedFetch(15_000);
pending.current = request; setBusy(true); setFeedback(null);
try {
const response = await fetch(`${apiBase}/api/native-reasoning-retention`, {
method: "PUT", headers: { "Content-Type": "application/json" }, signal: request.signal,
body: JSON.stringify(reset ? null : { modelSwitch, accountSwitch }),
});
const value = readSettings(await requireJson(response));
if (active.current && pending.current === request) { accept(value); setFeedback("saved"); }
} catch {
if (active.current && pending.current === request) setFeedback("failed");
} finally {
request.clear();
if (active.current && pending.current === request) setBusy(false);
if (pending.current === request) pending.current = null;
}
};
const disabled = busy || saved === undefined || loadError;
const changed = modelSwitch !== saved?.modelSwitch || accountSwitch !== saved?.accountSwitch;
return <section className="panel" aria-labelledby="native-reasoning-retention-title" aria-busy={busy || (saved === undefined && !loadError)}>
<div className="font-semibold" id="native-reasoning-retention-title">{t("nativeReasoningRetention.title")}</div>
<p className="muted setting-hint">{t("nativeReasoningRetention.description")}</p>
<div className="stack" style={{ gap: "var(--space-3)" }}>
<div className="spread">
<span>{t("nativeReasoningRetention.modelSwitch")}</span>
<Switch on={modelSwitch} disabled={disabled} label={t("nativeReasoningRetention.modelSwitch")}
onClick={() => { setModelSwitch(value => !value); setFeedback(null); }} />
</div>
<div className="spread">
<span>{t("nativeReasoningRetention.accountSwitch")}</span>
<Switch on={accountSwitch} disabled={disabled} label={t("nativeReasoningRetention.accountSwitch")}
onClick={() => { setAccountSwitch(value => !value); setFeedback(null); }} />
</div>
<p className="muted setting-hint">{t("nativeReasoningRetention.accountHint")}</p>
<div className="row">
<button type="button" className="btn btn-primary" disabled={disabled || !changed} onClick={() => { void save(); }}>{t("common.save")}</button>
<button type="button" className="btn btn-ghost" disabled={disabled || (!changed && !saved?.modelSwitch && !saved?.accountSwitch)} onClick={() => { void save(true); }}>{t("nativeReasoningRetention.reset")}</button>
</div>
</div>
{loadError && <div className="notice notice-err" role="alert">{t("nativeReasoningRetention.loadFailed")} <button type="button" className="btn btn-ghost btn-sm" onClick={() => { void load(); }}>{t("common.retry")}</button></div>}
{feedback === "failed" && <div className="notice notice-err" role="alert">{t("nativeReasoningRetention.saveFailed")}</div>}
{feedback === "saved" && <div className="muted setting-hint" role="status">{t("nativeReasoningRetention.saved")}</div>}
</section>;
}
9 changes: 9 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import type { TKey } from "./en";
* German i18n catalog, generated from en.ts. Must match the `TKey` set (compile-checked).
*/
export const de: Record<TKey, string> = {
"nativeReasoningRetention.title": "Native OpenAI-Denkdaten behalten",
"nativeReasoningRetention.description": "Nur für native Anfragen an das offizielle ChatGPT-Backend, auch bei tatsächlichen Zielwechseln in Combos und Kontopools. Alte verschlüsselte Denkdaten können erneut gesendet werden; die Wiederverwendung hängt vom Backend ab. Beide Optionen sind standardmäßig aus.",
"nativeReasoningRetention.modelSwitch": "Verschlüsselte Denkdaten bei Modellwechsel behalten",
"nativeReasoningRetention.accountSwitch": "Verschlüsselte Denkdaten bei Kontowechsel behalten (experimentell)",
"nativeReasoningRetention.accountHint": "Das Behalten der Daten gibt anderen Konten keinen Zugriff auf alte Antwort-IDs oder Dateien. Eine erfolgreiche Anfrage beweist keine Wiederverwendung der Denkdaten. Die native Kontextkomprimierung bleibt unverändert.",
"nativeReasoningRetention.reset": "Standard wiederherstellen",
"nativeReasoningRetention.loadFailed": "Native Denkdateneinstellungen konnten nicht geladen werden.",
"nativeReasoningRetention.saveFailed": "Native Denkdateneinstellungen konnten nicht gespeichert werden.",
"nativeReasoningRetention.saved": "Native Denkdateneinstellungen gespeichert.",
...desktopCompatibilityCopy("de"),
"claude.tierDefaults.unset": "Nicht festgelegt (Claude-Einstellung beibehalten)",
"claude.tierDefaults.recovery": "Anwendung gestoppt; automatische Wiederherstellung fehlgeschlagen. Prüfen Sie vor einem erneuten Versuch die Sicherung unten.",
Expand Down
Loading
Loading