Skip to content
220 changes: 205 additions & 15 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5611,6 +5611,37 @@ describe("CodexAdapterV2 post-settle continuation", () => {
notification: false,
expectedClass: "provider_error",
},
...([false, true] as const).map(
(notification) =>
({
name: `disconnected-${notification ? "notification" : "completion"}`,
code: "responseStreamDisconnected",
errorInfo: { responseStreamDisconnected: { httpStatusCode: null } },
notification,
expectedClass: "transport_error",
}) as const,
),
{
name: "connection-failed",
code: "responseStreamConnectionFailed",
errorInfo: { responseStreamConnectionFailed: { httpStatusCode: null } },
notification: false,
expectedClass: "transport_error",
},
{
name: "http-connection-failed",
code: "httpConnectionFailed",
errorInfo: { httpConnectionFailed: { httpStatusCode: null } },
notification: false,
expectedClass: "transport_error",
},
{
name: "http-provider-failure",
code: "httpConnectionFailed",
errorInfo: { httpConnectionFailed: { httpStatusCode: 503 } },
notification: false,
expectedClass: "provider_error",
},
{
name: "chatgpt-workspace-limit",
code: "other",
Expand Down Expand Up @@ -5651,7 +5682,15 @@ describe("CodexAdapterV2 post-settle continuation", () => {
errorInfo: { responseStreamDisconnected: { httpStatusCode: null } },
message: "Transport unavailable; this is not subscription_sharing_usage_limit_exceeded.",
notification: false,
expectedClass: "provider_error",
expectedClass: "transport_error",
},
{
name: "terminal-disconnection",
code: "responseStreamDisconnected",
notification: false,
terminalMessage: "Connection lost.",
terminalErrorInfo: { responseStreamDisconnected: { httpStatusCode: null } },
expectedClass: "transport_error",
},
{
name: "sharing-code-mention-retry",
Expand All @@ -5662,6 +5701,71 @@ describe("CodexAdapterV2 post-settle continuation", () => {
willRetry: true,
expectedClass: "transport_error",
},
...(
[
{
name: "generic-disconnection",
status: null,
terminalMessage: "Connection lost.",
expectedClass: "transport_error",
},
{
name: "generic-replacement",
status: null,
terminalMessage: "A different failure.",
expectedClass: "provider_error",
},
{
name: "generic-http-status",
status: 503,
terminalMessage: "Connection lost.",
expectedClass: "provider_error",
},
{
name: "generic-truncated-replacement",
status: null,
details: `${"Connection lost. ".repeat(300)}Network cause.`,
terminalMessage: `${"Connection lost. ".repeat(300)}Different cause.`,
expectedClass: "provider_error",
},
{
name: "generic-terminal-status",
status: null,
terminalMessage: "Connection lost.",
terminalErrorInfo: { responseStreamDisconnected: { httpStatusCode: 503 } },
expectedClass: "provider_error",
},
{
name: "generic-exhaustion",
status: null,
terminalMessage: "Connection lost.",
terminalErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: null } },
completionErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: null } },
expectedClass: "transport_error",
},
{
name: "generic-blank-details",
status: null,
details: " ",
terminalMessage: "Provider stopped this request.",
expectedClass: "transport_error",
},
{
name: "generic-exhaustion-status",
status: null,
terminalMessage: "Connection lost.",
terminalErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: 503 } },
completionErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: null } },
expectedClass: "provider_error",
},
] as const
).map((scenario) => ({
...scenario,
code: "responseStreamDisconnected" as const,
errorInfo: { responseStreamDisconnected: { httpStatusCode: scenario.status } },
notification: true,
details: "details" in scenario ? scenario.details : "Connection lost.",
})),
] as const)("classifies Codex terminal failures from $name evidence", (scenario) =>
Effect.scoped(
Effect.gen(function* () {
Expand Down Expand Up @@ -5754,7 +5858,10 @@ describe("CodexAdapterV2 post-settle continuation", () => {
params: {
threadId: nativeThreadId,
turnId: nativeTurnId,
willRetry: scenario.name === "retry" || "willRetry" in scenario,
willRetry:
scenario.name === "retry" ||
"willRetry" in scenario ||
"terminalMessage" in scenario,
error: {
message,
codexErrorInfo: errorInfo,
Expand All @@ -5770,6 +5877,27 @@ describe("CodexAdapterV2 post-settle continuation", () => {
},
]
: []),
...("terminalMessage" in scenario
? [
{
type: "emit_inbound" as const,
label: "error/terminal",
frame: {
method: "error",
params: {
threadId: nativeThreadId,
turnId: nativeTurnId,
willRetry: false,
error: {
message: scenario.terminalMessage,
codexErrorInfo:
"terminalErrorInfo" in scenario ? scenario.terminalErrorInfo : "other",
},
},
},
},
]
: []),
{
type: "emit_inbound",
label: "turn/completed",
Expand All @@ -5780,12 +5908,24 @@ describe("CodexAdapterV2 post-settle continuation", () => {
turn: {
...makeCodexReplayTurn({ id: nativeTurnId, status: "failed" }),
error: {
message: scenario.name === "replacement" ? "A different failure." : message,
...(scenario.notification &&
scenario.name !== "matching-details" &&
!("details" in scenario)
? {}
: { codexErrorInfo: errorInfo }),
message:
"terminalMessage" in scenario
? scenario.terminalMessage
: scenario.name === "replacement"
? "A different failure."
: message,
...("terminalMessage" in scenario
? {
codexErrorInfo:
"completionErrorInfo" in scenario
? scenario.completionErrorInfo
: "other",
}
: scenario.notification &&
scenario.name !== "matching-details" &&
!("details" in scenario)
? {}
: { codexErrorInfo: errorInfo }),
},
},
},
Expand Down Expand Up @@ -5905,35 +6045,78 @@ describe("CodexAdapterV2 post-settle continuation", () => {
// regression: #17501 Codex 429 retry exhaustion classifies as a usage limit
it.effect.each([
{
code: "responseTooManyFailedAttempts",
notificationStatus: 500,
completionStatus: 429,
willRetry: false,
expectedClass: "usage_limit",
},
{
code: "responseTooManyFailedAttempts",
notificationStatus: 429,
completionStatus: 500,
willRetry: true,
expectedClass: "provider_error",
},
{
code: "responseTooManyFailedAttempts",
notificationStatus: 429,
completionStatus: null,
willRetry: false,
expectedClass: "usage_limit",
},
{
code: "responseTooManyFailedAttempts",
notificationStatus: 429,
completionStatus: null,
willRetry: true,
expectedClass: "usage_limit",
},
...(
[
"responseStreamDisconnected",
"responseStreamConnectionFailed",
"httpConnectionFailed",
] as const
).flatMap(
(code) =>
[
{
code,
notificationStatus: null,
completionStatus: 503,
willRetry: false,
expectedClass: "provider_error",
},
{
code,
notificationStatus: 503,
completionStatus: null,
willRetry: false,
expectedClass: "transport_error",
},
] as const,
),
{
code: "responseStreamDisconnected",
notificationStatus: null,
completionStatus: 503,
willRetry: true,
expectedClass: "provider_error",
},
{
code: "responseStreamDisconnected",
notificationStatus: null,
completionStatus: null,
willRetry: false,
expectedClass: "transport_error",
},
] as const)(
"honors completion HTTP status from $notificationStatus to $completionStatus (retry: $willRetry)",
({ notificationStatus, completionStatus, expectedClass, willRetry }) =>
"honors $code completion HTTP status from $notificationStatus to $completionStatus (retry: $willRetry)",
({ code, notificationStatus, completionStatus, expectedClass, willRetry }) =>
Effect.scoped(
Effect.gen(function* () {
const name = `${notificationStatus}-${completionStatus}-${willRetry}`;
const name = `${code}-${notificationStatus}-${completionStatus}-${willRetry}`;
const nativeThreadId = `native-exhaustion-status-${name}`;
const nativeTurnId = `turn-exhaustion-status-${name}`;
const message = "Provider stopped this request.";
Expand All @@ -5954,7 +6137,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
error: {
message,
codexErrorInfo: {
responseTooManyFailedAttempts: { httpStatusCode: notificationStatus },
[code]: { httpStatusCode: notificationStatus },
},
additionalDetails,
},
Expand All @@ -5973,7 +6156,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
error: {
message,
codexErrorInfo: {
responseTooManyFailedAttempts: { httpStatusCode: completionStatus },
[code]: { httpStatusCode: completionStatus },
},
},
},
Expand All @@ -5997,9 +6180,16 @@ describe("CodexAdapterV2 post-settle continuation", () => {
assert.equal(terminal?.status, "failed");
if (terminal?.status !== "failed") return;
assert.equal(terminal.failure.class, expectedClass);
assert.equal(terminal.failure.code, "responseTooManyFailedAttempts");
assert.equal(terminal.failure.code, code);
assert.equal(terminal.threadDisposition, "reusable");
const notificationClass = notificationStatus === 429 ? "usage_limit" : "provider_error";
const notificationClass =
code === "responseTooManyFailedAttempts"
? notificationStatus === 429
? "usage_limit"
: "provider_error"
: notificationStatus === null
? "transport_error"
: "provider_error";
assert.equal(
terminal.failure.message,
expectedClass === notificationClass ? additionalDetails : message,
Expand Down
Loading
Loading