dfea3af778
## What & why Two related fixes that clean up Sentry reporting for the AI chat flow. ### 1. Enriched zero-output stream captures The AI chat stream's rejection handler previously skipped only `AbortError` and captured everything else to Sentry as-is. Two problems: - The SDK's bare `NoOutputGeneratedError` carries no troubleshooting context, so the Sentry issues were unactionable (no model, provider, workspace, or conversation size). - Expected interruptions (user abort, `STREAM_INTERRUPTED`) still generated noise. The rejection handler now handles three cases inline: - `AbortError` and `STREAM_INTERRUPTED` are expected interruptions and are not captured. - `NoOutputGeneratedError` is replaced with a single error whose message carries the full context as plain JSON: model, provider, workspace, thread, stream, turn, message count, conversation size, elapsed time, and the underlying stream error - recorded via a new `onError` handler, which also keeps stream-level errors visible in the worker logs. - Anything else is captured unchanged. The stable message prefix and single capture site keep zero-output events grouped separately from raw provider errors in Sentry. ### 2. Keep client-error domain exceptions out of Sentry `BILLING_CREDITS_EXHAUSTED` (a 402, i.e. an expected "user out of credits" condition) was landing in Sentry. Root cause: `CustomException` carries no HTTP status, so the worker/BullMQ path hands the raw exception to `shouldCaptureException`, which can't tell a 4xx client error from a 5xx server error and captures everything. The GraphQL/REST edges convert exceptions first, but background jobs bypass those converters. Fix, mirroring how `HttpException.getStatus()` already works: - `CustomException` gains an intrinsic `statusCode`. - `shouldCaptureException` skips a `CustomException` whose `statusCode < 500`, as a branch symmetric to the existing `HttpException` check. This covers every path, including the worker. - `BillingException` populates `statusCode` from the existing `getBillingExceptionStatusCode` mapping, so credits-exhausted (402) stays out of Sentry while the 500-mapped billing codes are still captured. Exceptions that don't set `statusCode` default to undefined and are captured exactly as before, so other domains are unaffected until they opt in. ## Tests - ai-chat unit suite passes (13 suites, 76 tests). - Existing billing exception handler tests pass. - `typecheck` passes. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23426?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
45 lines
1.1 KiB
TypeScript
45 lines
1.1 KiB
TypeScript
import { type MessageDescriptor } from '@lingui/core';
|
|
import { CustomError } from 'twenty-shared/utils';
|
|
|
|
const CommonExceptionCode = {
|
|
INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
|
|
} as const;
|
|
|
|
export const appendCommonExceptionCode = <
|
|
SpecificExceptionCode = Record<string, string>,
|
|
>(
|
|
specificExceptionCode: SpecificExceptionCode,
|
|
) => {
|
|
return {
|
|
...CommonExceptionCode,
|
|
...specificExceptionCode,
|
|
} as const;
|
|
};
|
|
|
|
export abstract class CustomException<
|
|
ExceptionCode extends string = string,
|
|
ExceptionMessage extends string = string,
|
|
> extends CustomError {
|
|
code: ExceptionCode;
|
|
userFriendlyMessage: MessageDescriptor;
|
|
statusCode?: number;
|
|
|
|
constructor(
|
|
message: ExceptionMessage,
|
|
code: ExceptionCode,
|
|
{
|
|
userFriendlyMessage,
|
|
}: { userFriendlyMessage: MessageDescriptor; statusCode?: number },
|
|
) {
|
|
super(message);
|
|
this.code = code;
|
|
this.userFriendlyMessage = userFriendlyMessage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exception class for test scenarios and edge cases.
|
|
* Prefer domain-specific exceptions in production code.
|
|
*/
|
|
export class UnknownException extends CustomException {}
|