@codemation/core-nodes 0.0.25 → 0.1.1
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/CHANGELOG.md +27 -0
- package/dist/index.cjs +267 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +260 -140
- package/dist/index.d.ts +260 -140
- package/dist/index.js +256 -137
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/index.ts +1 -0
- package/src/nodes/AIAgentConfig.ts +5 -10
- package/src/nodes/AIAgentNode.ts +7 -11
- package/src/nodes/AggregateNode.ts +6 -7
- package/src/nodes/CallbackNode.ts +18 -6
- package/src/nodes/CallbackNodeFactory.ts +31 -4
- package/src/nodes/CallbackResultNormalizerFactory.ts +7 -3
- package/src/nodes/ConnectionCredentialNode.ts +4 -4
- package/src/nodes/FilterNode.ts +6 -10
- package/src/nodes/HttpRequestNodeFactory.ts +4 -8
- package/src/nodes/IfNode.ts +15 -27
- package/src/nodes/MapDataNode.ts +3 -9
- package/src/nodes/NoOpNode.ts +4 -4
- package/src/nodes/NodeBackedToolRuntime.ts +40 -8
- package/src/nodes/SplitNode.ts +5 -15
- package/src/nodes/SubWorkflowNode.ts +43 -45
- package/src/nodes/SwitchNode.ts +29 -0
- package/src/nodes/WaitNode.ts +11 -9
- package/src/nodes/if.ts +1 -0
- package/src/nodes/mergeExecutionUtils.types.ts +31 -5
- package/src/nodes/switch.ts +33 -0
- package/src/register.types.ts +2 -0
- package/src/workflowAuthoring/WorkflowBranchBuilder.types.ts +108 -2
- package/src/workflowAuthoring/WorkflowChain.types.ts +207 -2
- package/src/workflowBuilder.types.ts +1 -4
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { Item,
|
|
2
|
-
|
|
3
|
-
import { CoreTokens, inject, node } from "@codemation/core";
|
|
1
|
+
import type { Item, RunnableNode, RunnableNodeExecuteArgs, WorkflowRunnerService } from "@codemation/core";
|
|
2
|
+
import { CoreTokens, emitPorts, inject, node } from "@codemation/core";
|
|
4
3
|
|
|
5
4
|
import { SubWorkflow } from "./subWorkflow";
|
|
6
5
|
|
|
7
6
|
@node({ packageName: "@codemation/core-nodes" })
|
|
8
|
-
export class SubWorkflowNode implements
|
|
7
|
+
export class SubWorkflowNode implements RunnableNode<SubWorkflow<any, any>> {
|
|
9
8
|
kind = "node" as const;
|
|
10
9
|
outputPorts = ["main"] as const;
|
|
11
10
|
|
|
@@ -14,52 +13,51 @@ export class SubWorkflowNode implements Node<SubWorkflow<any, any>> {
|
|
|
14
13
|
private readonly workflows: WorkflowRunnerService,
|
|
15
14
|
) {}
|
|
16
15
|
|
|
17
|
-
async execute(
|
|
16
|
+
async execute(args: RunnableNodeExecuteArgs<SubWorkflow<any, any>>): Promise<unknown> {
|
|
17
|
+
const current = args.item as Item;
|
|
18
|
+
const metaBase = (
|
|
19
|
+
current.meta && typeof current.meta === "object" ? (current.meta as Record<string, unknown>) : {}
|
|
20
|
+
) as Record<string, unknown>;
|
|
21
|
+
const cmBase =
|
|
22
|
+
metaBase._cm && typeof metaBase._cm === "object"
|
|
23
|
+
? (metaBase._cm as Record<string, unknown>)
|
|
24
|
+
: ({} as Record<string, unknown>);
|
|
25
|
+
const originIndex = typeof cmBase.originIndex === "number" ? (cmBase.originIndex as number) : undefined;
|
|
26
|
+
|
|
27
|
+
const result = await this.workflows.runById({
|
|
28
|
+
workflowId: args.ctx.config.workflowId,
|
|
29
|
+
startAt: args.ctx.config.startAt,
|
|
30
|
+
items: [current],
|
|
31
|
+
parent: {
|
|
32
|
+
runId: args.ctx.runId,
|
|
33
|
+
workflowId: args.ctx.workflowId,
|
|
34
|
+
nodeId: args.ctx.nodeId,
|
|
35
|
+
subworkflowDepth: args.ctx.subworkflowDepth,
|
|
36
|
+
engineMaxNodeActivations: args.ctx.engineMaxNodeActivations,
|
|
37
|
+
engineMaxSubworkflowDepth: args.ctx.engineMaxSubworkflowDepth,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (result.status !== "completed") {
|
|
41
|
+
throw new Error(`Subworkflow ${args.ctx.config.workflowId} did not complete (status=${result.status})`);
|
|
42
|
+
}
|
|
18
43
|
const out: Item[] = [];
|
|
19
|
-
for (
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
? (
|
|
44
|
+
for (const produced of result.outputs) {
|
|
45
|
+
const childMetaBase =
|
|
46
|
+
produced.meta && typeof produced.meta === "object"
|
|
47
|
+
? (produced.meta as Record<string, unknown>)
|
|
48
|
+
: ({} as Record<string, unknown>);
|
|
49
|
+
const childCmBase =
|
|
50
|
+
childMetaBase._cm && typeof childMetaBase._cm === "object"
|
|
51
|
+
? (childMetaBase._cm as Record<string, unknown>)
|
|
27
52
|
: ({} as Record<string, unknown>);
|
|
28
|
-
const originIndex = typeof cmBase.originIndex === "number" ? (cmBase.originIndex as number) : undefined;
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
parent: {
|
|
35
|
-
runId: ctx.runId,
|
|
36
|
-
workflowId: ctx.workflowId,
|
|
37
|
-
nodeId: ctx.nodeId,
|
|
38
|
-
subworkflowDepth: ctx.subworkflowDepth,
|
|
39
|
-
engineMaxNodeActivations: ctx.engineMaxNodeActivations,
|
|
40
|
-
engineMaxSubworkflowDepth: ctx.engineMaxSubworkflowDepth,
|
|
41
|
-
},
|
|
54
|
+
out.push({
|
|
55
|
+
...produced,
|
|
56
|
+
meta: originIndex === undefined ? childMetaBase : { ...childMetaBase, _cm: { ...childCmBase, originIndex } },
|
|
57
|
+
paired: current.paired ?? produced.paired,
|
|
42
58
|
});
|
|
43
|
-
if (result.status !== "completed")
|
|
44
|
-
throw new Error(`Subworkflow ${ctx.config.workflowId} did not complete (status=${result.status})`);
|
|
45
|
-
for (const produced of result.outputs) {
|
|
46
|
-
const childMetaBase =
|
|
47
|
-
produced.meta && typeof produced.meta === "object"
|
|
48
|
-
? (produced.meta as Record<string, unknown>)
|
|
49
|
-
: ({} as Record<string, unknown>);
|
|
50
|
-
const childCmBase =
|
|
51
|
-
childMetaBase._cm && typeof childMetaBase._cm === "object"
|
|
52
|
-
? (childMetaBase._cm as Record<string, unknown>)
|
|
53
|
-
: ({} as Record<string, unknown>);
|
|
54
|
-
|
|
55
|
-
out.push({
|
|
56
|
-
...produced,
|
|
57
|
-
meta: originIndex === undefined ? childMetaBase : { ...childMetaBase, _cm: { ...childCmBase, originIndex } },
|
|
58
|
-
paired: current.paired ?? produced.paired,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
return { main: out };
|
|
61
|
+
return emitPorts({ main: out });
|
|
64
62
|
}
|
|
65
63
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RunnableNode, RunnableNodeExecuteArgs } from "@codemation/core";
|
|
2
|
+
import { emitPorts, node } from "@codemation/core";
|
|
3
|
+
|
|
4
|
+
import type { Switch } from "./switch";
|
|
5
|
+
import { tagItemForRouterFanIn } from "./mergeExecutionUtils.types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Routes each item to exactly one output port. Port names must match workflow edges (see {@link Switch} config).
|
|
9
|
+
*/
|
|
10
|
+
@node({ packageName: "@codemation/core-nodes" })
|
|
11
|
+
export class SwitchNode implements RunnableNode<Switch<any>> {
|
|
12
|
+
kind = "node" as const;
|
|
13
|
+
|
|
14
|
+
async execute(args: RunnableNodeExecuteArgs<Switch<any>>): Promise<unknown> {
|
|
15
|
+
const tagged = tagItemForRouterFanIn({
|
|
16
|
+
item: args.item,
|
|
17
|
+
itemIndex: args.itemIndex,
|
|
18
|
+
nodeId: args.ctx.nodeId,
|
|
19
|
+
});
|
|
20
|
+
const key = await Promise.resolve(
|
|
21
|
+
args.ctx.config.cfg.resolveCaseKey(args.item, args.itemIndex, args.items, args.ctx),
|
|
22
|
+
);
|
|
23
|
+
const { cases, defaultCase } = args.ctx.config.cfg;
|
|
24
|
+
const port = cases.includes(key) ? key : defaultCase;
|
|
25
|
+
return emitPorts({
|
|
26
|
+
[port]: [tagged],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/nodes/WaitNode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RunnableNode, RunnableNodeExecuteArgs } from "@codemation/core";
|
|
2
2
|
|
|
3
3
|
import { node } from "@codemation/core";
|
|
4
4
|
|
|
@@ -6,17 +6,19 @@ import { Wait } from "./wait";
|
|
|
6
6
|
import { WaitDuration } from "./WaitDurationFactory";
|
|
7
7
|
|
|
8
8
|
@node({ packageName: "@codemation/core-nodes" })
|
|
9
|
-
export class WaitNode implements
|
|
9
|
+
export class WaitNode implements RunnableNode<Wait<any>> {
|
|
10
10
|
kind = "node" as const;
|
|
11
11
|
outputPorts = ["main"] as const;
|
|
12
12
|
|
|
13
|
-
async execute(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
async execute(args: RunnableNodeExecuteArgs<Wait<any>>): Promise<unknown> {
|
|
14
|
+
if (args.itemIndex === 0) {
|
|
15
|
+
const milliseconds = WaitDuration.normalize(args.ctx.config.milliseconds);
|
|
16
|
+
if (milliseconds > 0) {
|
|
17
|
+
await new Promise<void>((resolve) => {
|
|
18
|
+
setTimeout(resolve, milliseconds);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
19
21
|
}
|
|
20
|
-
return
|
|
22
|
+
return args.item;
|
|
21
23
|
}
|
|
22
24
|
}
|
package/src/nodes/if.ts
CHANGED
|
@@ -7,6 +7,7 @@ export class If<TInputJson = unknown> implements RunnableNodeConfig<TInputJson,
|
|
|
7
7
|
readonly type: TypeToken<unknown> = IfNode;
|
|
8
8
|
readonly execution = { hint: "local" } as const;
|
|
9
9
|
readonly icon = "lucide:split" as const;
|
|
10
|
+
readonly declaredOutputPorts = ["true", "false"] as const;
|
|
10
11
|
constructor(
|
|
11
12
|
public readonly name: string,
|
|
12
13
|
public readonly predicate: (
|
|
@@ -1,10 +1,36 @@
|
|
|
1
|
-
import type { InputPortKey, Item, Items } from "@codemation/core";
|
|
1
|
+
import type { InputPortKey, Item, Items, NodeId } from "@codemation/core";
|
|
2
|
+
import { getOriginIndexFromItem } from "@codemation/core";
|
|
2
3
|
|
|
3
4
|
export function getOriginIndex(item: Item): number | undefined {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
return getOriginIndexFromItem(item);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tags items routed to fan-in merge-by-origin (same contract as {@link IfNode} / {@link SwitchNode}).
|
|
10
|
+
*/
|
|
11
|
+
export function tagItemForRouterFanIn<TJson>(
|
|
12
|
+
args: Readonly<{
|
|
13
|
+
item: Item<TJson>;
|
|
14
|
+
itemIndex: number;
|
|
15
|
+
nodeId: NodeId;
|
|
16
|
+
inputPortLabel?: string;
|
|
17
|
+
}>,
|
|
18
|
+
): Item<TJson> {
|
|
19
|
+
const { item, itemIndex, nodeId, inputPortLabel = "$in" } = args;
|
|
20
|
+
const metaBase = (item.meta && typeof item.meta === "object" ? (item.meta as Record<string, unknown>) : {}) as Record<
|
|
21
|
+
string,
|
|
22
|
+
unknown
|
|
23
|
+
>;
|
|
24
|
+
const cmBase =
|
|
25
|
+
metaBase._cm && typeof metaBase._cm === "object"
|
|
26
|
+
? (metaBase._cm as Record<string, unknown>)
|
|
27
|
+
: ({} as Record<string, unknown>);
|
|
28
|
+
const originIndex = typeof cmBase.originIndex === "number" ? (cmBase.originIndex as number) : itemIndex;
|
|
29
|
+
return {
|
|
30
|
+
...item,
|
|
31
|
+
meta: { ...metaBase, _cm: { ...cmBase, originIndex } },
|
|
32
|
+
paired: [{ nodeId, output: inputPortLabel, itemIndex: originIndex }, ...(item.paired ?? [])],
|
|
33
|
+
};
|
|
8
34
|
}
|
|
9
35
|
|
|
10
36
|
export function orderedInputs(
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Item, Items, NodeExecutionContext, RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { SwitchNode } from "./SwitchNode";
|
|
4
|
+
|
|
5
|
+
export type SwitchCaseKeyResolver<TInputJson = unknown> = (
|
|
6
|
+
item: Item<TInputJson>,
|
|
7
|
+
index: number,
|
|
8
|
+
items: Items<TInputJson>,
|
|
9
|
+
ctx: NodeExecutionContext<Switch<TInputJson>>,
|
|
10
|
+
) => string | Promise<string>;
|
|
11
|
+
|
|
12
|
+
export class Switch<TInputJson = unknown> implements RunnableNodeConfig<TInputJson, TInputJson> {
|
|
13
|
+
readonly kind = "node" as const;
|
|
14
|
+
readonly type: TypeToken<unknown> = SwitchNode;
|
|
15
|
+
readonly execution = { hint: "local" } as const;
|
|
16
|
+
readonly icon = "lucide:git-branch-plus" as const;
|
|
17
|
+
readonly lineageCarry = "carryThrough" as const;
|
|
18
|
+
readonly declaredOutputPorts: ReadonlyArray<string>;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
public readonly name: string,
|
|
22
|
+
public readonly cfg: Readonly<{
|
|
23
|
+
cases: readonly string[];
|
|
24
|
+
defaultCase: string;
|
|
25
|
+
resolveCaseKey: SwitchCaseKeyResolver<TInputJson>;
|
|
26
|
+
}>,
|
|
27
|
+
public readonly id?: string,
|
|
28
|
+
) {
|
|
29
|
+
this.declaredOutputPorts = [...new Set([...cfg.cases, cfg.defaultCase])].sort();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { SwitchNode } from "./SwitchNode";
|
package/src/register.types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ConnectionCredentialNode } from "./nodes/ConnectionCredentialNode";
|
|
|
12
12
|
import { AggregateNode } from "./nodes/aggregate";
|
|
13
13
|
import { FilterNode } from "./nodes/filter";
|
|
14
14
|
import { SplitNode } from "./nodes/split";
|
|
15
|
+
import { SwitchNode } from "./nodes/switch";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Registrar for built-in nodes. In a real project, this would use tsyringe's
|
|
@@ -40,4 +41,5 @@ export function registerCoreNodes(container: Container): void {
|
|
|
40
41
|
void SplitNode;
|
|
41
42
|
void FilterNode;
|
|
42
43
|
void AggregateNode;
|
|
44
|
+
void SwitchNode;
|
|
43
45
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnyRunnableNodeConfig,
|
|
3
|
+
DefinedNode,
|
|
4
|
+
Item,
|
|
5
|
+
Items,
|
|
6
|
+
NodeExecutionContext,
|
|
7
|
+
RunnableNodeConfig,
|
|
8
|
+
RunnableNodeOutputJson,
|
|
9
|
+
} from "@codemation/core";
|
|
2
10
|
import { z } from "zod";
|
|
11
|
+
import { Aggregate } from "../nodes/aggregate";
|
|
12
|
+
import { Filter } from "../nodes/filter";
|
|
3
13
|
import { MapData } from "../nodes/mapData";
|
|
14
|
+
import { Split } from "../nodes/split";
|
|
4
15
|
import { Wait } from "../nodes/wait";
|
|
5
16
|
import type { WorkflowAgentOptions } from "./WorkflowAuthoringOptions.types";
|
|
6
17
|
import { WorkflowAgentNodeFactory } from "./WorkflowAgentNodeFactory.types";
|
|
@@ -10,7 +21,7 @@ import { WorkflowDurationParser } from "./WorkflowDurationParser.types";
|
|
|
10
21
|
export class WorkflowBranchBuilder<TCurrentJson> {
|
|
11
22
|
constructor(private readonly steps: ReadonlyArray<AnyRunnableNodeConfig> = []) {}
|
|
12
23
|
|
|
13
|
-
then<
|
|
24
|
+
then<TOutputJson, TConfig extends RunnableNodeConfig<TCurrentJson, TOutputJson>>(
|
|
14
25
|
config: TConfig,
|
|
15
26
|
): WorkflowBranchBuilder<RunnableNodeOutputJson<TConfig>> {
|
|
16
27
|
return new WorkflowBranchBuilder<RunnableNodeOutputJson<TConfig>>([...this.steps, config]);
|
|
@@ -48,6 +59,101 @@ export class WorkflowBranchBuilder<TCurrentJson> {
|
|
|
48
59
|
) as WorkflowBranchBuilder<TCurrentJson>;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
split<TElem>(
|
|
63
|
+
getElements: (item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[],
|
|
64
|
+
): WorkflowBranchBuilder<TElem>;
|
|
65
|
+
split<TElem>(
|
|
66
|
+
name: string,
|
|
67
|
+
getElements: (item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[],
|
|
68
|
+
id?: string,
|
|
69
|
+
): WorkflowBranchBuilder<TElem>;
|
|
70
|
+
split<TElem>(
|
|
71
|
+
nameOrGetter:
|
|
72
|
+
| string
|
|
73
|
+
| ((item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[]),
|
|
74
|
+
getElementsOrUndefined?: (
|
|
75
|
+
item: Item<TCurrentJson>,
|
|
76
|
+
ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>,
|
|
77
|
+
) => readonly TElem[],
|
|
78
|
+
id?: string,
|
|
79
|
+
): WorkflowBranchBuilder<TElem> {
|
|
80
|
+
const name = typeof nameOrGetter === "string" ? nameOrGetter : "Split";
|
|
81
|
+
const getElements = typeof nameOrGetter === "string" ? getElementsOrUndefined! : nameOrGetter;
|
|
82
|
+
return this.then(new Split<TCurrentJson, TElem>(name, getElements, id)) as WorkflowBranchBuilder<TElem>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
filter(
|
|
86
|
+
predicate: (
|
|
87
|
+
item: Item<TCurrentJson>,
|
|
88
|
+
index: number,
|
|
89
|
+
items: Items<TCurrentJson>,
|
|
90
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
91
|
+
) => boolean,
|
|
92
|
+
): WorkflowBranchBuilder<TCurrentJson>;
|
|
93
|
+
filter(
|
|
94
|
+
name: string,
|
|
95
|
+
predicate: (
|
|
96
|
+
item: Item<TCurrentJson>,
|
|
97
|
+
index: number,
|
|
98
|
+
items: Items<TCurrentJson>,
|
|
99
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
100
|
+
) => boolean,
|
|
101
|
+
id?: string,
|
|
102
|
+
): WorkflowBranchBuilder<TCurrentJson>;
|
|
103
|
+
filter(
|
|
104
|
+
nameOrPredicate:
|
|
105
|
+
| string
|
|
106
|
+
| ((
|
|
107
|
+
item: Item<TCurrentJson>,
|
|
108
|
+
index: number,
|
|
109
|
+
items: Items<TCurrentJson>,
|
|
110
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
111
|
+
) => boolean),
|
|
112
|
+
predicateOrUndefined?: (
|
|
113
|
+
item: Item<TCurrentJson>,
|
|
114
|
+
index: number,
|
|
115
|
+
items: Items<TCurrentJson>,
|
|
116
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
117
|
+
) => boolean,
|
|
118
|
+
id?: string,
|
|
119
|
+
): WorkflowBranchBuilder<TCurrentJson> {
|
|
120
|
+
const name = typeof nameOrPredicate === "string" ? nameOrPredicate : "Filter";
|
|
121
|
+
const predicate = typeof nameOrPredicate === "string" ? predicateOrUndefined! : nameOrPredicate;
|
|
122
|
+
return this.then(new Filter<TCurrentJson>(name, predicate, id)) as WorkflowBranchBuilder<TCurrentJson>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
aggregate<TOut>(
|
|
126
|
+
aggregateFn: (
|
|
127
|
+
items: Items<TCurrentJson>,
|
|
128
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
129
|
+
) => TOut | Promise<TOut>,
|
|
130
|
+
): WorkflowBranchBuilder<TOut>;
|
|
131
|
+
aggregate<TOut>(
|
|
132
|
+
name: string,
|
|
133
|
+
aggregateFn: (
|
|
134
|
+
items: Items<TCurrentJson>,
|
|
135
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
136
|
+
) => TOut | Promise<TOut>,
|
|
137
|
+
id?: string,
|
|
138
|
+
): WorkflowBranchBuilder<TOut>;
|
|
139
|
+
aggregate<TOut>(
|
|
140
|
+
nameOrFn:
|
|
141
|
+
| string
|
|
142
|
+
| ((
|
|
143
|
+
items: Items<TCurrentJson>,
|
|
144
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
145
|
+
) => TOut | Promise<TOut>),
|
|
146
|
+
aggregateFnOrUndefined?: (
|
|
147
|
+
items: Items<TCurrentJson>,
|
|
148
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
149
|
+
) => TOut | Promise<TOut>,
|
|
150
|
+
id?: string,
|
|
151
|
+
): WorkflowBranchBuilder<TOut> {
|
|
152
|
+
const name = typeof nameOrFn === "string" ? nameOrFn : "Aggregate";
|
|
153
|
+
const aggregateFn = typeof nameOrFn === "string" ? aggregateFnOrUndefined! : nameOrFn;
|
|
154
|
+
return this.then(new Aggregate<TCurrentJson, TOut>(name, aggregateFn, id)) as WorkflowBranchBuilder<TOut>;
|
|
155
|
+
}
|
|
156
|
+
|
|
51
157
|
agent<TOutputSchema extends z.ZodTypeAny>(
|
|
52
158
|
options: WorkflowAgentOptions<TCurrentJson, TOutputSchema>,
|
|
53
159
|
): WorkflowBranchBuilder<z.output<TOutputSchema>>;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DefinedNode,
|
|
3
|
+
Item,
|
|
4
|
+
Items,
|
|
5
|
+
NodeExecutionContext,
|
|
6
|
+
RunnableNodeConfig,
|
|
7
|
+
RunnableNodeOutputJson,
|
|
8
|
+
WorkflowDefinition,
|
|
9
|
+
} from "@codemation/core";
|
|
2
10
|
import { ChainCursor } from "@codemation/core";
|
|
3
11
|
import { z } from "zod";
|
|
12
|
+
import { Aggregate } from "../nodes/aggregate";
|
|
13
|
+
import { Filter } from "../nodes/filter";
|
|
4
14
|
import { If } from "../nodes/if";
|
|
5
15
|
import { MapData } from "../nodes/mapData";
|
|
16
|
+
import { Merge, type MergeMode } from "../nodes/merge";
|
|
17
|
+
import { Split } from "../nodes/split";
|
|
18
|
+
import { Switch } from "../nodes/switch";
|
|
6
19
|
import { Wait } from "../nodes/wait";
|
|
7
20
|
import type { WorkflowAgentOptions } from "./WorkflowAuthoringOptions.types";
|
|
8
21
|
import { WorkflowAgentNodeFactory } from "./WorkflowAgentNodeFactory.types";
|
|
@@ -13,12 +26,13 @@ import { WorkflowDurationParser } from "./WorkflowDurationParser.types";
|
|
|
13
26
|
type BranchCallback<TCurrentJson, TNextJson> = (
|
|
14
27
|
branch: WorkflowBranchBuilder<TCurrentJson>,
|
|
15
28
|
) => WorkflowBranchBuilder<TNextJson>;
|
|
29
|
+
type RouteBranchCallback<TCurrentJson, TNextJson> = (branch: WorkflowChain<TCurrentJson>) => WorkflowChain<TNextJson>;
|
|
16
30
|
type BranchOutputMatch<TLeft, TRight> = [TLeft] extends [TRight] ? ([TRight] extends [TLeft] ? true : false) : false;
|
|
17
31
|
|
|
18
32
|
export class WorkflowChain<TCurrentJson> {
|
|
19
33
|
constructor(private readonly chain: ChainCursor<TCurrentJson>) {}
|
|
20
34
|
|
|
21
|
-
then<
|
|
35
|
+
then<TOutputJson, TConfig extends RunnableNodeConfig<TCurrentJson, TOutputJson>>(
|
|
22
36
|
config: TConfig,
|
|
23
37
|
): WorkflowChain<RunnableNodeOutputJson<TConfig>> {
|
|
24
38
|
return new WorkflowChain(this.chain.then(config));
|
|
@@ -52,6 +66,124 @@ export class WorkflowChain<TCurrentJson> {
|
|
|
52
66
|
) as WorkflowChain<TCurrentJson>;
|
|
53
67
|
}
|
|
54
68
|
|
|
69
|
+
split<TElem>(
|
|
70
|
+
getElements: (item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[],
|
|
71
|
+
): WorkflowChain<TElem>;
|
|
72
|
+
split<TElem>(
|
|
73
|
+
name: string,
|
|
74
|
+
getElements: (item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[],
|
|
75
|
+
id?: string,
|
|
76
|
+
): WorkflowChain<TElem>;
|
|
77
|
+
split<TElem>(
|
|
78
|
+
nameOrGetter:
|
|
79
|
+
| string
|
|
80
|
+
| ((item: Item<TCurrentJson>, ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>) => readonly TElem[]),
|
|
81
|
+
getElementsOrUndefined?: (
|
|
82
|
+
item: Item<TCurrentJson>,
|
|
83
|
+
ctx: NodeExecutionContext<Split<TCurrentJson, TElem>>,
|
|
84
|
+
) => readonly TElem[],
|
|
85
|
+
id?: string,
|
|
86
|
+
): WorkflowChain<TElem> {
|
|
87
|
+
const name = typeof nameOrGetter === "string" ? nameOrGetter : "Split";
|
|
88
|
+
const getElements = typeof nameOrGetter === "string" ? getElementsOrUndefined! : nameOrGetter;
|
|
89
|
+
return this.then(new Split<TCurrentJson, TElem>(name, getElements, id)) as WorkflowChain<TElem>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
filter(
|
|
93
|
+
predicate: (
|
|
94
|
+
item: Item<TCurrentJson>,
|
|
95
|
+
index: number,
|
|
96
|
+
items: Items<TCurrentJson>,
|
|
97
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
98
|
+
) => boolean,
|
|
99
|
+
): WorkflowChain<TCurrentJson>;
|
|
100
|
+
filter(
|
|
101
|
+
name: string,
|
|
102
|
+
predicate: (
|
|
103
|
+
item: Item<TCurrentJson>,
|
|
104
|
+
index: number,
|
|
105
|
+
items: Items<TCurrentJson>,
|
|
106
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
107
|
+
) => boolean,
|
|
108
|
+
id?: string,
|
|
109
|
+
): WorkflowChain<TCurrentJson>;
|
|
110
|
+
filter(
|
|
111
|
+
nameOrPredicate:
|
|
112
|
+
| string
|
|
113
|
+
| ((
|
|
114
|
+
item: Item<TCurrentJson>,
|
|
115
|
+
index: number,
|
|
116
|
+
items: Items<TCurrentJson>,
|
|
117
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
118
|
+
) => boolean),
|
|
119
|
+
predicateOrUndefined?: (
|
|
120
|
+
item: Item<TCurrentJson>,
|
|
121
|
+
index: number,
|
|
122
|
+
items: Items<TCurrentJson>,
|
|
123
|
+
ctx: NodeExecutionContext<Filter<TCurrentJson>>,
|
|
124
|
+
) => boolean,
|
|
125
|
+
id?: string,
|
|
126
|
+
): WorkflowChain<TCurrentJson> {
|
|
127
|
+
const name = typeof nameOrPredicate === "string" ? nameOrPredicate : "Filter";
|
|
128
|
+
const predicate = typeof nameOrPredicate === "string" ? predicateOrUndefined! : nameOrPredicate;
|
|
129
|
+
return this.then(new Filter<TCurrentJson>(name, predicate, id)) as WorkflowChain<TCurrentJson>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
aggregate<TOut>(
|
|
133
|
+
aggregateFn: (
|
|
134
|
+
items: Items<TCurrentJson>,
|
|
135
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
136
|
+
) => TOut | Promise<TOut>,
|
|
137
|
+
): WorkflowChain<TOut>;
|
|
138
|
+
aggregate<TOut>(
|
|
139
|
+
name: string,
|
|
140
|
+
aggregateFn: (
|
|
141
|
+
items: Items<TCurrentJson>,
|
|
142
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
143
|
+
) => TOut | Promise<TOut>,
|
|
144
|
+
id?: string,
|
|
145
|
+
): WorkflowChain<TOut>;
|
|
146
|
+
aggregate<TOut>(
|
|
147
|
+
nameOrFn:
|
|
148
|
+
| string
|
|
149
|
+
| ((
|
|
150
|
+
items: Items<TCurrentJson>,
|
|
151
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
152
|
+
) => TOut | Promise<TOut>),
|
|
153
|
+
aggregateFnOrUndefined?: (
|
|
154
|
+
items: Items<TCurrentJson>,
|
|
155
|
+
ctx: NodeExecutionContext<Aggregate<TCurrentJson, TOut>>,
|
|
156
|
+
) => TOut | Promise<TOut>,
|
|
157
|
+
id?: string,
|
|
158
|
+
): WorkflowChain<TOut> {
|
|
159
|
+
const name = typeof nameOrFn === "string" ? nameOrFn : "Aggregate";
|
|
160
|
+
const aggregateFn = typeof nameOrFn === "string" ? aggregateFnOrUndefined! : nameOrFn;
|
|
161
|
+
return this.then(new Aggregate<TCurrentJson, TOut>(name, aggregateFn, id)) as WorkflowChain<TOut>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
merge(): WorkflowChain<TCurrentJson>;
|
|
165
|
+
merge(cfg: Readonly<{ mode: MergeMode; prefer?: ReadonlyArray<string> }>, id?: string): WorkflowChain<TCurrentJson>;
|
|
166
|
+
merge(
|
|
167
|
+
name: string,
|
|
168
|
+
cfg?: Readonly<{ mode: MergeMode; prefer?: ReadonlyArray<string> }>,
|
|
169
|
+
id?: string,
|
|
170
|
+
): WorkflowChain<TCurrentJson>;
|
|
171
|
+
merge(
|
|
172
|
+
nameOrCfg?: string | Readonly<{ mode: MergeMode; prefer?: ReadonlyArray<string> }>,
|
|
173
|
+
cfgOrId?: Readonly<{ mode: MergeMode; prefer?: ReadonlyArray<string> }> | string,
|
|
174
|
+
id?: string,
|
|
175
|
+
): WorkflowChain<TCurrentJson> {
|
|
176
|
+
const name = typeof nameOrCfg === "string" ? nameOrCfg : "Merge";
|
|
177
|
+
const cfg =
|
|
178
|
+
typeof nameOrCfg === "string"
|
|
179
|
+
? ((typeof cfgOrId === "string" ? undefined : cfgOrId) ?? { mode: "passThrough" as const })
|
|
180
|
+
: (nameOrCfg ?? { mode: "passThrough" as const });
|
|
181
|
+
const mergeId = typeof cfgOrId === "string" ? cfgOrId : id;
|
|
182
|
+
return new WorkflowChain(
|
|
183
|
+
this.chain.thenIntoInputHints(new Merge<TCurrentJson>(name, cfg, mergeId)),
|
|
184
|
+
) as WorkflowChain<TCurrentJson>;
|
|
185
|
+
}
|
|
186
|
+
|
|
55
187
|
if<TBranchJson>(
|
|
56
188
|
predicate: (item: TCurrentJson) => boolean,
|
|
57
189
|
branches: Readonly<{
|
|
@@ -95,6 +227,79 @@ export class WorkflowChain<TCurrentJson> {
|
|
|
95
227
|
) as WorkflowChain<BranchOutputMatch<TTrueJson, TFalseJson> extends true ? TTrueJson : never>;
|
|
96
228
|
}
|
|
97
229
|
|
|
230
|
+
route<TBranchJson>(
|
|
231
|
+
branches: Readonly<Record<string, RouteBranchCallback<TCurrentJson, TBranchJson> | undefined>>,
|
|
232
|
+
): WorkflowChain<TBranchJson> {
|
|
233
|
+
const mappedBranches = Object.fromEntries(
|
|
234
|
+
Object.entries(branches).map(([port, branchFactory]) => [
|
|
235
|
+
port,
|
|
236
|
+
branchFactory
|
|
237
|
+
? (branch: ChainCursor<TCurrentJson>) => branchFactory(new WorkflowChain(branch)).chain
|
|
238
|
+
: undefined,
|
|
239
|
+
]),
|
|
240
|
+
) as Readonly<
|
|
241
|
+
Record<string, ((branch: ChainCursor<TCurrentJson>) => ChainCursor<TBranchJson> | undefined) | undefined>
|
|
242
|
+
>;
|
|
243
|
+
return new WorkflowChain(
|
|
244
|
+
this.chain.route(
|
|
245
|
+
mappedBranches as Readonly<
|
|
246
|
+
Record<string, (branch: ChainCursor<TCurrentJson>) => ChainCursor<TBranchJson> | undefined>
|
|
247
|
+
>,
|
|
248
|
+
),
|
|
249
|
+
) as WorkflowChain<TBranchJson>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
switch<TBranchJson>(
|
|
253
|
+
cfg: Readonly<{
|
|
254
|
+
cases: readonly string[];
|
|
255
|
+
defaultCase: string;
|
|
256
|
+
resolveCaseKey: (item: TCurrentJson) => string | Promise<string>;
|
|
257
|
+
branches: Readonly<Record<string, RouteBranchCallback<TCurrentJson, TBranchJson> | undefined>>;
|
|
258
|
+
}>,
|
|
259
|
+
): WorkflowChain<TBranchJson>;
|
|
260
|
+
switch<TBranchJson>(
|
|
261
|
+
name: string,
|
|
262
|
+
cfg: Readonly<{
|
|
263
|
+
cases: readonly string[];
|
|
264
|
+
defaultCase: string;
|
|
265
|
+
resolveCaseKey: (item: TCurrentJson) => string | Promise<string>;
|
|
266
|
+
branches: Readonly<Record<string, RouteBranchCallback<TCurrentJson, TBranchJson> | undefined>>;
|
|
267
|
+
}>,
|
|
268
|
+
id?: string,
|
|
269
|
+
): WorkflowChain<TBranchJson>;
|
|
270
|
+
switch<TBranchJson>(
|
|
271
|
+
nameOrCfg:
|
|
272
|
+
| string
|
|
273
|
+
| Readonly<{
|
|
274
|
+
cases: readonly string[];
|
|
275
|
+
defaultCase: string;
|
|
276
|
+
resolveCaseKey: (item: TCurrentJson) => string | Promise<string>;
|
|
277
|
+
branches: Readonly<Record<string, RouteBranchCallback<TCurrentJson, TBranchJson> | undefined>>;
|
|
278
|
+
}>,
|
|
279
|
+
cfgOrUndefined?: Readonly<{
|
|
280
|
+
cases: readonly string[];
|
|
281
|
+
defaultCase: string;
|
|
282
|
+
resolveCaseKey: (item: TCurrentJson) => string | Promise<string>;
|
|
283
|
+
branches: Readonly<Record<string, RouteBranchCallback<TCurrentJson, TBranchJson> | undefined>>;
|
|
284
|
+
}>,
|
|
285
|
+
id?: string,
|
|
286
|
+
): WorkflowChain<TBranchJson> {
|
|
287
|
+
const name = typeof nameOrCfg === "string" ? nameOrCfg : "Switch";
|
|
288
|
+
const cfg = (typeof nameOrCfg === "string" ? cfgOrUndefined : nameOrCfg)!;
|
|
289
|
+
const switched = this.then(
|
|
290
|
+
new Switch<TCurrentJson>(
|
|
291
|
+
name,
|
|
292
|
+
{
|
|
293
|
+
cases: cfg.cases,
|
|
294
|
+
defaultCase: cfg.defaultCase,
|
|
295
|
+
resolveCaseKey: (item) => cfg.resolveCaseKey(item.json as TCurrentJson),
|
|
296
|
+
},
|
|
297
|
+
id,
|
|
298
|
+
),
|
|
299
|
+
) as WorkflowChain<TCurrentJson>;
|
|
300
|
+
return switched.route(cfg.branches);
|
|
301
|
+
}
|
|
302
|
+
|
|
98
303
|
agent<TOutputSchema extends z.ZodTypeAny>(
|
|
99
304
|
options: WorkflowAgentOptions<TCurrentJson, TOutputSchema>,
|
|
100
305
|
): WorkflowChain<z.output<TOutputSchema>>;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { WorkflowId } from "@codemation/core";
|
|
2
2
|
import { WorkflowBuilder } from "@codemation/core";
|
|
3
|
-
import { Merge } from "./nodes/merge";
|
|
4
3
|
|
|
5
4
|
export function createWorkflowBuilder(meta: Readonly<{ id: WorkflowId; name: string }>): WorkflowBuilder {
|
|
6
|
-
return new WorkflowBuilder(meta
|
|
7
|
-
makeMergeNode: (name) => new Merge(name, { mode: "passThrough", prefer: ["true", "false"] }),
|
|
8
|
-
});
|
|
5
|
+
return new WorkflowBuilder(meta);
|
|
9
6
|
}
|