@codemation/core 0.2.3 → 0.4.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/CHANGELOG.md +23 -0
- package/README.md +2 -0
- package/dist/{EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts → EngineRuntimeRegistration.types-DU6MsjU9.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-Dd4yD31l.d.cts → EngineWorkflowRunnerService-BBkL4VQF.d.cts} +2 -2
- package/dist/{InMemoryRunDataFactory-OUzDmAHt.d.cts → InMemoryRunDataFactory-CsYEMJK2.d.cts} +11 -3
- package/dist/{RunIntentService-Bkg4oYrM.d.cts → RunIntentService-BvlTpmEb.d.cts} +224 -237
- package/dist/{RunIntentService-BAKikN8h.d.ts → RunIntentService-zbTchO9T.d.ts} +305 -259
- package/dist/bootstrap/index.cjs +2 -2
- package/dist/bootstrap/index.d.cts +19 -7
- package/dist/bootstrap/index.d.ts +3 -3
- package/dist/bootstrap/index.js +2 -2
- package/dist/{bootstrap-DwS5S7s9.cjs → bootstrap-DHH2uo-W.cjs} +4 -2
- package/dist/bootstrap-DHH2uo-W.cjs.map +1 -0
- package/dist/{bootstrap-BD6CobHl.js → bootstrap-DbUlOl11.js} +4 -2
- package/dist/bootstrap-DbUlOl11.js.map +1 -0
- package/dist/{index-BDHCiN22.d.ts → index-CUt13qs1.d.ts} +85 -16
- package/dist/index.cjs +74 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -24
- package/dist/index.d.ts +3 -3
- package/dist/index.js +64 -13
- package/dist/index.js.map +1 -1
- package/dist/{runtime-Cy-3FTI_.js → runtime-BdH94eBR.js} +502 -123
- package/dist/runtime-BdH94eBR.js.map +1 -0
- package/dist/{runtime-ZJUpWmPH.cjs → runtime-feFn8OmG.cjs} +561 -122
- package/dist/runtime-feFn8OmG.cjs.map +1 -0
- package/dist/testing.cjs +40 -36
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +17 -26
- package/dist/testing.d.ts +17 -26
- package/dist/testing.js +40 -36
- package/dist/testing.js.map +1 -1
- package/dist/{workflowActivationPolicy-BzyzXLa_.cjs → workflowActivationPolicy-6V3OJD3N.cjs} +65 -19
- package/dist/workflowActivationPolicy-6V3OJD3N.cjs.map +1 -0
- package/dist/{workflowActivationPolicy-B8HzTk3o.js → workflowActivationPolicy-Td9HTOuD.js} +65 -19
- package/dist/workflowActivationPolicy-Td9HTOuD.js.map +1 -0
- package/package.json +2 -1
- package/src/ai/AgentConfigInspectorFactory.ts +4 -0
- package/src/ai/AgentMessageConfigNormalizerFactory.ts +7 -0
- package/src/ai/AgentToolFactory.ts +2 -2
- package/src/ai/AiHost.ts +11 -10
- package/src/ai/NodeBackedToolConfig.ts +1 -1
- package/src/authoring/defineNode.types.ts +144 -25
- package/src/authoring/index.ts +3 -1
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
- package/src/contracts/emitPorts.ts +27 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/itemMeta.ts +11 -0
- package/src/contracts/itemValue.ts +147 -0
- package/src/contracts/runtimeTypes.ts +39 -22
- package/src/contracts/workflowTypes.ts +26 -56
- package/src/execution/FanInMergeByOriginMerger.ts +67 -0
- package/src/execution/ItemValueResolver.ts +27 -0
- package/src/execution/NodeActivationRequestComposer.ts +25 -0
- package/src/execution/NodeActivationRequestInputPreparer.ts +57 -25
- package/src/execution/NodeExecutor.ts +199 -30
- package/src/execution/NodeOutputNormalizer.ts +90 -0
- package/src/execution/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +39 -18
- package/src/orchestration/RunContinuationService.ts +11 -17
- package/src/planning/CurrentStateFrontierPlanner.ts +20 -20
- package/src/planning/RunQueuePlanner.ts +56 -19
- package/src/planning/WorkflowTopologyPlanner.ts +57 -33
- package/src/testing/ItemHarnessNode.ts +4 -10
- package/src/testing/ItemHarnessNodeConfig.ts +7 -16
- package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
- package/src/testing/SubWorkflowRunnerTestNode.ts +28 -43
- package/src/testing/SwitchHarnessNode.ts +54 -0
- package/src/types/index.ts +3 -0
- package/src/workflow/dsl/ChainCursorResolver.ts +68 -23
- package/src/workflow/dsl/WorkflowBuilder.ts +3 -5
- package/src/workflow/dsl/workflowBuilderTypes.ts +5 -8
- package/src/workflowSnapshots/MissingRuntimeNode.ts +4 -4
- package/src/workflowSnapshots/MissingRuntimeNodeConfig.ts +2 -2
- package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +16 -7
- package/dist/bootstrap-BD6CobHl.js.map +0 -1
- package/dist/bootstrap-DwS5S7s9.cjs.map +0 -1
- package/dist/runtime-Cy-3FTI_.js.map +0 -1
- package/dist/runtime-ZJUpWmPH.cjs.map +0 -1
- package/dist/workflowActivationPolicy-B8HzTk3o.js.map +0 -1
- package/dist/workflowActivationPolicy-BzyzXLa_.cjs.map +0 -1
package/src/ai/AiHost.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TypeToken } from "../di";
|
|
2
2
|
|
|
3
3
|
import type { CredentialRequirement } from "../contracts/credentialTypes";
|
|
4
|
+
import type { ItemValue } from "../contracts/itemValue";
|
|
4
5
|
|
|
5
6
|
import type {
|
|
6
7
|
Item,
|
|
@@ -91,6 +92,7 @@ export type AgentMessageLine<TInputJson = unknown> = AgentMessageDto | AgentMess
|
|
|
91
92
|
* Use the object form only when you need `buildMessages` to append messages after optional `prompt` lines.
|
|
92
93
|
*/
|
|
93
94
|
export type AgentMessageConfig<TInputJson = unknown> =
|
|
95
|
+
| ItemValue<ReadonlyArray<AgentMessageLine<TInputJson>>, TInputJson>
|
|
94
96
|
| ReadonlyArray<AgentMessageLine<TInputJson>>
|
|
95
97
|
| {
|
|
96
98
|
readonly prompt?: ReadonlyArray<AgentMessageLine<TInputJson>>;
|
|
@@ -150,7 +152,7 @@ export interface ChatModelFactory<TConfig extends ChatModelConfig = ChatModelCon
|
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
export type NodeBackedToolInputMapperArgs<
|
|
153
|
-
TNodeConfig extends RunnableNodeConfig<any, any
|
|
155
|
+
TNodeConfig extends RunnableNodeConfig<any, any>,
|
|
154
156
|
TToolInput = unknown,
|
|
155
157
|
> = Readonly<{
|
|
156
158
|
input: TToolInput;
|
|
@@ -162,7 +164,7 @@ export type NodeBackedToolInputMapperArgs<
|
|
|
162
164
|
}>;
|
|
163
165
|
|
|
164
166
|
export type NodeBackedToolOutputMapperArgs<
|
|
165
|
-
TNodeConfig extends RunnableNodeConfig<any, any
|
|
167
|
+
TNodeConfig extends RunnableNodeConfig<any, any>,
|
|
166
168
|
TToolInput = unknown,
|
|
167
169
|
> = Readonly<{
|
|
168
170
|
input: TToolInput;
|
|
@@ -174,18 +176,18 @@ export type NodeBackedToolOutputMapperArgs<
|
|
|
174
176
|
outputs: NodeOutputs;
|
|
175
177
|
}>;
|
|
176
178
|
|
|
177
|
-
export type NodeBackedToolInputMapper<TNodeConfig extends RunnableNodeConfig<any, any
|
|
179
|
+
export type NodeBackedToolInputMapper<TNodeConfig extends RunnableNodeConfig<any, any>, TToolInput = unknown> = (
|
|
178
180
|
args: NodeBackedToolInputMapperArgs<TNodeConfig, TToolInput>,
|
|
179
181
|
) => Item<RunnableNodeInputJson<TNodeConfig>> | RunnableNodeInputJson<TNodeConfig>;
|
|
180
182
|
|
|
181
183
|
export type NodeBackedToolOutputMapper<
|
|
182
|
-
TNodeConfig extends RunnableNodeConfig<any, any
|
|
184
|
+
TNodeConfig extends RunnableNodeConfig<any, any>,
|
|
183
185
|
TToolInput = unknown,
|
|
184
186
|
TToolOutput = unknown,
|
|
185
187
|
> = (args: NodeBackedToolOutputMapperArgs<TNodeConfig, TToolInput>) => TToolOutput;
|
|
186
188
|
|
|
187
189
|
export type NodeBackedToolConfigOptions<
|
|
188
|
-
TNodeConfig extends RunnableNodeConfig<any, any
|
|
190
|
+
TNodeConfig extends RunnableNodeConfig<any, any>,
|
|
189
191
|
TInputSchema extends ZodSchemaAny,
|
|
190
192
|
TOutputSchema extends ZodSchemaAny,
|
|
191
193
|
> = Readonly<{
|
|
@@ -197,11 +199,10 @@ export type NodeBackedToolConfigOptions<
|
|
|
197
199
|
mapOutput?: NodeBackedToolOutputMapper<TNodeConfig, ZodInput<TInputSchema>, ZodOutput<TOutputSchema>>;
|
|
198
200
|
}>;
|
|
199
201
|
|
|
200
|
-
export interface AgentNodeConfig<
|
|
201
|
-
TInputJson
|
|
202
|
-
TOutputJson
|
|
203
|
-
|
|
204
|
-
> extends RunnableNodeConfig<TInputJson, TOutputJson, TWireJson> {
|
|
202
|
+
export interface AgentNodeConfig<TInputJson = unknown, TOutputJson = unknown> extends RunnableNodeConfig<
|
|
203
|
+
TInputJson,
|
|
204
|
+
TOutputJson
|
|
205
|
+
> {
|
|
205
206
|
readonly messages: AgentMessageConfig<TInputJson>;
|
|
206
207
|
readonly chatModel: ChatModelConfig;
|
|
207
208
|
readonly tools?: ReadonlyArray<ToolConfig>;
|
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from "./AiHost";
|
|
15
15
|
|
|
16
16
|
export class NodeBackedToolConfig<
|
|
17
|
-
TNodeConfig extends RunnableNodeConfig<any, any
|
|
17
|
+
TNodeConfig extends RunnableNodeConfig<any, any>,
|
|
18
18
|
TInputSchema extends ZodSchemaAny,
|
|
19
19
|
TOutputSchema extends ZodSchemaAny,
|
|
20
20
|
> implements ToolConfig {
|
|
@@ -4,10 +4,11 @@ import type {
|
|
|
4
4
|
CredentialRequirement,
|
|
5
5
|
CredentialTypeId,
|
|
6
6
|
} from "../contracts/credentialTypes";
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
7
|
+
import type { RunnableNode, RunnableNodeExecuteArgs, NodeExecutionContext } from "../contracts/runtimeTypes";
|
|
8
|
+
import type { Item, Items, RunnableNodeConfig } from "../contracts/workflowTypes";
|
|
9
9
|
import type { TypeToken } from "../di";
|
|
10
10
|
import { node as persistedNode } from "../runtime-types/runtimeTypeDecorators.types";
|
|
11
|
+
import type { ZodType } from "zod";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import { DefinedNodeRegistry } from "./DefinedNodeRegistry";
|
|
13
14
|
|
|
@@ -54,6 +55,18 @@ export interface DefinedNodeRunContext<
|
|
|
54
55
|
readonly execution: NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>;
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Arguments for {@link defineNode} `execute` (engine `ctx` matches {@link RunnableNode.execute};
|
|
60
|
+
* the second callback parameter adds {@link DefinedNodeRunContext} for credential accessors).
|
|
61
|
+
*/
|
|
62
|
+
export type DefineNodeExecuteArgs<TConfig extends CredentialJsonRecord, TInputJson> = Readonly<{
|
|
63
|
+
input: TInputJson;
|
|
64
|
+
item: Item;
|
|
65
|
+
itemIndex: number;
|
|
66
|
+
items: Items;
|
|
67
|
+
ctx: NodeExecutionContext<RunnableNodeConfig<TInputJson, unknown> & Readonly<{ config: TConfig }>>;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
57
70
|
export interface DefinedNode<
|
|
58
71
|
TKey extends string,
|
|
59
72
|
TConfig extends CredentialJsonRecord,
|
|
@@ -69,6 +82,9 @@ export interface DefinedNode<
|
|
|
69
82
|
register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Plugin / DSL-friendly node: per-item `execute` with optional {@link RunnableNodeConfig.inputSchema}.
|
|
87
|
+
*/
|
|
72
88
|
export interface DefineNodeOptions<
|
|
73
89
|
TKey extends string,
|
|
74
90
|
TConfig extends CredentialJsonRecord,
|
|
@@ -84,6 +100,34 @@ export interface DefineNodeOptions<
|
|
|
84
100
|
* The Next host resolves Lucide (`lucide:…`), built-in SVGs (`builtin:…`), Simple Icons (`si:…`), and image URLs (`https:`, `data:`, `/…`).
|
|
85
101
|
*/
|
|
86
102
|
readonly icon?: string;
|
|
103
|
+
/** Default values / form hints for **static** node configuration (credentials, retry, IDs), not per-item payload. */
|
|
104
|
+
readonly input?: Readonly<Record<keyof TConfig & string, unknown>>;
|
|
105
|
+
readonly configSchema?: z.ZodType<TConfig>;
|
|
106
|
+
readonly credentials?: TBindings;
|
|
107
|
+
/**
|
|
108
|
+
* Validates **`input`** (engine also accepts `inputSchema` on the node class).
|
|
109
|
+
*/
|
|
110
|
+
readonly inputSchema?: ZodType<TInputJson>;
|
|
111
|
+
execute(
|
|
112
|
+
args: DefineNodeExecuteArgs<TConfig, TInputJson>,
|
|
113
|
+
context: DefinedNodeRunContext<TConfig, TBindings>,
|
|
114
|
+
): MaybePromise<TOutputJson>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Batch-oriented defined node: `run` receives all item JSON once (last item in activation); emits one output per input row.
|
|
119
|
+
*/
|
|
120
|
+
export interface DefineBatchNodeOptions<
|
|
121
|
+
TKey extends string,
|
|
122
|
+
TConfig extends CredentialJsonRecord,
|
|
123
|
+
TInputJson,
|
|
124
|
+
TOutputJson,
|
|
125
|
+
TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
|
|
126
|
+
> {
|
|
127
|
+
readonly key: TKey;
|
|
128
|
+
readonly title: string;
|
|
129
|
+
readonly description?: string;
|
|
130
|
+
readonly icon?: string;
|
|
87
131
|
readonly input?: Readonly<Record<keyof TConfig & string, unknown>>;
|
|
88
132
|
readonly configSchema?: z.ZodType<TConfig>;
|
|
89
133
|
readonly credentials?: TBindings;
|
|
@@ -145,7 +189,7 @@ const definedNodeCredentialRequirementFactory = {
|
|
|
145
189
|
const definedNodeCredentialAccessorFactory = {
|
|
146
190
|
create<TBindings extends DefinedNodeCredentialBindings | undefined>(
|
|
147
191
|
bindings: TBindings,
|
|
148
|
-
ctx: NodeExecutionContext<RunnableNodeConfig<any, any
|
|
192
|
+
ctx: NodeExecutionContext<RunnableNodeConfig<any, any>>,
|
|
149
193
|
): DefinedNodeCredentialAccessors<TBindings> {
|
|
150
194
|
if (!bindings) {
|
|
151
195
|
return {} as DefinedNodeCredentialAccessors<TBindings>;
|
|
@@ -167,32 +211,107 @@ export function defineNode<
|
|
|
167
211
|
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
168
212
|
type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson> & Readonly<{ config: TConfig }>;
|
|
169
213
|
|
|
170
|
-
const DefinedNodeRuntime = class implements
|
|
214
|
+
const DefinedNodeRuntime = class implements RunnableNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
|
|
171
215
|
readonly kind = "node" as const;
|
|
172
216
|
readonly outputPorts = ["main"] as const;
|
|
217
|
+
readonly inputSchema = options.inputSchema;
|
|
173
218
|
|
|
174
|
-
async execute(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
async execute(
|
|
220
|
+
args: Readonly<RunnableNodeExecuteArgs<DefinedRunnableNodeConfigShape, TInputJson>>,
|
|
221
|
+
): Promise<unknown> {
|
|
222
|
+
const ctx = args.ctx;
|
|
223
|
+
const context: DefinedNodeRunContext<TConfig, TBindings> = {
|
|
224
|
+
config: ctx.config.config,
|
|
225
|
+
credentials: definedNodeCredentialAccessorFactory.create(
|
|
226
|
+
options.credentials,
|
|
227
|
+
ctx,
|
|
228
|
+
) as DefinedNodeCredentialAccessors<TBindings>,
|
|
229
|
+
execution: ctx as unknown as NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>,
|
|
230
|
+
};
|
|
231
|
+
const payload: DefineNodeExecuteArgs<TConfig, TInputJson> = {
|
|
232
|
+
input: args.input,
|
|
233
|
+
item: args.item,
|
|
234
|
+
itemIndex: args.itemIndex,
|
|
235
|
+
items: args.items,
|
|
236
|
+
ctx,
|
|
237
|
+
};
|
|
238
|
+
return await options.execute(payload, context);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
186
241
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
242
|
+
persistedNode({ name: options.key })(DefinedNodeRuntime);
|
|
243
|
+
|
|
244
|
+
const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson> {
|
|
245
|
+
readonly kind = "node" as const;
|
|
246
|
+
readonly type: TypeToken<unknown> = DefinedNodeRuntime;
|
|
247
|
+
readonly icon = options.icon;
|
|
248
|
+
readonly inputSchema = options.inputSchema;
|
|
249
|
+
|
|
250
|
+
constructor(
|
|
251
|
+
public readonly name: string,
|
|
252
|
+
public readonly config: TConfig,
|
|
253
|
+
public readonly id?: string,
|
|
254
|
+
) {}
|
|
255
|
+
|
|
256
|
+
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
257
|
+
return credentialRequirements;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
|
|
262
|
+
kind: "defined-node",
|
|
263
|
+
key: options.key,
|
|
264
|
+
title: options.title,
|
|
265
|
+
description: options.description,
|
|
266
|
+
create(config, name = options.title, id) {
|
|
267
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
268
|
+
},
|
|
269
|
+
register(context) {
|
|
270
|
+
context.registerNode(DefinedNodeRuntime);
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined>);
|
|
275
|
+
|
|
276
|
+
return definition;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function defineBatchNode<
|
|
280
|
+
TKey extends string,
|
|
281
|
+
TConfig extends CredentialJsonRecord,
|
|
282
|
+
TInputJson,
|
|
283
|
+
TOutputJson,
|
|
284
|
+
TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
|
|
285
|
+
>(
|
|
286
|
+
options: DefineBatchNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings>,
|
|
287
|
+
): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> {
|
|
288
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
289
|
+
type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson> & Readonly<{ config: TConfig }>;
|
|
290
|
+
|
|
291
|
+
const DefinedNodeRuntime = class implements RunnableNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
|
|
292
|
+
readonly kind = "node" as const;
|
|
293
|
+
readonly outputPorts = ["main"] as const;
|
|
294
|
+
|
|
295
|
+
async execute(
|
|
296
|
+
args: Readonly<RunnableNodeExecuteArgs<DefinedRunnableNodeConfigShape, TInputJson>>,
|
|
297
|
+
): Promise<unknown> {
|
|
298
|
+
if (args.itemIndex !== args.items.length - 1) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
const ctx = args.ctx;
|
|
302
|
+
const context: DefinedNodeRunContext<TConfig, TBindings> = {
|
|
303
|
+
config: ctx.config.config,
|
|
304
|
+
credentials: definedNodeCredentialAccessorFactory.create(
|
|
305
|
+
options.credentials,
|
|
306
|
+
ctx,
|
|
307
|
+
) as DefinedNodeCredentialAccessors<TBindings>,
|
|
308
|
+
execution: ctx as unknown as NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>,
|
|
195
309
|
};
|
|
310
|
+
const outputs = await options.run(
|
|
311
|
+
args.items.map((item) => item.json as TInputJson),
|
|
312
|
+
context,
|
|
313
|
+
);
|
|
314
|
+
return [...outputs];
|
|
196
315
|
}
|
|
197
316
|
};
|
|
198
317
|
|
|
@@ -227,7 +346,7 @@ export function defineNode<
|
|
|
227
346
|
},
|
|
228
347
|
};
|
|
229
348
|
|
|
230
|
-
DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown>);
|
|
349
|
+
DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined>);
|
|
231
350
|
|
|
232
351
|
return definition;
|
|
233
352
|
}
|
package/src/authoring/index.ts
CHANGED
|
@@ -5,8 +5,10 @@ export type {
|
|
|
5
5
|
DefinedNodeCredentialBinding,
|
|
6
6
|
DefinedNodeCredentialBindings,
|
|
7
7
|
DefinedNodeRunContext,
|
|
8
|
+
DefineBatchNodeOptions,
|
|
9
|
+
DefineNodeExecuteArgs,
|
|
8
10
|
DefineNodeOptions,
|
|
9
11
|
} from "./defineNode.types";
|
|
10
|
-
export { defineNode } from "./defineNode.types";
|
|
12
|
+
export { defineBatchNode, defineNode } from "./defineNode.types";
|
|
11
13
|
export type { DefineCredentialOptions } from "./defineCredential.types";
|
|
12
14
|
export { defineCredential } from "./defineCredential.types";
|
|
@@ -4,9 +4,11 @@ import { EngineExecutionLimitsPolicyFactory } from "../../policies/executionLimi
|
|
|
4
4
|
import {
|
|
5
5
|
DefaultAsyncSleeper,
|
|
6
6
|
InProcessRetryRunnerFactory,
|
|
7
|
+
ItemValueResolver,
|
|
7
8
|
NodeExecutor,
|
|
8
9
|
NodeExecutorFactory,
|
|
9
10
|
NodeInstanceFactoryFactory,
|
|
11
|
+
NodeOutputNormalizer,
|
|
10
12
|
} from "../../execution";
|
|
11
13
|
import {
|
|
12
14
|
EngineFactory,
|
|
@@ -38,6 +40,12 @@ export class EngineRuntimeRegistrar {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
private registerSupportFactories(container: DependencyContainer): void {
|
|
43
|
+
if (!container.isRegistered(ItemValueResolver, true)) {
|
|
44
|
+
container.registerSingleton(ItemValueResolver, ItemValueResolver);
|
|
45
|
+
}
|
|
46
|
+
if (!container.isRegistered(NodeOutputNormalizer, true)) {
|
|
47
|
+
container.registerSingleton(NodeOutputNormalizer, NodeOutputNormalizer);
|
|
48
|
+
}
|
|
41
49
|
container.register(EngineExecutionLimitsPolicyFactory, { useClass: EngineExecutionLimitsPolicyFactory });
|
|
42
50
|
container.register(NodeInstanceFactoryFactory, { useClass: NodeInstanceFactoryFactory });
|
|
43
51
|
container.register(DefaultAsyncSleeper, { useClass: DefaultAsyncSleeper });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Items, JsonNonArray, OutputPortKey } from "./workflowTypes";
|
|
2
|
+
|
|
3
|
+
const EMIT_PORTS_BRAND = Symbol.for("codemation.emitPorts");
|
|
4
|
+
|
|
5
|
+
export type PortsEmission = Readonly<{
|
|
6
|
+
readonly [EMIT_PORTS_BRAND]: true;
|
|
7
|
+
readonly ports: Readonly<Partial<Record<OutputPortKey, Items | ReadonlyArray<JsonNonArray>>>>;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export function emitPorts(
|
|
11
|
+
ports: Readonly<Partial<Record<OutputPortKey, Items | ReadonlyArray<JsonNonArray>>>>,
|
|
12
|
+
): PortsEmission {
|
|
13
|
+
return { [EMIT_PORTS_BRAND]: true, ports };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isPortsEmission(value: unknown): value is PortsEmission {
|
|
17
|
+
return (
|
|
18
|
+
typeof value === "object" &&
|
|
19
|
+
value !== null &&
|
|
20
|
+
EMIT_PORTS_BRAND in value &&
|
|
21
|
+
(value as Record<symbol, unknown>)[EMIT_PORTS_BRAND] === true
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isUnbrandedPortsEmissionShape(value: unknown): value is Readonly<{ ports: unknown }> {
|
|
26
|
+
return typeof value === "object" && value !== null && "ports" in value && !isPortsEmission(value);
|
|
27
|
+
}
|
package/src/contracts/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from "./credentialTypes";
|
|
2
|
+
export * from "./emitPorts";
|
|
2
3
|
export * from "./executionPersistenceContracts";
|
|
4
|
+
export * from "./itemMeta";
|
|
5
|
+
export * from "./itemValue";
|
|
3
6
|
export * from "./runtimeTypes";
|
|
4
7
|
export * from "./runFinishedAtFactory";
|
|
5
8
|
export * from "./runTypes";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Item } from "./workflowTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads `meta._cm.originIndex` when present (used for fan-in merge-by-origin and Merge routing).
|
|
5
|
+
*/
|
|
6
|
+
export function getOriginIndexFromItem(item: Item): number | undefined {
|
|
7
|
+
const meta = item.meta as Record<string, unknown> | undefined;
|
|
8
|
+
const cm = meta?._cm as Record<string, unknown> | undefined;
|
|
9
|
+
const v = cm?.originIndex;
|
|
10
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { NodeExecutionContext } from "./runtimeTypes";
|
|
2
|
+
import type { Item, Items, NodeActivationId, NodeId, RunDataSnapshot, RunId, WorkflowId } from "./workflowTypes";
|
|
3
|
+
|
|
4
|
+
const ITEM_VALUE_BRAND = Symbol.for("codemation.itemValue");
|
|
5
|
+
|
|
6
|
+
export type ItemValueResolvedContext = Readonly<{
|
|
7
|
+
runId: RunId;
|
|
8
|
+
workflowId: WorkflowId;
|
|
9
|
+
nodeId: NodeId;
|
|
10
|
+
activationId: NodeActivationId;
|
|
11
|
+
data: RunDataSnapshot;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context aligned with former {@link ItemInputMapperContext} — use **`data`** to read any completed upstream node.
|
|
16
|
+
*/
|
|
17
|
+
export type ItemValueContext = ItemValueResolvedContext;
|
|
18
|
+
|
|
19
|
+
export type ItemValueArgs<TItemJson = unknown> = Readonly<{
|
|
20
|
+
item: Item<TItemJson>;
|
|
21
|
+
itemIndex: number;
|
|
22
|
+
items: Items<TItemJson>;
|
|
23
|
+
ctx: ItemValueContext;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
export type ItemValueCallback<T, TItemJson = unknown> = (args: ItemValueArgs<TItemJson>) => T | Promise<T>;
|
|
27
|
+
|
|
28
|
+
export type ItemValue<T, TItemJson = unknown> = Readonly<{
|
|
29
|
+
readonly [ITEM_VALUE_BRAND]: true;
|
|
30
|
+
readonly fn: ItemValueCallback<T, TItemJson>;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
export function itemValue<T, TItemJson = unknown>(fn: ItemValueCallback<T, TItemJson>): ItemValue<T, TItemJson> {
|
|
34
|
+
return { [ITEM_VALUE_BRAND]: true, fn };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isItemValue<T, TItemJson = unknown>(value: unknown): value is ItemValue<T, TItemJson> {
|
|
38
|
+
if (typeof value !== "object" || value === null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const v = value as Record<PropertyKey, unknown>;
|
|
42
|
+
if (v[ITEM_VALUE_BRAND] === true) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Support snapshot-hydrated itemValue wrappers where the symbol brand was lost but the callback survived.
|
|
46
|
+
// Workflow snapshot hydration currently restores function-valued fields (like `fn`) but may drop symbol-keyed brands.
|
|
47
|
+
// We treat the minimal `{ fn: Function }` shape as an itemValue wrapper to keep runnable configs working.
|
|
48
|
+
const keys = Object.keys(v);
|
|
49
|
+
if (keys.length === 1 && keys[0] === "fn" && typeof (v as { fn?: unknown }).fn === "function") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// Support legacy module-local Symbol("codemation.itemValue") brands (e.g. duplicate module graphs).
|
|
53
|
+
for (const sym of Object.getOwnPropertySymbols(v)) {
|
|
54
|
+
if (sym.description === "codemation.itemValue" && v[sym] === true) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function containsItemValueInUnknown(value: unknown, seen: WeakSet<object> = new WeakSet()): boolean {
|
|
62
|
+
if (isItemValue(value)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (value === null || typeof value !== "object") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (seen.has(value as object)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
seen.add(value as object);
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
return value.some((entry) => containsItemValueInUnknown(entry, seen));
|
|
74
|
+
}
|
|
75
|
+
for (const entry of Object.values(value as Record<string, unknown>)) {
|
|
76
|
+
if (containsItemValueInUnknown(entry, seen)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Deep-resolves {@link itemValue} leaves. Returns a new graph (does not mutate the original config object).
|
|
85
|
+
*/
|
|
86
|
+
export async function resolveItemValuesInUnknown(
|
|
87
|
+
value: unknown,
|
|
88
|
+
args: ItemValueArgs,
|
|
89
|
+
seen: WeakSet<object> = new WeakSet(),
|
|
90
|
+
): Promise<unknown> {
|
|
91
|
+
if (isItemValue(value)) {
|
|
92
|
+
return await Promise.resolve(value.fn(args));
|
|
93
|
+
}
|
|
94
|
+
if (value === null || typeof value !== "object") {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
if (seen.has(value as object)) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
seen.add(value as object);
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
const out: unknown[] = [];
|
|
103
|
+
for (let i = 0; i < value.length; i++) {
|
|
104
|
+
out.push(await resolveItemValuesInUnknown(value[i], args, seen));
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
const rec = value as Record<string, unknown>;
|
|
109
|
+
const entries = Object.entries(rec);
|
|
110
|
+
const proto = Object.getPrototypeOf(value);
|
|
111
|
+
if (proto !== Object.prototype && proto !== null && entries.length === 0) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
const out = Object.create(proto) as Record<string, unknown>;
|
|
115
|
+
for (const [k, v] of entries) {
|
|
116
|
+
out[k] = await resolveItemValuesInUnknown(v, args, seen);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Clones runnable config (best-effort) so per-item {@link itemValue} resolution never mutates shared instances.
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveItemValuesForExecution(
|
|
125
|
+
config: unknown,
|
|
126
|
+
nodeCtx: NodeExecutionContext,
|
|
127
|
+
item: Item,
|
|
128
|
+
itemIndex: number,
|
|
129
|
+
items: Items,
|
|
130
|
+
): Promise<unknown | undefined> {
|
|
131
|
+
const ivArgs: ItemValueArgs = {
|
|
132
|
+
item,
|
|
133
|
+
itemIndex,
|
|
134
|
+
items,
|
|
135
|
+
ctx: {
|
|
136
|
+
runId: nodeCtx.runId,
|
|
137
|
+
workflowId: nodeCtx.workflowId,
|
|
138
|
+
nodeId: nodeCtx.nodeId,
|
|
139
|
+
activationId: nodeCtx.activationId,
|
|
140
|
+
data: nodeCtx.data,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
if (!containsItemValueInUnknown(config)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return await resolveItemValuesInUnknown(config, ivArgs);
|
|
147
|
+
}
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
NodeConfigBase,
|
|
26
26
|
NodeId,
|
|
27
27
|
NodeOutputs,
|
|
28
|
+
RunnableNodeConfig,
|
|
28
29
|
OutputPortKey,
|
|
29
30
|
ParentExecutionRef,
|
|
30
31
|
RunDataFactory,
|
|
@@ -221,39 +222,55 @@ export interface EngineHost {
|
|
|
221
222
|
workflows?: WorkflowRunnerService;
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
export interface Node<TConfig extends NodeConfigBase = NodeConfigBase> {
|
|
225
|
-
kind: "node";
|
|
226
|
-
outputPorts: ReadonlyArray<OutputPortKey>;
|
|
227
|
-
execute(items: Items, ctx: NodeExecutionContext<TConfig>): Promise<NodeOutputs>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
225
|
/**
|
|
231
|
-
*
|
|
232
|
-
* Engine applies
|
|
226
|
+
* Per-item runnable node: return JSON, an array to fan-out on `main`, or {@link emitPorts} for multi-port emission.
|
|
227
|
+
* Engine applies `inputSchema.parse(item.json)` and passes the result as `args.input` (wire `item.json` is unchanged).
|
|
233
228
|
*/
|
|
234
|
-
export interface
|
|
235
|
-
TConfig extends
|
|
229
|
+
export interface RunnableNodeExecuteArgs<
|
|
230
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
236
231
|
TInputJson = unknown,
|
|
237
|
-
|
|
232
|
+
> {
|
|
233
|
+
readonly input: TInputJson;
|
|
234
|
+
readonly item: Item;
|
|
235
|
+
readonly itemIndex: number;
|
|
236
|
+
readonly items: Items;
|
|
237
|
+
readonly ctx: NodeExecutionContext<TConfig>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface RunnableNode<
|
|
241
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
242
|
+
TInputJson = unknown,
|
|
243
|
+
_TOutputJson = unknown,
|
|
238
244
|
> {
|
|
239
245
|
readonly kind: "node";
|
|
240
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Declared output ports (e.g. `["main"]`).
|
|
248
|
+
*
|
|
249
|
+
* Prefer describing dynamic router ports (Switch) and fixed multi-ports (If true/false)
|
|
250
|
+
* via {@link NodeConfigBase.declaredOutputPorts}. Engine defaults to `["main"]` when omitted.
|
|
251
|
+
*/
|
|
252
|
+
readonly outputPorts?: ReadonlyArray<OutputPortKey>;
|
|
241
253
|
/** When omitted, engine uses {@link RunnableNodeConfig.inputSchema} or `z.unknown()`. */
|
|
242
254
|
readonly inputSchema?: ZodType<TInputJson>;
|
|
243
|
-
|
|
244
|
-
args: Readonly<{
|
|
245
|
-
input: TInputJson;
|
|
246
|
-
item: Item;
|
|
247
|
-
itemIndex: number;
|
|
248
|
-
items: Items;
|
|
249
|
-
ctx: NodeExecutionContext<TConfig>;
|
|
250
|
-
}>,
|
|
251
|
-
): Promise<TOutputJson> | TOutputJson;
|
|
255
|
+
execute(args: RunnableNodeExecuteArgs<TConfig, TInputJson>): Promise<unknown> | unknown;
|
|
252
256
|
}
|
|
253
257
|
|
|
258
|
+
/** @deprecated Use {@link RunnableNode} */
|
|
259
|
+
export type ItemNode<
|
|
260
|
+
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
261
|
+
TInputJson = unknown,
|
|
262
|
+
TOutputJson = unknown,
|
|
263
|
+
> = RunnableNode<TConfig, TInputJson, TOutputJson>;
|
|
264
|
+
|
|
254
265
|
export interface MultiInputNode<TConfig extends NodeConfigBase = NodeConfigBase> {
|
|
255
266
|
kind: "node";
|
|
256
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Declared output ports (typically `["main"]`).
|
|
269
|
+
*
|
|
270
|
+
* Prefer describing ports for authoring/canvas via {@link NodeConfigBase.declaredOutputPorts}.
|
|
271
|
+
* Engine defaults to `["main"]` when omitted.
|
|
272
|
+
*/
|
|
273
|
+
outputPorts?: ReadonlyArray<OutputPortKey>;
|
|
257
274
|
executeMulti(inputsByPort: NodeInputsByPort, ctx: NodeExecutionContext<TConfig>): Promise<NodeOutputs>;
|
|
258
275
|
}
|
|
259
276
|
|