@codemation/core 0.3.0 → 0.5.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/{EngineRuntimeRegistration.types-Bjeo7Sfq.d.ts → EngineRuntimeRegistration.types-BtTZolK0.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-Dd4yD31l.d.cts → EngineWorkflowRunnerService-Ddl0fekp.d.cts} +2 -2
  4. package/dist/{InMemoryRunDataFactory-OUzDmAHt.d.cts → InMemoryRunDataFactory-i-u2yngD.d.cts} +11 -3
  5. package/dist/{RunIntentService-Bkg4oYrM.d.cts → RunIntentService-Cjx-glgz.d.cts} +232 -237
  6. package/dist/{RunIntentService-BAKikN8h.d.ts → RunIntentService-Dkr4YwN8.d.ts} +313 -259
  7. package/dist/bootstrap/index.cjs +2 -2
  8. package/dist/bootstrap/index.d.cts +19 -7
  9. package/dist/bootstrap/index.d.ts +3 -3
  10. package/dist/bootstrap/index.js +2 -2
  11. package/dist/{bootstrap-DwS5S7s9.cjs → bootstrap-DHH2uo-W.cjs} +4 -2
  12. package/dist/bootstrap-DHH2uo-W.cjs.map +1 -0
  13. package/dist/{bootstrap-BD6CobHl.js → bootstrap-DbUlOl11.js} +4 -2
  14. package/dist/bootstrap-DbUlOl11.js.map +1 -0
  15. package/dist/{index-uCm9l0nw.d.ts → index-B2v4wtys.d.ts} +62 -34
  16. package/dist/index.cjs +22 -15
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +108 -42
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +13 -16
  21. package/dist/index.js.map +1 -1
  22. package/dist/{runtime-Cy-3FTI_.js → runtime-BdH94eBR.js} +502 -123
  23. package/dist/runtime-BdH94eBR.js.map +1 -0
  24. package/dist/{runtime-ZJUpWmPH.cjs → runtime-feFn8OmG.cjs} +561 -122
  25. package/dist/runtime-feFn8OmG.cjs.map +1 -0
  26. package/dist/testing.cjs +40 -36
  27. package/dist/testing.cjs.map +1 -1
  28. package/dist/testing.d.cts +17 -26
  29. package/dist/testing.d.ts +17 -26
  30. package/dist/testing.js +40 -36
  31. package/dist/testing.js.map +1 -1
  32. package/dist/{workflowActivationPolicy-BzyzXLa_.cjs → workflowActivationPolicy-6V3OJD3N.cjs} +65 -19
  33. package/dist/workflowActivationPolicy-6V3OJD3N.cjs.map +1 -0
  34. package/dist/{workflowActivationPolicy-B8HzTk3o.js → workflowActivationPolicy-Td9HTOuD.js} +65 -19
  35. package/dist/workflowActivationPolicy-Td9HTOuD.js.map +1 -0
  36. package/package.json +2 -1
  37. package/src/ai/AgentConfigInspectorFactory.ts +4 -0
  38. package/src/ai/AgentMessageConfigNormalizerFactory.ts +7 -0
  39. package/src/ai/AgentToolFactory.ts +2 -2
  40. package/src/ai/AiHost.ts +11 -10
  41. package/src/ai/NodeBackedToolConfig.ts +1 -1
  42. package/src/authoring/defineNode.types.ts +48 -72
  43. package/src/authoring/index.ts +1 -1
  44. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
  45. package/src/contracts/credentialTypes.ts +9 -0
  46. package/src/contracts/emitPorts.ts +27 -0
  47. package/src/contracts/index.ts +3 -0
  48. package/src/contracts/itemMeta.ts +11 -0
  49. package/src/contracts/itemValue.ts +147 -0
  50. package/src/contracts/runtimeTypes.ts +39 -22
  51. package/src/contracts/workflowTypes.ts +26 -56
  52. package/src/execution/FanInMergeByOriginMerger.ts +67 -0
  53. package/src/execution/ItemValueResolver.ts +27 -0
  54. package/src/execution/NodeActivationRequestComposer.ts +25 -0
  55. package/src/execution/NodeActivationRequestInputPreparer.ts +57 -25
  56. package/src/execution/NodeExecutor.ts +199 -30
  57. package/src/execution/NodeOutputNormalizer.ts +90 -0
  58. package/src/execution/index.ts +2 -0
  59. package/src/index.ts +2 -0
  60. package/src/orchestration/NodeExecutionRequestHandlerService.ts +39 -18
  61. package/src/orchestration/RunContinuationService.ts +11 -17
  62. package/src/planning/CurrentStateFrontierPlanner.ts +20 -20
  63. package/src/planning/RunQueuePlanner.ts +56 -19
  64. package/src/planning/WorkflowTopologyPlanner.ts +57 -33
  65. package/src/testing/ItemHarnessNode.ts +4 -10
  66. package/src/testing/ItemHarnessNodeConfig.ts +7 -16
  67. package/src/testing/RegistrarEngineTestKitFactory.ts +2 -0
  68. package/src/testing/SubWorkflowRunnerTestNode.ts +28 -43
  69. package/src/testing/SwitchHarnessNode.ts +54 -0
  70. package/src/types/index.ts +3 -0
  71. package/src/workflow/dsl/ChainCursorResolver.ts +68 -23
  72. package/src/workflow/dsl/WorkflowBuilder.ts +3 -5
  73. package/src/workflow/dsl/workflowBuilderTypes.ts +5 -8
  74. package/src/workflowSnapshots/MissingRuntimeNode.ts +4 -4
  75. package/src/workflowSnapshots/MissingRuntimeNodeConfig.ts +2 -2
  76. package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +16 -7
  77. package/dist/bootstrap-BD6CobHl.js.map +0 -1
  78. package/dist/bootstrap-DwS5S7s9.cjs.map +0 -1
  79. package/dist/runtime-Cy-3FTI_.js.map +0 -1
  80. package/dist/runtime-ZJUpWmPH.cjs.map +0 -1
  81. package/dist/workflowActivationPolicy-B8HzTk3o.js.map +0 -1
  82. 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, 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, 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, any>, TToolInput = unknown> = (
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, 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, 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 = unknown,
202
- TOutputJson = unknown,
203
- TWireJson = TInputJson,
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, any>,
17
+ TNodeConfig extends RunnableNodeConfig<any, any>,
18
18
  TInputSchema extends ZodSchemaAny,
19
19
  TOutputSchema extends ZodSchemaAny,
20
20
  > implements ToolConfig {
@@ -4,8 +4,8 @@ import type {
4
4
  CredentialRequirement,
5
5
  CredentialTypeId,
6
6
  } from "../contracts/credentialTypes";
7
- import type { ItemNode, Node, NodeExecutionContext } from "../contracts/runtimeTypes";
8
- import type { Item, ItemInputMapper, Items, NodeOutputs, RunnableNodeConfig } from "../contracts/workflowTypes";
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
11
  import type { ZodType } from "zod";
@@ -56,15 +56,15 @@ export interface DefinedNodeRunContext<
56
56
  }
57
57
 
58
58
  /**
59
- * Arguments for {@link defineNode} `executeOne` (engine `ctx` matches {@link ItemNode.executeOne};
59
+ * Arguments for {@link defineNode} `execute` (engine `ctx` matches {@link RunnableNode.execute};
60
60
  * the second callback parameter adds {@link DefinedNodeRunContext} for credential accessors).
61
61
  */
62
- export type DefineNodeExecuteOneArgs<TConfig extends CredentialJsonRecord, TInputJson, TWireJson> = Readonly<{
62
+ export type DefineNodeExecuteArgs<TConfig extends CredentialJsonRecord, TInputJson> = Readonly<{
63
63
  input: TInputJson;
64
64
  item: Item;
65
65
  itemIndex: number;
66
66
  items: Items;
67
- ctx: NodeExecutionContext<RunnableNodeConfig<TInputJson, unknown, TWireJson> & Readonly<{ config: TConfig }>>;
67
+ ctx: NodeExecutionContext<RunnableNodeConfig<TInputJson, unknown> & Readonly<{ config: TConfig }>>;
68
68
  }>;
69
69
 
70
70
  export interface DefinedNode<
@@ -73,20 +73,17 @@ export interface DefinedNode<
73
73
  TInputJson,
74
74
  TOutputJson,
75
75
  _TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
76
- TWireJson = TInputJson,
77
76
  > {
78
77
  readonly kind: "defined-node";
79
78
  readonly key: TKey;
80
79
  readonly title: string;
81
80
  readonly description?: string;
82
- create(config: TConfig, name?: string, id?: string): RunnableNodeConfig<TInputJson, TOutputJson, TWireJson>;
81
+ create(config: TConfig, name?: string, id?: string): RunnableNodeConfig<TInputJson, TOutputJson>;
83
82
  register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
84
83
  }
85
84
 
86
85
  /**
87
- * Plugin / DSL-friendly node: **one item in, one item out** per engine activation step.
88
- * The engine applies {@link RunnableNodeConfig.mapInput} (if any) + {@link RunnableNodeConfig.inputSchema}
89
- * before `executeOne`. Use {@link defineBatchNode} for legacy batch `run(items, …)` semantics.
86
+ * Plugin / DSL-friendly node: per-item `execute` with optional {@link RunnableNodeConfig.inputSchema}.
90
87
  */
91
88
  export interface DefineNodeOptions<
92
89
  TKey extends string,
@@ -94,7 +91,6 @@ export interface DefineNodeOptions<
94
91
  TInputJson,
95
92
  TOutputJson,
96
93
  TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
97
- TWireJson = TInputJson,
98
94
  > {
99
95
  readonly key: TKey;
100
96
  readonly title: string;
@@ -109,21 +105,17 @@ export interface DefineNodeOptions<
109
105
  readonly configSchema?: z.ZodType<TConfig>;
110
106
  readonly credentials?: TBindings;
111
107
  /**
112
- * Validates **`input`** after optional {@link mapInput} (engine also accepts `inputSchema` on the node class).
108
+ * Validates **`input`** (engine also accepts `inputSchema` on the node class).
113
109
  */
114
110
  readonly inputSchema?: ZodType<TInputJson>;
115
- /**
116
- * Maps wire JSON (`item.json` from upstream) to execute input before validation.
117
- */
118
- readonly mapInput?: ItemInputMapper<TWireJson, TInputJson>;
119
- executeOne(
120
- args: DefineNodeExecuteOneArgs<TConfig, TInputJson, TWireJson>,
111
+ execute(
112
+ args: DefineNodeExecuteArgs<TConfig, TInputJson>,
121
113
  context: DefinedNodeRunContext<TConfig, TBindings>,
122
114
  ): MaybePromise<TOutputJson>;
123
115
  }
124
116
 
125
117
  /**
126
- * Batch-oriented defined node (legacy): receives all items at once via `run`.
118
+ * Batch-oriented defined node: `run` receives all item JSON once (last item in activation); emits one output per input row.
127
119
  */
128
120
  export interface DefineBatchNodeOptions<
129
121
  TKey extends string,
@@ -197,7 +189,7 @@ const definedNodeCredentialRequirementFactory = {
197
189
  const definedNodeCredentialAccessorFactory = {
198
190
  create<TBindings extends DefinedNodeCredentialBindings | undefined>(
199
191
  bindings: TBindings,
200
- ctx: NodeExecutionContext<RunnableNodeConfig<any, any, any>>,
192
+ ctx: NodeExecutionContext<RunnableNodeConfig<any, any>>,
201
193
  ): DefinedNodeCredentialAccessors<TBindings> {
202
194
  if (!bindings) {
203
195
  return {} as DefinedNodeCredentialAccessors<TBindings>;
@@ -213,28 +205,20 @@ export function defineNode<
213
205
  TInputJson,
214
206
  TOutputJson,
215
207
  TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
216
- TWireJson = TInputJson,
217
208
  >(
218
- options: DefineNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson>,
219
- ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson> {
209
+ options: DefineNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings>,
210
+ ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> {
220
211
  const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
221
- type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson, TWireJson> &
222
- Readonly<{ config: TConfig }>;
212
+ type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson> & Readonly<{ config: TConfig }>;
223
213
 
224
- const DefinedNodeRuntime = class implements ItemNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
214
+ const DefinedNodeRuntime = class implements RunnableNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
225
215
  readonly kind = "node" as const;
226
216
  readonly outputPorts = ["main"] as const;
227
217
  readonly inputSchema = options.inputSchema;
228
218
 
229
- async executeOne(
230
- args: Readonly<{
231
- input: TInputJson;
232
- item: Item;
233
- itemIndex: number;
234
- items: Items;
235
- ctx: NodeExecutionContext<DefinedRunnableNodeConfigShape>;
236
- }>,
237
- ): Promise<TOutputJson> {
219
+ async execute(
220
+ args: Readonly<RunnableNodeExecuteArgs<DefinedRunnableNodeConfigShape, TInputJson>>,
221
+ ): Promise<unknown> {
238
222
  const ctx = args.ctx;
239
223
  const context: DefinedNodeRunContext<TConfig, TBindings> = {
240
224
  config: ctx.config.config,
@@ -244,25 +228,24 @@ export function defineNode<
244
228
  ) as DefinedNodeCredentialAccessors<TBindings>,
245
229
  execution: ctx as unknown as NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>,
246
230
  };
247
- const payload: DefineNodeExecuteOneArgs<TConfig, TInputJson, TWireJson> = {
231
+ const payload: DefineNodeExecuteArgs<TConfig, TInputJson> = {
248
232
  input: args.input,
249
233
  item: args.item,
250
234
  itemIndex: args.itemIndex,
251
235
  items: args.items,
252
236
  ctx,
253
237
  };
254
- return await options.executeOne(payload, context);
238
+ return await options.execute(payload, context);
255
239
  }
256
240
  };
257
241
 
258
242
  persistedNode({ name: options.key })(DefinedNodeRuntime);
259
243
 
260
- const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson, TWireJson> {
244
+ const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson> {
261
245
  readonly kind = "node" as const;
262
246
  readonly type: TypeToken<unknown> = DefinedNodeRuntime;
263
247
  readonly icon = options.icon;
264
248
  readonly inputSchema = options.inputSchema;
265
- readonly mapInput = options.mapInput;
266
249
 
267
250
  constructor(
268
251
  public readonly name: string,
@@ -275,7 +258,7 @@ export function defineNode<
275
258
  }
276
259
  };
277
260
 
278
- const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TWireJson> = {
261
+ const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
279
262
  kind: "defined-node",
280
263
  key: options.key,
281
264
  title: options.title,
@@ -288,9 +271,7 @@ export function defineNode<
288
271
  },
289
272
  };
290
273
 
291
- DefinedNodeRegistry.register(
292
- definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined, unknown>,
293
- );
274
+ DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined>);
294
275
 
295
276
  return definition;
296
277
  }
@@ -303,43 +284,40 @@ export function defineBatchNode<
303
284
  TBindings extends DefinedNodeCredentialBindings | undefined = undefined,
304
285
  >(
305
286
  options: DefineBatchNodeOptions<TKey, TConfig, TInputJson, TOutputJson, TBindings>,
306
- ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TInputJson> {
287
+ ): DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> {
307
288
  const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
308
- type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson, TInputJson> &
309
- Readonly<{ config: TConfig }>;
289
+ type DefinedRunnableNodeConfigShape = RunnableNodeConfig<TInputJson, TOutputJson> & Readonly<{ config: TConfig }>;
310
290
 
311
- const DefinedNodeRuntime = class implements Node<DefinedRunnableNodeConfigShape> {
291
+ const DefinedNodeRuntime = class implements RunnableNode<DefinedRunnableNodeConfigShape, TInputJson, TOutputJson> {
312
292
  readonly kind = "node" as const;
313
293
  readonly outputPorts = ["main"] as const;
314
294
 
315
- async execute(items: Items, ctx: NodeExecutionContext<DefinedRunnableNodeConfigShape>): Promise<NodeOutputs> {
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>>,
309
+ };
316
310
  const outputs = await options.run(
317
- items.map((item) => item.json as TInputJson),
318
- {
319
- config: ctx.config.config,
320
- credentials: definedNodeCredentialAccessorFactory.create(
321
- options.credentials,
322
- ctx,
323
- ) as DefinedNodeCredentialAccessors<TBindings>,
324
- execution: ctx as unknown as NodeExecutionContext<RunnableNodeConfig<TConfig, unknown>>,
325
- },
311
+ args.items.map((item) => item.json as TInputJson),
312
+ context,
326
313
  );
327
-
328
- return {
329
- main: outputs.map((json, index) => {
330
- const existing = items[index];
331
- if (!existing) {
332
- return { json };
333
- }
334
- return { ...existing, json };
335
- }),
336
- };
314
+ return [...outputs];
337
315
  }
338
316
  };
339
317
 
340
318
  persistedNode({ name: options.key })(DefinedNodeRuntime);
341
319
 
342
- const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson, TInputJson> {
320
+ const DefinedRunnableNodeConfig = class implements RunnableNodeConfig<TInputJson, TOutputJson> {
343
321
  readonly kind = "node" as const;
344
322
  readonly type: TypeToken<unknown> = DefinedNodeRuntime;
345
323
  readonly icon = options.icon;
@@ -355,7 +333,7 @@ export function defineBatchNode<
355
333
  }
356
334
  };
357
335
 
358
- const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings, TInputJson> = {
336
+ const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
359
337
  kind: "defined-node",
360
338
  key: options.key,
361
339
  title: options.title,
@@ -368,9 +346,7 @@ export function defineBatchNode<
368
346
  },
369
347
  };
370
348
 
371
- DefinedNodeRegistry.register(
372
- definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined, unknown>,
373
- );
349
+ DefinedNodeRegistry.register(definition as DefinedNode<string, Record<string, unknown>, unknown, unknown, undefined>);
374
350
 
375
351
  return definition;
376
352
  }
@@ -6,7 +6,7 @@ export type {
6
6
  DefinedNodeCredentialBindings,
7
7
  DefinedNodeRunContext,
8
8
  DefineBatchNodeOptions,
9
- DefineNodeExecuteOneArgs,
9
+ DefineNodeExecuteArgs,
10
10
  DefineNodeOptions,
11
11
  } from "./defineNode.types";
12
12
  export { defineBatchNode, defineNode } from "./defineNode.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 });
@@ -61,11 +61,19 @@ export type OAuth2ProviderFromPublicConfig = Readonly<{
61
61
  userInfoUrlFieldKey?: string;
62
62
  }>;
63
63
 
64
+ export type CredentialOAuth2ScopesFromPublicConfig = Readonly<{
65
+ presetFieldKey: string;
66
+ presetScopes: Readonly<Record<string, ReadonlyArray<string>>>;
67
+ customPresetKey?: string;
68
+ customScopesFieldKey?: string;
69
+ }>;
70
+
64
71
  export type CredentialOAuth2AuthDefinition = Readonly<
65
72
  | {
66
73
  kind: "oauth2";
67
74
  providerId: string;
68
75
  scopes: ReadonlyArray<string>;
76
+ scopesFromPublicConfig?: CredentialOAuth2ScopesFromPublicConfig;
69
77
  clientIdFieldKey?: string;
70
78
  clientSecretFieldKey?: string;
71
79
  }
@@ -73,6 +81,7 @@ export type CredentialOAuth2AuthDefinition = Readonly<
73
81
  kind: "oauth2";
74
82
  providerFromPublicConfig: OAuth2ProviderFromPublicConfig;
75
83
  scopes: ReadonlyArray<string>;
84
+ scopesFromPublicConfig?: CredentialOAuth2ScopesFromPublicConfig;
76
85
  clientIdFieldKey?: string;
77
86
  clientSecretFieldKey?: string;
78
87
  }
@@ -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
+ }
@@ -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
+ }