-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(providers): allow direct encrypted V2 task passthrough (carry of #3444) #3579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d1da9be
0cc8290
e2c9a66
b224ed5
560bc2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1759,6 +1759,30 @@ function unreadableEncryptedAgentTaskResponse(): Response { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Keep this trust boundary deliberately narrow: only a key-auth Responses route may consume | ||
| * opaque child-task ciphertext, and the model's final wire override must still be Responses. | ||
| * Callers keep combo attempts on their existing native-only recovery/fail-closed behavior. | ||
| */ | ||
| function canPassThroughEncryptedV2AgentTask( | ||
| route: RouteResult, | ||
| inboundWire: InboundWire, | ||
| ): boolean { | ||
| const provider = route.provider; | ||
| if ( | ||
| inboundWire !== "responses" | ||
| || provider.allowEncryptedV2AgentTasks !== true | ||
| || (provider.authMode ?? "key") !== "key" | ||
| ) return false; | ||
|
|
||
| return resolveWireProtocolOverride( | ||
| route.providerName, | ||
| route.modelId, | ||
| provider, | ||
| inboundWire, | ||
| ).adapter === "openai-responses"; | ||
| } | ||
|
|
||
| type ResponsesAuthResolution = | ||
| | { ok: true; authCtx: CodexAuthContext; headers: Headers; substituteMainCredential: boolean } | ||
| | { ok: false; response: Response }; | ||
|
|
@@ -3087,7 +3111,8 @@ async function handleResponsesInner( | |
| previewSelectionAdmission?.release(); | ||
| } | ||
|
|
||
| // Native fallback can consume ciphertext, so recover only after final route selection. | ||
| // Native fallback and explicitly trusted direct Responses routes can consume ciphertext, | ||
| // so recover only after final route selection. | ||
| if ( | ||
| inboundWire === "responses" | ||
| && | ||
|
|
@@ -3096,6 +3121,7 @@ async function handleResponsesInner( | |
| && agentTaskRecovery | ||
| && !isCanonicalOpenAiForwardProvider(route.provider) | ||
| && !options.comboAttempt | ||
| && !canPassThroughEncryptedV2AgentTask(route, inboundWire) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a thread-spawn request has any configured Useful? React with 👍 / 👎. |
||
| ) { | ||
| let recovered = false; | ||
| try { | ||
|
|
@@ -3219,9 +3245,16 @@ async function handleResponsesInner( | |
|
|
||
| if (options.abortSignal?.aborted) return clientCancelledResponse(); | ||
|
|
||
| // Encrypted child tasks may only reach the canonical native backend. This check | ||
| // runs against the FINAL route so native-only fallback can rescue a routed primary. | ||
| if (!isCanonicalOpenAiForwardProvider(route.provider) && unreadableEncryptedAgentTask) { | ||
| // Encrypted child tasks may reach the canonical native backend or an explicitly trusted | ||
| // direct Responses route. This runs against the FINAL route so native-only fallback can | ||
| // rescue an incompatible primary without weakening combo behavior. | ||
| const finalRouteCanPassThroughEncryptedTask = !options.comboAttempt | ||
| && canPassThroughEncryptedV2AgentTask(route, inboundWire); | ||
| if ( | ||
| !isCanonicalOpenAiForwardProvider(route.provider) | ||
| && !finalRouteCanPassThroughEncryptedTask | ||
| && unreadableEncryptedAgentTask | ||
| ) { | ||
| return unreadableEncryptedAgentTaskResponse(); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -172,6 +172,35 @@ describe("combo path encrypted agent task recovery", () => { | |||||||||||||||||||||
| expect(providerFetches).toBe(1); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| test("keeps an opted-in Responses target out of encrypted combo dispatch", async () => { | ||||||||||||||||||||||
| const config = comboConfig([ | ||||||||||||||||||||||
| { provider: "relay", model: "relay-model" }, | ||||||||||||||||||||||
| { provider: "openai", model: "gpt-5.5" }, | ||||||||||||||||||||||
| ]); | ||||||||||||||||||||||
| config.providers.relay = { | ||||||||||||||||||||||
| adapter: "openai-responses", | ||||||||||||||||||||||
| baseUrl: "https://relay.example.test/v1", | ||||||||||||||||||||||
| authMode: "key", | ||||||||||||||||||||||
| apiKey: "test-relay-key", | ||||||||||||||||||||||
| allowEncryptedV2AgentTasks: true, | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| const fetchedUrls: string[] = []; | ||||||||||||||||||||||
| const forwardedBodies: string[] = []; | ||||||||||||||||||||||
| globalThis.fetch = (async (input, init) => { | ||||||||||||||||||||||
| fetchedUrls.push(String(input)); | ||||||||||||||||||||||
| forwardedBodies.push(typeof init?.body === "string" ? init.body : ""); | ||||||||||||||||||||||
| return providerResponse(); | ||||||||||||||||||||||
| }) as typeof fetch; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const response = await post(config, "combo/routed", encryptedInput(), codexHeaders()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| expect(response.status).toBe(200); | ||||||||||||||||||||||
| expect(fetchedUrls).toEqual(["https://chatgpt.com/backend-api/codex/responses"]); | ||||||||||||||||||||||
| expect(forwardedBodies).toHaveLength(1); | ||||||||||||||||||||||
| expect(forwardedBodies[0]).toContain(FERNET_TASK); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Assert the exact encrypted payload field. Line [200] checks only that Proposed test assertion- expect(forwardedBodies[0]).toContain(FERNET_TASK);
+ const forwarded = JSON.parse(forwardedBodies[0]) as {
+ input?: Array<{
+ content?: Array<{ type?: string; encrypted_content?: unknown }>;
+ }>;
+ };
+ expect(
+ forwarded.input?.[0]?.content?.find(part => part.type === "encrypted_content")
+ ?.encrypted_content,
+ ).toBe(FERNET_TASK);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| expect(forwardedBodies[0]).not.toContain("capture_assignment"); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| test("keeps the canonical target bypass in a mixed combo without running recovery", async () => { | ||||||||||||||||||||||
| const forwardedBodies: string[] = []; | ||||||||||||||||||||||
| globalThis.fetch = (async (_input, init) => { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Document the final wire eligibility rule.
Line 111 describes only a direct
openai-responsesprovider. Runtime eligibility instead requires a key-auth route whose final resolved adapter isopenai-responses. Therefore, a model-levelopenai-responsesoverride can qualify, while a model-levelopenai-chatoverride must fail closed.State that this is an explicit custom-provider opt-in, the default is disabled, built-in presets do not enable it, existing provider fields must be preserved, and the user must reload or restart after editing the configuration.
As per coding guidelines, “Document current shipped or intentionally pending behavior.” As per path instructions, “Avoid implying that OAuth, Chat adapters, or model-level Chat overrides qualify.”
🤖 Prompt for AI Agents
Sources: Coding guidelines, Path instructions