refactor(server): rename Agent exception to Ai; add THREAD_NOT_FOUND / MESSAGE_NOT_FOUND codes (fixes 500s) (#19831)
## Summary - The exception class under `ai-agent/` was serving every AI surface (agent, chat, role, models, generate-text), so `Agent` was a misnomer. Promoted to the `ai/` namespace; renamed `AgentException` → `AiException`, `AgentExceptionCode` → `AiExceptionCode`, and related interceptor / filter / handler / file names accordingly. - Split the single `AGENT_NOT_FOUND` code into entity-specific codes. Chat-thread lookups no longer reuse the agent identifier. - **Fixes Sentry 500s on `GetChatMessages` / `chatThread`.** Every "Thread not found" and "Queued message not found" throw site in ai-chat was previously wired to `AGENT_EXECUTION_FAILED`, which maps to `InternalServerError` (HTTP 500). They now use `THREAD_NOT_FOUND` / `MESSAGE_NOT_FOUND`, both of which map to `NotFoundError` (HTTP 404) in the GraphQL and REST handlers. The underlying cause of *why* clients are asking for threads that no longer resolve for them — per-user chat-thread create events being broadcast workspace-wide — is addressed separately in a follow-up PR. ### Code map - Added: `ai/ai.exception.ts`, `ai/utils/ai-graphql-api-exception-handler.util.ts` (+ spec with new THREAD/MESSAGE cases), `ai/interceptors/ai-graphql-api-exception.interceptor.ts`, `ai/filters/ai-api-exception.filter.ts` - Deleted: `ai/ai-agent/agent.exception.ts`, `ai/ai-agent/utils/agent-graphql-api-exception-handler.util.ts` (+ spec), `ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor.ts`, `ai/ai-agent/filters/agent-api-exception.filter.ts` - Updated: 21 call sites across ai-agent, ai-agent-execution, ai-agent-role, ai-chat, ai-generate-text, ai-models, role, and workspace-migration validators. ## Test plan - [x] `npx nx typecheck twenty-server` - [x] `npx jest ai-graphql-api-exception-handler` (3/3 including new THREAD_NOT_FOUND and MESSAGE_NOT_FOUND cases) - [x] `npx jest agent-role.service` (9/9) - [x] `npx oxlint --type-aware` on all changed files (0 warnings/errors) - [x] `npx prettier --check` on all changed files - [ ] CI
This commit is contained in:
+5
-5
@@ -4,7 +4,7 @@ import { msg, t } from '@lingui/core/macro';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AiExceptionCode } from 'src/engine/metadata-modules/ai/ai.exception';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { type UniversalFlatAgent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-agent.type';
|
||||
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
|
||||
@@ -89,7 +89,7 @@ export class FlatAgentValidatorService {
|
||||
|
||||
if (!isDefined(existingAgent)) {
|
||||
validationResult.errors.push({
|
||||
code: AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
code: AiExceptionCode.AGENT_NOT_FOUND,
|
||||
message: t`Agent not found`,
|
||||
userFriendlyMessage: msg`Agent not found`,
|
||||
});
|
||||
@@ -106,7 +106,7 @@ export class FlatAgentValidatorService {
|
||||
})
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: AgentExceptionCode.AGENT_IS_STANDARD,
|
||||
code: AiExceptionCode.AGENT_IS_STANDARD,
|
||||
message: t`Cannot delete standard agent`,
|
||||
userFriendlyMessage: msg`Cannot delete standard agent`,
|
||||
});
|
||||
@@ -140,7 +140,7 @@ export class FlatAgentValidatorService {
|
||||
|
||||
if (!isDefined(fromFlatAgent)) {
|
||||
validationResult.errors.push({
|
||||
code: AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
code: AiExceptionCode.AGENT_NOT_FOUND,
|
||||
message: t`Agent not found`,
|
||||
userFriendlyMessage: msg`Agent not found`,
|
||||
});
|
||||
@@ -157,7 +157,7 @@ export class FlatAgentValidatorService {
|
||||
})
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: AgentExceptionCode.AGENT_IS_STANDARD,
|
||||
code: AiExceptionCode.AGENT_IS_STANDARD,
|
||||
message: t`Cannot update standard agent`,
|
||||
userFriendlyMessage: msg`Cannot update standard agent`,
|
||||
});
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
|
||||
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AiExceptionCode } from 'src/engine/metadata-modules/ai/ai.exception';
|
||||
import { type UniversalFlatAgent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-agent.type';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
@@ -10,12 +10,12 @@ export const validateAgentNameUniqueness = ({
|
||||
}: {
|
||||
name: string;
|
||||
existingFlatAgents: UniversalFlatAgent[];
|
||||
}): FlatEntityValidationError<AgentExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
|
||||
}): FlatEntityValidationError<AiExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AiExceptionCode>[] = [];
|
||||
|
||||
if (existingFlatAgents.some((agent) => agent.name === name)) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.AGENT_ALREADY_EXISTS,
|
||||
code: AiExceptionCode.AGENT_ALREADY_EXISTS,
|
||||
message: t`Agent with name "${name}" already exists`,
|
||||
userFriendlyMessage: msg`An agent with this name already exists`,
|
||||
});
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AiExceptionCode } from 'src/engine/metadata-modules/ai/ai.exception';
|
||||
import { type UniversalFlatAgent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-agent.type';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
@@ -13,8 +13,8 @@ type ValidateAgentRequiredPropertiesArgs = {
|
||||
export const validateAgentRequiredProperties = ({
|
||||
flatAgent,
|
||||
updatedProperties,
|
||||
}: ValidateAgentRequiredPropertiesArgs): FlatEntityValidationError<AgentExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
|
||||
}: ValidateAgentRequiredPropertiesArgs): FlatEntityValidationError<AiExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AiExceptionCode>[] = [];
|
||||
|
||||
// For updates, only validate properties that are being changed
|
||||
const isUpdate = updatedProperties !== undefined;
|
||||
@@ -24,7 +24,7 @@ export const validateAgentRequiredProperties = ({
|
||||
|
||||
if (shouldValidateLabel && !isNonEmptyString(flatAgent.label)) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Label cannot be empty`,
|
||||
userFriendlyMessage: msg`Label cannot be empty`,
|
||||
});
|
||||
@@ -32,7 +32,7 @@ export const validateAgentRequiredProperties = ({
|
||||
|
||||
if (shouldValidatePrompt && !isNonEmptyString(flatAgent.prompt)) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Prompt cannot be empty`,
|
||||
userFriendlyMessage: msg`Prompt cannot be empty`,
|
||||
});
|
||||
@@ -40,7 +40,7 @@ export const validateAgentRequiredProperties = ({
|
||||
|
||||
if (shouldValidateModelId && !isNonEmptyString(flatAgent.modelId)) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Model ID cannot be empty`,
|
||||
userFriendlyMessage: msg`Model ID cannot be empty`,
|
||||
});
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
import { msg, t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { AiExceptionCode } from 'src/engine/metadata-modules/ai/ai.exception';
|
||||
import {
|
||||
type AgentJsonResponseFormat,
|
||||
type AgentResponseFormat,
|
||||
@@ -12,13 +12,13 @@ export const validateAgentResponseFormat = ({
|
||||
responseFormat,
|
||||
}: {
|
||||
responseFormat: AgentResponseFormat;
|
||||
}): FlatEntityValidationError<AgentExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
|
||||
}): FlatEntityValidationError<AiExceptionCode>[] => {
|
||||
const errors: FlatEntityValidationError<AiExceptionCode>[] = [];
|
||||
const type = responseFormat.type;
|
||||
|
||||
if (type !== 'text' && type !== 'json') {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Response format type must be either "text" or "json"`,
|
||||
userFriendlyMessage: msg`Invalid response format type`,
|
||||
});
|
||||
@@ -26,7 +26,7 @@ export const validateAgentResponseFormat = ({
|
||||
|
||||
if (type === 'json' && !isDefined(responseFormat.schema)) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Response format with type "json" must include a schema`,
|
||||
userFriendlyMessage: msg`JSON response format requires a schema`,
|
||||
});
|
||||
@@ -37,7 +37,7 @@ export const validateAgentResponseFormat = ({
|
||||
isDefined((responseFormat as unknown as AgentJsonResponseFormat).schema)
|
||||
) {
|
||||
errors.push({
|
||||
code: AgentExceptionCode.INVALID_AGENT_INPUT,
|
||||
code: AiExceptionCode.INVALID_AGENT_INPUT,
|
||||
message: t`Response format with type "text" should not include a schema`,
|
||||
userFriendlyMessage: msg`Text response format should not have a schema`,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user