diff --git a/docs-site/public/pr-screenshots/encrypted-v2-provider-create.png b/docs-site/public/pr-screenshots/encrypted-v2-provider-create.png new file mode 100644 index 0000000000..4b0179f39b Binary files /dev/null and b/docs-site/public/pr-screenshots/encrypted-v2-provider-create.png differ diff --git a/docs-site/public/pr-screenshots/encrypted-v2-provider-settings.png b/docs-site/public/pr-screenshots/encrypted-v2-provider-settings.png new file mode 100644 index 0000000000..e36003b86a Binary files /dev/null and b/docs-site/public/pr-screenshots/encrypted-v2-provider-settings.png differ diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index e1451dcfbd..4b278711bb 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -87,6 +87,13 @@ what powers the [web-search and vision sidecars](/guides/sidecars/). The ChatGPT passthrough catalog also layers in the bare GPT-5.6 Sol/Terra/Luna slugs (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`) for accounts that can use them. +For encrypted V2 child tasks, canonical ChatGPT forwarding is trusted implicitly. A non-canonical +`openai-responses` provider may opt in with `allowEncryptedV2AgentTasks: true` only after its +upstream has been verified to consume or relay the opaque ciphertext unchanged. The option is +disabled by default, is evaluated against the model's final wire, and does not apply when a +`modelAdapters` override resolves to `openai-chat`. OpenCodex never decrypts, translates, or +recovers this payload; ineligible routes fail closed with `unreadable_encrypted_agent_task`. + ## 2. Account login (OAuth) Eight provider presets use OAuth login — plus GitHub Copilot via an experimental unofficial diff --git a/docs-site/src/content/docs/guides/sub-agent-surface.md b/docs-site/src/content/docs/guides/sub-agent-surface.md index 4a56c810bd..8f1d6a6daa 100644 --- a/docs-site/src/content/docs/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/guides/sub-agent-surface.md @@ -22,7 +22,7 @@ Choose the mode for **new sessions**. Existing sessions keep the surface they st On **v2**, an optional **Keep ChatGPT on v1** switch (`keepNativeChatGptOnV1`) leaves Sol/Terra on the v1 surface so they can still spawn Grok or Claude. ChatGPT-native parents encrypt v2 -`NEW_TASK` bodies; routed models cannot read them. Routed parents stay on v2, where child tasks +`NEW_TASK` bodies; ordinary routed models cannot read them. Routed parents stay on v2, where child tasks are plaintext. This is a switch *inside* v2, not a fourth catalog mode. :::tip[Not sure?] @@ -115,27 +115,37 @@ inside a cooldown, missing a usable pooled Codex account, or beyond the configur Availability probes are cached for `subagentModelFallbackPollMs` (60 seconds by default). Fallback does not make incompatible encrypted tasks readable. When the child task is encrypted for -ChatGPT, selection is restricted to canonical native ChatGPT targets even if an external model -appears earlier in the chain. +ChatGPT, selection is restricted to targets that can receive that ciphertext: canonical native +ChatGPT or a Responses provider with an explicit `allowEncryptedV2AgentTasks: true` opt-in. ## Encrypted v2 task delivery -Codex may send a v2 native-to-routed child task only as backend-encrypted `encrypted_content`. That -payload can be read by the native ChatGPT backend, but not by an external provider. This is the -known [#92 limitation](https://github.com/lidge-jun/opencodex/issues/92). +Codex may send a v2 native-to-routed child task only as backend-encrypted `encrypted_content`. The +native ChatGPT backend can consume that payload; some compatible relays may also be able to pass it +to a backend that can. OpenCodex cannot infer that capability from a provider name or Base URL. This +is the known [#92 limitation](https://github.com/lidge-jun/opencodex/issues/92). opencodex fails safely instead of forwarding an empty or unreadable task: -- A direct non-native route returns HTTP 400 with +- An ineligible direct non-native route returns HTTP 400 with `error.code = "unreadable_encrypted_agent_task"` and does not echo the ciphertext. -- A combo considers only canonical native ChatGPT targets for that task, including retries. If none - is available, it returns the same 400 error. +- A combo considers only canonical native ChatGPT targets and explicitly trusted Responses targets + for that task, including retries. If none is available, it returns the same 400 error. - A readable plaintext task keeps the normal route and fallback behavior. Recovery options are to select a native ChatGPT child, add a native ChatGPT target to the combo, use v1 for heterogeneous-provider delegation, or resend the task as plaintext v2 `agent_message` content when you control the caller. +If a non-canonical Responses endpoint has been verified to consume or relay this ciphertext, enable +**Providers → Settings → Pass through encrypted V2 agent tasks** for that provider, or set +`allowEncryptedV2AgentTasks: true` in its provider config. The option is disabled by default and is +valid only when the selected model's final wire is `openai-responses`; a `modelAdapters` override to +`openai-chat` remains ineligible. It passes the opaque task through unchanged; it does not decrypt, +translate, or recover plaintext, and it does not prove provider compatibility. The canonical ChatGPT +forward provider is always eligible and needs no flag. A provider opted into passthrough skips +`agentTaskRecovery` for that route because recovery and opaque forwarding are separate trust modes. + An experimental, disabled-by-default `agentTaskRecovery` option can recover this specific native- to-routed shape through a raw Responses passthrough to the fixed ChatGPT `/responses` endpoint using the incoming credential shape used by the canonical `openai` provider with `authMode: "forward"`. @@ -149,8 +159,8 @@ byte-for-byte fidelity is not guaranteed. It rejects generic/API-key proxy calle `unreadable_encrypted_agent_task` on any failure. See [Agent configuration: Encrypted v2 task recovery](/reference/configuration/agents/#encrypted-v2-task-recovery) for the full trust boundary and configuration. -Combo routing remains unchanged and continues to consider only canonical native ChatGPT targets for -encrypted tasks. +Combo routing applies the same eligibility rule and considers canonical native ChatGPT targets plus +Responses targets with the explicit provider opt-in. ## Changing the mode diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index 81a19e5e5b..0a93eaa337 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -82,6 +82,13 @@ ChatGPT パススルーカタログには GPT-5.6 Sol/Terra/Luna の名前空間 (`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`)も含まれます。実際の呼び出し可否はアカウント権限に 依存します。 +暗号化された V2 子タスクでは、正規の ChatGPT 転送は暗黙に信頼されます。非正規の +`openai-responses` プロバイダーは、アップストリームが不透明な暗号文をそのまま処理または +中継できることを確認した後に限り `allowEncryptedV2AgentTasks: true` を設定できます。この +オプションはデフォルトで無効で、モデルの最終 wire が `openai-responses` の場合だけ有効です。 +`modelAdapters` が `openai-chat` に解決される場合は対象外で、opencodex は復号・翻訳・復元を +行わず、対象外のルートは `unreadable_encrypted_agent_task` で失敗します。 + ## 2. アカウントログイン(OAuth) OAuth ログインを使うプロバイダープリセットは 8 つで、これに実験的な非公式デバイスフロー diff --git a/docs-site/src/content/docs/ja/guides/sub-agent-surface.md b/docs-site/src/content/docs/ja/guides/sub-agent-surface.md index 7a9ac7aa12..49ca7d32ff 100644 --- a/docs-site/src/content/docs/ja/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ja/guides/sub-agent-surface.md @@ -72,18 +72,17 @@ v1 では、opencodex は、`max` または `ultra` の取り組みでアップ 重複するモデル ID は、最初に出現したモデル ID を保持しながら削除されます。選択中、opencodex は、無効になっている、ルーティングできない、無効なプロバイダーによってサポートされている、異常とマークされている、クールダウン中、使用可能なプールされた Codex アカウントがない、または設定されたクォータしきい値を超えている候補をスキップします。可用性プローブは `subagentModelFallbackPollMs` に対してキャッシュされます (デフォルトでは 60 秒)。 -フォールバックでは、互換性のない暗号化タスクは読み取り可能になりません。子タスクが ChatGPT 用に暗号化されている場合、外部モデルがチェーンの前の方に表示されている場合でも、選択は正規のネイティブ ChatGPT ターゲットに制限されます。 +フォールバックでは、互換性のない暗号化タスクは読み取り可能になりません。子タスクが ChatGPT 用に暗号化されている場合、外部モデルがチェーンの前の方に表示されていても、選択は正規のネイティブ ChatGPT ターゲットまたは、解決後の最終 wire adapter が `openai-responses` のままである `allowEncryptedV2AgentTasks: true` を明示した Responses プロバイダーに制限されます。 ## 暗号化された v2 タスク配信 -Codex は、v2 ネイティブからルーティングされた子タスクを、バックエンドで暗号化された `encrypted_content` としてのみ送信できます。そのペイロードは、ネイティブ ChatGPT バックエンドによって読み取ることができますが、外部プロバイダーによっては読み取ることができません。これは既知の [#92限定](https://github.com/lidge-jun/opencodex/issues/92) です。 +Codex は、v2 ネイティブからルーティングされた子タスクを、バックエンドで暗号化された `encrypted_content` としてのみ送信できます。そのペイロードは、ネイティブ ChatGPT バックエンドで処理されるか、互換性を確認して明示的に許可された Responses プロバイダーによって不透明なまま処理または中継されます。これは既知の [#92 の制限](https://github.com/lidge-jun/opencodex/issues/92) です。 opencodex は、空のタスクまたは読み取り不可能なタスクを転送するのではなく、安全に失敗します。 -- 直接の非ネイティブ ルートは HTTP 400 を返します。 +- 対象外の直接非ネイティブ ルートは HTTP 400 を返します。 `error.code = "unreadable_encrypted_agent_task"` であり、暗号文はエコーされません。 -- コンボでは、再試行を含む、そのタスクの正規のネイティブ ChatGPT ターゲットのみが考慮されます。何もない場合 -が利用可能な場合は、同じ 400 エラーが返されます。 +- コンボでは、再試行を含め、そのタスクの正規のネイティブ ChatGPT ターゲットと、最終 wire adapter が `openai-responses` のままである明示的に許可された Responses ターゲットだけが考慮されます。利用可能なターゲットがない場合は、同じ 400 エラーが返されます。 - 読み取り可能なプレーンテキストのタスクは、通常のルートとフォールバック動作を維持します。 回復オプションは、ネイティブ ChatGPT 子の選択、コンボへのネイティブ ChatGPT ターゲットの追加、異種プロバイダーの委任に v1 を使用する、または呼び出し元を制御するときにタスクをプレーンテキスト v2 `agent_message` コンテンツとして再送信することです。 diff --git a/docs-site/src/content/docs/ja/reference/adapters.md b/docs-site/src/content/docs/ja/reference/adapters.md index 54bd07eef8..4e8504918e 100644 --- a/docs-site/src/content/docs/ja/reference/adapters.md +++ b/docs-site/src/content/docs/ja/reference/adapters.md @@ -65,6 +65,12 @@ HTTP リトライ ループの対象外です。 - `key` provider は検証済みの相対 `responsesPath` を設定できます。adapter は `baseUrl` 末尾の `/` を 1 つ除き、`{trimmedBaseUrl}{responsesPath}` に送信します。Ark Agent Plan では `baseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3"` と `responsesPath: "/responses"` を使います。 - `forward` モードでは安全なヘッダー許可リスト(`FORWARD_HEADERS`)だけを中継します。authorization、ChatGPT account id、OpenAI beta/originator/session ヘッダーが対象です。この ChatGPT ログイン経路は [サイドカー](/ja/guides/sidecars/) にも使われます。 +暗号化された V2 子タスクの暗号文はこのアダプター上でも不透明なままです。バイト単位で転送 +され、復号・翻訳・復元は行われません。正規の ChatGPT 転送は暗黙に信頼されますが、非正規の +Responses プロバイダーは互換性を確認したうえで `allowEncryptedV2AgentTasks: true` を明示的に +設定する必要があります。オプションはデフォルトで無効で、最終 wire が `openai-responses` の場合 +だけ有効です。`openai-chat` への override は対象外です。 + ## `anthropic` **対象:** Anthropic **Messages**(`/v1/messages`)。 diff --git a/docs-site/src/content/docs/ja/reference/proxy-formats.md b/docs-site/src/content/docs/ja/reference/proxy-formats.md index 2d6f4fdf6a..a8b0622c54 100644 --- a/docs-site/src/content/docs/ja/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ja/reference/proxy-formats.md @@ -209,7 +209,7 @@ Responses-family および Chat リクエストは、プロバイダーまたは | 401 | `authentication_error` |必要なプロキシ アドミッション資格情報が見つからないか無効です。 | 403 | `origin_rejected` | Responses/OpenAI データプレーン リクエストまたは WebSocket アップグレードが、許可されていないオリジンから送信されました。 | 503 | `combo_unavailable` |選択したコンボ内のすべてのターゲットは使用不可、クールダウン中、無効、またはその他の理由で不適格です。 -| 400 | `unreadable_encrypted_agent_task` |暗号化された v2 ワーカー タスクには、それを使用できる適格なネイティブ ChatGPT ターゲットがありません。 +| 400 | `unreadable_encrypted_agent_task` |暗号化された v2 ワーカー タスクに、それを不透明なまま処理または中継できる適格なネイティブ ChatGPT 消費者、または最終 wire adapter が `openai-responses` のままである明示的に許可された Responses ターゲットがありません。 | | 426 | `upgrade_required` |応答 WebSocket トランスポートが無効になっているか、アップグレードが失敗しました。 HTTP を使用する | Anthropic オリジンの失敗は Anthropic のエラー エンベロープでレンダリングされるため、オリジンの拒否は OpenAI スタイルの `origin_rejected` 本体ではなく、その方言上の 403 `permission_error` になります。 @@ -218,4 +218,4 @@ Anthropic オリジンの失敗は Anthropic のエラー エンベロープで プロキシは、本物のバックエンド暗号文を不透明なものとして扱います。構造的に有効な暗号文はバイト単位で保存されます。opencodex は暗号文を復号したり、その内容を変換したり、別のプロバイダー用に再暗号化したりしません。 -一部のエージェント フックはこれまで、プレーンテキストの制御テキストを `encrypted_content` スロットに配置していました。互換性を確保するために、プロキシは、構造的に有効な Fernet の実行を変更せずに保持しながら、プレーンテキストをテキスト部分に分割します。 `agent_message` が修復中にすべての暗号化された部分を失った場合、それは通常のユーザー メッセージになります。現在の v2 タスクが完全に暗号化されたままであるが、選択したルーティングされたターゲットがネイティブ ChatGPT 暗号文を読み取ることができない場合、opencodex は読み取り不能なバイトをそのプロバイダーに送信する代わりに `unreadable_encrypted_agent_task` で失敗します。ワーカー タスクに関するクライアントの動作については、[サブエージェントサーフェス](/guides/sub-agent-surface/) を参照してください。 +一部のエージェント フックはこれまで、プレーンテキストの制御テキストを `encrypted_content` スロットに配置していました。互換性を確保するために、プロキシは、構造的に有効な Fernet の実行を変更せずに保持しながら、プレーンテキストをテキスト部分に分割します。`agent_message` が修復中にすべての暗号化された部分を失った場合、それは通常のユーザー メッセージになります。現在の v2 タスクが完全に暗号化されたままで、利用可能な正規の ChatGPT 消費者または最終 wire adapter が `openai-responses` のままである明示的に許可された Responses ターゲットがない場合、opencodex は読み取り不能なバイトをプロバイダーに送信する代わりに `unreadable_encrypted_agent_task` で失敗します。ワーカー タスクに関するクライアントの動作については、[サブエージェントサーフェス](/ja/guides/sub-agent-surface/) を参照してください。 diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 4f57dab7cc..77ebdb6705 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -81,6 +81,13 @@ ChatGPT 패스스루 카탈로그에는 GPT-5.6 Sol/Terra/Luna의 네임스페 (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`)도 들어갑니다. 실제 호출 가능 여부는 계정 권한에 따라 달라집니다. +암호화된 V2 하위 작업에서는 정규 ChatGPT 포워딩을 암묵적으로 신뢰합니다. 비정규 +`openai-responses` 프로바이더는 업스트림이 불투명한 암호문을 그대로 처리하거나 중계할 수 +있음을 확인한 뒤에만 `allowEncryptedV2AgentTasks: true`를 설정할 수 있습니다. 이 옵션은 기본적으로 +꺼져 있고 모델의 최종 wire가 `openai-responses`일 때만 적용됩니다. `modelAdapters`가 +`openai-chat`으로 해석되면 대상에서 제외되며, opencodex는 payload를 복호화·번역·복구하지 않고 +부적격 경로를 `unreadable_encrypted_agent_task`로 fail closed 처리합니다. + ## 2. 계정 로그인 (OAuth) OAuth 로그인을 사용하는 프로바이더 프리셋은 여덟 개이며, 여기에 실험적 비공식 디바이스 플로우 diff --git a/docs-site/src/content/docs/ko/guides/sub-agent-surface.md b/docs-site/src/content/docs/ko/guides/sub-agent-surface.md index 6b8f0064a0..c56e5c8b3f 100644 --- a/docs-site/src/content/docs/ko/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ko/guides/sub-agent-surface.md @@ -72,16 +72,16 @@ v1에서는 opencodex가 `max` 또는 `ultra` 추론 강도에서만 업스트 중복 모델 id는 첫 번째 출현을 유지한 채 제거합니다. 선택 과정에서 opencodex는 비활성화된 후보, 라우팅 불가 후보, 비활성화된 프로바이더가 받쳐주는 후보, unhealthy로 표시된 후보, cooldown 중인 후보, 사용할 수 있는 pooled Codex 계정이 없는 후보, 또는 설정된 quota 임계치를 넘는 후보를 건너뜁니다. 가용성 프로브는 기본값 60초인 `subagentModelFallbackPollMs` 동안 캐시됩니다. -폴백이 호환되지 않는 암호화 작업을 읽을 수 있게 만들어 주지는 않습니다. 자식 작업이 ChatGPT용으로 암호화되어 있으면, 체인 앞쪽에 외부 모델이 있더라도 선택은 정규 네이티브 ChatGPT 대상만 허용됩니다. +폴백이 호환되지 않는 암호화 작업을 읽을 수 있게 만들어 주지는 않습니다. 자식 작업이 ChatGPT용으로 암호화되어 있으면, 체인 앞쪽에 외부 모델이 있더라도 선택은 정규 네이티브 ChatGPT 대상 또는 최종 wire adapter가 `openai-responses`로 유지되는 `allowEncryptedV2AgentTasks: true`를 명시한 Responses 프로바이더로 제한됩니다. ## 암호화된 v2 작업 전달 -Codex는 v2 네이티브→라우팅 자식 작업을 백엔드 암호화된 `encrypted_content`로만 보낼 수 있습니다. 이 페이로드는 네이티브 ChatGPT 백엔드가 읽을 수 있지만 외부 프로바이더는 읽을 수 없습니다. 이것이 알려진 [#92 제한](https://github.com/lidge-jun/opencodex/issues/92)입니다. +Codex는 v2 네이티브→라우팅 자식 작업을 백엔드 암호화된 `encrypted_content`로만 보낼 수 있습니다. 이 페이로드는 네이티브 ChatGPT 백엔드가 읽거나, 호환성을 확인하고 명시적으로 허용된 Responses 프로바이더가 불투명하게 처리하거나 중계할 수 있습니다. 이것이 알려진 [#92 제한](https://github.com/lidge-jun/opencodex/issues/92)입니다. opencodex는 읽을 수 없거나 빈 작업을 그대로 넘기지 않고 안전하게 실패합니다. -- 비네이티브 직접 라우팅은 HTTP 400과 `error.code = "unreadable_encrypted_agent_task"`를 반환하며, 암호문을 에코하지 않습니다. -- 콤보는 해당 작업에 대해 재시도를 포함해 정규 네이티브 ChatGPT 대상만 고려합니다. 사용할 수 있는 대상이 없으면 같은 400 오류를 반환합니다. +- 자격이 없는 비네이티브 직접 라우팅은 HTTP 400과 `error.code = "unreadable_encrypted_agent_task"`를 반환하며, 암호문을 에코하지 않습니다. +- 콤보는 해당 작업에 대해 재시도를 포함해 정규 네이티브 ChatGPT 대상과 최종 wire adapter가 `openai-responses`로 유지되는 명시적으로 허용된 Responses 대상만 고려합니다. 사용할 수 있는 대상이 없으면 같은 400 오류를 반환합니다. - 읽을 수 있는 평문 작업은 정상 라우트와 폴백 동작을 그대로 유지합니다. 복구 방법은 네이티브 ChatGPT 자식을 선택하거나, 콤보에 네이티브 ChatGPT 대상을 추가하거나, 이종 프로바이더 위임에는 v1을 사용하거나, 호출자를 제어할 수 있을 때 작업을 평문 v2 `agent_message` 콘텐츠로 다시 보내는 것입니다. diff --git a/docs-site/src/content/docs/ko/reference/adapters.md b/docs-site/src/content/docs/ko/reference/adapters.md index 06eedf2773..b5caf7c0d9 100644 --- a/docs-site/src/content/docs/ko/reference/adapters.md +++ b/docs-site/src/content/docs/ko/reference/adapters.md @@ -76,6 +76,12 @@ interface ProviderAdapter { ChatGPT account id, OpenAI beta/originator/session 헤더가 대상입니다. 이 ChatGPT 로그인 경로는 [사이드카](/ko/guides/sidecars/)에도 쓰입니다. +암호화된 V2 하위 작업의 ciphertext는 이 어댑터에서도 불투명하게 유지됩니다. 바이트 단위로 +전달되며 복호화·번역·복구하지 않습니다. 정규 ChatGPT 포워딩은 암묵적으로 신뢰하지만, 비정규 +Responses 프로바이더는 호환성을 확인한 뒤 `allowEncryptedV2AgentTasks: true`를 명시해야 합니다. +이 옵션은 기본적으로 꺼져 있고 최종 wire가 `openai-responses`일 때만 적용되며, `openai-chat` +override는 대상에서 제외됩니다. + ## `anthropic` **대상:** Anthropic **Messages**(`/v1/messages`). diff --git a/docs-site/src/content/docs/ko/reference/proxy-formats.md b/docs-site/src/content/docs/ko/reference/proxy-formats.md index 532fe0b4ef..e563812cc4 100644 --- a/docs-site/src/content/docs/ko/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ko/reference/proxy-formats.md @@ -261,7 +261,7 @@ data-plane key는 management credential이 아닙니다. management API는 별 | 401 | `authentication_error` | 필요한 프록시 admission credential이 없거나 유효하지 않습니다 | | 403 | `origin_rejected` | Responses/OpenAI data-plane 요청 또는 WebSocket 업그레이드가 허용되지 않은 origin에서 들어왔습니다 | | 503 | `combo_unavailable` | 선택한 combo의 모든 대상이 사용할 수 없거나, cooldown 중이거나, 비활성화되어 있거나, 다른 이유로 부적합합니다 | -| 400 | `unreadable_encrypted_agent_task` | 암호화된 v2 worker task를 소비할 수 있는 적격 네이티브 ChatGPT 대상이 없습니다 | +| 400 | `unreadable_encrypted_agent_task` | 암호화된 v2 worker task를 불투명하게 소비하거나 중계할 적격 네이티브 ChatGPT 대상 또는 최종 wire adapter가 `openai-responses`로 유지되는 명시적으로 허용된 Responses 대상이 없습니다 | | 426 | `upgrade_required` | Responses WebSocket transport가 비활성화되어 있거나 업그레이드에 실패했습니다. HTTP를 사용하십시오 | Anthropic-origin 실패는 Anthropic의 error envelope로 렌더링됩니다. 따라서 해당 방언에서 origin 거부는 @@ -276,7 +276,8 @@ OpenAI 스타일 `origin_rejected` body가 아니라 403 `permission_error`입 일부 에이전트 hook은 과거에 평문 제어 텍스트를 `encrypted_content` 슬롯에 넣었습니다. 호환성을 위해 프록시는 구조적으로 유효한 Fernet 구간은 그대로 유지하면서 해당 평문을 텍스트 파트로 분리합니다. 이 복구 과정에서 `agent_message`의 암호화된 파트가 모두 사라지면 일반 user message가 됩니다. 현재 v2 -작업이 실제로 암호화된 상태이고 선택된 라우팅 대상이 네이티브 ChatGPT 암호문을 읽을 수 없다면, -opencodex는 읽을 수 없는 바이트를 프로바이더에 보내는 대신 `unreadable_encrypted_agent_task`로 -실패합니다. worker task와 관련된 클라이언트 동작은 [서브에이전트 표면](/guides/sub-agent-surface/)을 -참조하세요. +작업이 실제로 암호화된 상태이고 적격 네이티브 ChatGPT 소비자 또는 최종 wire adapter가 +`openai-responses`로 유지되는 명시적으로 허용된 Responses +대상이 없다면 opencodex는 읽을 수 없는 바이트를 프로바이더에 보내는 대신 +`unreadable_encrypted_agent_task`로 실패합니다. worker task와 관련된 클라이언트 동작은 +[서브에이전트 표면](/ko/guides/sub-agent-surface/)을 참조하세요. diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 0895ad07e8..af00fa8e7e 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -76,6 +76,12 @@ of the HTTP retry loop. ChatGPT account id, and the OpenAI beta/originator/session headers. This is the ChatGPT-login path that also powers the [sidecars](/guides/sidecars/). +Encrypted V2 child-task ciphertext remains opaque on this adapter: it is forwarded byte for byte, +not decrypted, translated, or recovered. Canonical ChatGPT forwarding is trusted implicitly; a +non-canonical Responses provider must explicitly set `allowEncryptedV2AgentTasks: true` after +verifying upstream compatibility. The opt-in is disabled by default, applies only when the final +model wire is `openai-responses`, and does not make an `openai-chat` override eligible. + ## `anthropic` **Targets:** Anthropic **Messages** (`/v1/messages`). diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index a12ed68f3b..cd5ff9e887 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -73,6 +73,7 @@ differing backup and rewrites known legacy namespaced selected ids to bare ids. | `modelSupportsServiceTier?` | `Record` | Exact upstream model capability overrides. Exact `true` enables canonical Fast for that model; exact `false` narrows provider defaults. An explicit provider-level `supportsServiceTier: false` remains fail-closed and cannot be reopened. Exact `true` does not authorize foreign caller-tier forwarding on Chat. Undeclared models fall back to provider-wide behavior. Management `PATCH /api/providers` merges entries and accepts `null` to clear one. | | `chatServiceTier?` | `boolean` | Provider-wide Chat-wire opt-in for forwarding caller `service_tier` values. On a classified route it governs foreign values such as `flex`, not proxy-owned canonical Fast after capability validation; on an unclassified route it governs every caller value because no Fast capability has been validated. Exact model capability does not authorize foreign forwarding. Responses routes retain their capability-based caller forwarding behavior. | | `preserveResponsesReasoningContent?` | `boolean` | Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it (blanking is the ChatGPT backend's rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek. Proxy-minted `ocxr1` envelopes are always stripped. | +| `allowEncryptedV2AgentTasks?` | `boolean` | Disabled by default. Trust this non-canonical provider to consume or relay opaque encrypted V2 child-task ciphertext only when the selected model's final wire is `openai-responses`; a `modelAdapters` override to `openai-chat` remains ineligible. When enabled, opencodex passes matching `encrypted_content` through unchanged and skips plaintext recovery for that route. It does not decrypt, translate, or recover the task, prove compatibility, or grant decryption capability; enable only after verifying the upstream. Canonical ChatGPT forwarding is always eligible and does not need this flag. | | `disabled?` | `boolean` | Keep the provider on disk but exclude it from routing and model/catalog listings. | | `apiKey?` | `string` | API key, or an `${ENV_VAR}` / `$ENV_VAR` reference resolved at request time. | | `apiKeyTransport?` | `"x-api-key" \| "bearer"` | Anthropic key header style. Defaults to native `x-api-key`; valid only for key-auth `anthropic` providers. | diff --git a/docs-site/src/content/docs/reference/proxy-formats.md b/docs-site/src/content/docs/reference/proxy-formats.md index 2792bdf0f8..9c4c0948b3 100644 --- a/docs-site/src/content/docs/reference/proxy-formats.md +++ b/docs-site/src/content/docs/reference/proxy-formats.md @@ -294,7 +294,7 @@ Errors use the client dialect's envelope where needed, but these status/code mea | 401 | `authentication_error` | A required proxy admission credential is missing or invalid | | 403 | `origin_rejected` | A Responses/OpenAI data-plane request or WebSocket upgrade came from a disallowed origin | | 503 | `combo_unavailable` | Every target in the selected combo is unavailable, in cooldown, disabled, or otherwise ineligible | -| 400 | `unreadable_encrypted_agent_task` | An encrypted v2 worker task has no eligible native ChatGPT target that can consume it | +| 400 | `unreadable_encrypted_agent_task` | An encrypted v2 worker task has no eligible canonical ChatGPT consumer or explicitly trusted Responses target that can consume or relay it opaquely | | 426 | `upgrade_required` | The Responses WebSocket transport is disabled or the upgrade failed; use HTTP | Anthropic-origin failures are rendered in Anthropic's error envelope, so the origin rejection is a @@ -310,6 +310,7 @@ Some agent hooks have historically placed plaintext control text in an `encrypte For compatibility, the proxy separates that plaintext into text parts while retaining any structurally valid Fernet runs unchanged. If an `agent_message` loses all encrypted parts during that repair, it becomes a normal user message. If a current v2 task remains genuinely encrypted -but the selected routed target cannot read native ChatGPT ciphertext, opencodex fails with +but no canonical consumer or explicitly trusted opaque-passthrough Responses target is available, +opencodex fails with `unreadable_encrypted_agent_task` instead of sending unreadable bytes to that provider. See [Sub-agent Surface](/guides/sub-agent-surface/) for the client behavior around worker tasks. diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index 1966d9db63..e1a69d1557 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -91,6 +91,14 @@ account id, OpenAI beta/originator/session — см. [Адаптеры](/ru/refe Каталог сквозного режима ChatGPT дополнительно включает «голые» слаги GPT-5.6 Sol/Terra/Luna (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`) для аккаунтов, которым они доступны. +Для зашифрованных дочерних задач V2 каноническая пересылка ChatGPT доверена неявно. Неканонический +провайдер `openai-responses` может задать `allowEncryptedV2AgentTasks: true` только после проверки, +что upstream умеет обработать или прозрачно ретранслировать ciphertext без изменений. Опция по +умолчанию отключена и действует только при финальном wire `openai-responses`; если `modelAdapters` +разрешается в `openai-chat`, маршрут остаётся неподходящим. opencodex не расшифровывает, не переводит +и не восстанавливает payload, а неподходящие маршруты завершаются с +`unreadable_encrypted_agent_task`. + ## 2. Вход по аккаунту (OAuth) Восемь пресетов провайдеров используют вход через OAuth — плюс GitHub Copilot через diff --git a/docs-site/src/content/docs/ru/guides/sub-agent-surface.md b/docs-site/src/content/docs/ru/guides/sub-agent-surface.md index 8b6e7d504e..95a9eba0a2 100644 --- a/docs-site/src/content/docs/ru/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/ru/guides/sub-agent-surface.md @@ -111,21 +111,22 @@ opencodex по-прежнему читает устаревшую строку ` `subagentModelFallbackPollMs` (по умолчанию 60 секунд). Fallback не делает несовместимые encrypted task читаемыми. Когда задача потомка зашифрована для -ChatGPT, выбор ограничивается каноническими нативными целями ChatGPT, даже если внешняя модель -появляется раньше в цепочке. +ChatGPT, выбор ограничивается каноническими нативными целями ChatGPT или Responses-провайдерами +с явным `allowEncryptedV2AgentTasks: true`, даже если внешняя модель появляется раньше в цепочке. ## Доставка шифрованных задач v2 Codex может отправить задачу child v2 из native-to-routed пути только как backend-encrypted -`encrypted_content`. Эту нагрузку может прочитать нативный backend ChatGPT, но не внешний -провайдер. Это известное ограничение [#92](https://github.com/lidge-jun/opencodex/issues/92). +`encrypted_content`. Эту нагрузку может прочитать нативный backend ChatGPT или явно разрешённый +Responses-провайдер, способный прозрачно передать ciphertext. Это известное ограничение +[#92](https://github.com/lidge-jun/opencodex/issues/92). -opencodex завершаетcя безопасно и не пересылает пустую или нечитаемую задачу: +opencodex завершается безопасно и не пересылает пустую или нечитаемую задачу: -- Прямой не-нативный маршрут возвращает HTTP 400 с +- Неподходящий прямой не-нативный маршрут возвращает HTTP 400 с `error.code = "unreadable_encrypted_agent_task"` и не отражает ciphertext назад. -- Combo для такой задачи рассматривает только канонические нативные цели ChatGPT, включая retry. - Если ни одной подходящей цели нет, возвращается тот же HTTP 400. +- Combo для такой задачи рассматривает канонические нативные цели ChatGPT и явно разрешённые + Responses-цели, включая retry. Если ни одной подходящей цели нет, возвращается тот же HTTP 400. - Читаемая plaintext-задача сохраняет обычное поведение маршрутизации и fallback. Варианты восстановления: выбрать нативного потомка ChatGPT, добавить нативную цель ChatGPT в diff --git a/docs-site/src/content/docs/ru/reference/adapters.md b/docs-site/src/content/docs/ru/reference/adapters.md index eca16ca669..9b082be784 100644 --- a/docs-site/src/content/docs/ru/reference/adapters.md +++ b/docs-site/src/content/docs/ru/reference/adapters.md @@ -74,6 +74,12 @@ interface ProviderAdapter { authorization, ChatGPT account id и заголовки OpenAI beta/originator/session. Это путь входа через ChatGPT, на котором также работают [сайдкары](/ru/guides/sidecars/). +Ciphertext зашифрованных дочерних задач V2 на этом адаптере остаётся непрозрачным: он пересылается +побайтно без расшифровки, перевода или восстановления. Каноническая пересылка ChatGPT доверена +неявно, но неканонический Responses-провайдер должен после проверки совместимости явно задать +`allowEncryptedV2AgentTasks: true`. Опция по умолчанию отключена и действует только при финальном wire +`openai-responses`; override в `openai-chat` не подходит. + ## `anthropic` **Назначение:** Anthropic **Messages** (`/v1/messages`). diff --git a/docs-site/src/content/docs/ru/reference/proxy-formats.md b/docs-site/src/content/docs/ru/reference/proxy-formats.md index 6a461e8b9e..a381da4a78 100644 --- a/docs-site/src/content/docs/ru/reference/proxy-formats.md +++ b/docs-site/src/content/docs/ru/reference/proxy-formats.md @@ -270,7 +270,7 @@ Direct, поэтому remote proxy key здесь обязан идти чер | 401 | `authentication_error` | Отсутствует обязательный credential для proxy-admission или он неверен | | 403 | `origin_rejected` | Data-plane запрос или WebSocket-upgrade Responses/OpenAI пришёл с запрещённого origin | | 503 | `combo_unavailable` | Все цели выбранной combo недоступны, в cooldown, отключены или иным образом не подходят | -| 400 | `unreadable_encrypted_agent_task` | У шифрованной задачи воркера v2 нет подходящей нативной цели ChatGPT, способной её прочитать | +| 400 | `unreadable_encrypted_agent_task` | У шифрованной задачи воркера v2 нет подходящего канонического потребителя ChatGPT или явно разрешённой Responses-цели, способной прозрачно обработать или передать ciphertext | | 426 | `upgrade_required` | Транспорт Responses WebSocket выключен или upgrade не удался; используйте HTTP | Сбои, пришедшие с Anthropic-side, отрисовываются в error envelope Anthropic, поэтому отклонение @@ -286,7 +286,7 @@ ciphertext сохраняется байт в байт: opencodex его не р совместимости proxy отделяет такой plaintext в текстовые части, сохраняя нетронутыми все структурно валидные фрагменты Fernet. Если после такой починки у `agent_message` не остаётся ни одной шифрованной части, сообщение становится обычным user-message. Если текущая задача v2 -остаётся по-настоящему зашифрованной, а выбранная routed-цель не умеет читать ciphertext нативного -ChatGPT, opencodex завершит запрос ошибкой `unreadable_encrypted_agent_task`, вместо того чтобы +остаётся по-настоящему зашифрованной, а подходящего канонического потребителя ChatGPT или явно +разрешённой Responses-цели нет, opencodex завершит запрос ошибкой `unreadable_encrypted_agent_task`, вместо того чтобы отправить нечитаемые байты этому провайдеру. О поведении клиента вокруг worker-task'ов см. [Поверхность подагентов](/guides/sub-agent-surface/). diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index 4e924458ee..a70fa432f3 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -73,6 +73,12 @@ shipped v1 配置自动迁移到 marker 2 的单一选项行。原配置只保 ChatGPT 透传目录也会加入 GPT-5.6 Sol/Terra/Luna 的裸 slug(`gpt-5.6-sol`、 `gpt-5.6-terra`、`gpt-5.6-luna`);账号具备相应权限时才能实际调用。 +对于加密的 V2 子任务,规范 ChatGPT 透传会被隐式信任。非规范的 `openai-responses` 提供商只有在 +确认上游能够原样消费或中继不透明密文后,才能设置 `allowEncryptedV2AgentTasks: true`。该选项 +默认关闭,只在模型的最终 wire 为 `openai-responses` 时生效;如果 `modelAdapters` 解析为 +`openai-chat`,该路由仍不具备资格。opencodex 不会解密、翻译或恢复 payload,不合格路由会以 +`unreadable_encrypted_agent_task` fail closed。 + ## 2. 账号登录(OAuth) 有八个提供商预设使用 OAuth 登录,另加通过实验性非官方设备流桥接的 GitHub Copilot。 diff --git a/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md b/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md index 3882d15c9a..d88fd56475 100644 --- a/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/zh-cn/guides/sub-agent-surface.md @@ -72,16 +72,16 @@ per-role fallback 链应该放在 opencodex 配置里,而不是 `$CODEX_HOME/a 重复的模型 id 会在保留第一次出现的前提下移除。在选择过程中,opencodex 会跳过已禁用、不可路由、由已禁用 provider 支撑、标记为 unhealthy、处于 cooldown、没有可用 pooled Codex 账户,或者超出配置配额阈值的候选项。可用性探测会缓存 `subagentModelFallbackPollMs` 的时长,默认 60 秒。 -fallback 不会让不兼容的加密任务变得可读。当子任务为 ChatGPT 加密时,即使链中更靠前出现了外部模型,选择也只会限制在规范的原生 ChatGPT 目标上。 +fallback 不会让不兼容的加密任务变得可读。当子任务为 ChatGPT 加密时,即使链中更靠前出现了外部模型,选择也只会限制在规范的原生 ChatGPT 目标,或明确设置 `allowEncryptedV2AgentTasks: true` 且最终 wire adapter 仍为 `openai-responses` 的 Responses 提供商上。 ## 加密的 v2 任务传递 -Codex 只能把 v2 原生到路由的子任务作为后端加密的 `encrypted_content` 发送。这个载荷可以被原生 ChatGPT 后端读取,但外部 provider 不能读取。这就是已知的 [#92](https://github.com/lidge-jun/opencodex/issues/92) 限制。 +Codex 只能把 v2 原生到路由的子任务作为后端加密的 `encrypted_content` 发送。这个载荷可以由原生 ChatGPT 后端读取;明确信任且设置了 `allowEncryptedV2AgentTasks: true` 的 Responses 提供商只能原样、不透明地中继密文,不能读取、解密或翻译它。这就是已知的 [#92](https://github.com/lidge-jun/opencodex/issues/92) 限制。 opencodex 会安全失败,而不是转发空任务或不可读任务: -- 直接的非原生路由会返回 HTTP 400,并且 `error.code = "unreadable_encrypted_agent_task"`,不会回显密文。 -- 对于该任务,combo 只会考虑规范的原生 ChatGPT 目标,包括重试。如果没有可用目标,则返回相同的 400 错误。 +- 不具备资格的直接非原生路由会返回 HTTP 400,并且 `error.code = "unreadable_encrypted_agent_task"`,不会回显密文。 +- 对于该任务,combo 只会考虑规范的原生 ChatGPT 目标和最终 wire adapter 仍为 `openai-responses` 的明确允许 Responses 目标,包括重试。如果没有可用目标,则返回相同的 400 错误。 - 可读的明文任务会保持正常的路由和 fallback 行为。 恢复选项是选择原生 ChatGPT 子级、在 combo 中添加原生 ChatGPT 目标、在异构 provider 委派中使用 v1,或者在你控制调用方时将任务作为明文 v2 `agent_message` 内容重新发送。 diff --git a/docs-site/src/content/docs/zh-cn/reference/adapters.md b/docs-site/src/content/docs/zh-cn/reference/adapters.md index 5786952810..a9c7f10add 100644 --- a/docs-site/src/content/docs/zh-cn/reference/adapters.md +++ b/docs-site/src/content/docs/zh-cn/reference/adapters.md @@ -68,6 +68,11 @@ interface ProviderAdapter { account id 和 OpenAI beta/originator/session header。这条 ChatGPT 登录路径也为 [sidecar](/zh-cn/guides/sidecars/) 提供支持。 +加密的 V2 子任务密文在此 adapter 上也保持不透明:按字节转发,不会解密、翻译或恢复。规范 +ChatGPT 透传会被隐式信任;非规范 Responses 提供商必须在确认兼容性后显式设置 +`allowEncryptedV2AgentTasks: true`。该选项默认关闭,只在最终 wire 为 `openai-responses` 时 +生效;解析到 `openai-chat` 的 override 不具备资格。 + ## `anthropic` **目标:** Anthropic **Messages**(`/v1/messages`)。 diff --git a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md index a469e2f628..8bf0543741 100644 --- a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md +++ b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md @@ -230,7 +230,7 @@ Responses 家族和 Chat 请求会把 `Authorization` 留给提供方或 Codex D | 401 | `authentication_error` | 所需的代理准入凭证缺失或无效 | | 403 | `origin_rejected` | 一条 Responses/OpenAI 数据平面请求或 WebSocket 升级来自不允许的 origin | | 503 | `combo_unavailable` | 所选 combo 中的所有目标都不可用、处于冷却、已禁用或以其他方式不具备资格 | -| 400 | `unreadable_encrypted_agent_task` | 一个加密的 v2 worker task 没有任何可消费它的合格原生 ChatGPT 目标 | +| 400 | `unreadable_encrypted_agent_task` | 一个加密的 v2 worker task 没有任何能够不透明消费或中继它的合格原生 ChatGPT 目标或明确允许的 Responses 目标 | | 426 | `upgrade_required` | Responses WebSocket 传输被禁用,或升级失败;请改用 HTTP | Anthropic 来源的失败会以 Anthropic 的错误封装呈现,因此该方言中的 origin 拒绝会是 @@ -240,5 +240,5 @@ Anthropic 来源的失败会以 Anthropic 的错误封装呈现,因此该方 代理把真正的后端密文视为不透明数据。结构有效的密文会逐字节保留:opencodex 不会对其解密、翻译其内容,或为另一个提供方重新加密。 -某些 agent hook 历史上会把明文控制文本放进 `encrypted_content` 槽。为兼容起见,代理会把那部分明文拆分为文本片段,同时保持任何结构有效的 Fernet 片段不变。如果一个 `agent_message` 在该修复过程中失去了所有加密部分,它就会变成普通的 user message。如果当前的 v2 task 仍然真的是加密的,但所选路由目标无法读取原生 ChatGPT 密文,opencodex 会以 +某些 agent hook 历史上会把明文控制文本放进 `encrypted_content` 槽。为兼容起见,代理会把那部分明文拆分为文本片段,同时保持任何结构有效的 Fernet 片段不变。如果一个 `agent_message` 在该修复过程中失去了所有加密部分,它就会变成普通的 user message。如果当前的 v2 task 仍然真的是加密的,但没有合格的原生 ChatGPT 消费者或明确允许的不透明中继 Responses 目标,opencodex 会以 `unreadable_encrypted_agent_task` 失败,而不是把不可读字节发送给该提供方。有关 worker task 周边的客户端行为,请参见 [Sub-agent Surface](/guides/sub-agent-surface/)。 diff --git a/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md b/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md index 7cdb8fb49d..29dcc40618 100644 --- a/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/zh-tw/guides/sub-agent-surface.md @@ -6,7 +6,7 @@ description: 全域控制 Codex 在所有模型上生成和管理子代理的方 opencodex 允許你為目錄中的所有模型選擇多代理協作介面。儀表板和 Models 頁面中的 **Sub-agent** 開關會全域控制這一設定。 :::note -在 v2 介面(`multi_agent_v2`)上,子代理**預設**繼承父會話的模型:`fork_turns` 預設為 `all`,而全量歷史 fork 會拒絕覆蓋。自 v2.7.2 起,opencodex 注入的指引會教模型如何打破繼承 —— 將 `fork_turns` 設為 `"none"`(或如 `"3"` 的部分 fork)的 `spawn_agent` 呼叫可以傳入 `model` / `reasoning_effort` 引數;即使公開的工具 schema 中看不到這些引數,Codex 執行環境也會解析並應用。已知傳輸限制:當**原生**父代理 spawn 一個路由到**非原生** provider 的子代理時,Codex 用戶端可能只以後端加密的 `encrypted_content` 傳送 `NEW_TASK` 載荷([#92](https://github.com/lidge-jun/opencodex/issues/92))。opencodex 不會把這種無法讀取的任務轉發給外部 provider:直接路由會回傳 HTTP 400 和錯誤碼 `unreadable_encrypted_agent_task`;組合路由則會跳過無法解密的目標,並在存在可用目標時選擇規範的原生 ChatGPT 目標。恢復方法:異構 provider 委派改用 v1、選擇原生 ChatGPT 子代理,或將任務重新作為明文 v2 `agent_message` 內容傳送。另有預設停用的實驗性 `agentTaskRecovery`;它會增加 ChatGPT 配額用量與延遲,且依賴非公開後端行為。 +在 v2 介面(`multi_agent_v2`)上,子代理**預設**繼承父會話的模型:`fork_turns` 預設為 `all`,而全量歷史 fork 會拒絕覆蓋。自 v2.7.2 起,opencodex 注入的指引會教模型如何打破繼承 —— 將 `fork_turns` 設為 `"none"`(或如 `"3"` 的部分 fork)的 `spawn_agent` 呼叫可以傳入 `model` / `reasoning_effort` 引數;即使公開的工具 schema 中看不到這些引數,Codex 執行環境也會解析並應用。已知傳輸限制:當**原生**父代理 spawn 一個路由到**非原生** provider 的子代理時,Codex 用戶端可能只以後端加密的 `encrypted_content` 傳送 `NEW_TASK` 載荷([#92](https://github.com/lidge-jun/opencodex/issues/92))。opencodex 只會把這種無法讀取的任務送到規範的原生 ChatGPT 目標,或明確設定 `allowEncryptedV2AgentTasks: true` 且最終 wire adapter 仍為 `openai-responses`、能逐位元組中繼不透明密文的 Responses provider。其他不具資格的外部直接路由會回傳 HTTP 400 和錯誤碼 `unreadable_encrypted_agent_task`;組合路由則會跳過不具資格的目標,並在存在可用目標時選擇上述合格目標。恢復方法:異構 provider 委派改用 v1、選擇原生 ChatGPT 子代理、使用已驗證且明確信任的 Responses provider,或將任務重新作為明文 v2 `agent_message` 內容傳送。另有預設停用的實驗性 `agentTaskRecovery`;它會增加 ChatGPT 配額用量與延遲,且依賴非公開後端行為。 ::: ## What sub-agents are @@ -21,7 +21,7 @@ opencodex 允許你為目錄中的所有模型選擇多代理協作介面。儀 | --- | --- | --- | | **v1** | `multi_agent_v1` | 使用經典的名稱空間代理工具,以及 `send_input` / `close_agent` / `resume_agent`。`spawn_agent` 的模型覆蓋可以在其他模型上生成子代理。 | | **base**(預設) | 上游固定值 | 恢復上游模型的固定值:gpt-5.6-sol 和 gpt-5.6-terra 使用 v2,gpt-5.6-luna 使用 v1;未固定的模型遵循 Codex 的 `multi_agent_v2` 功能開關。生成行為取決於該模型最終使用的介面。 | -| **v2** | `multi_agent_v2` | 使用扁平的 `spawn_agent` 工具、併發會話,以及 `send_message` / `followup_task` / `wait_agent` / `interrupt_agent`。全量歷史 fork 時子代理繼承父模型;`fork_turns: "none"`(或部分 fork)時接受 `model` / `reasoning_effort` 覆蓋。如果原生→路由子代理只收到後端加密的任務內容,外部路由會回傳 `unreadable_encrypted_agent_task`;混合組合會優先選擇可解密的原生目標([#92](https://github.com/lidge-jun/opencodex/issues/92))。 | +| **v2** | `multi_agent_v2` | 使用扁平的 `spawn_agent` 工具、併發會話,以及 `send_message` / `followup_task` / `wait_agent` / `interrupt_agent`。全量歷史 fork 時子代理繼承父模型;`fork_turns: "none"`(或部分 fork)時接受 `model` / `reasoning_effort` 覆蓋。如果原生→路由子代理只收到後端加密的任務內容,規範的原生 ChatGPT 目標可以讀取,而明確設定 `allowEncryptedV2AgentTasks: true` 且最終 wire adapter 仍為 `openai-responses` 的 Responses provider 可以逐位元組中繼不透明密文;其他不具資格的外部直接路由會回傳 `unreadable_encrypted_agent_task`,混合組合只會選擇上述合格目標([#92](https://github.com/lidge-jun/opencodex/issues/92))。 | ## 運作原理 @@ -88,19 +88,22 @@ opencodex 仍可為了向後相容從 TOML 讀取舊版 `model_fallback` 列, 候選。可用性探測會快取 `subagentModelFallbackPollMs`(預設 60 秒)。 Fallback 不能讓不相容的加密任務變成可讀。當子任務是為 ChatGPT 加密時,即使外部模型在鏈中出現得 -更早,選擇也會限制在規範的原生 ChatGPT 目標。 +更早,選擇也會限制在規範的原生 ChatGPT 目標,或明確設定 +`allowEncryptedV2AgentTasks: true` 且最終 wire adapter 仍為 `openai-responses` 的 Responses provider。 ## 加密的 v2 任務傳輸 -Codex 可能只以後端加密的 `encrypted_content` 傳送 v2 原生→路由子任務。該載荷可以被原生 ChatGPT -後端讀取,但外部 provider 無法讀取。這是已知的 +Codex 可能只以後端加密的 `encrypted_content` 傳送 v2 原生→路由子任務。原生 ChatGPT +後端可以讀取該載荷;明確信任、最終 wire adapter 仍為 `openai-responses` 且能不透明中繼密文的 Responses provider +則只能逐位元組中繼,不能讀取。 +這是已知的 [#92 限制](https://github.com/lidge-jun/opencodex/issues/92)。 opencodex 會安全失敗,而不是轉發空或無法讀取的任務: -- 直接的非原生路由回傳 HTTP 400,帶有 `error.code = "unreadable_encrypted_agent_task"`,且不會回顯 +- 不具資格的直接非原生路由回傳 HTTP 400,帶有 `error.code = "unreadable_encrypted_agent_task"`,且不會回顯 密文。 -- 組合只會為該任務考慮規範的原生 ChatGPT 目標,包括重試。若沒有可用目標,回傳相同的 400。 +- 組合只會為該任務考慮規範的原生 ChatGPT 目標與最終 wire adapter 仍為 `openai-responses` 的明確信任 Responses 目標,包括重試。若沒有可用目標,回傳相同的 400。 - 可讀取的明文任務保持正常的路由與 fallback 行為。 恢復方法:選擇原生 ChatGPT 子代理、在組合中加入原生 ChatGPT 目標、異構 provider 委派改用 v1, diff --git a/gui/src/components/AddProviderModal.tsx b/gui/src/components/AddProviderModal.tsx index ae080fb8a6..8ac74a95fa 100644 --- a/gui/src/components/AddProviderModal.tsx +++ b/gui/src/components/AddProviderModal.tsx @@ -148,6 +148,7 @@ export default function AddProviderModal({ apiKey: "", apiKeyTransport: undefined, defaultModel: p.defaultModel ?? "", + allowEncryptedV2AgentTasks: false, allowPrivateNetwork: false, }, }); diff --git a/gui/src/components/add-provider-form-pane.tsx b/gui/src/components/add-provider-form-pane.tsx index 78cba31d34..ecf803dc2a 100644 --- a/gui/src/components/add-provider-form-pane.tsx +++ b/gui/src/components/add-provider-form-pane.tsx @@ -66,7 +66,18 @@ export function AddProviderFormPane({ {dup &&
{t("modal.duplicateWarn", { name: form.name.trim() })}
} {!isReservedForward && <> - { + const adapter = e.target.value; + onFormChange({ + ...form, + adapter, + ...(adapter === "openai-responses" ? {} : { allowEncryptedV2AgentTasks: false }), + }); + }} + > {["openai-responses", "openai-chat", "anthropic", "google", "azure-openai", "cursor"].map(a => )} @@ -120,6 +131,23 @@ export function AddProviderFormPane({ {!isReservedForward && (form?.allowPrivateNetwork ?? false) && (

{t("modal.allowPrivateNetworkHint")}

)} + {!isReservedForward && form.adapter === "openai-responses" && ( + <> + +

{t("pws.encryptedV2PassthroughDesc")}

+ + )} } {form.authMode === "forward" ? (
diff --git a/gui/src/components/add-provider-modal-reducer.ts b/gui/src/components/add-provider-modal-reducer.ts index 97ee7cb17f..8dc929f35d 100644 --- a/gui/src/components/add-provider-modal-reducer.ts +++ b/gui/src/components/add-provider-modal-reducer.ts @@ -56,7 +56,7 @@ export function createInitialAddProviderState( return { preset: initialCustom ? customPreset : null, form: initialCustom - ? { name: "", adapter: "openai-chat", baseUrl: "", authMode: "key", apiKey: "", apiKeyTransport: undefined, defaultModel: "", allowPrivateNetwork: false } + ? { name: "", adapter: "openai-chat", baseUrl: "", authMode: "key", apiKey: "", apiKeyTransport: undefined, defaultModel: "", allowEncryptedV2AgentTasks: false, allowPrivateNetwork: false } : null, saving: false, error: "", diff --git a/gui/src/components/provider-workspace/ProviderSettings.tsx b/gui/src/components/provider-workspace/ProviderSettings.tsx index 1509e13a14..01b217dd74 100644 --- a/gui/src/components/provider-workspace/ProviderSettings.tsx +++ b/gui/src/components/provider-workspace/ProviderSettings.tsx @@ -79,6 +79,7 @@ export default function ProviderSettings({ const [authMode, setAuthMode] = useState(initialAuth); const [apiKeyTransport, setApiKeyTransport] = useState(item.apiKeyTransport ?? "x-api-key"); const [note, setNote] = useState(item.note ?? ""); + const [allowEncryptedV2AgentTasks, setAllowEncryptedV2AgentTasks] = useState(item.allowEncryptedV2AgentTasks ?? false); const [allowPrivateNetwork, setAllowPrivateNetwork] = useState(item.allowPrivateNetwork ?? false); const [liveModels, setLiveModels] = useState(savedLiveModels); const [cursorHttpVersion, setCursorHttpVersion] = useState(savedCursorHttpVersion); @@ -107,6 +108,7 @@ export default function ProviderSettings({ setAuthMode(String(item.authMode ?? (item.keyOptional ? "local" : "key"))); setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); setNote(item.note ?? ""); + setAllowEncryptedV2AgentTasks(item.allowEncryptedV2AgentTasks ?? false); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(savedLiveModels); setCursorHttpVersion(savedCursorHttpVersion); @@ -117,7 +119,7 @@ export default function ProviderSettings({ setMsg(null); setModeMsg(null); queueMicrotask(() => setEndpointChoice(matchChoiceId(baseUrlChoices, item.baseUrl))); - }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.apiKeyTransport, item.keyOptional, item.note, item.allowPrivateNetwork, savedLiveModels, savedCursorHttpVersion, item.requestPacing, baseUrlChoices]); + }, [item.adapter, item.baseUrl, item.defaultModel, item.authMode, item.apiKeyTransport, item.keyOptional, item.note, item.allowEncryptedV2AgentTasks, item.allowPrivateNetwork, savedLiveModels, savedCursorHttpVersion, item.requestPacing, baseUrlChoices]); /* eslint-enable react-hooks/set-state-in-effect */ // Account mode syncs on its own: a mode PATCH refresh must not reset an in-progress @@ -192,6 +194,7 @@ export default function ProviderSettings({ || authMode !== String(item.authMode ?? (item.keyOptional ? "local" : "key")) || (adapter.trim() === "anthropic" && authMode === "key" && apiKeyTransport !== (item.apiKeyTransport ?? "x-api-key")) || note.trim() !== (item.note ?? "") + || (item.name !== "openai" && allowEncryptedV2AgentTasks !== (item.allowEncryptedV2AgentTasks ?? false)) || allowPrivateNetwork !== (item.allowPrivateNetwork ?? false) || liveModels !== savedLiveModels || (adapter.trim() === "cursor" && cursorHttpVersion !== savedCursorHttpVersion); @@ -236,6 +239,7 @@ export default function ProviderSettings({ setMsg({ ok: false, text: t("pws.pacingRuleRequired") }); return false; } const pacingOnly = pacingDirty && !dirty; + const encryptedV2Value = adapter.trim() === "openai-responses" && allowEncryptedV2AgentTasks; const patch: ProviderUpdatePatch = pacingOnly ? { requestPacing: pacingDraft } : { @@ -244,6 +248,9 @@ export default function ProviderSettings({ defaultModel: defaultModel.trim(), authMode, note: note.trim(), + ...(item.name !== "openai" && encryptedV2Value !== (item.allowEncryptedV2AgentTasks ?? false) + ? { allowEncryptedV2AgentTasks: encryptedV2Value } + : {}), allowPrivateNetwork, ...(pacingDirty ? { requestPacing: pacingDraft } : {}), }; @@ -299,7 +306,8 @@ export default function ProviderSettings({ setAdapter(item.adapter); setBaseUrl(item.baseUrl); setDefaultModel(item.defaultModel ?? ""); setAuthMode(initialAuth); setApiKeyTransport(item.apiKeyTransport ?? "x-api-key"); - setNote(item.note ?? ""); setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(savedLiveModels); + setNote(item.note ?? ""); setAllowEncryptedV2AgentTasks(item.allowEncryptedV2AgentTasks ?? false); + setAllowPrivateNetwork(item.allowPrivateNetwork ?? false); setLiveModels(savedLiveModels); setCursorHttpVersion(savedCursorHttpVersion); setMsg(null); setPacingEnabled(item.requestPacing?.enabled === true); setPacingRpm(numberDraft(item.requestPacing?.requestsPerMinute)); setPacingDelay(numberDraft(item.requestPacing?.minIntervalMs)); setPacingModels({ ...(item.requestPacing?.models ?? {}) }); @@ -333,7 +341,10 @@ export default function ProviderSettings({ + {item.name !== "openai" && adapter.trim() === "openai-responses" && ( + + )}