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:
@@ -9,4 +9,7 @@ export enum AgentExceptionCode {
|
||||
USER_WORKSPACE_ID_NOT_FOUND = 'USER_WORKSPACE_ID_NOT_FOUND',
|
||||
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
|
||||
ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS = 'ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS',
|
||||
INVALID_AGENT_INPUT = 'INVALID_AGENT_INPUT',
|
||||
AGENT_ALREADY_EXISTS = 'AGENT_ALREADY_EXISTS',
|
||||
AGENT_IS_STANDARD = 'AGENT_IS_STANDARD',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
|
||||
import { AgentService } from './agent.service';
|
||||
|
||||
@@ -18,20 +20,30 @@ import { AgentIdInput } from './dtos/agent-id.input';
|
||||
import { AgentDTO } from './dtos/agent.dto';
|
||||
import { CreateAgentInput } from './dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from './dtos/update-agent.input';
|
||||
import { AgentGraphqlApiExceptionInterceptor } from './interceptors/agent-graphql-api-exception.interceptor';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
FeatureFlagGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.AI),
|
||||
)
|
||||
@UseInterceptors(
|
||||
WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor,
|
||||
AgentGraphqlApiExceptionInterceptor,
|
||||
)
|
||||
@Resolver()
|
||||
export class AgentResolver {
|
||||
constructor(private readonly agentService: AgentService) {}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findManyAgents(@AuthWorkspace() { id: workspaceId }: WorkspaceEntity) {
|
||||
return this.agentService.findManyAgents(workspaceId);
|
||||
async findManyAgents(
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<AgentDTO[]> {
|
||||
const flatAgentsWithRoleId =
|
||||
await this.agentService.findManyAgents(workspaceId);
|
||||
|
||||
return flatAgentsWithRoleId.map(fromFlatAgentWithRoleIdToAgentDto);
|
||||
}
|
||||
|
||||
@Query(() => AgentDTO)
|
||||
@@ -39,8 +51,13 @@ export class AgentResolver {
|
||||
async findOneAgent(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
return this.agentService.findOneAgent(workspaceId, { id });
|
||||
): Promise<AgentDTO> {
|
||||
const fatAgentWithRoleId = await this.agentService.findOneAgentById({
|
||||
workspaceId,
|
||||
id,
|
||||
});
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(fatAgentWithRoleId);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@@ -49,11 +66,13 @@ export class AgentResolver {
|
||||
async createOneAgent(
|
||||
@Args('input') input: CreateAgentInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
return this.agentService.createOneAgent(
|
||||
): Promise<AgentDTO> {
|
||||
const createdAgent = await this.agentService.createOneAgent(
|
||||
{ ...input, isCustom: true },
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(createdAgent);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@@ -62,8 +81,13 @@ export class AgentResolver {
|
||||
async updateOneAgent(
|
||||
@Args('input') input: UpdateAgentInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
return this.agentService.updateOneAgent(input, workspaceId);
|
||||
): Promise<AgentDTO> {
|
||||
const updatedAgent = await this.agentService.updateOneAgent({
|
||||
input,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(updatedAgent);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@@ -72,7 +96,12 @@ export class AgentResolver {
|
||||
async deleteOneAgent(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
return this.agentService.deleteOneAgent(id, workspaceId);
|
||||
): Promise<AgentDTO> {
|
||||
const deletedFlatAgent = await this.agentService.deleteOneAgent(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return fromFlatAgentWithRoleIdToAgentDto(deletedFlatAgent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
|
||||
import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { fromCreateAgentInputToFlatAgent } from 'src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util';
|
||||
import { fromUpdateAgentInputToFlatAgentToUpdate } from 'src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util';
|
||||
import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
|
||||
@@ -19,110 +26,43 @@ export class AgentService {
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
private readonly agentRoleService: AiAgentRoleService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {}
|
||||
|
||||
async findManyAgents(workspaceId: string) {
|
||||
const agents = await this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
async findManyAgents(workspaceId: string): Promise<FlatAgentWithRoleId[]> {
|
||||
const { flatAgentMaps, flatRoleTargetByAgentIdMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]);
|
||||
|
||||
if (agents.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return Object.values(flatAgentMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((flatAgent) => {
|
||||
const roleId = flatRoleTargetByAgentIdMaps[flatAgent.id]?.roleId;
|
||||
|
||||
const agentRoleMap = await this.buildAgentRoleMap(workspaceId, agents);
|
||||
|
||||
return agents.map((agent) => ({
|
||||
...agent,
|
||||
roleId: agentRoleMap.get(agent.id) || null,
|
||||
}));
|
||||
return {
|
||||
...flatAgent,
|
||||
roleId: roleId ?? null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async buildAgentRoleMap(
|
||||
workspaceId: string,
|
||||
agents: AgentEntity[],
|
||||
): Promise<Map<string, string>> {
|
||||
const roleTargets = await this.roleTargetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
agentId: In(agents.map((agent) => agent.id)),
|
||||
},
|
||||
});
|
||||
|
||||
const agentRoleMap = new Map<string, string>();
|
||||
|
||||
roleTargets.forEach((roleTarget) => {
|
||||
if (roleTarget.agentId) {
|
||||
agentRoleMap.set(roleTarget.agentId, roleTarget.roleId);
|
||||
}
|
||||
});
|
||||
|
||||
return agentRoleMap;
|
||||
}
|
||||
|
||||
async findOneByApplicationAndStandardId({
|
||||
applicationId,
|
||||
standardId,
|
||||
async findOneAgentByName({
|
||||
name,
|
||||
workspaceId,
|
||||
}: {
|
||||
applicationId: string;
|
||||
standardId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
return await this.agentRepository.findOne({
|
||||
where: { applicationId, standardId, workspaceId },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneAgent(
|
||||
workspaceId: string,
|
||||
{ id, name }: { id?: string; name?: string },
|
||||
) {
|
||||
this.validateAgentIdentifier(id, name);
|
||||
|
||||
const agent = await this.fetchAgent(workspaceId, id, name);
|
||||
const roleId = await this.fetchAgentRoleId(workspaceId, agent.id);
|
||||
|
||||
return {
|
||||
...agent,
|
||||
roleId,
|
||||
};
|
||||
}
|
||||
|
||||
private validateAgentIdentifier(
|
||||
id: string | undefined,
|
||||
name: string | undefined,
|
||||
): void {
|
||||
if (!isNonEmptyString(id) && !isNonEmptyString(name)) {
|
||||
throw new AgentException(
|
||||
'Either id or name must be provided',
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (isNonEmptyString(id) && isNonEmptyString(name)) {
|
||||
throw new AgentException(
|
||||
'Cannot specify both id and name',
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchAgent(
|
||||
workspaceId: string,
|
||||
id: string | undefined,
|
||||
name: string | undefined,
|
||||
): Promise<AgentEntity> {
|
||||
name: string;
|
||||
}): Promise<AgentEntity> {
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: id ? { id, workspaceId } : { name, workspaceId },
|
||||
where: { name, workspaceId },
|
||||
});
|
||||
|
||||
if (!agent) {
|
||||
const identifier = id ? `id "${id}"` : `name "${name}"`;
|
||||
const identifier = `name "${name}"`;
|
||||
|
||||
throw new AgentException(
|
||||
`Agent with ${identifier} not found`,
|
||||
@@ -133,125 +73,267 @@ export class AgentService {
|
||||
return agent;
|
||||
}
|
||||
|
||||
private async fetchAgentRoleId(
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
): Promise<string | null> {
|
||||
const roleTarget = await this.roleTargetRepository.findOne({
|
||||
where: {
|
||||
agentId,
|
||||
workspaceId,
|
||||
},
|
||||
select: ['roleId'],
|
||||
async findOneAgentById({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
}): Promise<FlatAgentWithRoleId> {
|
||||
const { flatAgentMaps, flatRoleTargetByAgentIdMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]);
|
||||
|
||||
const flatAgent = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatAgentMaps,
|
||||
});
|
||||
|
||||
return roleTarget?.roleId || null;
|
||||
if (!isDefined(flatAgent)) {
|
||||
throw new AgentException(
|
||||
`Agent not found`,
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const roleId = flatRoleTargetByAgentIdMaps[flatAgent.id]?.roleId;
|
||||
|
||||
return { ...flatAgent, roleId: roleId ?? null };
|
||||
}
|
||||
|
||||
async createOneAgent(
|
||||
input: CreateAgentInput & { isCustom: boolean },
|
||||
workspaceId: string,
|
||||
) {
|
||||
const agent = this.buildNewAgent(input, workspaceId);
|
||||
const createdAgent = await this.agentRepository.save(agent);
|
||||
): Promise<FlatAgentWithRoleId> {
|
||||
const {
|
||||
flatAgentMaps: existingFlatAgentMaps,
|
||||
flatRoleTargetMaps: existingFlatRoleTargetMaps,
|
||||
flatRoleMaps: existingFlatRoleMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetMaps',
|
||||
'flatRoleMaps',
|
||||
]);
|
||||
|
||||
if (isNonEmptyString(input.roleId)) {
|
||||
await this.assignRoleToNewAgent(
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const { flatAgentToCreate, flatRoleTargetToCreate } =
|
||||
fromCreateAgentInputToFlatAgent({
|
||||
createAgentInput: {
|
||||
...input,
|
||||
applicationId:
|
||||
input.applicationId ?? workspaceCustomFlatApplication.id,
|
||||
},
|
||||
workspaceId,
|
||||
createdAgent.id,
|
||||
input.roleId,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatAgentMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatAgentMaps,
|
||||
flatEntityToCreate: [flatAgentToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
}),
|
||||
flatRoleTargetMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatRoleTargetMaps,
|
||||
flatEntityToCreate: isDefined(flatRoleTargetToCreate)
|
||||
? [flatRoleTargetToCreate]
|
||||
: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
}),
|
||||
},
|
||||
dependencyAllFlatEntityMaps: { flatRoleMaps: existingFlatRoleMaps },
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating agent',
|
||||
);
|
||||
}
|
||||
|
||||
return this.findOneAgent(workspaceId, { id: createdAgent.id });
|
||||
}
|
||||
const { flatAgentMaps: recomputedFlatAgentMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
]);
|
||||
|
||||
private buildNewAgent(
|
||||
input: CreateAgentInput & { isCustom: boolean },
|
||||
workspaceId: string,
|
||||
): AgentEntity {
|
||||
return this.agentRepository.create({
|
||||
...input,
|
||||
name: isNonEmptyString(input.name)
|
||||
? input.name
|
||||
: computeMetadataNameFromLabel(input.label),
|
||||
workspaceId,
|
||||
isCustom: input.isCustom,
|
||||
const createdAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatAgentToCreate.id,
|
||||
flatEntityMaps: recomputedFlatAgentMaps,
|
||||
});
|
||||
}
|
||||
|
||||
private async assignRoleToNewAgent(
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
roleId: string,
|
||||
): Promise<void> {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId,
|
||||
roleId,
|
||||
});
|
||||
}
|
||||
|
||||
async updateOneAgent(input: UpdateAgentInput, workspaceId: string) {
|
||||
const agent = await this.findOneAgent(workspaceId, { id: input.id });
|
||||
const updateData = this.buildUpdateData(agent, input);
|
||||
const updatedAgent = await this.agentRepository.save(updateData);
|
||||
|
||||
if (!('roleId' in input)) {
|
||||
return updatedAgent;
|
||||
}
|
||||
|
||||
await this.updateAgentRole(workspaceId, agent.id, input.roleId);
|
||||
|
||||
return this.findOneAgent(workspaceId, { id: updatedAgent.id });
|
||||
}
|
||||
|
||||
private buildUpdateData(
|
||||
agent: AgentEntity & { roleId: string | null },
|
||||
input: UpdateAgentInput,
|
||||
): Partial<AgentEntity> {
|
||||
const updateData: Partial<AgentEntity> = {
|
||||
...agent,
|
||||
...Object.fromEntries(
|
||||
Object.entries(input).filter(([_, value]) => value !== undefined),
|
||||
),
|
||||
return {
|
||||
...createdAgent,
|
||||
roleId: flatRoleTargetToCreate?.roleId ?? null,
|
||||
};
|
||||
|
||||
if (input.label !== undefined) {
|
||||
updateData.name = computeMetadataNameFromLabel(input.label);
|
||||
} else if (input.name !== undefined) {
|
||||
updateData.name = input.name;
|
||||
}
|
||||
|
||||
return updateData;
|
||||
}
|
||||
|
||||
private async updateAgentRole(
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
roleId: string | null | undefined,
|
||||
): Promise<void> {
|
||||
if (isNonEmptyString(roleId)) {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId,
|
||||
roleId,
|
||||
});
|
||||
async updateOneAgent({
|
||||
input,
|
||||
workspaceId,
|
||||
}: {
|
||||
input: UpdateAgentInput;
|
||||
workspaceId: string;
|
||||
}): Promise<FlatAgentWithRoleId> {
|
||||
const {
|
||||
flatAgentMaps: existingFlatAgentMaps,
|
||||
flatRoleTargetMaps: existingFlatRoleTargetMaps,
|
||||
flatRoleMaps: existingFlatRoleMaps,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetMaps',
|
||||
'flatRoleMaps',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.agentRoleService.removeRoleFromAgent({
|
||||
workspaceId,
|
||||
agentId,
|
||||
const {
|
||||
flatAgentToUpdate,
|
||||
flatRoleTargetToCreate,
|
||||
flatRoleTargetToDelete,
|
||||
flatRoleTargetToUpdate,
|
||||
} = fromUpdateAgentInputToFlatAgentToUpdate({
|
||||
updateAgentInput: input,
|
||||
flatAgentMaps: existingFlatAgentMaps,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatAgentMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatAgentMaps,
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatAgentToUpdate],
|
||||
}),
|
||||
flatRoleTargetMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatRoleTargetMaps,
|
||||
flatEntityToCreate: isDefined(flatRoleTargetToCreate)
|
||||
? [flatRoleTargetToCreate]
|
||||
: [],
|
||||
flatEntityToDelete: isDefined(flatRoleTargetToDelete)
|
||||
? [flatRoleTargetToDelete]
|
||||
: [],
|
||||
flatEntityToUpdate: isDefined(flatRoleTargetToUpdate)
|
||||
? [flatRoleTargetToUpdate]
|
||||
: [],
|
||||
}),
|
||||
},
|
||||
dependencyAllFlatEntityMaps: { flatRoleMaps: existingFlatRoleMaps },
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: {
|
||||
roleTarget: isDefined(flatRoleTargetToDelete),
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating agent',
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
flatAgentMaps: recomputedFlatAgentMaps,
|
||||
flatRoleTargetByAgentIdMaps: recmputedFlatRoleTargetByAgentIdMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]);
|
||||
|
||||
const updatedAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: input.id,
|
||||
flatEntityMaps: recomputedFlatAgentMaps,
|
||||
});
|
||||
|
||||
const existingRoleTarget =
|
||||
recmputedFlatRoleTargetByAgentIdMaps[flatAgentToUpdate.id];
|
||||
|
||||
return {
|
||||
...updatedAgent,
|
||||
roleId: existingRoleTarget?.roleId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteOneAgent(id: string, workspaceId: string) {
|
||||
const agent = await this.findOneAgent(workspaceId, { id });
|
||||
async deleteOneAgent(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<FlatAgentWithRoleId> {
|
||||
const {
|
||||
flatAgentMaps: existingFlatAgentMaps,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatAgentMaps',
|
||||
'flatRoleTargetByAgentIdMaps',
|
||||
]);
|
||||
|
||||
await this.agentRepository.softDelete({ id: agent.id });
|
||||
const agentToDelete = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: existingFlatAgentMaps,
|
||||
});
|
||||
|
||||
return agent;
|
||||
if (!isDefined(agentToDelete)) {
|
||||
throw new AgentException(
|
||||
`Agent not found`,
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const roleId = flatRoleTargetByAgentIdMaps[agentToDelete.id]?.roleId;
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatAgentMaps: computeFlatEntityMapsFromTo({
|
||||
flatEntityMaps: existingFlatAgentMaps,
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [agentToDelete],
|
||||
flatEntityToUpdate: [],
|
||||
}),
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: {
|
||||
agent: true,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting agent',
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...agentToDelete,
|
||||
roleId: roleId ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
@@ -10,18 +11,23 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module';
|
||||
import { AgentGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor';
|
||||
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
|
||||
import { AiChatRouterModule } from 'src/engine/metadata-modules/ai/ai-chat-router/ai-chat-router.module';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { AiToolsModule } from 'src/engine/metadata-modules/ai/ai-tools/ai-tools.module';
|
||||
import { FlatAgentModule } from 'src/engine/metadata-modules/flat-agent/flat-agent.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
import { WorkflowToolsModule } from 'src/modules/workflow/workflow-tools/workflow-tools.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
|
||||
import { AgentResolver } from './agent.resolver';
|
||||
import { AgentService } from './agent.service';
|
||||
@@ -55,6 +61,11 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi
|
||||
UserWorkspaceModule,
|
||||
UserRoleModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
ApplicationModule,
|
||||
FlatAgentModule,
|
||||
WorkspaceCacheModule,
|
||||
],
|
||||
providers: [
|
||||
AgentResolver,
|
||||
@@ -65,6 +76,8 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi
|
||||
AgentToolGeneratorService,
|
||||
AgentTitleGenerationService,
|
||||
AgentActorContextService,
|
||||
WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor,
|
||||
AgentGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
exports: [
|
||||
AgentService,
|
||||
|
||||
@@ -21,7 +21,7 @@ export class AgentDTO {
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
standardId: string | null;
|
||||
standardId?: string | null;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
@@ -37,7 +37,7 @@ export class AgentDTO {
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@@ -48,7 +48,7 @@ export class AgentDTO {
|
||||
modelId: ModelId;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
responseFormat: object;
|
||||
responseFormat?: object;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
@@ -72,7 +72,7 @@ export class AgentDTO {
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
modelConfiguration: ModelConfiguration;
|
||||
modelConfiguration?: ModelConfiguration;
|
||||
|
||||
@Field(() => [String])
|
||||
evaluationInputs: string[];
|
||||
|
||||
+17
-2
@@ -1,5 +1,6 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
@@ -7,12 +8,16 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { AgentResponseFormatJson } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator';
|
||||
import { AgentResponseFormatText } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentInput {
|
||||
@@ -51,10 +56,20 @@ export class CreateAgentInput {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Object, {
|
||||
keepDiscriminatorProperty: true,
|
||||
discriminator: {
|
||||
property: 'type',
|
||||
subTypes: [
|
||||
{ value: AgentResponseFormatText, name: 'text' },
|
||||
{ value: AgentResponseFormatJson, name: 'json' },
|
||||
],
|
||||
},
|
||||
})
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
responseFormat?: object;
|
||||
responseFormat?: AgentResponseFormat;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
|
||||
+18
-3
@@ -1,5 +1,6 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
@@ -7,11 +8,15 @@ import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type';
|
||||
import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration';
|
||||
import { AgentResponseFormatJson } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator';
|
||||
import { AgentResponseFormatText } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator';
|
||||
import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
|
||||
@InputType()
|
||||
@@ -54,12 +59,22 @@ export class UpdateAgentInput {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
roleId?: string | null;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Object, {
|
||||
keepDiscriminatorProperty: true,
|
||||
discriminator: {
|
||||
property: 'type',
|
||||
subTypes: [
|
||||
{ value: AgentResponseFormatText, name: 'text' },
|
||||
{ value: AgentResponseFormatJson, name: 'json' },
|
||||
],
|
||||
},
|
||||
})
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
responseFormat?: object;
|
||||
responseFormat?: AgentResponseFormat;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
|
||||
+6
-5
@@ -43,11 +43,11 @@ export class AgentEntity
|
||||
@Column({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
icon: string;
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
icon: string | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string;
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
description: string | null;
|
||||
|
||||
@Column({ nullable: false, type: 'text' })
|
||||
prompt: string;
|
||||
@@ -55,6 +55,7 @@ export class AgentEntity
|
||||
@Column({ nullable: false, type: 'varchar', default: DEFAULT_SMART_MODEL })
|
||||
modelId: ModelId;
|
||||
|
||||
// Should not be nullable
|
||||
@Column({ nullable: true, type: 'jsonb', default: { type: 'text' } })
|
||||
responseFormat: AgentResponseFormat;
|
||||
|
||||
@@ -80,7 +81,7 @@ export class AgentEntity
|
||||
deletedAt: Date | null;
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
modelConfiguration: ModelConfiguration;
|
||||
modelConfiguration: ModelConfiguration | null;
|
||||
|
||||
@Column({ type: 'text', array: true, default: '{}' })
|
||||
evaluationInputs: string[];
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
type CallHandler,
|
||||
type ExecutionContext,
|
||||
Injectable,
|
||||
type NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Observable, catchError } from 'rxjs';
|
||||
|
||||
import { agentGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/ai/ai-agent/utils/agent-graphql-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class AgentGraphqlApiExceptionInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
_context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<unknown> {
|
||||
return next.handle().pipe(catchError(agentGraphqlApiExceptionHandler));
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -24,7 +24,6 @@ import {
|
||||
import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service';
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const';
|
||||
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-system-prompts.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AgentActorContextService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-actor-context.service';
|
||||
import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service';
|
||||
import { AgentToolGeneratorService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-tool-generator.service';
|
||||
@@ -34,6 +33,7 @@ import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/serv
|
||||
import { ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
|
||||
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
|
||||
@@ -77,7 +77,7 @@ export class AgentExecutionService {
|
||||
toolHints,
|
||||
}: {
|
||||
system: string;
|
||||
agent: AgentEntity | null;
|
||||
agent: FlatAgentWithRoleId | null;
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
actorContext?: ActorMetadata;
|
||||
roleIds?: string[];
|
||||
@@ -300,7 +300,8 @@ export class AgentExecutionService {
|
||||
};
|
||||
}> {
|
||||
try {
|
||||
const agent = await this.agentService.findOneAgent(workspace.id, {
|
||||
const agent = await this.agentService.findOneAgentById({
|
||||
workspaceId: workspace.id,
|
||||
id: agentId,
|
||||
});
|
||||
|
||||
|
||||
+7
-4
@@ -6,9 +6,9 @@ import { ProviderOptions } from '@ai-sdk/provider-utils';
|
||||
import { ToolSet } from 'ai';
|
||||
|
||||
import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { ModelProvider } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { RegisteredAIModel } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
|
||||
import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
|
||||
@Injectable()
|
||||
export class AgentModelConfigService {
|
||||
@@ -16,7 +16,7 @@ export class AgentModelConfigService {
|
||||
|
||||
getProviderOptions(
|
||||
model: RegisteredAIModel,
|
||||
agent: AgentEntity,
|
||||
agent: FlatAgentWithRoleId,
|
||||
): ProviderOptions {
|
||||
switch (model.provider) {
|
||||
case ModelProvider.XAI:
|
||||
@@ -28,7 +28,10 @@ export class AgentModelConfigService {
|
||||
}
|
||||
}
|
||||
|
||||
getNativeModelTools(model: RegisteredAIModel, agent: AgentEntity): ToolSet {
|
||||
getNativeModelTools(
|
||||
model: RegisteredAIModel,
|
||||
agent: FlatAgentWithRoleId,
|
||||
): ToolSet {
|
||||
const tools: ToolSet = {};
|
||||
|
||||
if (!agent.modelConfiguration) {
|
||||
@@ -51,7 +54,7 @@ export class AgentModelConfigService {
|
||||
return tools;
|
||||
}
|
||||
|
||||
private getXaiProviderOptions(agent: AgentEntity): ProviderOptions {
|
||||
private getXaiProviderOptions(agent: FlatAgentWithRoleId): ProviderOptions {
|
||||
if (
|
||||
!agent.modelConfiguration ||
|
||||
(!agent.modelConfiguration.webSearch?.enabled &&
|
||||
|
||||
+4
-4
@@ -71,10 +71,10 @@ export class AgentPlanExecutorService {
|
||||
);
|
||||
|
||||
const agent =
|
||||
await this.agentExecutionService.agentService.findOneAgent(
|
||||
workspace.id,
|
||||
{ name: step.agentName },
|
||||
);
|
||||
await this.agentExecutionService.agentService.findOneAgentByName({
|
||||
name: step.agentName,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`[PLAN EXECUTION] Step ${step.stepNumber}: Found agent "${agent.label}" (${agent.id})`,
|
||||
|
||||
+8
-6
@@ -1,10 +1,12 @@
|
||||
import { type AgentResponseSchema } from 'twenty-shared/ai';
|
||||
|
||||
export type AgentResponseFormatType = 'text' | 'json';
|
||||
export type AgentResponseFormatType = AgentResponseFormat['type'];
|
||||
|
||||
export type AgentTextResponseFormat = { type: 'text' };
|
||||
export type AgentJsonResponseFormat = {
|
||||
type: 'json';
|
||||
schema: AgentResponseSchema;
|
||||
};
|
||||
export type AgentResponseFormat =
|
||||
| { type: 'text' }
|
||||
| {
|
||||
type: 'json';
|
||||
schema: AgentResponseSchema;
|
||||
};
|
||||
| AgentTextResponseFormat
|
||||
| AgentJsonResponseFormat;
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ConflictError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
|
||||
export const agentGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof AgentException) {
|
||||
switch (error.code) {
|
||||
case AgentExceptionCode.AGENT_NOT_FOUND:
|
||||
case AgentExceptionCode.ROLE_NOT_FOUND:
|
||||
throw new NotFoundError(error);
|
||||
case AgentExceptionCode.INVALID_AGENT_INPUT:
|
||||
throw new UserInputError(error);
|
||||
case AgentExceptionCode.AGENT_ALREADY_EXISTS:
|
||||
throw new ConflictError(error);
|
||||
case AgentExceptionCode.AGENT_IS_STANDARD:
|
||||
case AgentExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS:
|
||||
throw new ForbiddenError(error);
|
||||
case AgentExceptionCode.AGENT_EXECUTION_FAILED:
|
||||
case AgentExceptionCode.API_KEY_NOT_CONFIGURED:
|
||||
case AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND:
|
||||
throw error;
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { computeMetadataNameFromLabel } from 'twenty-shared/metadata';
|
||||
|
||||
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
|
||||
|
||||
export type FromCreateAgentInputToFlatAgentArgs = {
|
||||
createAgentInput: CreateAgentInput & { applicationId: string };
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const fromCreateAgentInputToFlatAgent = ({
|
||||
createAgentInput: rawCreateAgentInput,
|
||||
workspaceId,
|
||||
}: FromCreateAgentInputToFlatAgentArgs): {
|
||||
flatAgentToCreate: FlatAgent;
|
||||
flatRoleTargetToCreate: FlatRoleTarget | null;
|
||||
} => {
|
||||
const { roleId, ...createAgentInput } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawCreateAgentInput,
|
||||
[
|
||||
'name',
|
||||
'label',
|
||||
'icon',
|
||||
'description',
|
||||
'prompt',
|
||||
'modelId',
|
||||
'standardId',
|
||||
'applicationId',
|
||||
'roleId',
|
||||
],
|
||||
);
|
||||
|
||||
const createdAt = new Date();
|
||||
const agentId = v4();
|
||||
const standardId = createAgentInput.standardId ?? null;
|
||||
const universalIdentifier = standardId ?? agentId;
|
||||
|
||||
const flatAgentToCreate: FlatAgent = {
|
||||
id: agentId,
|
||||
standardId,
|
||||
name: isNonEmptyString(createAgentInput.name)
|
||||
? createAgentInput.name
|
||||
: computeMetadataNameFromLabel(createAgentInput.label),
|
||||
label: createAgentInput.label,
|
||||
icon: createAgentInput.icon ?? null,
|
||||
description: createAgentInput.description ?? null,
|
||||
prompt: createAgentInput.prompt,
|
||||
modelId: createAgentInput.modelId,
|
||||
responseFormat: createAgentInput.responseFormat ?? { type: 'text' },
|
||||
workspaceId,
|
||||
isCustom: true,
|
||||
universalIdentifier,
|
||||
applicationId: createAgentInput.applicationId,
|
||||
modelConfiguration: createAgentInput.modelConfiguration ?? null,
|
||||
evaluationInputs: createAgentInput.evaluationInputs ?? [],
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
const flatRoleTargetToCreate: FlatRoleTarget | null = isDefined(roleId)
|
||||
? {
|
||||
id: v4(),
|
||||
roleId,
|
||||
userWorkspaceId: null,
|
||||
agentId,
|
||||
apiKeyId: null,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
universalIdentifier: v4(),
|
||||
workspaceId,
|
||||
applicationId: createAgentInput.applicationId,
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
flatAgentToCreate,
|
||||
flatRoleTargetToCreate,
|
||||
};
|
||||
};
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception';
|
||||
import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input';
|
||||
import { FLAT_AGENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant';
|
||||
import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { type FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type';
|
||||
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
|
||||
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
type FlatRoleTargetToUpdateCreateDelete = {
|
||||
flatRoleTargetToUpdate?: FlatRoleTarget;
|
||||
flatRoleTargetToCreate?: FlatRoleTarget;
|
||||
flatRoleTargetToDelete?: FlatRoleTarget;
|
||||
};
|
||||
const computeAgentFlatRoleTargetToUpdate = ({
|
||||
roleId,
|
||||
flatAgent,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
}: {
|
||||
roleId: string | null | undefined;
|
||||
flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps;
|
||||
flatAgent: FlatAgent;
|
||||
}): FlatRoleTargetToUpdateCreateDelete => {
|
||||
if (roleId === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const existingRoleTarget = flatRoleTargetByAgentIdMaps[flatAgent.id];
|
||||
const updatedAt = new Date();
|
||||
|
||||
if (roleId === null) {
|
||||
if (isDefined(existingRoleTarget)) {
|
||||
return {
|
||||
flatRoleTargetToDelete: existingRoleTarget,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isDefined(existingRoleTarget)) {
|
||||
return {
|
||||
flatRoleTargetToUpdate: {
|
||||
...existingRoleTarget,
|
||||
roleId,
|
||||
updatedAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
flatRoleTargetToCreate: {
|
||||
id: v4(),
|
||||
roleId,
|
||||
userWorkspaceId: null,
|
||||
agentId: flatAgent.id,
|
||||
apiKeyId: null,
|
||||
createdAt: updatedAt,
|
||||
updatedAt,
|
||||
universalIdentifier: v4(),
|
||||
workspaceId: flatAgent.workspaceId,
|
||||
applicationId: flatAgent.applicationId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export type FromUpdateAgentInputToFlatAgentToUpdateArgs = {
|
||||
updateAgentInput: UpdateAgentInput;
|
||||
flatAgentMaps: FlatAgentMaps;
|
||||
flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps;
|
||||
};
|
||||
|
||||
export const fromUpdateAgentInputToFlatAgentToUpdate = ({
|
||||
updateAgentInput: rawUpdateAgentInput,
|
||||
flatAgentMaps,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
}: FromUpdateAgentInputToFlatAgentToUpdateArgs): {
|
||||
flatAgentToUpdate: FlatAgent;
|
||||
} & FlatRoleTargetToUpdateCreateDelete => {
|
||||
const { id: agentIdToUpdate } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawUpdateAgentInput,
|
||||
['id'],
|
||||
);
|
||||
|
||||
const existingFlatAgent = flatAgentMaps.byId[agentIdToUpdate];
|
||||
|
||||
if (!isDefined(existingFlatAgent)) {
|
||||
throw new AgentException(
|
||||
'Agent not found',
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
{
|
||||
userFriendlyMessage: msg`The agent you are looking for could not be found. It may have been deleted or you may not have access to it.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const updatedEditableAgentProperties = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateAgentInput,
|
||||
FLAT_AGENT_EDITABLE_PROPERTIES,
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(updatedEditableAgentProperties.label) &&
|
||||
!isDefined(updatedEditableAgentProperties.name)
|
||||
) {
|
||||
updatedEditableAgentProperties.name = computeMetadataNameFromLabelOrThrow(
|
||||
updatedEditableAgentProperties.label,
|
||||
);
|
||||
}
|
||||
|
||||
const flatAgentToUpdate: FlatAgent = mergeUpdateInExistingRecord({
|
||||
existing: existingFlatAgent,
|
||||
properties: FLAT_AGENT_EDITABLE_PROPERTIES,
|
||||
update: updatedEditableAgentProperties,
|
||||
});
|
||||
|
||||
const {
|
||||
flatRoleTargetToUpdate,
|
||||
flatRoleTargetToCreate,
|
||||
flatRoleTargetToDelete,
|
||||
} = computeAgentFlatRoleTargetToUpdate({
|
||||
roleId: rawUpdateAgentInput.roleId,
|
||||
flatAgent: existingFlatAgent,
|
||||
flatRoleTargetByAgentIdMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
flatAgentToUpdate,
|
||||
flatRoleTargetToUpdate,
|
||||
flatRoleTargetToCreate,
|
||||
flatRoleTargetToDelete,
|
||||
};
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { IsEnum, IsNotEmpty, IsObject } from 'class-validator';
|
||||
|
||||
export class AgentResponseFormatJson {
|
||||
@IsEnum(['json'])
|
||||
type: 'json';
|
||||
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
schema: Record<string, unknown>;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { IsEnum } from 'class-validator';
|
||||
|
||||
export class AgentResponseFormatText {
|
||||
@IsEnum(['text'])
|
||||
type: 'text';
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
|
||||
export const FLAT_AGENT_EDITABLE_PROPERTIES = [
|
||||
'name',
|
||||
'label',
|
||||
'icon',
|
||||
'description',
|
||||
'prompt',
|
||||
'modelId',
|
||||
'responseFormat',
|
||||
'modelConfiguration',
|
||||
'evaluationInputs',
|
||||
] as const satisfies (keyof FlatAgent)[];
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { WorkspaceFlatAgentMapCacheService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service';
|
||||
import { WorkspaceFlatRoleTargetByAgentIdService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AgentEntity, RoleTargetEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceFlatAgentMapCacheService,
|
||||
WorkspaceFlatRoleTargetByAgentIdService,
|
||||
],
|
||||
exports: [
|
||||
WorkspaceFlatAgentMapCacheService,
|
||||
WorkspaceFlatRoleTargetByAgentIdService,
|
||||
],
|
||||
})
|
||||
export class FlatAgentModule {}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type';
|
||||
import { transformAgentEntityToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatAgentMaps')
|
||||
export class WorkspaceFlatAgentMapCacheService extends WorkspaceCacheProvider<FlatAgentMaps> {
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatAgentMaps> {
|
||||
const agents = await this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const flatAgentMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const agentEntity of agents) {
|
||||
const flatAgent = transformAgentEntityToFlatAgent(agentEntity);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatAgent,
|
||||
flatEntityMapsToMutate: flatAgentMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return flatAgentMaps;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { NonNullableRequired } from 'twenty-shared/types';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type';
|
||||
import { fromRoleTargetsEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatRoleTargetByAgentIdMaps')
|
||||
export class WorkspaceFlatRoleTargetByAgentIdService extends WorkspaceCacheProvider<FlatRoleTargetByAgentIdMaps> {
|
||||
constructor(
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(
|
||||
workspaceId: string,
|
||||
): Promise<FlatRoleTargetByAgentIdMaps> {
|
||||
const roleTargetEntities = await this.roleTargetRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
agentId: Not(IsNull()),
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps = {};
|
||||
|
||||
for (const roleTargetEntity of roleTargetEntities as Array<
|
||||
Omit<RoleTargetEntity, 'agentId'> &
|
||||
NonNullableRequired<Pick<RoleTargetEntity, 'agentId'>>
|
||||
>) {
|
||||
const flatRoleTarget =
|
||||
fromRoleTargetsEntityToFlatRoleTarget(roleTargetEntity);
|
||||
|
||||
flatRoleTargetByAgentIdMaps[roleTargetEntity.agentId] = flatRoleTarget;
|
||||
}
|
||||
|
||||
return flatRoleTargetByAgentIdMaps;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
|
||||
export type FlatAgentMaps = FlatEntityMaps<FlatAgent>;
|
||||
+3
-1
@@ -11,5 +11,7 @@ export type AgentEntityRelationProperties =
|
||||
|
||||
export type FlatAgent = FlatEntityFrom<
|
||||
AgentEntity,
|
||||
AgentEntityRelationProperties | 'createdAt' | 'updatedAt' | 'deletedAt'
|
||||
AgentEntityRelationProperties
|
||||
>;
|
||||
|
||||
export type FlatAgentWithRoleId = FlatAgent & { roleId: string | null };
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
|
||||
|
||||
export type FlatRoleTargetByAgentIdMaps = Partial<
|
||||
Record<string, FlatRoleTarget>
|
||||
>;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { type FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
|
||||
export const fromFlatAgentWithRoleIdToAgentDto = ({
|
||||
applicationId,
|
||||
createdAt,
|
||||
description,
|
||||
evaluationInputs,
|
||||
icon,
|
||||
id,
|
||||
isCustom,
|
||||
label,
|
||||
modelConfiguration,
|
||||
modelId,
|
||||
name,
|
||||
prompt,
|
||||
responseFormat,
|
||||
standardId,
|
||||
updatedAt,
|
||||
workspaceId,
|
||||
roleId,
|
||||
}: FlatAgentWithRoleId): AgentDTO => ({
|
||||
createdAt,
|
||||
description: description ?? undefined,
|
||||
evaluationInputs,
|
||||
id,
|
||||
isCustom,
|
||||
label,
|
||||
modelConfiguration: modelConfiguration ?? undefined,
|
||||
modelId,
|
||||
name,
|
||||
prompt,
|
||||
responseFormat,
|
||||
standardId,
|
||||
updatedAt,
|
||||
workspaceId,
|
||||
applicationId: applicationId ?? undefined,
|
||||
icon: icon ?? undefined,
|
||||
roleId: roleId ?? undefined,
|
||||
});
|
||||
+3
@@ -5,6 +5,9 @@ export const transformAgentEntityToFlatAgent = (
|
||||
agentEntity: AgentEntity,
|
||||
): FlatAgent => {
|
||||
return {
|
||||
createdAt: agentEntity.createdAt,
|
||||
deletedAt: agentEntity.deletedAt,
|
||||
updatedAt: agentEntity.updatedAt,
|
||||
id: agentEntity.id,
|
||||
standardId: agentEntity.standardId,
|
||||
name: agentEntity.name,
|
||||
|
||||
+19
-6
@@ -1,22 +1,35 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
|
||||
export const transformStandardAgentDefinitionToFlatAgent = (
|
||||
standardAgentDefinition: StandardAgentDefinition,
|
||||
workspaceId: string,
|
||||
): FlatAgent => {
|
||||
export const transformStandardAgentDefinitionToFlatAgent = ({
|
||||
standardAgentDefinition,
|
||||
workspaceId,
|
||||
existingAgentEntity,
|
||||
}: {
|
||||
standardAgentDefinition: StandardAgentDefinition;
|
||||
workspaceId: string;
|
||||
existingAgentEntity?: Pick<
|
||||
AgentEntity,
|
||||
'createdAt' | 'deletedAt' | 'updatedAt' | 'id'
|
||||
>;
|
||||
}): FlatAgent => {
|
||||
const {
|
||||
standardRoleId: _standardRoleId,
|
||||
outputStrategy: _outputStrategy,
|
||||
...agentData
|
||||
} = standardAgentDefinition;
|
||||
const createdAt = new Date();
|
||||
|
||||
return {
|
||||
id: existingAgentEntity?.id ?? v4(),
|
||||
createdAt: existingAgentEntity?.createdAt ?? createdAt,
|
||||
updatedAt: existingAgentEntity?.updatedAt ?? createdAt,
|
||||
deletedAt: existingAgentEntity?.deletedAt ?? null,
|
||||
...agentData,
|
||||
id: v4(),
|
||||
workspaceId,
|
||||
universalIdentifier: standardAgentDefinition.standardId || v4(),
|
||||
universalIdentifier: standardAgentDefinition.standardId,
|
||||
};
|
||||
};
|
||||
|
||||
+9
@@ -2,6 +2,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-editable-properties.constant';
|
||||
import { FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-editable-properties.constant';
|
||||
import { FLAT_AGENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
|
||||
import { FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-editable-properties.constant';
|
||||
@@ -97,6 +98,14 @@ export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = {
|
||||
propertiesToCompare: [...FLAT_ROLE_TARGET_EDITABLE_PROPERTIES],
|
||||
propertiesToStringify: [],
|
||||
},
|
||||
agent: {
|
||||
propertiesToCompare: [...FLAT_AGENT_EDITABLE_PROPERTIES],
|
||||
propertiesToStringify: [
|
||||
'responseFormat',
|
||||
'modelConfiguration',
|
||||
'evaluationInputs',
|
||||
],
|
||||
},
|
||||
} as const satisfies {
|
||||
[P in AllMetadataName]: OneFlatEntityConfiguration<P>;
|
||||
};
|
||||
|
||||
+1
@@ -112,4 +112,5 @@ export const ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY = {
|
||||
flatEntityForeignKeyAggregator: 'roleTargetIds',
|
||||
},
|
||||
},
|
||||
agent: {},
|
||||
} as const satisfies MetadataNameAndRelations;
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
export const ALL_METADATA_NAME = {
|
||||
fieldMetadata: 'fieldMetadata',
|
||||
objectMetadata: 'objectMetadata',
|
||||
view: 'view',
|
||||
viewField: 'viewField',
|
||||
viewGroup: 'viewGroup',
|
||||
index: 'index',
|
||||
serverlessFunction: 'serverlessFunction',
|
||||
cronTrigger: 'cronTrigger',
|
||||
databaseEventTrigger: 'databaseEventTrigger',
|
||||
routeTrigger: 'routeTrigger',
|
||||
viewFilter: 'viewFilter',
|
||||
role: 'role',
|
||||
roleTarget: 'roleTarget',
|
||||
} as const;
|
||||
+1
@@ -53,4 +53,5 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
|
||||
roleTarget: {
|
||||
role: true,
|
||||
},
|
||||
agent: {},
|
||||
} as const satisfies MetadataRequiredForValidation;
|
||||
|
||||
+49
-4
@@ -1,12 +1,57 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceFlatFieldMetadataMapCacheService } from 'src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service';
|
||||
import { WorkspaceFlatIndexMapCacheService } from 'src/engine/metadata-modules/flat-index-metadata/services/workspace-flat-index-map-cache.service';
|
||||
import { WorkspaceFlatObjectMetadataMapCacheService } from 'src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service';
|
||||
import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/metadata-modules/flat-view-field/services/workspace-flat-view-field-map-cache.service';
|
||||
import { WorkspaceFlatViewFilterMapCacheService } from 'src/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service';
|
||||
import { WorkspaceFlatViewGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-group/services/workspace-flat-view-group-map-cache.service';
|
||||
import { WorkspaceFlatViewMapCacheService } from 'src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service';
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceFlatMapCacheModule } from 'src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceFlatMapCacheModule, WorkspaceCacheModule],
|
||||
providers: [WorkspaceManyOrAllFlatEntityMapsCacheService],
|
||||
exports: [WorkspaceManyOrAllFlatEntityMapsCacheService],
|
||||
imports: [
|
||||
WorkspaceCacheModule,
|
||||
TypeOrmModule.forFeature([
|
||||
ViewEntity,
|
||||
ViewFieldEntity,
|
||||
ViewFilterEntity,
|
||||
ViewGroupEntity,
|
||||
IndexMetadataEntity,
|
||||
IndexFieldMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
WorkspaceFlatObjectMetadataMapCacheService,
|
||||
WorkspaceFlatViewMapCacheService,
|
||||
WorkspaceFlatViewFieldMapCacheService,
|
||||
WorkspaceFlatViewFilterMapCacheService,
|
||||
WorkspaceFlatIndexMapCacheService,
|
||||
WorkspaceFlatFieldMetadataMapCacheService,
|
||||
WorkspaceFlatViewGroupMapCacheService,
|
||||
],
|
||||
exports: [
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
WorkspaceFlatObjectMetadataMapCacheService,
|
||||
WorkspaceFlatViewMapCacheService,
|
||||
WorkspaceFlatViewFieldMapCacheService,
|
||||
WorkspaceFlatViewFilterMapCacheService,
|
||||
WorkspaceFlatIndexMapCacheService,
|
||||
WorkspaceFlatFieldMetadataMapCacheService,
|
||||
WorkspaceFlatViewGroupMapCacheService,
|
||||
],
|
||||
})
|
||||
export class WorkspaceManyOrAllFlatEntityMapsCacheModule {}
|
||||
|
||||
+16
@@ -1,8 +1,10 @@
|
||||
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { type CronTriggerEntity } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
|
||||
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
|
||||
import { type DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
|
||||
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
@@ -24,6 +26,11 @@ import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/ent
|
||||
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import {
|
||||
type CreateAgentAction,
|
||||
type DeleteAgentAction,
|
||||
type UpdateAgentAction,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service';
|
||||
import {
|
||||
type CreateCronTriggerAction,
|
||||
type DeleteCronTriggerAction,
|
||||
@@ -207,4 +214,13 @@ export type AllFlatEntityTypesByMetadataName = {
|
||||
flatEntity: FlatRoleTarget;
|
||||
entity: RoleTargetEntity;
|
||||
};
|
||||
agent: {
|
||||
actions: {
|
||||
created: CreateAgentAction;
|
||||
updated: UpdateAgentAction;
|
||||
deleted: DeleteAgentAction;
|
||||
};
|
||||
flatEntity: FlatAgent;
|
||||
entity: AgentEntity;
|
||||
};
|
||||
};
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
import { type ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant';
|
||||
|
||||
export type AllMetadataName = keyof typeof ALL_METADATA_NAME;
|
||||
+1
-1
@@ -15,7 +15,7 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module';
|
||||
import { FlatAgentModule } from 'src/engine/metadata-modules/flat-agent/flat-agent.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatRoleTargetMapCacheService } from 'src/engine/metadata-modules/flat-role-target/services/workspace-flat-role-target-map-cache.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -51,6 +52,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
FileModule,
|
||||
ApplicationModule,
|
||||
WorkspaceCacheModule,
|
||||
FlatAgentModule,
|
||||
],
|
||||
providers: [
|
||||
RoleService,
|
||||
|
||||
@@ -32,6 +32,7 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util';
|
||||
import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input';
|
||||
@@ -284,10 +285,13 @@ export class RoleResolver {
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return agents.map((agent) => ({
|
||||
...agent,
|
||||
applicationId: agent.applicationId ?? undefined,
|
||||
}));
|
||||
return agents.map((agentEntity) =>
|
||||
fromFlatAgentWithRoleIdToAgentDto({
|
||||
...agentEntity,
|
||||
universalIdentifier: agentEntity.universalIdentifier ?? agentEntity.id,
|
||||
roleId: role.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ResolveField('apiKeys', () => [ApiKeyForRoleDTO])
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-fla
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { fromServerlessFunctionEntityToFlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/utils/from-serverless-function-entity-to-flat-serverless-function.type';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
|
||||
Reference in New Issue
Block a user