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:
+1
@@ -17,6 +17,7 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE
|
||||
|
||||
return {
|
||||
summary: {
|
||||
invalidAgent: 0,
|
||||
invalidViewFilter: 0,
|
||||
invalidObjectMetadata: 0,
|
||||
invalidView: 0,
|
||||
|
||||
+36
@@ -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,
|
||||
},
|
||||
|
||||
+18
@@ -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;
|
||||
};
|
||||
+96
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
+193
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
};
|
||||
+40
@@ -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;
|
||||
};
|
||||
+47
@@ -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;
|
||||
};
|
||||
+3
@@ -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 {}
|
||||
|
||||
+3
@@ -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,
|
||||
],
|
||||
})
|
||||
|
||||
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
+56
@@ -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;
|
||||
}
|
||||
}
|
||||
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
+7
@@ -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 {}
|
||||
|
||||
+2
@@ -122,7 +122,9 @@ export class WorkspaceMigrationRunnerV2Service {
|
||||
this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'rolesPermissions',
|
||||
'userWorkspaceRoleMap',
|
||||
'flatRoleTargetMaps',
|
||||
'apiKeyRoleMap',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
+9
-19
@@ -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;
|
||||
|
||||
+7
-1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user