|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
ConditionalWorkflow, |
|
|
WorkflowStep, |
|
|
WorkflowJSON, |
|
|
StepType, |
|
|
Condition, |
|
|
CreateWorkflowRequest, |
|
|
UpdateWorkflowRequest, |
|
|
workflowToJSON, |
|
|
validateWorkflow |
|
|
} from './conditional-workflow-types'; |
|
|
|
|
|
export class WorkflowBuilder { |
|
|
public workflow: Partial<ConditionalWorkflow>; |
|
|
public steps: Map<string, WorkflowStep>; |
|
|
public stepOrder: number = 1; |
|
|
|
|
|
constructor(agentId: string, name: string) { |
|
|
this.workflow = { |
|
|
agent_id: agentId, |
|
|
name, |
|
|
status: 'draft', |
|
|
is_default: false, |
|
|
created_at: new Date().toISOString(), |
|
|
updated_at: new Date().toISOString() |
|
|
}; |
|
|
this.steps = new Map(); |
|
|
} |
|
|
|
|
|
|
|
|
setDescription(description: string): this { |
|
|
this.workflow.description = description; |
|
|
return this; |
|
|
} |
|
|
|
|
|
setTriggerPhrase(phrase: string): this { |
|
|
this.workflow.trigger_phrase = phrase; |
|
|
return this; |
|
|
} |
|
|
|
|
|
setAsDefault(isDefault: boolean = true): this { |
|
|
this.workflow.is_default = isDefault; |
|
|
return this; |
|
|
} |
|
|
|
|
|
setStatus(status: 'draft' | 'active' | 'paused' | 'archived'): this { |
|
|
this.workflow.status = status; |
|
|
return this; |
|
|
} |
|
|
|
|
|
setRootStep(stepId: string): this { |
|
|
this.workflow.root_step_id = stepId; |
|
|
return this; |
|
|
} |
|
|
|
|
|
setVariables(variables: Record<string, any>): this { |
|
|
this.workflow.variables = variables; |
|
|
return this; |
|
|
} |
|
|
|
|
|
|
|
|
addInstructionStep( |
|
|
id: string, |
|
|
name: string, |
|
|
instruction: string, |
|
|
options: { |
|
|
description?: string; |
|
|
nextStepId?: string; |
|
|
order?: number; |
|
|
} = {} |
|
|
): this { |
|
|
const step: WorkflowStep = { |
|
|
id, |
|
|
name, |
|
|
description: options.description, |
|
|
type: 'instruction', |
|
|
order: options.order || this.stepOrder++, |
|
|
instruction, |
|
|
next_step_id: options.nextStepId |
|
|
}; |
|
|
|
|
|
this.steps.set(id, step); |
|
|
return this; |
|
|
} |
|
|
|
|
|
addToolStep( |
|
|
id: string, |
|
|
name: string, |
|
|
toolName: string, |
|
|
options: { |
|
|
description?: string; |
|
|
nextStepId?: string; |
|
|
order?: number; |
|
|
} = {} |
|
|
): this { |
|
|
const step: WorkflowStep = { |
|
|
id, |
|
|
name, |
|
|
description: options.description, |
|
|
type: 'instruction', |
|
|
order: options.order || this.stepOrder++, |
|
|
tool_name: toolName, |
|
|
next_step_id: options.nextStepId |
|
|
}; |
|
|
|
|
|
this.steps.set(id, step); |
|
|
return this; |
|
|
} |
|
|
|
|
|
addIfStep( |
|
|
id: string, |
|
|
name: string, |
|
|
condition: Condition, |
|
|
options: { |
|
|
description?: string; |
|
|
ifTrueStepId?: string; |
|
|
ifFalseStepId?: string; |
|
|
order?: number; |
|
|
} = {} |
|
|
): this { |
|
|
const step: WorkflowStep = { |
|
|
id, |
|
|
name, |
|
|
description: options.description, |
|
|
type: 'if', |
|
|
order: options.order || this.stepOrder++, |
|
|
condition, |
|
|
if_true_step_id: options.ifTrueStepId, |
|
|
if_false_step_id: options.ifFalseStepId |
|
|
}; |
|
|
|
|
|
this.steps.set(id, step); |
|
|
return this; |
|
|
} |
|
|
|
|
|
addSequenceStep( |
|
|
id: string, |
|
|
name: string, |
|
|
childStepIds: string[], |
|
|
options: { |
|
|
description?: string; |
|
|
nextStepId?: string; |
|
|
order?: number; |
|
|
} = {} |
|
|
): this { |
|
|
const step: WorkflowStep = { |
|
|
id, |
|
|
name, |
|
|
description: options.description, |
|
|
type: 'sequence', |
|
|
order: options.order || this.stepOrder++, |
|
|
child_step_ids: childStepIds, |
|
|
next_step_id: options.nextStepId |
|
|
}; |
|
|
|
|
|
this.steps.set(id, step); |
|
|
return this; |
|
|
} |
|
|
|
|
|
addTriggerStep( |
|
|
id: string, |
|
|
name: string, |
|
|
options: { |
|
|
description?: string; |
|
|
nextStepId?: string; |
|
|
order?: number; |
|
|
} = {} |
|
|
): this { |
|
|
const step: WorkflowStep = { |
|
|
id, |
|
|
name, |
|
|
description: options.description, |
|
|
type: 'trigger', |
|
|
order: options.order || this.stepOrder++, |
|
|
next_step_id: options.nextStepId |
|
|
}; |
|
|
|
|
|
this.steps.set(id, step); |
|
|
return this; |
|
|
} |
|
|
|
|
|
|
|
|
updateStep( |
|
|
id: string, |
|
|
updates: Partial<Omit<WorkflowStep, 'id'>> |
|
|
): this { |
|
|
const existingStep = this.steps.get(id); |
|
|
if (existingStep) { |
|
|
this.steps.set(id, { ...existingStep, ...updates }); |
|
|
} |
|
|
return this; |
|
|
} |
|
|
|
|
|
|
|
|
connectSteps(fromStepId: string, toStepId: string): this { |
|
|
const fromStep = this.steps.get(fromStepId); |
|
|
if (fromStep) { |
|
|
fromStep.next_step_id = toStepId; |
|
|
this.steps.set(fromStepId, fromStep); |
|
|
} |
|
|
return this; |
|
|
} |
|
|
|
|
|
connectIfBranch( |
|
|
ifStepId: string, |
|
|
trueStepId?: string, |
|
|
falseStepId?: string |
|
|
): this { |
|
|
const ifStep = this.steps.get(ifStepId); |
|
|
if (ifStep && ifStep.type === 'if') { |
|
|
if (trueStepId) ifStep.if_true_step_id = trueStepId; |
|
|
if (falseStepId) ifStep.if_false_step_id = falseStepId; |
|
|
this.steps.set(ifStepId, ifStep); |
|
|
} |
|
|
return this; |
|
|
} |
|
|
|
|
|
|
|
|
removeStep(id: string): this { |
|
|
this.steps.delete(id); |
|
|
|
|
|
|
|
|
for (const [stepId, step] of this.steps) { |
|
|
if (step.next_step_id === id) { |
|
|
step.next_step_id = undefined; |
|
|
} |
|
|
if (step.if_true_step_id === id) { |
|
|
step.if_true_step_id = undefined; |
|
|
} |
|
|
if (step.if_false_step_id === id) { |
|
|
step.if_false_step_id = undefined; |
|
|
} |
|
|
if (step.child_step_ids?.includes(id)) { |
|
|
step.child_step_ids = step.child_step_ids.filter(childId => childId !== id); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this.workflow.root_step_id === id) { |
|
|
this.workflow.root_step_id = undefined; |
|
|
} |
|
|
|
|
|
return this; |
|
|
} |
|
|
|
|
|
|
|
|
build(): ConditionalWorkflow { |
|
|
if (!this.workflow.id) { |
|
|
this.workflow.id = `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
|
} |
|
|
|
|
|
const workflow = { |
|
|
...this.workflow, |
|
|
steps: Array.from(this.steps.values()).sort((a, b) => a.order - b.order) |
|
|
} as ConditionalWorkflow; |
|
|
|
|
|
|
|
|
if (!workflow.root_step_id && workflow.steps.length > 0) { |
|
|
workflow.root_step_id = workflow.steps[0].id; |
|
|
} |
|
|
|
|
|
return workflow; |
|
|
} |
|
|
|
|
|
|
|
|
buildJSON(): WorkflowJSON { |
|
|
const workflow = this.build(); |
|
|
return workflowToJSON(workflow); |
|
|
} |
|
|
|
|
|
|
|
|
buildCreateRequest(): CreateWorkflowRequest { |
|
|
const workflow = this.build(); |
|
|
|
|
|
return { |
|
|
name: workflow.name, |
|
|
description: workflow.description, |
|
|
trigger_phrase: workflow.trigger_phrase, |
|
|
is_default: workflow.is_default, |
|
|
steps: workflow.steps.map(step => ({ |
|
|
name: step.name, |
|
|
description: step.description, |
|
|
type: step.type, |
|
|
instruction: step.instruction, |
|
|
tool_name: step.tool_name, |
|
|
condition: step.condition, |
|
|
if_true_step_id: step.if_true_step_id, |
|
|
if_false_step_id: step.if_false_step_id, |
|
|
child_step_ids: step.child_step_ids, |
|
|
next_step_id: step.next_step_id, |
|
|
order: step.order |
|
|
})), |
|
|
root_step_id: workflow.root_step_id, |
|
|
variables: workflow.variables |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
validate(): { isValid: boolean; errors: string[] } { |
|
|
const workflow = this.build(); |
|
|
return validateWorkflow(workflow); |
|
|
} |
|
|
|
|
|
|
|
|
clone(): WorkflowBuilder { |
|
|
const newBuilder = new WorkflowBuilder( |
|
|
this.workflow.agent_id!, |
|
|
this.workflow.name! |
|
|
); |
|
|
|
|
|
newBuilder.workflow = { ...this.workflow }; |
|
|
newBuilder.steps = new Map(this.steps); |
|
|
newBuilder.stepOrder = this.stepOrder; |
|
|
|
|
|
return newBuilder; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function createLinearWorkflow( |
|
|
agentId: string, |
|
|
name: string, |
|
|
steps: Array<{ |
|
|
id: string; |
|
|
name: string; |
|
|
instruction: string; |
|
|
description?: string; |
|
|
}> |
|
|
): ConditionalWorkflow { |
|
|
const builder = new WorkflowBuilder(agentId, name); |
|
|
|
|
|
for (let i = 0; i < steps.length; i++) { |
|
|
const step = steps[i]; |
|
|
const nextStepId = i < steps.length - 1 ? steps[i + 1].id : undefined; |
|
|
|
|
|
builder.addInstructionStep(step.id, step.name, step.instruction, { |
|
|
description: step.description, |
|
|
nextStepId |
|
|
}); |
|
|
} |
|
|
|
|
|
return builder.build(); |
|
|
} |
|
|
|
|
|
|
|
|
export function createConditionalWorkflow( |
|
|
agentId: string, |
|
|
name: string, |
|
|
initialStep: { id: string; name: string; instruction: string }, |
|
|
condition: { id: string; name: string; condition: Condition }, |
|
|
trueBranch: { id: string; name: string; instruction: string }, |
|
|
falseBranch: { id: string; name: string; instruction: string } |
|
|
): ConditionalWorkflow { |
|
|
const builder = new WorkflowBuilder(agentId, name); |
|
|
|
|
|
|
|
|
builder.addInstructionStep( |
|
|
initialStep.id, |
|
|
initialStep.name, |
|
|
initialStep.instruction, |
|
|
{ nextStepId: condition.id } |
|
|
); |
|
|
|
|
|
|
|
|
builder.addIfStep( |
|
|
condition.id, |
|
|
condition.name, |
|
|
condition.condition, |
|
|
{ |
|
|
ifTrueStepId: trueBranch.id, |
|
|
ifFalseStepId: falseBranch.id |
|
|
} |
|
|
); |
|
|
|
|
|
|
|
|
builder.addInstructionStep( |
|
|
trueBranch.id, |
|
|
trueBranch.name, |
|
|
trueBranch.instruction |
|
|
); |
|
|
|
|
|
builder.addInstructionStep( |
|
|
falseBranch.id, |
|
|
falseBranch.name, |
|
|
falseBranch.instruction |
|
|
); |
|
|
|
|
|
return builder.build(); |
|
|
} |
|
|
|
|
|
|
|
|
export function createWorkflowFromJSON(json: WorkflowJSON): WorkflowBuilder { |
|
|
const builder = new WorkflowBuilder( |
|
|
json.workflow.agent_id, |
|
|
json.workflow.name |
|
|
); |
|
|
|
|
|
builder.workflow.id = json.workflow.id; |
|
|
builder.workflow.description = json.workflow.description; |
|
|
builder.workflow.status = json.workflow.status; |
|
|
builder.workflow.trigger_phrase = json.workflow.trigger_phrase; |
|
|
builder.workflow.is_default = json.workflow.is_default; |
|
|
builder.workflow.created_at = json.workflow.created_at; |
|
|
builder.workflow.updated_at = json.workflow.updated_at; |
|
|
builder.workflow.root_step_id = json.flow.root_step_id; |
|
|
builder.workflow.variables = json.flow.variables; |
|
|
|
|
|
|
|
|
for (const step of json.flow.steps) { |
|
|
builder.steps.set(step.id, step); |
|
|
} |
|
|
|
|
|
return builder; |
|
|
} |
|
|
|
|
|
|
|
|
export function generateStepId(prefix: string = 'step'): string { |
|
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`; |
|
|
} |
|
|
|
|
|
|
|
|
export function generateWorkflowId(): string { |
|
|
return `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
|
} |
|
|
|
|
|
|
|
|
export const STEP_TYPES: Record<string, StepType> = { |
|
|
INSTRUCTION: 'instruction', |
|
|
IF: 'if', |
|
|
SEQUENCE: 'sequence', |
|
|
TRIGGER: 'trigger' |
|
|
}; |
|
|
|
|
|
|
|
|
export const CONDITION_OPERATIONS = { |
|
|
CONTAINS: 'contains', |
|
|
EQUALS: 'equals', |
|
|
NOT_EQUALS: 'not_equals', |
|
|
IS_EMPTY: 'is_empty', |
|
|
IS_NOT_EMPTY: 'is_not_empty' |
|
|
} as const; |