| ... | ... | @@ -434,26 +434,9 @@ function resultErrorsText(result: SDKResultMessage): string { |
| 434 | 434 | : ""; |
| 435 | 435 | } |
| 436 | 436 | |
| 437 | | /** |
| 438 | | * First user-facing error from a non-success result. "[ede_diagnostic] ..." |
| 439 | | * entries are CLI-internal telemetry (the CLI hides them from its own UI too), |
| 440 | | * so they must never become the error banner. |
| 441 | | */ |
| 442 | | function resultUserFacingError(result: SDKResultMessage): string | undefined { |
| 443 | | const listed = |
| 444 | | result.subtype === "success" || !Array.isArray(result.errors) |
| 445 | | ? undefined |
| 446 | | : result.errors.find((error) => !error.startsWith("[ede_diagnostic]")); |
| 447 | | if (listed) { |
| 448 | | return listed; |
| 449 | | } |
| 450 | | // Structured failure markers for results whose error list is empty or |
| 451 | | // diagnostic-only: an overloaded API (529) and the terminal reasons the |
| 452 | | // CLI stamps when it gives up on a turn. |
| 453 | | if (isOverloadedResult(result)) { |
| 454 | | return "Claude API is overloaded (529). Try again shortly."; |
| 455 | | } |
| 456 | | switch (result.terminal_reason) { |
| 437 | /** Failure text for structured terminal reasons, including success-tagged failures. */ |
| 438 | function terminalResultError(reason: SDKResultMessage["terminal_reason"]): string | undefined { |
| 439 | switch (reason) { |
| 457 | 440 | case "api_error": |
| 458 | 441 | return "Claude gave up after repeated API errors."; |
| 459 | 442 | case "malformed_tool_use_exhausted": |
| ... | ... | @@ -1559,27 +1542,6 @@ const buildUserMessageEffect = Effect.fn("buildUserMessageEffect")(function* ( |
| 1559 | 1542 | return buildUserMessage({ sdkContent }); |
| 1560 | 1543 | }); |
| 1561 | 1544 | |
| 1562 | | /** |
| 1563 | | * terminal_reason values the CLI classifies as dead turns: the turn died |
| 1564 | | * rather than finished, even when the result subtype is success and the |
| 1565 | | * error list is empty. Kept in sync with the messages in |
| 1566 | | * resultUserFacingError. |
| 1567 | | */ |
| 1568 | | const FAILED_TERMINAL_REASONS: ReadonlySet<NonNullable<SDKResultMessage["terminal_reason"]>> = |
| 1569 | | new Set([ |
| 1570 | | "api_error", |
| 1571 | | "malformed_tool_use_exhausted", |
| 1572 | | "budget_exhausted", |
| 1573 | | "structured_output_retry_exhausted", |
| 1574 | | "tool_deferred_unavailable", |
| 1575 | | "turn_setup_failed", |
| 1576 | | "blocking_limit", |
| 1577 | | "rapid_refill_breaker", |
| 1578 | | "prompt_too_long", |
| 1579 | | "image_error", |
| 1580 | | "model_error", |
| 1581 | | ]); |
| 1582 | | |
| 1583 | 1545 | /** |
| 1584 | 1546 | * The CLI reports repeated 529 overload failures as a success-subtype result |
| 1585 | 1547 | * with api_error_status 529 and an empty error list; the status code is the |
| ... | ... | @@ -1589,25 +1551,27 @@ function isOverloadedResult(result: SDKResultMessage): boolean { |
| 1589 | 1551 | return result.subtype === "success" && result.api_error_status === 529; |
| 1590 | 1552 | } |
| 1591 | 1553 | |
| 1592 | | function turnStatusFromResult(result: SDKResultMessage): ProviderRuntimeTurnStatus { |
| 1593 | | if ( |
| 1594 | | isOverloadedResult(result) || |
| 1595 | | (result.terminal_reason !== undefined && FAILED_TERMINAL_REASONS.has(result.terminal_reason)) |
| 1596 | | ) { |
| 1597 | | return "failed"; |
| 1598 | | } |
| 1599 | | if (result.subtype === "success") { |
| 1600 | | return "completed"; |
| 1601 | | } |
| 1602 | | |
| 1603 | | const errors = resultErrorsText(result); |
| 1604 | | if (isInterruptedResult(result)) { |
| 1605 | | return "interrupted"; |
| 1606 | | } |
| 1607 | | if (errors.includes("cancel")) { |
| 1608 | | return "cancelled"; |
| 1609 | | } |
| 1610 | | return "failed"; |
| 1554 | /** Derives turn status and its error from the same provider result. */ |
| 1555 | function resultOutcome(result: SDKResultMessage): { |
| 1556 | status: ProviderRuntimeTurnStatus; |
| 1557 | errorMessage: string | undefined; |
| 1558 | } { |
| 1559 | const structuredError = isOverloadedResult(result) |
| 1560 | ? "Claude API is overloaded (529). Try again shortly." |
| 1561 | : terminalResultError(result.terminal_reason); |
| 1562 | // CLI diagnostic entries must not become the error banner. |
| 1563 | const listedError = |
| 1564 | result.subtype === "success" || !Array.isArray(result.errors) |
| 1565 | ? undefined |
| 1566 | : result.errors.find((error) => !error.startsWith("[ede_diagnostic]")); |
| 1567 | const errorMessage = listedError || structuredError; |
| 1568 | if (structuredError !== undefined) return { status: "failed", errorMessage }; |
| 1569 | if (result.subtype === "success") return { status: "completed", errorMessage }; |
| 1570 | if (isInterruptedResult(result)) return { status: "interrupted", errorMessage }; |
| 1571 | return { |
| 1572 | status: resultErrorsText(result).includes("cancel") ? "cancelled" : "failed", |
| 1573 | errorMessage, |
| 1574 | }; |
| 1611 | 1575 | } |
| 1612 | 1576 | |
| 1613 | 1577 | function streamKindFromDeltaType(deltaType: string): ClaudeTextStreamKind { |
| ... | ... | @@ -3268,8 +3232,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 3268 | 3232 | return; |
| 3269 | 3233 | } |
| 3270 | 3234 | |
| 3271 | | const status = turnStatusFromResult(message); |
| 3272 | | const errorMessage = resultUserFacingError(message); |
| 3235 | const { status, errorMessage } = resultOutcome(message); |
| 3273 | 3236 | |
| 3274 | 3237 | if (status === "failed") { |
| 3275 | 3238 | yield* emitRuntimeError(context, errorMessage ?? "Claude turn failed."); |