(
streamInfo: WorkspaceStreamInfo,
error: unknown
)
| 3081 | } |
| 3082 | |
| 3083 | private buildStreamErrorPayload( |
| 3084 | streamInfo: WorkspaceStreamInfo, |
| 3085 | error: unknown |
| 3086 | ): StreamErrorPayload & { errorType: StreamErrorType } { |
| 3087 | if (error instanceof EmptyStreamOutputError) { |
| 3088 | return { |
| 3089 | messageId: streamInfo.messageId, |
| 3090 | error: error.message, |
| 3091 | errorType: "empty_output", |
| 3092 | acpPromptId: streamInfo.initialMetadata?.acpPromptId, |
| 3093 | }; |
| 3094 | } |
| 3095 | |
| 3096 | if (error instanceof ModelRefusalError) { |
| 3097 | return { |
| 3098 | messageId: streamInfo.messageId, |
| 3099 | error: error.message, |
| 3100 | errorType: "model_refusal", |
| 3101 | acpPromptId: streamInfo.initialMetadata?.acpPromptId, |
| 3102 | }; |
| 3103 | } |
| 3104 | |
| 3105 | if (error instanceof StreamTruncatedError) { |
| 3106 | return { |
| 3107 | messageId: streamInfo.messageId, |
| 3108 | error: error.message, |
| 3109 | errorType: "stream_truncated", |
| 3110 | acpPromptId: streamInfo.initialMetadata?.acpPromptId, |
| 3111 | }; |
| 3112 | } |
| 3113 | |
| 3114 | // Extract error message (errors thrown from 'error' parts already have the correct message) |
| 3115 | // Apply prefix stripping to remove noisy "undefined: " prefixes from provider errors |
| 3116 | let errorMessage: string = stripNoisyErrorPrefix(getErrorMessage(error)); |
| 3117 | let actualError: unknown = error; |
| 3118 | |
| 3119 | // For categorization, use the cause if available (preserves the original error structure) |
| 3120 | if (error instanceof Error && error.cause) { |
| 3121 | actualError = error.cause; |
| 3122 | } |
| 3123 | |
| 3124 | let errorType = this.categorizeError(actualError); |
| 3125 | |
| 3126 | // Enhance previous-response and model-not-found error messages |
| 3127 | |
| 3128 | const previousResponseId = this.extractPreviousResponseIdFromError(actualError); |
| 3129 | if (previousResponseId) { |
| 3130 | errorMessage = "OpenAI lost the previous response state while streaming. Retry to continue."; |
| 3131 | } |
| 3132 | if (errorType === "model_not_found") { |
| 3133 | // Extract model name from model string (e.g., "anthropic:sonnet-1m" -> "sonnet-1m") |
| 3134 | const [, modelName] = streamInfo.model.split(":"); |
| 3135 | errorMessage = `Model '${modelName || streamInfo.model}' does not exist or is not available. Please check your model selection.`; |
| 3136 | } |
| 3137 | |
| 3138 | // Normalize Anthropic overload errors (HTTP 529 / overloaded_error) into a stable, |
| 3139 | // user-friendly message. Keep errorType = server_error so the frontend's auto-retry |
| 3140 | // behavior remains unchanged. |
no test coverage detected