Migrate agent v2 (#16214)

# Introduction
Closes https://github.com/twentyhq/core-team-issues/issues/1980

In this PR we migrate the agent from v1 to v2.

## New FlatRoleTargetByAgentIdMaps
Derivated the `flatRoleTargetMaps` to be building a
`flatRoleTargetByAgentIdMaps` to ease retrieving a roleId to associate
to an agent

## Coverage
Added strong coverage on both failing and successful CRU agents
operations

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Paul Rastoin
2025-12-03 11:51:19 +01:00
committed by GitHub
parent a7c5969ede
commit 59672e3e34
110 changed files with 4864 additions and 849 deletions
@@ -17,6 +17,7 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE
return {
summary: {
invalidAgent: 0,
invalidViewFilter: 0,
invalidObjectMetadata: 0,
invalidView: 0,
@@ -14,6 +14,7 @@ import {
WorkspaceMigrationOrchestratorSuccessfulResult,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report.util';
import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service';
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
@@ -44,6 +45,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationV2RoleTargetActionsBuilderService: WorkspaceMigrationV2RoleTargetActionsBuilderService,
private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService,
private readonly workspaceMigrationV2RoleActionsBuilderService: WorkspaceMigrationV2RoleActionsBuilderService,
private readonly workspaceMigrationV2AgentActionsBuilderService: WorkspaceMigrationV2AgentActionsBuilderService,
) {}
private setupOptimisticCache({
@@ -131,6 +133,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatViewGroupMaps,
flatRoleMaps,
flatRoleTargetMaps,
flatAgentMaps,
} = fromToAllFlatEntityMaps;
if (isDefined(flatObjectMetadataMaps)) {
@@ -578,6 +581,33 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatAgentMaps)) {
const { from: fromFlatAgentMaps, to: toFlatAgentMaps } = flatAgentMaps;
const agentResult =
this.workspaceMigrationV2AgentActionsBuilderService.validateAndBuild({
from: fromFlatAgentMaps,
to: toFlatAgentMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: undefined,
workspaceId,
});
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
{
allFlatEntityMaps: optimisticAllFlatEntityMaps,
flatEntityMapsAndRelatedFlatEntityMaps:
agentResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
},
);
if (agentResult.status === 'fail') {
orchestratorFailureReport.agent.push(...agentResult.errors);
} else {
orchestratorActionsReport.agent = agentResult.actions;
}
}
const allErrors = Object.values(orchestratorFailureReport);
if (allErrors.some((report) => report.length > 0)) {
@@ -666,6 +696,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.roleTarget.created,
...aggregatedOrchestratorActionsReport.roleTarget.updated,
///
//
...aggregatedOrchestratorActionsReport.agent.deleted,
...aggregatedOrchestratorActionsReport.agent.created,
...aggregatedOrchestratorActionsReport.agent.updated,
///
],
workspaceId,
},
@@ -0,0 +1,18 @@
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
export type UpdateAgentAction = {
type: 'update_agent';
flatEntityId: string;
flatEntityUpdates: FlatEntityPropertiesUpdates<'agent'>;
};
export type CreateAgentAction = {
type: 'create_agent';
flatEntity: FlatAgent;
};
export type DeleteAgentAction = {
type: 'delete_agent';
flatEntityId: string;
};
@@ -0,0 +1,96 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { UpdateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service';
import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type';
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service';
@Injectable()
export class WorkspaceMigrationV2AgentActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
typeof ALL_METADATA_NAME.agent
> {
constructor(
private readonly flatAgentValidatorService: FlatAgentValidatorService,
) {
super(ALL_METADATA_NAME.agent);
}
protected validateFlatEntityCreation(
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.agent>,
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.agent, 'created'> {
const validationResult =
this.flatAgentValidatorService.validateFlatAgentCreation(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatAgentToValidate } = args;
return {
status: 'success',
action: {
type: 'create_agent',
flatEntity: flatAgentToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.agent>,
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.agent, 'deleted'> {
const validationResult =
this.flatAgentValidatorService.validateFlatAgentDeletion(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatAgentToValidate } = args;
return {
status: 'success',
action: {
type: 'delete_agent',
flatEntityId: flatAgentToValidate.id,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<typeof ALL_METADATA_NAME.agent>,
): FlatEntityValidationReturnType<typeof ALL_METADATA_NAME.agent, 'updated'> {
const validationResult =
this.flatAgentValidatorService.validateFlatAgentUpdate(args);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityId, flatEntityUpdates } = args;
const updateAgentAction: UpdateAgentAction = {
type: 'update_agent',
flatEntityId,
flatEntityUpdates,
};
return {
status: 'success',
action: updateAgentAction,
};
}
}
@@ -0,0 +1,193 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { type 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 { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { validateAgentNameUniqueness } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-name-uniqueness.util';
import { validateAgentRequiredProperties } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-required-properties.util';
import { validateAgentResponseFormat } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-response-format.util';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class FlatAgentValidatorService {
public validateFlatAgentCreation({
flatEntityToValidate: flatAgent,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatAgentMaps: optimisticFlatAgentMaps,
},
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.agent
>): FailedFlatEntityValidation<FlatAgent> {
const validationResult: FailedFlatEntityValidation<FlatAgent> = {
type: 'create_agent',
errors: [],
flatEntityMinimalInformation: {
id: flatAgent.id,
name: flatAgent.name,
},
};
const existingAgents = Object.values(optimisticFlatAgentMaps.byId).filter(
isDefined,
);
validationResult.errors.push(
...validateAgentRequiredProperties({
flatAgent,
}),
);
validationResult.errors.push(
...validateAgentNameUniqueness({
name: flatAgent.name,
existingFlatAgents: existingAgents,
}),
);
if (isDefined(flatAgent.responseFormat)) {
validationResult.errors.push(
...validateAgentResponseFormat({
responseFormat: flatAgent.responseFormat,
}),
);
}
return validationResult;
}
public validateFlatAgentDeletion({
flatEntityToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatAgentMaps: optimisticFlatAgentMaps,
},
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.agent
>): FailedFlatEntityValidation<FlatAgent> {
const validationResult: FailedFlatEntityValidation<FlatAgent> = {
type: 'delete_agent',
errors: [],
flatEntityMinimalInformation: {
id: flatEntityToValidate.id,
name: flatEntityToValidate.name,
},
};
const existingAgent = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatEntityToValidate.id,
flatEntityMaps: optimisticFlatAgentMaps,
});
if (!isDefined(existingAgent)) {
validationResult.errors.push({
code: AgentExceptionCode.AGENT_NOT_FOUND,
message: t`Agent not found`,
userFriendlyMessage: msg`Agent not found`,
});
return validationResult;
}
if (
existingAgent.isCustom === false &&
isDefined(existingAgent.standardId)
) {
validationResult.errors.push({
code: AgentExceptionCode.AGENT_IS_STANDARD,
message: t`Cannot delete standard agent`,
userFriendlyMessage: msg`Cannot delete standard agent`,
});
}
return validationResult;
}
public validateFlatAgentUpdate({
flatEntityId,
flatEntityUpdates,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatAgentMaps: optimisticFlatAgentMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.agent
>): FailedFlatEntityValidation<FlatAgent> {
const validationResult: FailedFlatEntityValidation<FlatAgent> = {
type: 'update_agent',
errors: [],
flatEntityMinimalInformation: {
id: flatEntityId,
},
};
const fromFlatAgent = findFlatEntityByIdInFlatEntityMaps({
flatEntityId,
flatEntityMaps: optimisticFlatAgentMaps,
});
if (!isDefined(fromFlatAgent)) {
validationResult.errors.push({
code: AgentExceptionCode.AGENT_NOT_FOUND,
message: t`Agent not found`,
userFriendlyMessage: msg`Agent not found`,
});
return validationResult;
}
if (
fromFlatAgent.isCustom === false &&
isDefined(fromFlatAgent.standardId)
) {
validationResult.errors.push({
code: AgentExceptionCode.AGENT_IS_STANDARD,
message: t`Cannot update standard agent`,
userFriendlyMessage: msg`Cannot update standard agent`,
});
}
const partialFlatAgent: Partial<FlatAgent> =
fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
});
const optimisticFlatAgent: FlatAgent = {
...fromFlatAgent,
...partialFlatAgent,
};
const existingAgents = Object.values(optimisticFlatAgentMaps.byId)
.filter(isDefined)
.filter((agent) => agent.id !== flatEntityId);
validationResult.errors.push(
...validateAgentRequiredProperties({
flatAgent: optimisticFlatAgent,
}),
);
if (isDefined(partialFlatAgent.name)) {
validationResult.errors.push(
...validateAgentNameUniqueness({
name: partialFlatAgent.name,
existingFlatAgents: existingAgents,
}),
);
}
if (isDefined(partialFlatAgent.responseFormat)) {
validationResult.errors.push(
...validateAgentResponseFormat({
responseFormat: partialFlatAgent.responseFormat,
}),
);
}
return validationResult;
}
}
@@ -0,0 +1,25 @@
import { msg, t } from '@lingui/core/macro';
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export const validateAgentNameUniqueness = ({
name,
existingFlatAgents,
}: {
name: string;
existingFlatAgents: FlatAgent[];
}): FlatEntityValidationError<AgentExceptionCode>[] => {
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
if (existingFlatAgents.some((agent) => agent.name === name)) {
errors.push({
code: AgentExceptionCode.AGENT_ALREADY_EXISTS,
message: t`Agent with name "${name}" already exists`,
userFriendlyMessage: msg`An agent with this name already exists`,
});
}
return errors;
};
@@ -0,0 +1,40 @@
import { isNonEmptyString } from '@sniptt/guards';
import { msg, t } from '@lingui/core/macro';
import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export const validateAgentRequiredProperties = ({
flatAgent,
}: {
flatAgent: FlatAgent;
}): FlatEntityValidationError<AgentExceptionCode>[] => {
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
if (!isNonEmptyString(flatAgent.label)) {
errors.push({
code: AgentExceptionCode.INVALID_AGENT_INPUT,
message: t`Label cannot be empty`,
userFriendlyMessage: msg`Label cannot be empty`,
});
}
if (!isNonEmptyString(flatAgent.prompt)) {
errors.push({
code: AgentExceptionCode.INVALID_AGENT_INPUT,
message: t`Prompt cannot be empty`,
userFriendlyMessage: msg`Prompt cannot be empty`,
});
}
if (!isNonEmptyString(flatAgent.modelId)) {
errors.push({
code: AgentExceptionCode.INVALID_AGENT_INPUT,
message: t`Model ID cannot be empty`,
userFriendlyMessage: msg`Model ID cannot be empty`,
});
}
return errors;
};
@@ -0,0 +1,47 @@
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 {
type AgentResponseFormat,
type AgentJsonResponseFormat,
} from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type';
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export const validateAgentResponseFormat = ({
responseFormat,
}: {
responseFormat: AgentResponseFormat;
}): FlatEntityValidationError<AgentExceptionCode>[] => {
const errors: FlatEntityValidationError<AgentExceptionCode>[] = [];
const type = responseFormat.type;
if (type !== 'text' && type !== 'json') {
errors.push({
code: AgentExceptionCode.INVALID_AGENT_INPUT,
message: t`Response format type must be either "text" or "json"`,
userFriendlyMessage: msg`Invalid response format type`,
});
}
if (type === 'json' && !isDefined(responseFormat.schema)) {
errors.push({
code: AgentExceptionCode.INVALID_AGENT_INPUT,
message: t`Response format with type "json" must include a schema`,
userFriendlyMessage: msg`JSON response format requires a schema`,
});
}
if (
type === 'text' &&
isDefined((responseFormat as unknown as AgentJsonResponseFormat).schema)
) {
errors.push({
code: AgentExceptionCode.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`,
});
}
return errors;
};
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service';
import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service';
import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
@@ -33,6 +34,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatRouteTriggerValidatorService,
FlatRoleValidatorService,
FlatRoleTargetValidatorService,
FlatAgentValidatorService,
],
exports: [
FlatViewValidatorService,
@@ -49,6 +51,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatRouteTriggerValidatorService,
FlatRoleValidatorService,
FlatRoleTargetValidatorService,
FlatAgentValidatorService,
],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service';
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
@@ -34,6 +35,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2RouteTriggerActionsBuilderService,
WorkspaceMigrationV2RoleActionsBuilderService,
WorkspaceMigrationV2RoleTargetActionsBuilderService,
WorkspaceMigrationV2AgentActionsBuilderService,
],
exports: [
WorkspaceMigrationV2ViewActionsBuilderService,
@@ -49,6 +51,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2RouteTriggerActionsBuilderService,
WorkspaceMigrationV2RoleActionsBuilderService,
WorkspaceMigrationV2RoleTargetActionsBuilderService,
WorkspaceMigrationV2AgentActionsBuilderService,
FlatFieldMetadataTypeValidatorService,
],
})
@@ -0,0 +1,59 @@
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { CreateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_agent',
) {
constructor() {
super();
}
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateAgentAction>): Partial<AllFlatEntityMaps> {
const { flatAgentMaps } = allFlatEntityMaps;
const { flatEntity } = action;
const updatedFlatAgentMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps: flatAgentMaps,
});
return {
flatAgentMaps: updatedFlatAgentMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateAgentAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { flatEntity } = action;
const agentRepository =
queryRunner.manager.getRepository<AgentEntity>(AgentEntity);
await agentRepository.save({
...flatEntity,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateAgentAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,56 @@
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { DeleteAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_agent',
) {
constructor() {
super();
}
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteAgentAction>): Partial<AllFlatEntityMaps> {
const { flatAgentMaps } = allFlatEntityMaps;
const { flatEntityId } = action;
const updatedFlatAgentMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntityId,
flatEntityMaps: flatAgentMaps,
});
return {
flatAgentMaps: updatedFlatAgentMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteAgentAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { flatEntityId } = action;
const agentRepository =
queryRunner.manager.getRepository<AgentEntity>(AgentEntity);
await agentRepository.delete({ id: flatEntityId, workspaceId });
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteAgentAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,71 @@
import { Injectable } from '@nestjs/common';
import {
OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { UpdateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service';
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class UpdateAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_agent',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateAgentAction>): Partial<AllFlatEntityMaps> {
const { flatAgentMaps } = allFlatEntityMaps;
const { flatEntityId, flatEntityUpdates } = action;
const existingAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId,
flatEntityMaps: flatAgentMaps,
});
const updatedAgent = {
...existingAgent,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
}),
};
const updatedFlatAgentMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedAgent,
flatEntityMaps: flatAgentMaps,
});
return {
flatAgentMaps: updatedFlatAgentMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateAgentAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { flatEntityId, flatEntityUpdates } = action;
const agentRepository =
queryRunner.manager.getRepository<AgentEntity>(AgentEntity);
await agentRepository.update(
{ id: flatEntityId, workspaceId },
fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
}),
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateAgentAction>,
): Promise<void> {
return;
}
}
@@ -1,6 +1,9 @@
import { Module } from '@nestjs/common';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service';
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service';
import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service';
import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service';
import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service';
import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service';
@@ -93,6 +96,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
CreateRoleTargetActionHandlerService,
DeleteRoleTargetActionHandlerService,
UpdateRoleTargetActionHandlerService,
CreateAgentActionHandlerService,
UpdateAgentActionHandlerService,
DeleteAgentActionHandlerService,
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
@@ -122,7 +122,9 @@ export class WorkspaceMigrationRunnerV2Service {
this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'rolesPermissions',
'userWorkspaceRoleMap',
'flatRoleTargetMaps',
'apiKeyRoleMap',
'flatRoleTargetByAgentIdMaps',
]),
);
}
@@ -16,28 +16,18 @@ export class StandardAgentFactory {
): FlatAgent[] {
const computedAgents: FlatAgent[] = [];
for (const agentDefinition of agentDefinitions) {
const existingAgent = existingAgents.find(
(agent) => agent.standardId === agentDefinition.standardId,
for (const standardAgentDefinition of agentDefinitions) {
const existingAgentEntity = existingAgents.find(
(agent) => agent.standardId === standardAgentDefinition.standardId,
);
const flatAgent = transformStandardAgentDefinitionToFlatAgent(
agentDefinition,
context.workspaceId,
);
const flatAgent = transformStandardAgentDefinitionToFlatAgent({
standardAgentDefinition,
workspaceId: context.workspaceId,
existingAgentEntity,
});
if (existingAgent) {
computedAgents.push({
...flatAgent,
id: existingAgent.id,
universalIdentifier: agentDefinition.standardId,
});
} else {
computedAgents.push({
...flatAgent,
universalIdentifier: agentDefinition.standardId,
});
}
computedAgents.push(flatAgent);
}
return computedAgents;
@@ -4,7 +4,13 @@ export type AgentOutputStrategy = 'direct' | 'synthesize';
export type StandardAgentDefinition = Omit<
FlatAgent,
'id' | 'workspaceId' | 'universalIdentifier' | 'standardId'
| 'id'
| 'workspaceId'
| 'universalIdentifier'
| 'standardId'
| 'updatedAt'
| 'deletedAt'
| 'createdAt'
> & {
standardId: string;
standardRoleId?: string;