-
Notifications
You must be signed in to change notification settings - Fork 890
fix(responses): normalize SSE terminal tails and policy failures #2488
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
base: dev
Are you sure you want to change the base?
Changes from all commits
57f2d4d
9a350cb
097663a
d8d3141
7dd669e
bf1b280
b77b4da
9de0d56
0849af3
574b77a
7bb99b4
4575a37
13fa29e
117ea95
963af13
2fb77a1
a6cfb31
c8c640e
30295ad
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 | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,15 @@ import type { | |||||||||||||||||||||||||||||||||||||
| OcxUsage, | ||||||||||||||||||||||||||||||||||||||
| } from "./types"; | ||||||||||||||||||||||||||||||||||||||
| import { coerceIntegerToolArguments } from "./lib/tool-argument-integers"; | ||||||||||||||||||||||||||||||||||||||
| import { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors"; | ||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||
| adapterFailureFromMessage, | ||||||||||||||||||||||||||||||||||||||
| classifyError, | ||||||||||||||||||||||||||||||||||||||
| cyberPolicyErrorType, | ||||||||||||||||||||||||||||||||||||||
| CYBER_POLICY_ERROR_CODE, | ||||||||||||||||||||||||||||||||||||||
| isCyberPolicyCode, | ||||||||||||||||||||||||||||||||||||||
| type OcxErrorPayload, | ||||||||||||||||||||||||||||||||||||||
| } from "./lib/errors"; | ||||||||||||||||||||||||||||||||||||||
| import { redactSecretString } from "./lib/redact"; | ||||||||||||||||||||||||||||||||||||||
| import { repairFreeformToolInput } from "./responses/apply-patch-envelope"; | ||||||||||||||||||||||||||||||||||||||
| import { encodeCompactionSummary } from "./responses/compaction"; | ||||||||||||||||||||||||||||||||||||||
| import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason"; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -114,18 +122,19 @@ function toolCallArgumentsUsable(args: string): boolean { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } { | ||||||||||||||||||||||||||||||||||||||
| const message = redactSecretString(event.message); | ||||||||||||||||||||||||||||||||||||||
| if (event.status === undefined && event.errorType === undefined && event.code === undefined) { | ||||||||||||||||||||||||||||||||||||||
| return adapterFailureFromMessage(event.message); | ||||||||||||||||||||||||||||||||||||||
| return adapterFailureFromMessage(message); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const fallback = adapterFailureFromMessage(event.message); | ||||||||||||||||||||||||||||||||||||||
| const fallback = adapterFailureFromMessage(message); | ||||||||||||||||||||||||||||||||||||||
| let httpStatus = event.status ?? fallback.httpStatus; | ||||||||||||||||||||||||||||||||||||||
| const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, event.message); | ||||||||||||||||||||||||||||||||||||||
| const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message); | ||||||||||||||||||||||||||||||||||||||
| if (event.errorType !== undefined) error.type = event.errorType; | ||||||||||||||||||||||||||||||||||||||
| if (event.code !== undefined) error.code = event.code; | ||||||||||||||||||||||||||||||||||||||
| // Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502. | ||||||||||||||||||||||||||||||||||||||
| if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) { | ||||||||||||||||||||||||||||||||||||||
| error.code = CYBER_POLICY_ERROR_CODE; | ||||||||||||||||||||||||||||||||||||||
| error.type = "invalid_request_error"; | ||||||||||||||||||||||||||||||||||||||
| error.type = cyberPolicyErrorType(event.errorType); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+131
to
+137
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve the classified policy result before assigning Line 133 can replace Capture the policy result immediately after Proposed fix const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message);
+ const cyberPolicy = error.code === CYBER_POLICY_ERROR_CODE
+ || event.errorType === CYBER_POLICY_ERROR_CODE
+ || isCyberPolicyCode(event.code);
if (event.errorType !== undefined) error.type = event.errorType;
if (event.code !== undefined) error.code = event.code;
- if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) {
+ if (cyberPolicy) {
error.code = CYBER_POLICY_ERROR_CODE;
error.type = cyberPolicyErrorType(event.errorType);
httpStatus = 400;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| httpStatus = 400; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return { httpStatus, error }; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1296,7 +1305,9 @@ export function bridgeToResponsesSSE( | |||||||||||||||||||||||||||||||||||||
| ...(event.usage ? { usage: responsesUsage(event.usage) } : {}), | ||||||||||||||||||||||||||||||||||||||
| error: failure.error, | ||||||||||||||||||||||||||||||||||||||
| last_error: failure.error, | ||||||||||||||||||||||||||||||||||||||
| ...(event.retryable !== undefined ? { retryable: event.retryable } : {}), | ||||||||||||||||||||||||||||||||||||||
| ...(isCyberPolicyCode(failure.error.code) | ||||||||||||||||||||||||||||||||||||||
| ? { retryable: false } | ||||||||||||||||||||||||||||||||||||||
| : event.retryable !== undefined ? { retryable: event.retryable } : {}), | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| reportTerminal("failed"); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1320,11 +1331,17 @@ export function bridgeToResponsesSSE( | |||||||||||||||||||||||||||||||||||||
| if (currentToolCall) failCurrentToolCall(); | ||||||||||||||||||||||||||||||||||||||
| if (currentWebSearch) closeCurrentWebSearch("failed", []); | ||||||||||||||||||||||||||||||||||||||
| releasePendingWebSources(); | ||||||||||||||||||||||||||||||||||||||
| const failure = responseError( | ||||||||||||||||||||||||||||||||||||||
| 500, | ||||||||||||||||||||||||||||||||||||||
| "proxy_error", | ||||||||||||||||||||||||||||||||||||||
| redactSecretString(err instanceof Error ? err.message : String(err)), | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| emit("response.failed", { | ||||||||||||||||||||||||||||||||||||||
| response: { | ||||||||||||||||||||||||||||||||||||||
| ...responseSnapshot("failed", finishedItems), | ||||||||||||||||||||||||||||||||||||||
| error: responseError(500, "proxy_error", err instanceof Error ? err.message : String(err)), | ||||||||||||||||||||||||||||||||||||||
| last_error: responseError(500, "proxy_error", err instanceof Error ? err.message : String(err)), | ||||||||||||||||||||||||||||||||||||||
| error: failure, | ||||||||||||||||||||||||||||||||||||||
| last_error: failure, | ||||||||||||||||||||||||||||||||||||||
| ...(isCyberPolicyCode(failure.code) ? { retryable: false } : {}), | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| reportTerminal("failed"); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1952,7 +1969,9 @@ function buildResponseJSONWithBudget( | |||||||||||||||||||||||||||||||||||||
| model: modelId, output, | ||||||||||||||||||||||||||||||||||||||
| ...(endTurn !== undefined ? { end_turn: endTurn } : {}), | ||||||||||||||||||||||||||||||||||||||
| ...(failure ? { error: failure.error, last_error: failure.error } : {}), | ||||||||||||||||||||||||||||||||||||||
| ...(errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}), | ||||||||||||||||||||||||||||||||||||||
| ...(failure && isCyberPolicyCode(failure.error.code) | ||||||||||||||||||||||||||||||||||||||
| ? { retryable: false } | ||||||||||||||||||||||||||||||||||||||
| : errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}), | ||||||||||||||||||||||||||||||||||||||
| ...(incompleteEvent ? { | ||||||||||||||||||||||||||||||||||||||
| incomplete_details: { | ||||||||||||||||||||||||||||||||||||||
| reason: incompleteEvent.reason, | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -1981,12 +2000,15 @@ export function formatErrorResponse( | |||||||||||||||||||||||||||||||||||||
| const error = classifyError(status, type, message); | ||||||||||||||||||||||||||||||||||||||
| if (isCyberPolicyCode(options?.code)) { | ||||||||||||||||||||||||||||||||||||||
| error.code = CYBER_POLICY_ERROR_CODE; | ||||||||||||||||||||||||||||||||||||||
| error.type = "invalid_request_error"; | ||||||||||||||||||||||||||||||||||||||
| error.type = cyberPolicyErrorType(type); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| const finalStatus = error.code === CYBER_POLICY_ERROR_CODE ? 400 : status; | ||||||||||||||||||||||||||||||||||||||
| const headers = new Headers({ "Content-Type": "application/json" }); | ||||||||||||||||||||||||||||||||||||||
| const retryAfter = options?.retryAfter?.trim(); | ||||||||||||||||||||||||||||||||||||||
| if (retryAfter && retryAfter.length > 0 && retryAfter.length <= 128) { | ||||||||||||||||||||||||||||||||||||||
| if (error.code !== CYBER_POLICY_ERROR_CODE | ||||||||||||||||||||||||||||||||||||||
| && retryAfter | ||||||||||||||||||||||||||||||||||||||
| && retryAfter.length > 0 | ||||||||||||||||||||||||||||||||||||||
| && retryAfter.length <= 128) { | ||||||||||||||||||||||||||||||||||||||
| headers.set("Retry-After", retryAfter); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return new Response(JSON.stringify({ error }), { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the open-issue links from the reference page.
Lines 86-88 state that this boundary "does not resolve" issues
#2423and#2486and link both.Two problems follow. First, a reference page documents current API behavior; a statement about which tracker items remain unresolved is release-note or PR context, not API contract. A reader looking up Responses terminal semantics gains nothing from it. Second, the text goes stale the moment either issue closes, and nothing ties this page to those issues, so no one will update it. The page then tells readers that a fixed problem is still open.
The four translated pages confirm the mismatch.
docs-site/src/content/docs/ja/reference/proxy-formats.mdLine 59,ko/...Line 69,ru/...Line 73, andzh-cn/...Line 68 all end the equivalent note at the no-retry sentence and carry no issue links. Keeping the links means either translating a stale-by-design sentence into four locales or leaving the English source permanently divergent.Keep the behavioral sentence and drop the tracker references.
📝 Proposed fix
As per path instructions for
docs-site/**: "Check that user-facing docs stay in sync with actual CLI/API behavior and that translated locale pages (ja, ko, ru, zh-cn) are not left contradicting the English source."📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Path instructions