-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(responses): reject expired forward continuations #749
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
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 |
|---|---|---|
|
|
@@ -1144,10 +1144,6 @@ export async function handleResponses( | |
| } | ||
| if (parsed._compactionRequest === true) parsed._cursorIsolateConversation = true; | ||
|
|
||
| if (isThreadSpawnRequest(req.headers)) { | ||
| await maybePrimeSubagentQuota(config); | ||
| } | ||
|
|
||
| let route: RouteResult; | ||
| try { | ||
| route = routeModel(config, parsed.modelId); | ||
|
|
@@ -1158,6 +1154,17 @@ export async function handleResponses( | |
| return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err)); | ||
| } | ||
|
|
||
| const hasUnexpandedPreviousResponse = !!parsed.previousResponseId | ||
| && parsed._previousResponseInputExpanded !== true; | ||
| // A canonical replay miss must not poll quota upstream before the final fail-closed decision. | ||
| // Cached fallback state can still select a provider with native continuation support below. | ||
| if ( | ||
| isThreadSpawnRequest(req.headers) | ||
| && !(hasUnexpandedPreviousResponse && isCanonicalOpenAiForwardProvider(route.provider)) | ||
| ) { | ||
| await maybePrimeSubagentQuota(config); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| let authCtx: CodexAuthContext = { kind: "main", accountId: null }; | ||
| let selectedForwardHeaders = req.headers; | ||
| let subagentFallbackAccountId = config.activeCodexAccountId ?? null; | ||
|
|
@@ -1206,6 +1213,20 @@ export async function handleResponses( | |
| return unreadableEncryptedAgentTaskResponse(); | ||
| } | ||
|
|
||
| // The canonical ChatGPT backend rejects previous_response_id, so a local replay miss leaves no | ||
| // safe way to recover the omitted history. Fail before auth, adapter construction, or upstream | ||
| // I/O instead of stripping the id and silently forwarding a context-free delta (#702). | ||
| if ( | ||
| hasUnexpandedPreviousResponse | ||
| && isCanonicalOpenAiForwardProvider(route.provider) | ||
| ) { | ||
|
Comment on lines
+1220
to
+1222
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 custom or noncanonical Useful? React with 👍 / 👎. |
||
| return formatErrorResponse( | ||
|
Comment on lines
+1219
to
+1223
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 combo continuation's local replay has expired and the current target is canonical OpenAI, this local Useful? React with 👍 / 👎. |
||
| 400, | ||
| "invalid_request_error", | ||
| "OpenAI forward continuation state is unavailable or expired; start a new session instead of reusing this previous_response_id.", | ||
|
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.
This adds a user-visible terminal condition: after replay TTL expiry, eviction, or loss of the local snapshot, canonical continuations now return AGENTS.md reference: AGENTS.md:L152-L153 Useful? React with 👍 / 👎. |
||
| ); | ||
| } | ||
|
|
||
| await applyFinalRouteRequestNormalization({ parsed, route, config, req, logCtx }); | ||
|
|
||
| { | ||
|
|
||
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.
When a
thread_spawncontinuation was previously served by an API-key fallback, then local replay and quota caches become stale (for example after a proxy restart), this condition suppressesmaybePrimeSubagentQuota. Unknown quota is treated as available by fallback selection, so the request remains on the canonical route and is rejected below, even though priming would identify the exhausted primary and return the continuation to the API-key provider that owns its upstream response ID. Preserve quota-aware fallback selection, or otherwise evaluate continuation-capable fallbacks, before rejecting.Useful? React with 👍 / 👎.