refactor(workflow-tools): reorganize to one file per tool with co-located schemas (#16313)
## Summary Reorganizes workflow tools to improve maintainability and discoverability by having one file per tool with co-located input schemas. ## Changes - Create individual tool files in `tools/` directory (11 files) - Co-locate input schemas with their tool implementations - Add shared types file for dependencies and context - Simplify workspace service to aggregate tool factories - Remove centralized `schemas/` directory ## New Structure ``` workflow-tools/ ├── services/ │ └── workflow-tool.workspace-service.ts ├── tools/ │ ├── activate-workflow-version.tool.ts │ ├── compute-step-output-schema.tool.ts │ ├── create-complete-workflow.tool.ts │ ├── create-draft-from-workflow-version.tool.ts │ ├── create-workflow-version-edge.tool.ts │ ├── create-workflow-version-step.tool.ts │ ├── deactivate-workflow-version.tool.ts │ ├── delete-workflow-version-edge.tool.ts │ ├── delete-workflow-version-step.tool.ts │ ├── update-workflow-version-positions.tool.ts │ └── update-workflow-version-step.tool.ts ├── types/ │ └── workflow-tool-dependencies.type.ts └── workflow-tools.module.ts ``` ## Benefits - **Co-location**: Schema and tool logic are in the same file - **Single responsibility**: Each file handles one tool - **Easier maintenance**: Changes to a tool only touch one file - **Better discoverability**: File names match tool names
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import { type RestrictedFieldsPermissions } from 'twenty-shared/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type ObjectMetadataForToolSchema } from 'src/engine/core-modules/record-crud/types/object-metadata-for-tool-schema.type';
|
||||
import { generateRecordPropertiesZodSchema } from 'src/engine/core-modules/record-crud/zod-schemas/record-properties.zod-schema';
|
||||
import { type ToolGeneratorContext } from 'src/engine/core-modules/tool-generator/types/tool-generator.types';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
import {
|
||||
createAndConfigureStep,
|
||||
type WorkflowStepToolsDeps,
|
||||
} from './step-builder.utils';
|
||||
|
||||
export function buildCreateRecordStepTool(
|
||||
deps: WorkflowStepToolsDeps,
|
||||
objectMetadata: ObjectMetadataForToolSchema,
|
||||
restrictedFields: RestrictedFieldsPermissions,
|
||||
context: ToolGeneratorContext,
|
||||
): ToolSet {
|
||||
const recordPropertiesSchema = generateRecordPropertiesZodSchema(
|
||||
objectMetadata,
|
||||
false,
|
||||
restrictedFields,
|
||||
);
|
||||
|
||||
const inputSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
stepName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
`Name for this step (default: "Create ${objectMetadata.labelSingular}")`,
|
||||
),
|
||||
input: recordPropertiesSchema.describe(
|
||||
`The ${objectMetadata.labelSingular} record data. Use {{trigger.fieldName}} or {{stepId.fieldName}} syntax to reference dynamic values from previous steps.`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
[`configure_create_${objectMetadata.nameSingular}_step`]: {
|
||||
description:
|
||||
`Add a workflow step that creates a ${objectMetadata.labelSingular} record. ` +
|
||||
`Provide the record fields directly - use {{trigger.fieldName}} or {{stepId.fieldName}} to reference values from previous steps.`,
|
||||
inputSchema,
|
||||
execute: async (parameters: z.infer<typeof inputSchema>) => {
|
||||
try {
|
||||
const { stepId, result } = await createAndConfigureStep(
|
||||
deps,
|
||||
context.workspaceId,
|
||||
parameters.workflowVersionId,
|
||||
WorkflowActionType.CREATE_RECORD,
|
||||
parameters.parentStepId,
|
||||
{
|
||||
name:
|
||||
parameters.stepName || `Create ${objectMetadata.labelSingular}`,
|
||||
type: WorkflowActionType.CREATE_RECORD,
|
||||
valid: true,
|
||||
settings: {
|
||||
input: {
|
||||
objectName: objectMetadata.nameSingular,
|
||||
objectRecord: parameters.input,
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created workflow step to create ${objectMetadata.labelSingular}`,
|
||||
result: { stepId, step: result },
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create ${objectMetadata.labelSingular} workflow step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type ObjectMetadataForToolSchema } from 'src/engine/core-modules/record-crud/types/object-metadata-for-tool-schema.type';
|
||||
import { type ToolGeneratorContext } from 'src/engine/core-modules/tool-generator/types/tool-generator.types';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
import {
|
||||
createAndConfigureStep,
|
||||
type WorkflowStepToolsDeps,
|
||||
} from './step-builder.utils';
|
||||
|
||||
export function buildDeleteRecordStepTool(
|
||||
deps: WorkflowStepToolsDeps,
|
||||
objectMetadata: ObjectMetadataForToolSchema,
|
||||
context: ToolGeneratorContext,
|
||||
): ToolSet {
|
||||
const inputSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
stepName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
`Name for this step (default: "Delete ${objectMetadata.labelSingular}")`,
|
||||
),
|
||||
objectRecordId: z
|
||||
.string()
|
||||
.describe(
|
||||
`The ID of the ${objectMetadata.labelSingular} record to delete. Use {{trigger.id}} or {{stepId.result.id}} to reference a dynamic ID.`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
[`configure_delete_${objectMetadata.nameSingular}_step`]: {
|
||||
description:
|
||||
`Add a workflow step that deletes a ${objectMetadata.labelSingular} record. ` +
|
||||
`This performs a soft delete (marks as deleted but preserves data).`,
|
||||
inputSchema,
|
||||
execute: async (parameters: z.infer<typeof inputSchema>) => {
|
||||
try {
|
||||
const { stepId, result } = await createAndConfigureStep(
|
||||
deps,
|
||||
context.workspaceId,
|
||||
parameters.workflowVersionId,
|
||||
WorkflowActionType.DELETE_RECORD,
|
||||
parameters.parentStepId,
|
||||
{
|
||||
name:
|
||||
parameters.stepName || `Delete ${objectMetadata.labelSingular}`,
|
||||
type: WorkflowActionType.DELETE_RECORD,
|
||||
valid: true,
|
||||
settings: {
|
||||
input: {
|
||||
objectName: objectMetadata.nameSingular,
|
||||
objectRecordId: parameters.objectRecordId,
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created workflow step to delete ${objectMetadata.labelSingular}`,
|
||||
result: { stepId, step: result },
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create ${objectMetadata.labelSingular} delete workflow step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
type RestrictedFieldsPermissions,
|
||||
} from 'twenty-shared/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type ObjectMetadataForToolSchema } from 'src/engine/core-modules/record-crud/types/object-metadata-for-tool-schema.type';
|
||||
import { generateFieldFilterZodSchema } from 'src/engine/core-modules/record-crud/zod-schemas/field-filters.zod-schema';
|
||||
import { ObjectRecordOrderBySchema } from 'src/engine/core-modules/record-crud/zod-schemas/order-by.zod-schema';
|
||||
import { type ToolGeneratorContext } from 'src/engine/core-modules/tool-generator/types/tool-generator.types';
|
||||
import { shouldExcludeFieldFromAgentToolSchema } from 'src/engine/metadata-modules/field-metadata/utils/should-exclude-field-from-agent-tool-schema.util';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
import {
|
||||
createAndConfigureStep,
|
||||
type WorkflowStepToolsDeps,
|
||||
} from './step-builder.utils';
|
||||
|
||||
export function buildFindRecordsStepTool(
|
||||
deps: WorkflowStepToolsDeps,
|
||||
objectMetadata: ObjectMetadataForToolSchema,
|
||||
restrictedFields: RestrictedFieldsPermissions,
|
||||
context: ToolGeneratorContext,
|
||||
): ToolSet {
|
||||
const filterShape: Record<string, z.ZodTypeAny> = {};
|
||||
|
||||
objectMetadata.fields.forEach((field) => {
|
||||
if (shouldExcludeFieldFromAgentToolSchema(field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (restrictedFields?.[field.id]?.canRead === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterSchema = generateFieldFilterZodSchema(field);
|
||||
|
||||
if (!filterSchema) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isManyToOneRelationField =
|
||||
isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION) &&
|
||||
field.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||
|
||||
filterShape[isManyToOneRelationField ? `${field.name}Id` : field.name] =
|
||||
filterSchema;
|
||||
});
|
||||
|
||||
const inputSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
stepName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
`Name for this step (default: "Find ${objectMetadata.labelPlural}")`,
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.max(1000)
|
||||
.optional()
|
||||
.default(100)
|
||||
.describe('Maximum number of records to return (default: 100)'),
|
||||
orderBy: ObjectRecordOrderBySchema.optional().describe(
|
||||
'Sort records by field(s). Each item is an object with field name as key, sort direction as value.',
|
||||
),
|
||||
filter: z
|
||||
.object(filterShape)
|
||||
.partial()
|
||||
.optional()
|
||||
.describe(
|
||||
`Filter criteria for ${objectMetadata.labelPlural}. Use {{trigger.fieldName}} or {{stepId.fieldName}} to reference dynamic values.`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
[`configure_find_${objectMetadata.namePlural}_step`]: {
|
||||
description:
|
||||
`Add a workflow step that searches for ${objectMetadata.labelPlural} records. ` +
|
||||
`Results can be used in subsequent steps via {{stepId.result}}.`,
|
||||
inputSchema,
|
||||
execute: async (parameters: z.infer<typeof inputSchema>) => {
|
||||
try {
|
||||
const filterConfig = parameters.filter
|
||||
? {
|
||||
recordFilters: Object.entries(parameters.filter).map(
|
||||
([fieldName, filterValue]) => ({
|
||||
fieldName,
|
||||
filter: filterValue,
|
||||
}),
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const { stepId, result } = await createAndConfigureStep(
|
||||
deps,
|
||||
context.workspaceId,
|
||||
parameters.workflowVersionId,
|
||||
WorkflowActionType.FIND_RECORDS,
|
||||
parameters.parentStepId,
|
||||
{
|
||||
name: parameters.stepName || `Find ${objectMetadata.labelPlural}`,
|
||||
type: WorkflowActionType.FIND_RECORDS,
|
||||
valid: true,
|
||||
settings: {
|
||||
input: {
|
||||
objectName: objectMetadata.nameSingular,
|
||||
limit: parameters.limit,
|
||||
filter: filterConfig,
|
||||
orderBy: parameters.orderBy
|
||||
? { gqlOperationOrderBy: parameters.orderBy }
|
||||
: undefined,
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created workflow step to find ${objectMetadata.labelPlural}`,
|
||||
result: { stepId, step: result },
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create ${objectMetadata.labelPlural} find workflow step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { type WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
export type WorkflowStepToolsDeps = {
|
||||
workflowVersionStepService: {
|
||||
createWorkflowVersionStep: (args: {
|
||||
workspaceId: string;
|
||||
input: {
|
||||
workflowVersionId: string;
|
||||
stepType: WorkflowActionType;
|
||||
parentStepId?: string;
|
||||
id?: string;
|
||||
};
|
||||
}) => Promise<unknown>;
|
||||
updateWorkflowVersionStep: (args: {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
step: unknown;
|
||||
}) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
export async function createAndConfigureStep(
|
||||
deps: WorkflowStepToolsDeps,
|
||||
workspaceId: string,
|
||||
workflowVersionId: string,
|
||||
stepType: WorkflowActionType,
|
||||
parentStepId: string | undefined,
|
||||
stepConfig: object,
|
||||
) {
|
||||
const stepId = uuidv4();
|
||||
|
||||
await deps.workflowVersionStepService.createWorkflowVersionStep({
|
||||
workspaceId,
|
||||
input: {
|
||||
workflowVersionId,
|
||||
stepType,
|
||||
parentStepId,
|
||||
id: stepId,
|
||||
},
|
||||
});
|
||||
|
||||
const result =
|
||||
await deps.workflowVersionStepService.updateWorkflowVersionStep({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
step: { id: stepId, ...stepConfig },
|
||||
});
|
||||
|
||||
return { stepId, result };
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
import { type RestrictedFieldsPermissions } from 'twenty-shared/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type ObjectMetadataForToolSchema } from 'src/engine/core-modules/record-crud/types/object-metadata-for-tool-schema.type';
|
||||
import { generateRecordPropertiesZodSchema } from 'src/engine/core-modules/record-crud/zod-schemas/record-properties.zod-schema';
|
||||
import { type ToolGeneratorContext } from 'src/engine/core-modules/tool-generator/types/tool-generator.types';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
|
||||
import {
|
||||
createAndConfigureStep,
|
||||
type WorkflowStepToolsDeps,
|
||||
} from './step-builder.utils';
|
||||
|
||||
export function buildUpdateRecordStepTool(
|
||||
deps: WorkflowStepToolsDeps,
|
||||
objectMetadata: ObjectMetadataForToolSchema,
|
||||
restrictedFields: RestrictedFieldsPermissions,
|
||||
context: ToolGeneratorContext,
|
||||
): ToolSet {
|
||||
const recordPropertiesSchema = generateRecordPropertiesZodSchema(
|
||||
objectMetadata,
|
||||
false,
|
||||
restrictedFields,
|
||||
);
|
||||
const updateSchema = recordPropertiesSchema.partial();
|
||||
|
||||
const inputSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
stepName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
`Name for this step (default: "Update ${objectMetadata.labelSingular}")`,
|
||||
),
|
||||
objectRecordId: z
|
||||
.string()
|
||||
.describe(
|
||||
`The ID of the ${objectMetadata.labelSingular} record to update. Use {{trigger.id}} or {{stepId.result.id}} to reference a dynamic ID.`,
|
||||
),
|
||||
fieldsToUpdate: updateSchema.describe(
|
||||
`The fields to update on the ${objectMetadata.labelSingular} record. Only include fields you want to change. Use {{trigger.fieldName}} or {{stepId.fieldName}} to reference dynamic values.`,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
[`configure_update_${objectMetadata.nameSingular}_step`]: {
|
||||
description:
|
||||
`Add a workflow step that updates an existing ${objectMetadata.labelSingular} record. ` +
|
||||
`Specify the record ID and only the fields you want to update.`,
|
||||
inputSchema,
|
||||
execute: async (parameters: z.infer<typeof inputSchema>) => {
|
||||
try {
|
||||
const { stepId, result } = await createAndConfigureStep(
|
||||
deps,
|
||||
context.workspaceId,
|
||||
parameters.workflowVersionId,
|
||||
WorkflowActionType.UPDATE_RECORD,
|
||||
parameters.parentStepId,
|
||||
{
|
||||
name:
|
||||
parameters.stepName || `Update ${objectMetadata.labelSingular}`,
|
||||
type: WorkflowActionType.UPDATE_RECORD,
|
||||
valid: true,
|
||||
settings: {
|
||||
input: {
|
||||
objectName: objectMetadata.nameSingular,
|
||||
objectRecordId: parameters.objectRecordId,
|
||||
objectRecord: parameters.fieldsToUpdate,
|
||||
},
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: false },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created workflow step to update ${objectMetadata.labelSingular}`,
|
||||
result: { stepId, step: result },
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create ${objectMetadata.labelSingular} update workflow step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { type ToolSet } from 'ai';
|
||||
|
||||
import {
|
||||
type ObjectWithPermission,
|
||||
type ToolGeneratorContext,
|
||||
} from 'src/engine/core-modules/tool-generator/types/tool-generator.types';
|
||||
|
||||
import { buildCreateRecordStepTool } from './builders/create-record-step.builder';
|
||||
import { buildDeleteRecordStepTool } from './builders/delete-record-step.builder';
|
||||
import { buildFindRecordsStepTool } from './builders/find-records-step.builder';
|
||||
import { type WorkflowStepToolsDeps } from './builders/step-builder.utils';
|
||||
import { buildUpdateRecordStepTool } from './builders/update-record-step.builder';
|
||||
|
||||
export { type WorkflowStepToolsDeps } from './builders/step-builder.utils';
|
||||
|
||||
export const createWorkflowStepToolsFactory = (deps: WorkflowStepToolsDeps) => {
|
||||
return (
|
||||
{
|
||||
objectMetadata,
|
||||
restrictedFields,
|
||||
canCreate,
|
||||
canRead,
|
||||
canUpdate,
|
||||
canDelete,
|
||||
}: ObjectWithPermission,
|
||||
context: ToolGeneratorContext,
|
||||
): ToolSet => {
|
||||
const tools: ToolSet = {};
|
||||
|
||||
if (canCreate) {
|
||||
Object.assign(
|
||||
tools,
|
||||
buildCreateRecordStepTool(
|
||||
deps,
|
||||
objectMetadata,
|
||||
restrictedFields,
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (canUpdate) {
|
||||
Object.assign(
|
||||
tools,
|
||||
buildUpdateRecordStepTool(
|
||||
deps,
|
||||
objectMetadata,
|
||||
restrictedFields,
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (canRead) {
|
||||
Object.assign(
|
||||
tools,
|
||||
buildFindRecordsStepTool(
|
||||
deps,
|
||||
objectMetadata,
|
||||
restrictedFields,
|
||||
context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (canDelete) {
|
||||
Object.assign(
|
||||
tools,
|
||||
buildDeleteRecordStepTool(deps, objectMetadata, context),
|
||||
);
|
||||
}
|
||||
|
||||
return tools;
|
||||
};
|
||||
};
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
import {
|
||||
workflowActionSchema,
|
||||
workflowTriggerSchema,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
export const createWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
stepType: z
|
||||
.enum(Object.values(WorkflowActionType) as [string, ...string[]])
|
||||
.describe('The type of step to create'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
nextStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the step this new step should connect to'),
|
||||
position: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.optional()
|
||||
.describe('Optional position coordinates for the step'),
|
||||
});
|
||||
|
||||
export const updateWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version containing the step'),
|
||||
step: z
|
||||
.union([workflowTriggerSchema, workflowActionSchema])
|
||||
.describe('The updated step configuration'),
|
||||
});
|
||||
|
||||
export const deleteWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version containing the step'),
|
||||
stepId: z.string().describe('The ID of the step to delete'),
|
||||
});
|
||||
|
||||
export const createWorkflowVersionEdgeSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
source: z.string().describe('The ID of the source step'),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
});
|
||||
|
||||
export const deleteWorkflowVersionEdgeSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
source: z.string().describe('The ID of the source step'),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
});
|
||||
|
||||
export const createDraftFromWorkflowVersionSchema = z.object({
|
||||
workflowId: z.string().describe('The ID of the workflow'),
|
||||
workflowVersionIdToCopy: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to create a draft from'),
|
||||
});
|
||||
|
||||
export const updateWorkflowVersionPositionsSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
positions: z
|
||||
.array(
|
||||
z.object({
|
||||
stepId: z.string(),
|
||||
position: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.describe('Array of step positions to update'),
|
||||
});
|
||||
|
||||
export const activateWorkflowVersionSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to activate'),
|
||||
});
|
||||
|
||||
export const deactivateWorkflowVersionSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to deactivate'),
|
||||
});
|
||||
|
||||
export const computeStepOutputSchemaSchema = z.object({
|
||||
step: z
|
||||
.union([workflowTriggerSchema, workflowActionSchema])
|
||||
.describe('The workflow step configuration'),
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
});
|
||||
|
||||
export const createCompleteWorkflowSchema = z.object({
|
||||
name: z.string().describe('The name of the workflow'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description of the workflow'),
|
||||
trigger: workflowTriggerSchema,
|
||||
steps: z
|
||||
.array(workflowActionSchema)
|
||||
.describe('Array of workflow action steps'),
|
||||
stepPositions: z
|
||||
.array(
|
||||
z.object({
|
||||
stepId: z
|
||||
.string()
|
||||
.describe('The ID of the step (use "trigger" for trigger step)'),
|
||||
position: z.object({
|
||||
x: z.number().describe('X coordinate for the step position'),
|
||||
y: z.number().describe('Y coordinate for the step position'),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe('Optional array of step positions for layout'),
|
||||
edges: z
|
||||
.array(
|
||||
z.object({
|
||||
source: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the source step (use "trigger" for trigger step)',
|
||||
),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe('Optional array of connections between steps'),
|
||||
activate: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to activate the workflow immediately (default: false)'),
|
||||
});
|
||||
+113
-480
@@ -1,510 +1,143 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ToolSet } from 'ai';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import type { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
import type { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
|
||||
import type { UpdateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-step-input.dto';
|
||||
import { PerObjectToolGeneratorService } from 'src/engine/core-modules/tool-generator/services/per-object-tool-generator.service';
|
||||
import { type ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
|
||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
|
||||
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import {
|
||||
activateWorkflowVersionSchema,
|
||||
computeStepOutputSchemaSchema,
|
||||
createCompleteWorkflowSchema,
|
||||
createDraftFromWorkflowVersionSchema,
|
||||
createWorkflowVersionEdgeSchema,
|
||||
createWorkflowVersionStepSchema,
|
||||
deactivateWorkflowVersionSchema,
|
||||
deleteWorkflowVersionEdgeSchema,
|
||||
deleteWorkflowVersionStepSchema,
|
||||
updateWorkflowVersionPositionsSchema,
|
||||
updateWorkflowVersionStepSchema,
|
||||
} from 'src/modules/workflow/workflow-tools/schemas/workflow-tool-schemas';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
createWorkflowStepToolsFactory,
|
||||
type WorkflowStepToolsDeps,
|
||||
} from 'src/modules/workflow/workflow-tools/factories/workflow-step-tools.factory';
|
||||
import { createActivateWorkflowVersionTool } from 'src/modules/workflow/workflow-tools/tools/activate-workflow-version.tool';
|
||||
import { createComputeStepOutputSchemaTool } from 'src/modules/workflow/workflow-tools/tools/compute-step-output-schema.tool';
|
||||
import { createCreateCompleteWorkflowTool } from 'src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool';
|
||||
import { createCreateDraftFromWorkflowVersionTool } from 'src/modules/workflow/workflow-tools/tools/create-draft-from-workflow-version.tool';
|
||||
import { createCreateWorkflowVersionEdgeTool } from 'src/modules/workflow/workflow-tools/tools/create-workflow-version-edge.tool';
|
||||
import { createCreateWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool';
|
||||
import { createDeactivateWorkflowVersionTool } from 'src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool';
|
||||
import { createDeleteWorkflowVersionEdgeTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-edge.tool';
|
||||
import { createDeleteWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-step.tool';
|
||||
import { createUpdateWorkflowVersionPositionsTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-positions.tool';
|
||||
import { createUpdateWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-step.tool';
|
||||
import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowToolWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowVersionStepService: WorkflowVersionStepWorkspaceService,
|
||||
private readonly workflowVersionEdgeService: WorkflowVersionEdgeWorkspaceService,
|
||||
private readonly workflowVersionService: WorkflowVersionWorkspaceService,
|
||||
private readonly workflowTriggerService: WorkflowTriggerWorkspaceService,
|
||||
private readonly workflowSchemaService: WorkflowSchemaWorkspaceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly recordPositionService: RecordPositionService,
|
||||
) {}
|
||||
private readonly deps: WorkflowToolDependencies;
|
||||
private readonly workflowStepToolsDeps: WorkflowStepToolsDeps;
|
||||
|
||||
constructor(
|
||||
workflowVersionStepService: WorkflowVersionStepWorkspaceService,
|
||||
workflowVersionEdgeService: WorkflowVersionEdgeWorkspaceService,
|
||||
workflowVersionService: WorkflowVersionWorkspaceService,
|
||||
workflowTriggerService: WorkflowTriggerWorkspaceService,
|
||||
workflowSchemaService: WorkflowSchemaWorkspaceService,
|
||||
twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
recordPositionService: RecordPositionService,
|
||||
private readonly perObjectToolGenerator: PerObjectToolGeneratorService,
|
||||
) {
|
||||
this.deps = {
|
||||
workflowVersionStepService,
|
||||
workflowVersionEdgeService,
|
||||
workflowVersionService,
|
||||
workflowTriggerService,
|
||||
workflowSchemaService,
|
||||
twentyORMGlobalManager,
|
||||
recordPositionService,
|
||||
};
|
||||
|
||||
this.workflowStepToolsDeps = {
|
||||
workflowVersionStepService,
|
||||
};
|
||||
}
|
||||
|
||||
// Generates static workflow tools that don't depend on workspace objects
|
||||
generateWorkflowTools(
|
||||
workspaceId: string,
|
||||
rolePermissionConfig: RolePermissionConfig,
|
||||
): ToolSet {
|
||||
const tools: ToolSet = {};
|
||||
const context = { workspaceId };
|
||||
const contextWithPermissions = { workspaceId, rolePermissionConfig };
|
||||
|
||||
tools.create_complete_workflow = {
|
||||
description: `Create a complete workflow with trigger, steps, and connections in a single operation.
|
||||
const createCompleteWorkflow = createCreateCompleteWorkflowTool(
|
||||
this.deps,
|
||||
contextWithPermissions,
|
||||
);
|
||||
const createWorkflowVersionStep = createCreateWorkflowVersionStepTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
const updateWorkflowVersionStep = createUpdateWorkflowVersionStepTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
const deleteWorkflowVersionStep = createDeleteWorkflowVersionStepTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
const createWorkflowVersionEdge = createCreateWorkflowVersionEdgeTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
const deleteWorkflowVersionEdge = createDeleteWorkflowVersionEdgeTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
const createDraftFromWorkflowVersion =
|
||||
createCreateDraftFromWorkflowVersionTool(this.deps, context);
|
||||
const updateWorkflowVersionPositions =
|
||||
createUpdateWorkflowVersionPositionsTool(this.deps, context);
|
||||
const activateWorkflowVersion = createActivateWorkflowVersionTool(
|
||||
this.deps,
|
||||
);
|
||||
const deactivateWorkflowVersion = createDeactivateWorkflowVersionTool(
|
||||
this.deps,
|
||||
);
|
||||
const computeStepOutputSchema = createComputeStepOutputSchemaTool(
|
||||
this.deps,
|
||||
context,
|
||||
);
|
||||
|
||||
CRITICAL SCHEMA REQUIREMENTS:
|
||||
- Trigger type MUST be one of: DATABASE_EVENT, MANUAL, CRON, WEBHOOK
|
||||
- NEVER use "RECORD_CREATED" - this is invalid. Use "DATABASE_EVENT" instead.
|
||||
- Each step MUST include: id, name, type, valid, settings
|
||||
- CREATE_RECORD actions MUST have objectName and objectRecord in settings.input
|
||||
- objectRecord must contain actual field values, not just field names
|
||||
- Use "trigger" as stepId for trigger step in stepPositions and edges
|
||||
|
||||
Common mistakes to avoid:
|
||||
- Using "RECORD_CREATED" instead of "DATABASE_EVENT"
|
||||
- Missing the "name" and "valid" fields in steps
|
||||
- Missing the "objectRecord" field in CREATE_RECORD actions
|
||||
- Using "fieldsToUpdate" instead of "objectRecord" in CREATE_RECORD actions
|
||||
|
||||
IMPORTANT: The tool schema provides comprehensive field descriptions, examples, and validation rules. Always refer to the schema for:
|
||||
- Field requirements and data types
|
||||
- Common object patterns and field structures
|
||||
- Proper relationship field formats
|
||||
- Variable reference syntax (e.g., {{trigger.object.fieldName}})
|
||||
- Error handling options
|
||||
|
||||
This is the most efficient way for AI to create workflows as it handles all the complexity in one call.`,
|
||||
inputSchema: createCompleteWorkflowSchema,
|
||||
execute: async (parameters: {
|
||||
name: string;
|
||||
description?: string;
|
||||
trigger: WorkflowTrigger;
|
||||
steps: WorkflowAction[];
|
||||
stepPositions?: Array<{
|
||||
stepId: string;
|
||||
position: { x: number; y: number };
|
||||
}>;
|
||||
edges?: Array<{ source: string; target: string }>;
|
||||
activate?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const workflowId = await this.createWorkflow({
|
||||
workspaceId,
|
||||
name: parameters.name,
|
||||
rolePermissionConfig,
|
||||
});
|
||||
|
||||
const workflowVersionId = await this.createWorkflowVersion({
|
||||
workspaceId,
|
||||
workflowId,
|
||||
trigger: parameters.trigger,
|
||||
steps: parameters.steps,
|
||||
rolePermissionConfig,
|
||||
});
|
||||
|
||||
if (parameters.stepPositions && parameters.stepPositions.length > 0) {
|
||||
const positions = parameters.stepPositions.map((pos) => ({
|
||||
id: pos.stepId === 'trigger' ? 'trigger' : pos.stepId,
|
||||
position: pos.position,
|
||||
}));
|
||||
|
||||
await this.workflowVersionService.updateWorkflowVersionPositions({
|
||||
workflowVersionId,
|
||||
positions,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.edges && parameters.edges.length > 0) {
|
||||
for (const edge of parameters.edges) {
|
||||
await this.workflowVersionEdgeService.createWorkflowVersionEdge({
|
||||
source: edge.source === 'trigger' ? 'trigger' : edge.source,
|
||||
target: edge.target,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.activate) {
|
||||
await this.workflowTriggerService.activateWorkflowVersion(
|
||||
workflowVersionId,
|
||||
);
|
||||
|
||||
await this.updateWorkflowStatus({
|
||||
workspaceId,
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
rolePermissionConfig,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Workflow "${parameters.name}" created successfully with ${parameters.steps.length} steps`,
|
||||
result: {
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
name: parameters.name,
|
||||
trigger: parameters.trigger,
|
||||
steps: parameters.steps,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to create workflow "${parameters.name}": ${error.message}`,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
return {
|
||||
[createCompleteWorkflow.name]: createCompleteWorkflow,
|
||||
[createWorkflowVersionStep.name]: createWorkflowVersionStep,
|
||||
[updateWorkflowVersionStep.name]: updateWorkflowVersionStep,
|
||||
[deleteWorkflowVersionStep.name]: deleteWorkflowVersionStep,
|
||||
[createWorkflowVersionEdge.name]: createWorkflowVersionEdge,
|
||||
[deleteWorkflowVersionEdge.name]: deleteWorkflowVersionEdge,
|
||||
[createDraftFromWorkflowVersion.name]: createDraftFromWorkflowVersion,
|
||||
[updateWorkflowVersionPositions.name]: updateWorkflowVersionPositions,
|
||||
[activateWorkflowVersion.name]: activateWorkflowVersion,
|
||||
[deactivateWorkflowVersion.name]: deactivateWorkflowVersion,
|
||||
[computeStepOutputSchema.name]: computeStepOutputSchema,
|
||||
};
|
||||
|
||||
tools.create_workflow_version_step = {
|
||||
description:
|
||||
'Create a new step in a workflow version. This adds a step to the specified workflow version with the given configuration.',
|
||||
inputSchema: createWorkflowVersionStepSchema,
|
||||
execute: async (parameters: CreateWorkflowVersionStepInput) => {
|
||||
try {
|
||||
return await this.workflowVersionStepService.createWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
input: parameters,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.update_workflow_version_step = {
|
||||
description:
|
||||
'Update an existing step in a workflow version. This modifies the step configuration.',
|
||||
inputSchema: updateWorkflowVersionStepSchema,
|
||||
execute: async (parameters: UpdateWorkflowVersionStepInput) => {
|
||||
try {
|
||||
return await this.workflowVersionStepService.updateWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
step: parameters.step,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to update workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.delete_workflow_version_step = {
|
||||
description:
|
||||
'Delete a step from a workflow version. This removes the step and updates the workflow structure.',
|
||||
inputSchema: deleteWorkflowVersionStepSchema,
|
||||
execute: async (parameters: {
|
||||
workflowVersionId: string;
|
||||
stepId: string;
|
||||
}) => {
|
||||
try {
|
||||
return await this.workflowVersionStepService.deleteWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
stepIdToDelete: parameters.stepId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to delete workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.create_workflow_version_edge = {
|
||||
description:
|
||||
'Create a connection (edge) between two workflow steps. This defines the flow between steps.',
|
||||
inputSchema: createWorkflowVersionEdgeSchema,
|
||||
execute: async (parameters: {
|
||||
workflowVersionId: string;
|
||||
source: string;
|
||||
target: string;
|
||||
}) => {
|
||||
try {
|
||||
return await this.workflowVersionEdgeService.createWorkflowVersionEdge(
|
||||
{
|
||||
source: parameters.source,
|
||||
target: parameters.target,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create workflow version edge: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.delete_workflow_version_edge = {
|
||||
description: 'Delete a connection (edge) between workflow steps.',
|
||||
inputSchema: deleteWorkflowVersionEdgeSchema,
|
||||
execute: async (parameters: {
|
||||
workflowVersionId: string;
|
||||
source: string;
|
||||
target: string;
|
||||
}) => {
|
||||
try {
|
||||
return await this.workflowVersionEdgeService.deleteWorkflowVersionEdge(
|
||||
{
|
||||
source: parameters.source,
|
||||
target: parameters.target,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to delete workflow version edge: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.create_draft_from_workflow_version = {
|
||||
description:
|
||||
'Create a new draft workflow version from an existing one. This allows for iterative workflow development.',
|
||||
inputSchema: createDraftFromWorkflowVersionSchema,
|
||||
execute: async (parameters: {
|
||||
workflowId: string;
|
||||
workflowVersionIdToCopy: string;
|
||||
}) => {
|
||||
try {
|
||||
return await this.workflowVersionService.createDraftFromWorkflowVersion(
|
||||
{
|
||||
workspaceId,
|
||||
workflowId: parameters.workflowId,
|
||||
workflowVersionIdToCopy: parameters.workflowVersionIdToCopy,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create draft from workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.update_workflow_version_positions = {
|
||||
description:
|
||||
'Update the positions of multiple workflow steps. This is useful for reorganizing the workflow layout.',
|
||||
inputSchema: updateWorkflowVersionPositionsSchema,
|
||||
execute: async (parameters: UpdateWorkflowVersionPositionsInput) => {
|
||||
try {
|
||||
return await this.workflowVersionService.updateWorkflowVersionPositions(
|
||||
{
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
positions: parameters.positions,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to update workflow version step positions: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.activate_workflow_version = {
|
||||
description:
|
||||
'Activate a workflow version. This makes the workflow version active and available for execution.',
|
||||
inputSchema: activateWorkflowVersionSchema,
|
||||
execute: async (parameters: { workflowVersionId: string }) => {
|
||||
try {
|
||||
return await this.workflowTriggerService.activateWorkflowVersion(
|
||||
parameters.workflowVersionId,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to activate workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.deactivate_workflow_version = {
|
||||
description:
|
||||
'Deactivate a workflow version. This makes the workflow version inactive and unavailable for execution.',
|
||||
inputSchema: deactivateWorkflowVersionSchema,
|
||||
execute: async (parameters: { workflowVersionId: string }) => {
|
||||
try {
|
||||
return await this.workflowTriggerService.deactivateWorkflowVersion(
|
||||
parameters.workflowVersionId,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to deactivate workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tools.compute_step_output_schema = {
|
||||
description:
|
||||
'Compute the output schema for a workflow step. This determines what data the step produces. The step parameter must be a valid WorkflowTrigger or WorkflowAction with the correct settings structure for its type.',
|
||||
inputSchema: computeStepOutputSchemaSchema,
|
||||
execute: async (parameters: {
|
||||
step: WorkflowTrigger | WorkflowAction;
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
try {
|
||||
return await this.workflowSchemaService.computeStepOutputSchema({
|
||||
step: parameters.step,
|
||||
workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to compute step output schema: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
private async createWorkflow({
|
||||
workspaceId,
|
||||
name,
|
||||
rolePermissionConfig,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
rolePermissionConfig: RolePermissionConfig;
|
||||
}): Promise<string> {
|
||||
const workflowRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
// Generates dynamic step configurator tools for each workspace object
|
||||
async generateRecordStepConfiguratorTools(
|
||||
workspaceId: string,
|
||||
rolePermissionConfig: RolePermissionConfig,
|
||||
toolHints?: ToolHints,
|
||||
): Promise<ToolSet> {
|
||||
const workflowStepToolsFactory = createWorkflowStepToolsFactory(
|
||||
this.workflowStepToolsDeps,
|
||||
);
|
||||
|
||||
return this.perObjectToolGenerator.generate(
|
||||
{
|
||||
workspaceId,
|
||||
'workflow',
|
||||
rolePermissionConfig,
|
||||
);
|
||||
|
||||
const workflowPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const workflow = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
};
|
||||
|
||||
await workflowRepository.insert(workflow);
|
||||
|
||||
return workflow.id;
|
||||
}
|
||||
|
||||
private async createWorkflowVersion({
|
||||
workspaceId,
|
||||
workflowId,
|
||||
trigger,
|
||||
steps,
|
||||
rolePermissionConfig,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowId: string;
|
||||
trigger: WorkflowTrigger;
|
||||
steps: WorkflowAction[];
|
||||
rolePermissionConfig: RolePermissionConfig;
|
||||
}): Promise<string> {
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
rolePermissionConfig,
|
||||
);
|
||||
|
||||
const versionPosition =
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const workflowVersion = {
|
||||
id: uuidv4(),
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps,
|
||||
position: versionPosition,
|
||||
};
|
||||
|
||||
await workflowVersionRepository.insert(workflowVersion);
|
||||
|
||||
return workflowVersion.id;
|
||||
}
|
||||
|
||||
private async updateWorkflowStatus({
|
||||
workspaceId,
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
rolePermissionConfig,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowId: string;
|
||||
workflowVersionId: string;
|
||||
rolePermissionConfig: RolePermissionConfig;
|
||||
}) {
|
||||
const workflowRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
workspaceId,
|
||||
'workflow',
|
||||
rolePermissionConfig,
|
||||
);
|
||||
|
||||
await workflowRepository.update(workflowId, {
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
lastPublishedVersionId: workflowVersionId,
|
||||
});
|
||||
},
|
||||
[workflowStepToolsFactory],
|
||||
toolHints,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const activateWorkflowVersionSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to activate'),
|
||||
});
|
||||
|
||||
type ActivateWorkflowVersionInput = z.infer<
|
||||
typeof activateWorkflowVersionSchema
|
||||
>;
|
||||
|
||||
export const createActivateWorkflowVersionTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowTriggerService'>,
|
||||
) => ({
|
||||
name: 'activate_workflow_version' as const,
|
||||
description:
|
||||
'Activate a workflow version. This makes the workflow version active and available for execution.',
|
||||
inputSchema: activateWorkflowVersionSchema,
|
||||
execute: async (parameters: ActivateWorkflowVersionInput) => {
|
||||
try {
|
||||
return await deps.workflowTriggerService.activateWorkflowVersion(
|
||||
parameters.workflowVersionId,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to activate workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
workflowActionSchema,
|
||||
workflowTriggerSchema,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
const computeStepOutputSchemaSchema = z.object({
|
||||
step: z
|
||||
.union([workflowTriggerSchema, workflowActionSchema])
|
||||
.describe('The workflow step configuration'),
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
});
|
||||
|
||||
export const createComputeStepOutputSchemaTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowSchemaService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'compute_step_output_schema' as const,
|
||||
description:
|
||||
'Compute the output schema for a workflow step. This determines what data the step produces. The step parameter must be a valid WorkflowTrigger or WorkflowAction with the correct settings structure for its type.',
|
||||
inputSchema: computeStepOutputSchemaSchema,
|
||||
execute: async (parameters: {
|
||||
step: WorkflowTrigger | WorkflowAction;
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
try {
|
||||
return await deps.workflowSchemaService.computeStepOutputSchema({
|
||||
step: parameters.step,
|
||||
workspaceId: context.workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to compute step output schema: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
import {
|
||||
workflowActionSchema,
|
||||
workflowTriggerSchema,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
const createCompleteWorkflowSchema = z.object({
|
||||
name: z.string().describe('The name of the workflow'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description of the workflow'),
|
||||
trigger: workflowTriggerSchema,
|
||||
steps: z
|
||||
.array(workflowActionSchema)
|
||||
.describe('Array of workflow action steps'),
|
||||
stepPositions: z
|
||||
.array(
|
||||
z.object({
|
||||
stepId: z
|
||||
.string()
|
||||
.describe('The ID of the step (use "trigger" for trigger step)'),
|
||||
position: z.object({
|
||||
x: z.number().describe('X coordinate for the step position'),
|
||||
y: z.number().describe('Y coordinate for the step position'),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe('Optional array of step positions for layout'),
|
||||
edges: z
|
||||
.array(
|
||||
z.object({
|
||||
source: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the source step (use "trigger" for trigger step)',
|
||||
),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe('Optional array of connections between steps'),
|
||||
activate: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to activate the workflow immediately (default: false)'),
|
||||
});
|
||||
|
||||
type CreateCompleteWorkflowToolDeps = Pick<
|
||||
WorkflowToolDependencies,
|
||||
| 'workflowVersionService'
|
||||
| 'workflowVersionEdgeService'
|
||||
| 'workflowTriggerService'
|
||||
| 'twentyORMGlobalManager'
|
||||
| 'recordPositionService'
|
||||
>;
|
||||
|
||||
type CreateCompleteWorkflowToolContext = WorkflowToolContext & {
|
||||
rolePermissionConfig: RolePermissionConfig;
|
||||
};
|
||||
|
||||
export const createCreateCompleteWorkflowTool = (
|
||||
deps: CreateCompleteWorkflowToolDeps,
|
||||
context: CreateCompleteWorkflowToolContext,
|
||||
) => ({
|
||||
name: 'create_complete_workflow' as const,
|
||||
description: `Create a complete workflow with trigger, steps, and connections in a single operation.
|
||||
|
||||
CRITICAL SCHEMA REQUIREMENTS:
|
||||
- Trigger type MUST be one of: DATABASE_EVENT, MANUAL, CRON, WEBHOOK
|
||||
- NEVER use "RECORD_CREATED" - this is invalid. Use "DATABASE_EVENT" instead.
|
||||
- Each step MUST include: id, name, type, valid, settings
|
||||
- CREATE_RECORD actions MUST have objectName and objectRecord in settings.input
|
||||
- objectRecord must contain actual field values, not just field names
|
||||
- Use "trigger" as stepId for trigger step in stepPositions and edges
|
||||
|
||||
Common mistakes to avoid:
|
||||
- Using "RECORD_CREATED" instead of "DATABASE_EVENT"
|
||||
- Missing the "name" and "valid" fields in steps
|
||||
- Missing the "objectRecord" field in CREATE_RECORD actions
|
||||
- Using "fieldsToUpdate" instead of "objectRecord" in CREATE_RECORD actions
|
||||
|
||||
IMPORTANT: The tool schema provides comprehensive field descriptions, examples, and validation rules. Always refer to the schema for:
|
||||
- Field requirements and data types
|
||||
- Common object patterns and field structures
|
||||
- Proper relationship field formats
|
||||
- Variable reference syntax (e.g., {{trigger.object.fieldName}})
|
||||
- Error handling options
|
||||
|
||||
This is the most efficient way for AI to create workflows as it handles all the complexity in one call.`,
|
||||
inputSchema: createCompleteWorkflowSchema,
|
||||
execute: async (parameters: {
|
||||
name: string;
|
||||
description?: string;
|
||||
trigger: WorkflowTrigger;
|
||||
steps: WorkflowAction[];
|
||||
stepPositions?: Array<{
|
||||
stepId: string;
|
||||
position: { x: number; y: number };
|
||||
}>;
|
||||
edges?: Array<{ source: string; target: string }>;
|
||||
activate?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const workflowId = await createWorkflow({
|
||||
deps,
|
||||
context,
|
||||
name: parameters.name,
|
||||
});
|
||||
|
||||
const workflowVersionId = await createWorkflowVersion({
|
||||
deps,
|
||||
context,
|
||||
workflowId,
|
||||
trigger: parameters.trigger,
|
||||
steps: parameters.steps,
|
||||
});
|
||||
|
||||
if (parameters.stepPositions && parameters.stepPositions.length > 0) {
|
||||
const positions = parameters.stepPositions.map((pos) => ({
|
||||
id: pos.stepId === 'trigger' ? 'trigger' : pos.stepId,
|
||||
position: pos.position,
|
||||
}));
|
||||
|
||||
await deps.workflowVersionService.updateWorkflowVersionPositions({
|
||||
workflowVersionId,
|
||||
positions,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.edges && parameters.edges.length > 0) {
|
||||
for (const edge of parameters.edges) {
|
||||
await deps.workflowVersionEdgeService.createWorkflowVersionEdge({
|
||||
source: edge.source === 'trigger' ? 'trigger' : edge.source,
|
||||
target: edge.target,
|
||||
workflowVersionId,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.activate) {
|
||||
await deps.workflowTriggerService.activateWorkflowVersion(
|
||||
workflowVersionId,
|
||||
);
|
||||
|
||||
await updateWorkflowStatus({
|
||||
deps,
|
||||
context,
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Workflow "${parameters.name}" created successfully with ${parameters.steps.length} steps`,
|
||||
result: {
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
name: parameters.name,
|
||||
trigger: parameters.trigger,
|
||||
steps: parameters.steps,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to create workflow "${parameters.name}": ${error.message}`,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const createWorkflow = async ({
|
||||
deps,
|
||||
context,
|
||||
name,
|
||||
}: {
|
||||
deps: CreateCompleteWorkflowToolDeps;
|
||||
context: CreateCompleteWorkflowToolContext;
|
||||
name: string;
|
||||
}): Promise<string> => {
|
||||
const workflowRepository =
|
||||
await deps.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
const workflowPosition = await deps.recordPositionService.buildRecordPosition(
|
||||
{
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const workflow = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
};
|
||||
|
||||
await workflowRepository.insert(workflow);
|
||||
|
||||
return workflow.id;
|
||||
};
|
||||
|
||||
const createWorkflowVersion = async ({
|
||||
deps,
|
||||
context,
|
||||
workflowId,
|
||||
trigger,
|
||||
steps,
|
||||
}: {
|
||||
deps: CreateCompleteWorkflowToolDeps;
|
||||
context: CreateCompleteWorkflowToolContext;
|
||||
workflowId: string;
|
||||
trigger: WorkflowTrigger;
|
||||
steps: WorkflowAction[];
|
||||
}): Promise<string> => {
|
||||
const workflowVersionRepository =
|
||||
await deps.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
context.workspaceId,
|
||||
'workflowVersion',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
const versionPosition = await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const workflowVersion = {
|
||||
id: uuidv4(),
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps,
|
||||
position: versionPosition,
|
||||
};
|
||||
|
||||
await workflowVersionRepository.insert(workflowVersion);
|
||||
|
||||
return workflowVersion.id;
|
||||
};
|
||||
|
||||
const updateWorkflowStatus = async ({
|
||||
deps,
|
||||
context,
|
||||
workflowId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
deps: CreateCompleteWorkflowToolDeps;
|
||||
context: CreateCompleteWorkflowToolContext;
|
||||
workflowId: string;
|
||||
workflowVersionId: string;
|
||||
}) => {
|
||||
const workflowRepository =
|
||||
await deps.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
await workflowRepository.update(workflowId, {
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
lastPublishedVersionId: workflowVersionId,
|
||||
});
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const createDraftFromWorkflowVersionSchema = z.object({
|
||||
workflowId: z.string().describe('The ID of the workflow'),
|
||||
workflowVersionIdToCopy: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to create a draft from'),
|
||||
});
|
||||
|
||||
type CreateDraftFromWorkflowVersionInput = z.infer<
|
||||
typeof createDraftFromWorkflowVersionSchema
|
||||
>;
|
||||
|
||||
export const createCreateDraftFromWorkflowVersionTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'create_draft_from_workflow_version' as const,
|
||||
description:
|
||||
'Create a new draft workflow version from an existing one. This allows for iterative workflow development.',
|
||||
inputSchema: createDraftFromWorkflowVersionSchema,
|
||||
execute: async (parameters: CreateDraftFromWorkflowVersionInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionService.createDraftFromWorkflowVersion({
|
||||
workspaceId: context.workspaceId,
|
||||
workflowId: parameters.workflowId,
|
||||
workflowVersionIdToCopy: parameters.workflowVersionIdToCopy,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create draft from workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const createWorkflowVersionEdgeSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
source: z.string().describe('The ID of the source step'),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
sourceConnectionOptions: z
|
||||
.object({
|
||||
connectedStepType: z.literal(WorkflowActionType.ITERATOR),
|
||||
settings: z.object({
|
||||
isConnectedToLoop: z.boolean(),
|
||||
}),
|
||||
})
|
||||
.optional()
|
||||
.describe('Optional connection options for iterator steps'),
|
||||
});
|
||||
|
||||
type CreateWorkflowVersionEdgeInput = z.infer<
|
||||
typeof createWorkflowVersionEdgeSchema
|
||||
>;
|
||||
|
||||
export const createCreateWorkflowVersionEdgeTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionEdgeService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'create_workflow_version_edge' as const,
|
||||
description:
|
||||
'Create a connection (edge) between two workflow steps. This defines the flow between steps.',
|
||||
inputSchema: createWorkflowVersionEdgeSchema,
|
||||
execute: async (parameters: CreateWorkflowVersionEdgeInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionEdgeService.createWorkflowVersionEdge({
|
||||
source: parameters.source,
|
||||
target: parameters.target,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
workspaceId: context.workspaceId,
|
||||
sourceConnectionOptions: parameters.sourceConnectionOptions,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create workflow version edge: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const createWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to add the step to'),
|
||||
stepType: z
|
||||
.enum(Object.values(WorkflowActionType) as [string, ...string[]])
|
||||
.describe('The type of step to create'),
|
||||
parentStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the parent step this step should come after'),
|
||||
parentStepConnectionOptions: z
|
||||
.object({
|
||||
type: z.string().optional(),
|
||||
conditionGroupIndex: z.number().optional(),
|
||||
})
|
||||
.optional()
|
||||
.describe('Optional parent step connection options'),
|
||||
nextStepId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional ID of the step this new step should connect to'),
|
||||
position: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.optional()
|
||||
.describe('Optional position coordinates for the step'),
|
||||
id: z.string().optional().describe('Optional step ID'),
|
||||
});
|
||||
|
||||
export const createCreateWorkflowVersionStepTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionStepService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'create_workflow_version_step' as const,
|
||||
description:
|
||||
'Create a new step in a workflow version. This adds a step to the specified workflow version with the given configuration.',
|
||||
inputSchema: createWorkflowVersionStepSchema,
|
||||
execute: async (parameters: CreateWorkflowVersionStepInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionStepService.createWorkflowVersionStep({
|
||||
workspaceId: context.workspaceId,
|
||||
input: parameters,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to create workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const deactivateWorkflowVersionSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version to deactivate'),
|
||||
});
|
||||
|
||||
type DeactivateWorkflowVersionInput = z.infer<
|
||||
typeof deactivateWorkflowVersionSchema
|
||||
>;
|
||||
|
||||
export const createDeactivateWorkflowVersionTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowTriggerService'>,
|
||||
) => ({
|
||||
name: 'deactivate_workflow_version' as const,
|
||||
description:
|
||||
'Deactivate a workflow version. This makes the workflow version inactive and unavailable for execution.',
|
||||
inputSchema: deactivateWorkflowVersionSchema,
|
||||
execute: async (parameters: DeactivateWorkflowVersionInput) => {
|
||||
try {
|
||||
return await deps.workflowTriggerService.deactivateWorkflowVersion(
|
||||
parameters.workflowVersionId,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to deactivate workflow version: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const deleteWorkflowVersionEdgeSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
source: z.string().describe('The ID of the source step'),
|
||||
target: z.string().describe('The ID of the target step'),
|
||||
sourceConnectionOptions: z
|
||||
.object({
|
||||
connectedStepType: z.literal(WorkflowActionType.ITERATOR),
|
||||
settings: z.object({
|
||||
isConnectedToLoop: z.boolean(),
|
||||
}),
|
||||
})
|
||||
.optional()
|
||||
.describe('Optional connection options for iterator steps'),
|
||||
});
|
||||
|
||||
type DeleteWorkflowVersionEdgeInput = z.infer<
|
||||
typeof deleteWorkflowVersionEdgeSchema
|
||||
>;
|
||||
|
||||
export const createDeleteWorkflowVersionEdgeTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionEdgeService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'delete_workflow_version_edge' as const,
|
||||
description: 'Delete a connection (edge) between workflow steps.',
|
||||
inputSchema: deleteWorkflowVersionEdgeSchema,
|
||||
execute: async (parameters: DeleteWorkflowVersionEdgeInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionEdgeService.deleteWorkflowVersionEdge({
|
||||
source: parameters.source,
|
||||
target: parameters.target,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
workspaceId: context.workspaceId,
|
||||
sourceConnectionOptions: parameters.sourceConnectionOptions,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to delete workflow version edge: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const deleteWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version containing the step'),
|
||||
stepId: z.string().describe('The ID of the step to delete'),
|
||||
});
|
||||
|
||||
type DeleteWorkflowVersionStepInput = z.infer<
|
||||
typeof deleteWorkflowVersionStepSchema
|
||||
>;
|
||||
|
||||
export const createDeleteWorkflowVersionStepTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionStepService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'delete_workflow_version_step' as const,
|
||||
description:
|
||||
'Delete a step from a workflow version. This removes the step and updates the workflow structure.',
|
||||
inputSchema: deleteWorkflowVersionStepSchema,
|
||||
execute: async (parameters: DeleteWorkflowVersionStepInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionStepService.deleteWorkflowVersionStep({
|
||||
workspaceId: context.workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
stepIdToDelete: parameters.stepId,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to delete workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const updateWorkflowVersionPositionsSchema = z.object({
|
||||
workflowVersionId: z.string().describe('The ID of the workflow version'),
|
||||
positions: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string().describe('Step or trigger ID'),
|
||||
position: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.describe('Array of step positions to update'),
|
||||
});
|
||||
|
||||
export const createUpdateWorkflowVersionPositionsTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'update_workflow_version_positions' as const,
|
||||
description:
|
||||
'Update the positions of multiple workflow steps. This is useful for reorganizing the workflow layout.',
|
||||
inputSchema: updateWorkflowVersionPositionsSchema,
|
||||
execute: async (parameters: UpdateWorkflowVersionPositionsInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionService.updateWorkflowVersionPositions({
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
positions: parameters.positions,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to update workflow version step positions: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
workflowActionSchema,
|
||||
workflowTriggerSchema,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { UpdateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-step-input.dto';
|
||||
import {
|
||||
type WorkflowToolContext,
|
||||
type WorkflowToolDependencies,
|
||||
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
|
||||
|
||||
const updateWorkflowVersionStepSchema = z.object({
|
||||
workflowVersionId: z
|
||||
.string()
|
||||
.describe('The ID of the workflow version containing the step'),
|
||||
step: z
|
||||
.union([workflowTriggerSchema, workflowActionSchema])
|
||||
.describe('The updated step configuration'),
|
||||
});
|
||||
|
||||
export const createUpdateWorkflowVersionStepTool = (
|
||||
deps: Pick<WorkflowToolDependencies, 'workflowVersionStepService'>,
|
||||
context: WorkflowToolContext,
|
||||
) => ({
|
||||
name: 'update_workflow_version_step' as const,
|
||||
description:
|
||||
'Update an existing step in a workflow version. This modifies the step configuration.',
|
||||
inputSchema: updateWorkflowVersionStepSchema,
|
||||
execute: async (parameters: UpdateWorkflowVersionStepInput) => {
|
||||
try {
|
||||
return await deps.workflowVersionStepService.updateWorkflowVersionStep({
|
||||
workspaceId: context.workspaceId,
|
||||
workflowVersionId: parameters.workflowVersionId,
|
||||
step: parameters.step,
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
message: `Failed to update workflow version step: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import type { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import type { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
|
||||
import type { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
|
||||
import type { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
||||
import type { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||
|
||||
export type WorkflowToolDependencies = {
|
||||
workflowVersionStepService: WorkflowVersionStepWorkspaceService;
|
||||
workflowVersionEdgeService: WorkflowVersionEdgeWorkspaceService;
|
||||
workflowVersionService: WorkflowVersionWorkspaceService;
|
||||
workflowTriggerService: WorkflowTriggerWorkspaceService;
|
||||
workflowSchemaService: WorkflowSchemaWorkspaceService;
|
||||
twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
recordPositionService: RecordPositionService;
|
||||
};
|
||||
|
||||
export type WorkflowToolContext = {
|
||||
workspaceId: string;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||
import { ToolGeneratorModule } from 'src/engine/core-modules/tool-generator/tool-generator.module';
|
||||
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
|
||||
import { WorkflowVersionEdgeModule } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module';
|
||||
import { WorkflowVersionStepModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module';
|
||||
@@ -17,6 +18,7 @@ import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace
|
||||
WorkflowTriggerModule,
|
||||
WorkflowSchemaModule,
|
||||
RecordPositionModule,
|
||||
ToolGeneratorModule,
|
||||
],
|
||||
providers: [WorkflowToolWorkspaceService],
|
||||
exports: [WorkflowToolWorkspaceService],
|
||||
|
||||
Reference in New Issue
Block a user