@geenius/ai-workflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/config.json +11 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.nvmrc +1 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +11 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONTRIBUTING.md +26 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +15 -0
- package/SUPPORT.md +8 -0
- package/package.json +74 -0
- package/packages/convex/README.md +1 -0
- package/packages/convex/package.json +12 -0
- package/packages/convex/src/convex.config.ts +3 -0
- package/packages/convex/src/index.ts +3 -0
- package/packages/convex/src/mutations.ts +36 -0
- package/packages/convex/src/queries.ts +19 -0
- package/packages/convex/src/schema.ts +24 -0
- package/packages/convex/tsconfig.json +25 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +46 -0
- package/packages/react/src/components/ApprovalModal.tsx +47 -0
- package/packages/react/src/components/StepConfigPanel.tsx +67 -0
- package/packages/react/src/components/StepConnector.tsx +47 -0
- package/packages/react/src/components/StepNode.tsx +38 -0
- package/packages/react/src/components/StepPalette.tsx +48 -0
- package/packages/react/src/components/WorkflowCanvas.tsx +42 -0
- package/packages/react/src/components/WorkflowRunPanel.tsx +64 -0
- package/packages/react/src/components/WorkflowToolbar.tsx +43 -0
- package/packages/react/src/components/index.ts +9 -0
- package/packages/react/src/hooks/index.ts +10 -0
- package/packages/react/src/hooks/useApprovalGate.ts +59 -0
- package/packages/react/src/hooks/useWorkflow.ts +39 -0
- package/packages/react/src/hooks/useWorkflowBuilder.ts +121 -0
- package/packages/react/src/hooks/useWorkflowRun.ts +75 -0
- package/packages/react/src/hooks/useWorkflowStep.ts +52 -0
- package/packages/react/src/hooks/useWorkflowTemplates.ts +54 -0
- package/packages/react/src/index.ts +16 -0
- package/packages/react/src/pages/WorkflowBuilderPage.tsx +81 -0
- package/packages/react/src/pages/WorkflowRunsPage.tsx +59 -0
- package/packages/react/src/pages/index.ts +3 -0
- package/packages/react/tsconfig.json +1 -0
- package/packages/react/tsup.config.ts +7 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +44 -0
- package/packages/react-css/src/components/ApprovalModal.tsx +6 -0
- package/packages/react-css/src/components/StepConfigPanel.tsx +7 -0
- package/packages/react-css/src/components/StepConnector.tsx +6 -0
- package/packages/react-css/src/components/StepNode.tsx +7 -0
- package/packages/react-css/src/components/StepPalette.tsx +6 -0
- package/packages/react-css/src/components/WorkflowCanvas.tsx +6 -0
- package/packages/react-css/src/components/WorkflowRunPanel.tsx +9 -0
- package/packages/react-css/src/components/WorkflowToolbar.tsx +4 -0
- package/packages/react-css/src/components/index.ts +9 -0
- package/packages/react-css/src/hooks/index.ts +3 -0
- package/packages/react-css/src/hooks/useWorkflow.ts +39 -0
- package/packages/react-css/src/hooks/useWorkflowBuilder.ts +121 -0
- package/packages/react-css/src/index.ts +7 -0
- package/packages/react-css/src/pages/WorkflowBuilderPage.tsx +16 -0
- package/packages/react-css/src/pages/WorkflowRunsPage.tsx +6 -0
- package/packages/react-css/src/pages/index.ts +3 -0
- package/packages/react-css/src/styles.css +945 -0
- package/packages/react-css/tsconfig.json +26 -0
- package/packages/react-css/tsup.config.ts +2 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +56 -0
- package/packages/shared/src/__tests__/ai-workflow.test.ts +217 -0
- package/packages/shared/src/config.ts +49 -0
- package/packages/shared/src/convex/index.ts +2 -0
- package/packages/shared/src/convex/schemas.ts +42 -0
- package/packages/shared/src/engine.test.ts +1 -0
- package/packages/shared/src/engine.ts +295 -0
- package/packages/shared/src/index.ts +43 -0
- package/packages/shared/src/steps.ts +68 -0
- package/packages/shared/src/templates.ts +172 -0
- package/packages/shared/src/types.ts +237 -0
- package/packages/shared/src/utils/cost.ts +79 -0
- package/packages/shared/src/utils/dag.ts +133 -0
- package/packages/shared/src/utils/index.ts +5 -0
- package/packages/shared/src/utils/interpolation.ts +53 -0
- package/packages/shared/src/validators.ts +215 -0
- package/packages/shared/tsconfig.json +1 -0
- package/packages/shared/tsup.config.ts +5 -0
- package/packages/shared/vitest.config.ts +4 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +45 -0
- package/packages/solidjs/src/components/ApprovalModal.tsx +18 -0
- package/packages/solidjs/src/components/StepConfigPanel.tsx +14 -0
- package/packages/solidjs/src/components/StepConnector.tsx +11 -0
- package/packages/solidjs/src/components/StepNode.tsx +12 -0
- package/packages/solidjs/src/components/StepPalette.tsx +22 -0
- package/packages/solidjs/src/components/WorkflowCanvas.tsx +23 -0
- package/packages/solidjs/src/components/WorkflowRunPanel.tsx +18 -0
- package/packages/solidjs/src/components/WorkflowToolbar.tsx +13 -0
- package/packages/solidjs/src/components/index.ts +9 -0
- package/packages/solidjs/src/index.ts +7 -0
- package/packages/solidjs/src/pages/WorkflowBuilderPage.tsx +37 -0
- package/packages/solidjs/src/pages/WorkflowRunsPage.tsx +20 -0
- package/packages/solidjs/src/pages/index.ts +3 -0
- package/packages/solidjs/src/primitives/createApprovalGate.ts +29 -0
- package/packages/solidjs/src/primitives/createWorkflow.ts +28 -0
- package/packages/solidjs/src/primitives/createWorkflowBuilder.ts +56 -0
- package/packages/solidjs/src/primitives/createWorkflowRun.ts +32 -0
- package/packages/solidjs/src/primitives/createWorkflowStep.ts +23 -0
- package/packages/solidjs/src/primitives/createWorkflowTemplates.ts +28 -0
- package/packages/solidjs/src/primitives/index.ts +8 -0
- package/packages/solidjs/tsconfig.json +1 -0
- package/packages/solidjs/tsup.config.ts +7 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +43 -0
- package/packages/solidjs-css/src/components/ApprovalModal.tsx +6 -0
- package/packages/solidjs-css/src/components/StepConfigPanel.tsx +7 -0
- package/packages/solidjs-css/src/components/StepConnector.tsx +6 -0
- package/packages/solidjs-css/src/components/StepNode.tsx +7 -0
- package/packages/solidjs-css/src/components/StepPalette.tsx +7 -0
- package/packages/solidjs-css/src/components/WorkflowCanvas.tsx +7 -0
- package/packages/solidjs-css/src/components/WorkflowRunPanel.tsx +8 -0
- package/packages/solidjs-css/src/components/WorkflowToolbar.tsx +5 -0
- package/packages/solidjs-css/src/components/index.ts +9 -0
- package/packages/solidjs-css/src/index.ts +7 -0
- package/packages/solidjs-css/src/pages/WorkflowBuilderPage.tsx +2 -0
- package/packages/solidjs-css/src/pages/WorkflowRunsPage.tsx +7 -0
- package/packages/solidjs-css/src/pages/index.ts +3 -0
- package/packages/solidjs-css/src/primitives/createWorkflow.ts +28 -0
- package/packages/solidjs-css/src/primitives/createWorkflowBuilder.ts +56 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/src/styles.css +945 -0
- package/packages/solidjs-css/tsconfig.json +27 -0
- package/packages/solidjs-css/tsup.config.ts +2 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// @geenius-ai-workflow/shared — src/engine.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workflow execution engine — DAG-based step execution with variable passing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
WorkflowDefinition,
|
|
9
|
+
WorkflowStepDef,
|
|
10
|
+
WorkflowRun,
|
|
11
|
+
StepResult,
|
|
12
|
+
RunStatus,
|
|
13
|
+
StepStatus,
|
|
14
|
+
LLMCallConfig,
|
|
15
|
+
TransformConfig,
|
|
16
|
+
ConditionConfig,
|
|
17
|
+
DelayConfig,
|
|
18
|
+
} from './types'
|
|
19
|
+
|
|
20
|
+
export interface WorkflowEngineOptions {
|
|
21
|
+
/** LLM call function for llm-call steps */
|
|
22
|
+
callLLM?: (systemPrompt: string, userPrompt: string, model?: string) => Promise<{
|
|
23
|
+
content: string; tokens?: number; costUsd?: number
|
|
24
|
+
}>
|
|
25
|
+
/** Webhook call function */
|
|
26
|
+
callWebhook?: (url: string, method: string, body?: string, headers?: Record<string, string>) => Promise<string>
|
|
27
|
+
/** Human approval callback */
|
|
28
|
+
onApprovalRequired?: (message: string, stepId: string) => Promise<boolean>
|
|
29
|
+
/** Step event callback */
|
|
30
|
+
onStepComplete?: (result: StepResult) => void
|
|
31
|
+
/** Custom step handlers */
|
|
32
|
+
customHandlers?: Record<string, (params: Record<string, unknown>, vars: Record<string, unknown>) => Promise<unknown>>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class WorkflowEngine {
|
|
36
|
+
private options: WorkflowEngineOptions
|
|
37
|
+
private cancelled = false
|
|
38
|
+
|
|
39
|
+
constructor(options: WorkflowEngineOptions) {
|
|
40
|
+
this.options = options
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cancel(): void {
|
|
44
|
+
this.cancelled = true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async execute(
|
|
48
|
+
definition: WorkflowDefinition,
|
|
49
|
+
input: Record<string, unknown> = {},
|
|
50
|
+
): Promise<WorkflowRun> {
|
|
51
|
+
this.cancelled = false
|
|
52
|
+
|
|
53
|
+
const run: WorkflowRun = {
|
|
54
|
+
id: crypto.randomUUID?.() ?? String(Date.now()),
|
|
55
|
+
workflowId: definition.id,
|
|
56
|
+
workflowVersion: definition.version,
|
|
57
|
+
status: 'running',
|
|
58
|
+
input,
|
|
59
|
+
variables: { ...definition.variables, ...input },
|
|
60
|
+
stepResults: [],
|
|
61
|
+
currentStepIndex: 0,
|
|
62
|
+
triggeredBy: '',
|
|
63
|
+
startedAt: Date.now(),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Topologically sort steps based on connections
|
|
67
|
+
const sortedSteps = this.topoSort(definition)
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < sortedSteps.length; i++) {
|
|
70
|
+
if (this.cancelled) {
|
|
71
|
+
run.status = 'cancelled'
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
run.currentStepIndex = i
|
|
76
|
+
const stepDef = sortedSteps[i]
|
|
77
|
+
const result = await this.executeStep(stepDef, run.variables, definition)
|
|
78
|
+
|
|
79
|
+
run.stepResults.push(result)
|
|
80
|
+
this.options.onStepComplete?.(result)
|
|
81
|
+
|
|
82
|
+
if (result.status === 'failed' && !stepDef.optional) {
|
|
83
|
+
run.status = 'failed'
|
|
84
|
+
run.error = result.error
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Store output in variables
|
|
89
|
+
if (result.output !== undefined && result.output !== null) {
|
|
90
|
+
const config = stepDef.config as { outputVar?: string }
|
|
91
|
+
if (config.outputVar) {
|
|
92
|
+
run.variables[config.outputVar] = result.output
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle condition branching
|
|
97
|
+
if (stepDef.config.type === 'condition') {
|
|
98
|
+
const condConfig = stepDef.config as ConditionConfig
|
|
99
|
+
const condResult = result.output as boolean
|
|
100
|
+
const nextStepId = condResult ? condConfig.trueStepId : condConfig.falseStepId
|
|
101
|
+
|
|
102
|
+
// Skip to the target step
|
|
103
|
+
const targetIndex = sortedSteps.findIndex(s => s.id === nextStepId)
|
|
104
|
+
if (targetIndex > i) {
|
|
105
|
+
// Mark skipped steps
|
|
106
|
+
for (let j = i + 1; j < targetIndex; j++) {
|
|
107
|
+
run.stepResults.push({
|
|
108
|
+
stepId: sortedSteps[j].id,
|
|
109
|
+
stepName: sortedSteps[j].name,
|
|
110
|
+
type: sortedSteps[j].type,
|
|
111
|
+
status: 'skipped',
|
|
112
|
+
durationMs: 0,
|
|
113
|
+
startedAt: Date.now(),
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
i = targetIndex - 1 // Will be incremented by loop
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (run.status === 'running') {
|
|
122
|
+
run.status = 'completed'
|
|
123
|
+
}
|
|
124
|
+
run.completedAt = Date.now()
|
|
125
|
+
|
|
126
|
+
return run
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async executeStep(
|
|
130
|
+
stepDef: WorkflowStepDef,
|
|
131
|
+
variables: Record<string, unknown>,
|
|
132
|
+
_definition: WorkflowDefinition,
|
|
133
|
+
): Promise<StepResult> {
|
|
134
|
+
const start = Date.now()
|
|
135
|
+
const baseResult: StepResult = {
|
|
136
|
+
stepId: stepDef.id,
|
|
137
|
+
stepName: stepDef.name,
|
|
138
|
+
type: stepDef.type,
|
|
139
|
+
status: 'running',
|
|
140
|
+
durationMs: 0,
|
|
141
|
+
startedAt: start,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let attempts = 0
|
|
145
|
+
const maxAttempts = stepDef.retries?.maxAttempts ?? 1
|
|
146
|
+
|
|
147
|
+
while (attempts < maxAttempts) {
|
|
148
|
+
attempts++
|
|
149
|
+
try {
|
|
150
|
+
const output = await this.runStepLogic(stepDef, variables)
|
|
151
|
+
return {
|
|
152
|
+
...baseResult,
|
|
153
|
+
status: 'completed',
|
|
154
|
+
output,
|
|
155
|
+
durationMs: Date.now() - start,
|
|
156
|
+
completedAt: Date.now(),
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (attempts >= maxAttempts) {
|
|
160
|
+
return {
|
|
161
|
+
...baseResult,
|
|
162
|
+
status: 'failed',
|
|
163
|
+
error: err instanceof Error ? err.message : String(err),
|
|
164
|
+
durationMs: Date.now() - start,
|
|
165
|
+
completedAt: Date.now(),
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Wait before retry
|
|
169
|
+
const backoff = stepDef.retries?.backoffMs ?? 1000
|
|
170
|
+
await new Promise(r => setTimeout(r, backoff * attempts))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { ...baseResult, status: 'failed', error: 'Max retries exceeded', durationMs: Date.now() - start }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async runStepLogic(
|
|
178
|
+
stepDef: WorkflowStepDef,
|
|
179
|
+
variables: Record<string, unknown>,
|
|
180
|
+
): Promise<unknown> {
|
|
181
|
+
switch (stepDef.config.type) {
|
|
182
|
+
case 'llm-call': {
|
|
183
|
+
if (!this.options.callLLM) throw new Error('No LLM function provided')
|
|
184
|
+
const config = stepDef.config as LLMCallConfig
|
|
185
|
+
const userPrompt = this.interpolate(config.userPromptTemplate, variables)
|
|
186
|
+
const result = await this.options.callLLM(config.systemPrompt, userPrompt, config.model)
|
|
187
|
+
if (config.parseJson) {
|
|
188
|
+
try { return JSON.parse(result.content) }
|
|
189
|
+
catch { return result.content }
|
|
190
|
+
}
|
|
191
|
+
return result.content
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case 'transform': {
|
|
195
|
+
const config = stepDef.config as TransformConfig
|
|
196
|
+
// Simple expression evaluation with variable access
|
|
197
|
+
const varEntries = config.inputVars.map(v => [v, variables[v]])
|
|
198
|
+
const fn = new Function(
|
|
199
|
+
...config.inputVars,
|
|
200
|
+
`"use strict"; return (${config.expression})`
|
|
201
|
+
)
|
|
202
|
+
return fn(...varEntries.map(([_, v]) => v))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case 'condition': {
|
|
206
|
+
const config = stepDef.config as ConditionConfig
|
|
207
|
+
const fn = new Function(
|
|
208
|
+
'vars',
|
|
209
|
+
`"use strict"; with(vars) { return Boolean(${config.expression}) }`
|
|
210
|
+
)
|
|
211
|
+
return fn(variables)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
case 'human-approval': {
|
|
215
|
+
if (!this.options.onApprovalRequired) {
|
|
216
|
+
// Auto-approve if no handler
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
const config = stepDef.config
|
|
220
|
+
return this.options.onApprovalRequired(
|
|
221
|
+
(config as { message: string }).message,
|
|
222
|
+
stepDef.id,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case 'webhook': {
|
|
227
|
+
if (!this.options.callWebhook) throw new Error('No webhook function provided')
|
|
228
|
+
const config = stepDef.config as { url: string; method: string; bodyTemplate?: string; headers?: Record<string, string> }
|
|
229
|
+
const body = config.bodyTemplate ? this.interpolate(config.bodyTemplate, variables) : undefined
|
|
230
|
+
return this.options.callWebhook(config.url, config.method, body, config.headers)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'delay': {
|
|
234
|
+
const config = stepDef.config as DelayConfig
|
|
235
|
+
await new Promise(r => setTimeout(r, config.durationMs))
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 'custom': {
|
|
240
|
+
const config = stepDef.config as { handler: string; params: Record<string, unknown> }
|
|
241
|
+
const handler = this.options.customHandlers?.[config.handler]
|
|
242
|
+
if (!handler) throw new Error(`No handler for custom step: ${config.handler}`)
|
|
243
|
+
return handler(config.params, variables)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
default:
|
|
247
|
+
throw new Error(`Unsupported step type: ${stepDef.config.type}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Interpolate {{variable}} references in a string */
|
|
252
|
+
private interpolate(template: string, vars: Record<string, unknown>): string {
|
|
253
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
254
|
+
const val = vars[key]
|
|
255
|
+
return val !== undefined ? String(val) : `{{${key}}}`
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Topological sort of steps based on connections */
|
|
260
|
+
private topoSort(def: WorkflowDefinition): WorkflowStepDef[] {
|
|
261
|
+
const stepMap = new Map(def.steps.map(s => [s.id, s]))
|
|
262
|
+
const inDegree = new Map<string, number>()
|
|
263
|
+
const adjacency = new Map<string, string[]>()
|
|
264
|
+
|
|
265
|
+
for (const step of def.steps) {
|
|
266
|
+
inDegree.set(step.id, 0)
|
|
267
|
+
adjacency.set(step.id, [])
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const conn of def.connections) {
|
|
271
|
+
adjacency.get(conn.fromStepId)?.push(conn.toStepId)
|
|
272
|
+
inDegree.set(conn.toStepId, (inDegree.get(conn.toStepId) ?? 0) + 1)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const queue: string[] = []
|
|
276
|
+
for (const [id, degree] of inDegree) {
|
|
277
|
+
if (degree === 0) queue.push(id)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const sorted: WorkflowStepDef[] = []
|
|
281
|
+
while (queue.length > 0) {
|
|
282
|
+
const id = queue.shift()!
|
|
283
|
+
const step = stepMap.get(id)
|
|
284
|
+
if (step) sorted.push(step)
|
|
285
|
+
|
|
286
|
+
for (const neighbor of adjacency.get(id) ?? []) {
|
|
287
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1
|
|
288
|
+
inDegree.set(neighbor, newDegree)
|
|
289
|
+
if (newDegree === 0) queue.push(neighbor)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return sorted
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @geenius-ai-workflow/shared — src/index.ts
|
|
2
|
+
|
|
3
|
+
// Config
|
|
4
|
+
export { configureWorkflow, defaultWorkflowConfig } from './config'
|
|
5
|
+
export type { ConfigureWorkflowOptions } from './config'
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
WorkflowStatus, RunStatus, StepStatus, StepType,
|
|
10
|
+
WorkflowDefinition, WorkflowStepDef, StepConfig,
|
|
11
|
+
LLMCallConfig, TransformConfig, ConditionConfig, HumanApprovalConfig,
|
|
12
|
+
WebhookConfig, DelayConfig, ParallelConfig, LoopConfig, SubWorkflowConfig, CustomStepConfig,
|
|
13
|
+
StepConnection, WorkflowVariable, WorkflowRun, StepResult, WorkflowBuilderState,
|
|
14
|
+
} from './types'
|
|
15
|
+
|
|
16
|
+
// Engine
|
|
17
|
+
export { WorkflowEngine } from './engine'
|
|
18
|
+
export type { WorkflowEngineOptions } from './engine'
|
|
19
|
+
|
|
20
|
+
// Step factories
|
|
21
|
+
export { llmStep, transformStep, conditionStep, approvalStep, webhookStep, delayStep, connect, linearWorkflow } from './steps'
|
|
22
|
+
|
|
23
|
+
// Validators
|
|
24
|
+
export {
|
|
25
|
+
workflowDefinitionSchema, workflowStepDefSchema, stepConfigSchema, stepConnectionSchema,
|
|
26
|
+
workflowRunSchema, stepResultSchema, workflowVariableSchema,
|
|
27
|
+
validateWorkflow, validateRun, validateConnectionIntegrity,
|
|
28
|
+
} from './validators'
|
|
29
|
+
|
|
30
|
+
// Templates
|
|
31
|
+
export { WORKFLOW_TEMPLATES, getTemplate, getTemplatesByCategory } from './templates'
|
|
32
|
+
export type { WorkflowTemplate } from './templates'
|
|
33
|
+
|
|
34
|
+
// Utilities
|
|
35
|
+
export {
|
|
36
|
+
interpolate, extractVariables, findMissingVariables,
|
|
37
|
+
topoSort, detectCycle, findPaths, findEntryNodes, findExitNodes,
|
|
38
|
+
estimateTokens, estimateStepTokens, estimateStepCost, estimateWorkflowCost, calculateRunCost, formatCost, MODEL_RATES,
|
|
39
|
+
} from './utils'
|
|
40
|
+
export type { DAGNode, DAGEdge } from './utils'
|
|
41
|
+
|
|
42
|
+
// Convex schemas
|
|
43
|
+
export { workflowTables, workflowsTable, workflowRunsTable, workflowStepResultsTable } from './convex/index'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @geenius-ai-workflow/shared — src/steps.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in step factory helpers for easy workflow construction.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WorkflowStepDef, LLMCallConfig, TransformConfig, ConditionConfig, HumanApprovalConfig, WebhookConfig, DelayConfig } from './types'
|
|
8
|
+
|
|
9
|
+
let stepCounter = 0
|
|
10
|
+
function nextId(): string {
|
|
11
|
+
return `step_${++stepCounter}_${Date.now()}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Create an LLM call step */
|
|
15
|
+
export function llmStep(name: string, config: Omit<LLMCallConfig, 'type'>): WorkflowStepDef {
|
|
16
|
+
return { id: nextId(), name, type: 'llm-call', config: { type: 'llm-call', ...config } }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Create a data transform step */
|
|
20
|
+
export function transformStep(name: string, config: Omit<TransformConfig, 'type'>): WorkflowStepDef {
|
|
21
|
+
return { id: nextId(), name, type: 'transform', config: { type: 'transform', ...config } }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Create a condition (if/else) step */
|
|
25
|
+
export function conditionStep(name: string, config: Omit<ConditionConfig, 'type'>): WorkflowStepDef {
|
|
26
|
+
return { id: nextId(), name, type: 'condition', config: { type: 'condition', ...config } }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Create a human approval gate */
|
|
30
|
+
export function approvalStep(name: string, config: Omit<HumanApprovalConfig, 'type'>): WorkflowStepDef {
|
|
31
|
+
return { id: nextId(), name, type: 'human-approval', config: { type: 'human-approval', ...config } }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Create a webhook call step */
|
|
35
|
+
export function webhookStep(name: string, config: Omit<WebhookConfig, 'type'>): WorkflowStepDef {
|
|
36
|
+
return { id: nextId(), name, type: 'webhook', config: { type: 'webhook', ...config } }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Create a delay step */
|
|
40
|
+
export function delayStep(name: string, durationMs: number): WorkflowStepDef {
|
|
41
|
+
return { id: nextId(), name, type: 'delay', config: { type: 'delay', durationMs } }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Connect two steps (create a DAG edge).
|
|
46
|
+
*/
|
|
47
|
+
export function connect(fromStepId: string, toStepId: string, label?: string) {
|
|
48
|
+
return { fromStepId, toStepId, label }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build a linear workflow from an ordered list of steps.
|
|
53
|
+
*/
|
|
54
|
+
export function linearWorkflow(
|
|
55
|
+
name: string,
|
|
56
|
+
steps: WorkflowStepDef[],
|
|
57
|
+
): { steps: WorkflowStepDef[]; connections: Array<{ fromStepId: string; toStepId: string }> } {
|
|
58
|
+
const connections = []
|
|
59
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
60
|
+
connections.push({ fromStepId: steps[i].id, toStepId: steps[i + 1].id })
|
|
61
|
+
}
|
|
62
|
+
return { steps, connections }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Reset the step counter (for testing) */
|
|
66
|
+
export function resetStepCounter(): void {
|
|
67
|
+
stepCounter = 0
|
|
68
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// @geenius-ai-workflow/shared — src/templates.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pre-built workflow templates for common AI/business patterns.
|
|
4
|
+
*/
|
|
5
|
+
import type { WorkflowDefinition, WorkflowStepDef, StepConnection } from './types'
|
|
6
|
+
|
|
7
|
+
export interface WorkflowTemplate {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
category: 'content' | 'data' | 'approval' | 'research' | 'devops'
|
|
12
|
+
tags: string[]
|
|
13
|
+
/** Factory that creates a fresh WorkflowDefinition from this template */
|
|
14
|
+
create: (createdBy: string) => WorkflowDefinition
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function uid(): string { return `step_tpl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` }
|
|
18
|
+
function wfId(): string { return `wf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` }
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// 1. Content Pipeline
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
const contentPipeline: WorkflowTemplate = {
|
|
25
|
+
id: 'tpl-content-pipeline',
|
|
26
|
+
name: 'Content Pipeline',
|
|
27
|
+
description: 'Generate, review, and publish content with AI refinement and human approval.',
|
|
28
|
+
category: 'content',
|
|
29
|
+
tags: ['content', 'blog', 'marketing'],
|
|
30
|
+
create: (createdBy) => {
|
|
31
|
+
const draft = uid(), refine = uid(), approve = uid(), publish = uid()
|
|
32
|
+
const steps: WorkflowStepDef[] = [
|
|
33
|
+
{ id: draft, name: 'Generate Draft', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a content writer.', userPromptTemplate: 'Write a blog post about {{topic}}. Tone: {{tone}}.', outputVar: 'draft' }, position: { x: 100, y: 200 } },
|
|
34
|
+
{ id: refine, name: 'Refine Content', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are an editor. Improve clarity, grammar, and engagement.', userPromptTemplate: 'Polish this draft:\n\n{{draft}}', outputVar: 'polished' }, position: { x: 350, y: 200 } },
|
|
35
|
+
{ id: approve, name: 'Human Review', type: 'human-approval', config: { type: 'human-approval', message: 'Please review the polished content before publishing.' }, position: { x: 600, y: 200 } },
|
|
36
|
+
{ id: publish, name: 'Format for Publishing', type: 'transform', config: { type: 'transform', expression: '({ title: polished.split("\\n")[0], body: polished, publishedAt: Date.now() })', inputVars: ['polished'], outputVar: 'publishData' }, position: { x: 850, y: 200 } },
|
|
37
|
+
]
|
|
38
|
+
const connections: StepConnection[] = [
|
|
39
|
+
{ fromStepId: draft, toStepId: refine },
|
|
40
|
+
{ fromStepId: refine, toStepId: approve },
|
|
41
|
+
{ fromStepId: approve, toStepId: publish },
|
|
42
|
+
]
|
|
43
|
+
return { id: wfId(), name: 'Content Pipeline', description: 'AI-powered content generation with editorial review.', version: 1, status: 'draft', steps, connections, inputSchema: [{ name: 'topic', type: 'string', required: true }, { name: 'tone', type: 'string', required: false, default: 'professional' }], tags: ['content'], createdBy, createdAt: Date.now(), updatedAt: Date.now() }
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// 2. Data Enrichment
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
const dataEnrichment: WorkflowTemplate = {
|
|
52
|
+
id: 'tpl-data-enrichment',
|
|
53
|
+
name: 'Data Enrichment',
|
|
54
|
+
description: 'Fetch external data, enrich with AI analysis, and transform for downstream use.',
|
|
55
|
+
category: 'data',
|
|
56
|
+
tags: ['data', 'analytics', 'enrichment'],
|
|
57
|
+
create: (createdBy) => {
|
|
58
|
+
const fetch_ = uid(), analyze = uid(), transform = uid()
|
|
59
|
+
const steps: WorkflowStepDef[] = [
|
|
60
|
+
{ id: fetch_, name: 'Fetch Data', type: 'webhook', config: { type: 'webhook', url: '{{dataSourceUrl}}', method: 'GET', outputVar: 'rawData' }, position: { x: 100, y: 200 } },
|
|
61
|
+
{ id: analyze, name: 'AI Analysis', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a data analyst. Analyze the data and extract key insights.', userPromptTemplate: 'Analyze this dataset and return a JSON summary with key metrics:\n\n{{rawData}}', parseJson: true, outputVar: 'analysis' }, position: { x: 400, y: 200 } },
|
|
62
|
+
{ id: transform, name: 'Transform Output', type: 'transform', config: { type: 'transform', expression: '({ ...analysis, enrichedAt: Date.now(), source: dataSourceUrl })', inputVars: ['analysis', 'dataSourceUrl'], outputVar: 'enrichedData' }, position: { x: 700, y: 200 } },
|
|
63
|
+
]
|
|
64
|
+
const connections: StepConnection[] = [
|
|
65
|
+
{ fromStepId: fetch_, toStepId: analyze },
|
|
66
|
+
{ fromStepId: analyze, toStepId: transform },
|
|
67
|
+
]
|
|
68
|
+
return { id: wfId(), name: 'Data Enrichment', version: 1, status: 'draft', steps, connections, inputSchema: [{ name: 'dataSourceUrl', type: 'string', required: true }], tags: ['data'], createdBy, createdAt: Date.now(), updatedAt: Date.now() }
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// 3. Approval Chain
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
const approvalChain: WorkflowTemplate = {
|
|
77
|
+
id: 'tpl-approval-chain',
|
|
78
|
+
name: 'Approval Chain',
|
|
79
|
+
description: 'Multi-level approval workflow with conditional escalation.',
|
|
80
|
+
category: 'approval',
|
|
81
|
+
tags: ['approval', 'governance', 'compliance'],
|
|
82
|
+
create: (createdBy) => {
|
|
83
|
+
const checkRisk = uid(), lowApproval = uid(), highApproval = uid(), finalize = uid()
|
|
84
|
+
const steps: WorkflowStepDef[] = [
|
|
85
|
+
{ id: checkRisk, name: 'Assess Risk Level', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'Assess the risk level of this request. Respond with JSON: {"riskLevel": "low"|"medium"|"high", "reason": "..."}', userPromptTemplate: '{{request}}', parseJson: true, outputVar: 'riskAssessment' }, position: { x: 100, y: 200 } },
|
|
86
|
+
{ id: lowApproval, name: 'Manager Approval', type: 'human-approval', config: { type: 'human-approval', message: 'Low/medium risk: Manager approval needed for: {{request}}' }, position: { x: 400, y: 100 } },
|
|
87
|
+
{ id: highApproval, name: 'Executive Approval', type: 'human-approval', config: { type: 'human-approval', message: 'High risk: Executive approval required for: {{request}}' }, position: { x: 400, y: 300 } },
|
|
88
|
+
{ id: finalize, name: 'Record Decision', type: 'transform', config: { type: 'transform', expression: '({ approved: true, approvedAt: Date.now(), riskLevel: riskAssessment.riskLevel })', inputVars: ['riskAssessment'], outputVar: 'decision' }, position: { x: 700, y: 200 } },
|
|
89
|
+
]
|
|
90
|
+
const connections: StepConnection[] = [
|
|
91
|
+
{ fromStepId: checkRisk, toStepId: lowApproval, condition: 'riskAssessment.riskLevel !== "high"' },
|
|
92
|
+
{ fromStepId: checkRisk, toStepId: highApproval, condition: 'riskAssessment.riskLevel === "high"' },
|
|
93
|
+
{ fromStepId: lowApproval, toStepId: finalize },
|
|
94
|
+
{ fromStepId: highApproval, toStepId: finalize },
|
|
95
|
+
]
|
|
96
|
+
return { id: wfId(), name: 'Approval Chain', version: 1, status: 'draft', steps, connections, inputSchema: [{ name: 'request', type: 'string', required: true }], tags: ['approval'], createdBy, createdAt: Date.now(), updatedAt: Date.now() }
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// 4. Research Pipeline
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
const researchPipeline: WorkflowTemplate = {
|
|
105
|
+
id: 'tpl-research',
|
|
106
|
+
name: 'Research Pipeline',
|
|
107
|
+
description: 'Multi-source research with AI synthesis and summary generation.',
|
|
108
|
+
category: 'research',
|
|
109
|
+
tags: ['research', 'analysis', 'summary'],
|
|
110
|
+
create: (createdBy) => {
|
|
111
|
+
const research = uid(), synthesize = uid(), summarize = uid()
|
|
112
|
+
const steps: WorkflowStepDef[] = [
|
|
113
|
+
{ id: research, name: 'Deep Research', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a research assistant. Provide comprehensive, well-sourced research on the given topic.', userPromptTemplate: 'Research the following topic in depth: {{topic}}\n\nFocus areas: {{focusAreas}}', outputVar: 'researchOutput' }, position: { x: 100, y: 200 } },
|
|
114
|
+
{ id: synthesize, name: 'Synthesize Findings', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a synthesis expert. Combine research findings into structured insights with citations.', userPromptTemplate: 'Synthesize these research findings into key themes and actionable insights:\n\n{{researchOutput}}', parseJson: true, outputVar: 'synthesis' }, position: { x: 400, y: 200 } },
|
|
115
|
+
{ id: summarize, name: 'Executive Summary', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'Write a concise executive summary (3-5 paragraphs) suitable for senior leadership.', userPromptTemplate: 'Create an executive summary from:\n\n{{synthesis}}', outputVar: 'executiveSummary' }, position: { x: 700, y: 200 } },
|
|
116
|
+
]
|
|
117
|
+
const connections: StepConnection[] = [
|
|
118
|
+
{ fromStepId: research, toStepId: synthesize },
|
|
119
|
+
{ fromStepId: synthesize, toStepId: summarize },
|
|
120
|
+
]
|
|
121
|
+
return { id: wfId(), name: 'Research Pipeline', version: 1, status: 'draft', steps, connections, inputSchema: [{ name: 'topic', type: 'string', required: true }, { name: 'focusAreas', type: 'string', required: false, default: 'general overview' }], tags: ['research'], createdBy, createdAt: Date.now(), updatedAt: Date.now() }
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// 5. Code Review Pipeline
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
const codeReviewPipeline: WorkflowTemplate = {
|
|
130
|
+
id: 'tpl-code-review',
|
|
131
|
+
name: 'Code Review Pipeline',
|
|
132
|
+
description: 'Automated code review with AI analysis, security check, and human approval.',
|
|
133
|
+
category: 'devops',
|
|
134
|
+
tags: ['code', 'review', 'security', 'devops'],
|
|
135
|
+
create: (createdBy) => {
|
|
136
|
+
const analyze = uid(), security = uid(), approve = uid(), report = uid()
|
|
137
|
+
const steps: WorkflowStepDef[] = [
|
|
138
|
+
{ id: analyze, name: 'AI Code Review', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a senior code reviewer. Analyze the code for bugs, performance issues, and best practices. Return JSON: {"issues": [], "suggestions": [], "quality": 1-10}', userPromptTemplate: 'Review this code:\n\n```{{language}}\n{{code}}\n```', parseJson: true, outputVar: 'codeReview' }, position: { x: 100, y: 200 } },
|
|
139
|
+
{ id: security, name: 'Security Scan', type: 'llm-call', config: { type: 'llm-call', systemPrompt: 'You are a security auditor. Check for vulnerabilities (XSS, SQL injection, secrets exposure, etc). Return JSON: {"vulnerabilities": [], "riskLevel": "low"|"medium"|"high"}', userPromptTemplate: 'Security audit for:\n\n```{{language}}\n{{code}}\n```', parseJson: true, outputVar: 'securityReport' }, position: { x: 100, y: 400 } },
|
|
140
|
+
{ id: approve, name: 'Developer Approval', type: 'human-approval', config: { type: 'human-approval', message: 'Review the AI code analysis and security scan before merging.' }, position: { x: 450, y: 300 } },
|
|
141
|
+
{ id: report, name: 'Generate Report', type: 'transform', config: { type: 'transform', expression: '({ codeQuality: codeReview.quality, issues: codeReview.issues.length, vulnerabilities: securityReport.vulnerabilities.length, riskLevel: securityReport.riskLevel, reviewedAt: Date.now() })', inputVars: ['codeReview', 'securityReport'], outputVar: 'finalReport' }, position: { x: 700, y: 300 } },
|
|
142
|
+
]
|
|
143
|
+
const connections: StepConnection[] = [
|
|
144
|
+
{ fromStepId: analyze, toStepId: approve },
|
|
145
|
+
{ fromStepId: security, toStepId: approve },
|
|
146
|
+
{ fromStepId: approve, toStepId: report },
|
|
147
|
+
]
|
|
148
|
+
return { id: wfId(), name: 'Code Review Pipeline', version: 1, status: 'draft', steps, connections, inputSchema: [{ name: 'code', type: 'string', required: true }, { name: 'language', type: 'string', required: false, default: 'typescript' }], tags: ['devops', 'review'], createdBy, createdAt: Date.now(), updatedAt: Date.now() }
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Template Registry
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
export const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [
|
|
157
|
+
contentPipeline,
|
|
158
|
+
dataEnrichment,
|
|
159
|
+
approvalChain,
|
|
160
|
+
researchPipeline,
|
|
161
|
+
codeReviewPipeline,
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
/** Look up a template by ID */
|
|
165
|
+
export function getTemplate(id: string): WorkflowTemplate | undefined {
|
|
166
|
+
return WORKFLOW_TEMPLATES.find(t => t.id === id)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Filter templates by category */
|
|
170
|
+
export function getTemplatesByCategory(category: WorkflowTemplate['category']): WorkflowTemplate[] {
|
|
171
|
+
return WORKFLOW_TEMPLATES.filter(t => t.category === category)
|
|
172
|
+
}
|