@codemation/core 0.5.0 → 0.7.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 +42 -0
- package/dist/{EngineRuntimeRegistration.types-BtTZolK0.d.ts → EngineRuntimeRegistration.types-_M7KFD3D.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-Ddl0fekp.d.cts → EngineWorkflowRunnerService-D0Cwngv7.d.cts} +2 -2
- package/dist/{InMemoryRunDataFactory-i-u2yngD.d.cts → InMemoryRunDataFactory-BIWx6e02.d.cts} +15 -6
- package/dist/{RunIntentService-Cjx-glgz.d.cts → RunIntentService-5k0p-J67.d.cts} +31 -12
- package/dist/{RunIntentService-Dkr4YwN8.d.ts → RunIntentService-CuXAIO6_.d.ts} +52 -28
- package/dist/bootstrap/index.cjs +2 -2
- package/dist/bootstrap/index.d.cts +6 -6
- package/dist/bootstrap/index.d.ts +3 -3
- package/dist/bootstrap/index.js +2 -2
- package/dist/{bootstrap-DbUlOl11.js → bootstrap-BhYxSivA.js} +5 -4
- package/dist/bootstrap-BhYxSivA.js.map +1 -0
- package/dist/{bootstrap-DHH2uo-W.cjs → bootstrap-D-TDU9Lu.cjs} +5 -4
- package/dist/bootstrap-D-TDU9Lu.cjs.map +1 -0
- package/dist/{index-B2v4wtys.d.ts → index-BnJ7_IrO.d.ts} +92 -13
- package/dist/index.cjs +94 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -23
- package/dist/index.d.ts +3 -3
- package/dist/index.js +84 -6
- package/dist/index.js.map +1 -1
- package/dist/{runtime-feFn8OmG.cjs → runtime-3YVDd2vY.cjs} +81 -73
- package/dist/runtime-3YVDd2vY.cjs.map +1 -0
- package/dist/{runtime-BdH94eBR.js → runtime-CJnObwsU.js} +66 -64
- package/dist/runtime-CJnObwsU.js.map +1 -0
- package/dist/testing.cjs +2 -2
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/AgentConfigInspectorFactory.ts +2 -2
- package/src/ai/AgentMessageConfigNormalizerFactory.ts +3 -3
- package/src/ai/AiHost.ts +22 -2
- package/src/ai/CallableToolConfig.ts +84 -0
- package/src/ai/CallableToolFactory.ts +13 -0
- package/src/ai/CallableToolKindToken.ts +5 -0
- package/src/authoring/callableTool.types.ts +12 -0
- package/src/authoring/defineNode.types.ts +38 -9
- package/src/authoring/index.ts +2 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +12 -4
- package/src/contracts/credentialTypes.ts +20 -0
- package/src/contracts/index.ts +2 -1
- package/src/contracts/{itemValue.ts → itemExpr.ts} +31 -32
- package/src/contracts/params.ts +10 -0
- package/src/contracts/runtimeTypes.ts +4 -2
- package/src/contracts/workflowTypes.ts +11 -9
- package/src/execution/{ItemValueResolver.ts → ItemExprResolver.ts} +5 -5
- package/src/execution/NodeExecutor.ts +13 -31
- package/src/execution/NodeExecutorFactory.ts +7 -2
- package/src/execution/NodeOutputNormalizer.ts +22 -23
- package/src/execution/RunnableOutputBehaviorResolver.ts +23 -0
- package/src/execution/index.ts +2 -1
- package/src/index.ts +2 -1
- package/src/runStorage/InMemoryRunData.ts +9 -5
- package/src/testing/SwitchHarnessNode.ts +0 -1
- package/src/types/index.ts +2 -1
- package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +1 -1
- package/dist/bootstrap-DHH2uo-W.cjs.map +0 -1
- package/dist/bootstrap-DbUlOl11.js.map +0 -1
- package/dist/runtime-BdH94eBR.js.map +0 -1
- package/dist/runtime-feFn8OmG.cjs.map +0 -1
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CredentialRequirement,
|
|
5
5
|
CredentialTypeId,
|
|
6
6
|
} from "../contracts/credentialTypes";
|
|
7
|
+
import type { ParamDeep } from "../contracts/params";
|
|
7
8
|
import type { RunnableNode, RunnableNodeExecuteArgs, NodeExecutionContext } from "../contracts/runtimeTypes";
|
|
8
9
|
import type { Item, Items, RunnableNodeConfig } from "../contracts/workflowTypes";
|
|
9
10
|
import type { TypeToken } from "../di";
|
|
@@ -67,6 +68,11 @@ export type DefineNodeExecuteArgs<TConfig extends CredentialJsonRecord, TInputJs
|
|
|
67
68
|
ctx: NodeExecutionContext<RunnableNodeConfig<TInputJson, unknown> & Readonly<{ config: TConfig }>>;
|
|
68
69
|
}>;
|
|
69
70
|
|
|
71
|
+
export type DefinedNodeConfigInput<TConfigResolved extends CredentialJsonRecord, TItemJson> = ParamDeep<
|
|
72
|
+
TConfigResolved,
|
|
73
|
+
TItemJson
|
|
74
|
+
>;
|
|
75
|
+
|
|
70
76
|
export interface DefinedNode<
|
|
71
77
|
TKey extends string,
|
|
72
78
|
TConfig extends CredentialJsonRecord,
|
|
@@ -78,7 +84,11 @@ export interface DefinedNode<
|
|
|
78
84
|
readonly key: TKey;
|
|
79
85
|
readonly title: string;
|
|
80
86
|
readonly description?: string;
|
|
81
|
-
create
|
|
87
|
+
create<TConfigItemJson = TInputJson>(
|
|
88
|
+
config: DefinedNodeConfigInput<TConfig, TConfigItemJson>,
|
|
89
|
+
name?: string,
|
|
90
|
+
id?: string,
|
|
91
|
+
): RunnableNodeConfig<TInputJson, TOutputJson>;
|
|
82
92
|
register(context: { registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>): void }): void;
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -108,6 +118,8 @@ export interface DefineNodeOptions<
|
|
|
108
118
|
* Validates **`input`** (engine also accepts `inputSchema` on the node class).
|
|
109
119
|
*/
|
|
110
120
|
readonly inputSchema?: ZodType<TInputJson>;
|
|
121
|
+
/** Preserve inbound `item.binary` when `execute` returns plain JSON or item-shaped results without `binary`. */
|
|
122
|
+
readonly keepBinaries?: boolean;
|
|
111
123
|
execute(
|
|
112
124
|
args: DefineNodeExecuteArgs<TConfig, TInputJson>,
|
|
113
125
|
context: DefinedNodeRunContext<TConfig, TBindings>,
|
|
@@ -246,12 +258,17 @@ export function defineNode<
|
|
|
246
258
|
readonly type: TypeToken<unknown> = DefinedNodeRuntime;
|
|
247
259
|
readonly icon = options.icon;
|
|
248
260
|
readonly inputSchema = options.inputSchema;
|
|
261
|
+
readonly keepBinaries = options.keepBinaries ?? false;
|
|
249
262
|
|
|
250
263
|
constructor(
|
|
251
264
|
public readonly name: string,
|
|
252
|
-
|
|
265
|
+
config: DefinedNodeConfigInput<TConfig, unknown>,
|
|
253
266
|
public readonly id?: string,
|
|
254
|
-
) {
|
|
267
|
+
) {
|
|
268
|
+
this.config = config as unknown as TConfig;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public readonly config: TConfig;
|
|
255
272
|
|
|
256
273
|
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
257
274
|
return credentialRequirements;
|
|
@@ -263,8 +280,12 @@ export function defineNode<
|
|
|
263
280
|
key: options.key,
|
|
264
281
|
title: options.title,
|
|
265
282
|
description: options.description,
|
|
266
|
-
create
|
|
267
|
-
|
|
283
|
+
create<TConfigItemJson = TInputJson>(
|
|
284
|
+
config: DefinedNodeConfigInput<TConfig, TConfigItemJson>,
|
|
285
|
+
name = options.title,
|
|
286
|
+
id?: string,
|
|
287
|
+
) {
|
|
288
|
+
return new DefinedRunnableNodeConfig(name, config as DefinedNodeConfigInput<TConfig, unknown>, id);
|
|
268
289
|
},
|
|
269
290
|
register(context) {
|
|
270
291
|
context.registerNode(DefinedNodeRuntime);
|
|
@@ -324,9 +345,13 @@ export function defineBatchNode<
|
|
|
324
345
|
|
|
325
346
|
constructor(
|
|
326
347
|
public readonly name: string,
|
|
327
|
-
|
|
348
|
+
config: DefinedNodeConfigInput<TConfig, unknown>,
|
|
328
349
|
public readonly id?: string,
|
|
329
|
-
) {
|
|
350
|
+
) {
|
|
351
|
+
this.config = config as unknown as TConfig;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public readonly config: TConfig;
|
|
330
355
|
|
|
331
356
|
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
332
357
|
return credentialRequirements;
|
|
@@ -338,8 +363,12 @@ export function defineBatchNode<
|
|
|
338
363
|
key: options.key,
|
|
339
364
|
title: options.title,
|
|
340
365
|
description: options.description,
|
|
341
|
-
create
|
|
342
|
-
|
|
366
|
+
create<TConfigItemJson = TInputJson>(
|
|
367
|
+
config: DefinedNodeConfigInput<TConfig, TConfigItemJson>,
|
|
368
|
+
name = options.title,
|
|
369
|
+
id?: string,
|
|
370
|
+
) {
|
|
371
|
+
return new DefinedRunnableNodeConfig(name, config as DefinedNodeConfigInput<TConfig, unknown>, id);
|
|
343
372
|
},
|
|
344
373
|
register(context) {
|
|
345
374
|
context.registerNode(DefinedNodeRuntime);
|
package/src/authoring/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { DefinedNodeRegistry } from "./DefinedNodeRegistry";
|
|
2
2
|
export type {
|
|
3
3
|
DefinedNode,
|
|
4
|
+
DefinedNodeConfigInput,
|
|
4
5
|
DefinedNodeCredentialAccessors,
|
|
5
6
|
DefinedNodeCredentialBinding,
|
|
6
7
|
DefinedNodeCredentialBindings,
|
|
@@ -12,3 +13,4 @@ export type {
|
|
|
12
13
|
export { defineBatchNode, defineNode } from "./defineNode.types";
|
|
13
14
|
export type { DefineCredentialOptions } from "./defineCredential.types";
|
|
14
15
|
export { defineCredential } from "./defineCredential.types";
|
|
16
|
+
export { callableTool } from "./callableTool.types";
|
|
@@ -4,11 +4,12 @@ import { EngineExecutionLimitsPolicyFactory } from "../../policies/executionLimi
|
|
|
4
4
|
import {
|
|
5
5
|
DefaultAsyncSleeper,
|
|
6
6
|
InProcessRetryRunnerFactory,
|
|
7
|
-
|
|
7
|
+
ItemExprResolver,
|
|
8
8
|
NodeExecutor,
|
|
9
9
|
NodeExecutorFactory,
|
|
10
10
|
NodeInstanceFactoryFactory,
|
|
11
11
|
NodeOutputNormalizer,
|
|
12
|
+
RunnableOutputBehaviorResolver,
|
|
12
13
|
} from "../../execution";
|
|
13
14
|
import {
|
|
14
15
|
EngineFactory,
|
|
@@ -40,12 +41,15 @@ export class EngineRuntimeRegistrar {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
private registerSupportFactories(container: DependencyContainer): void {
|
|
43
|
-
if (!container.isRegistered(
|
|
44
|
-
container.registerSingleton(
|
|
44
|
+
if (!container.isRegistered(ItemExprResolver, true)) {
|
|
45
|
+
container.registerSingleton(ItemExprResolver, ItemExprResolver);
|
|
45
46
|
}
|
|
46
47
|
if (!container.isRegistered(NodeOutputNormalizer, true)) {
|
|
47
48
|
container.registerSingleton(NodeOutputNormalizer, NodeOutputNormalizer);
|
|
48
49
|
}
|
|
50
|
+
if (!container.isRegistered(RunnableOutputBehaviorResolver, true)) {
|
|
51
|
+
container.registerSingleton(RunnableOutputBehaviorResolver, RunnableOutputBehaviorResolver);
|
|
52
|
+
}
|
|
49
53
|
container.register(EngineExecutionLimitsPolicyFactory, { useClass: EngineExecutionLimitsPolicyFactory });
|
|
50
54
|
container.register(NodeInstanceFactoryFactory, { useClass: NodeInstanceFactoryFactory });
|
|
51
55
|
container.register(DefaultAsyncSleeper, { useClass: DefaultAsyncSleeper });
|
|
@@ -101,7 +105,11 @@ export class EngineRuntimeRegistrar {
|
|
|
101
105
|
.create(dependencyContainer.resolve(DefaultAsyncSleeper));
|
|
102
106
|
return dependencyContainer
|
|
103
107
|
.resolve(NodeExecutorFactory)
|
|
104
|
-
.create(
|
|
108
|
+
.create(
|
|
109
|
+
dependencyContainer.resolve(CoreTokens.WorkflowNodeInstanceFactory),
|
|
110
|
+
retryRunner,
|
|
111
|
+
dependencyContainer.resolve(RunnableOutputBehaviorResolver),
|
|
112
|
+
);
|
|
105
113
|
}),
|
|
106
114
|
});
|
|
107
115
|
}
|
|
@@ -13,6 +13,12 @@ export type CredentialFieldSchema = Readonly<{
|
|
|
13
13
|
type: "string" | "password" | "textarea" | "json" | "boolean";
|
|
14
14
|
required?: true;
|
|
15
15
|
order?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Where this field appears in the credential dialog. Use `"advanced"` for optional or
|
|
18
|
+
* power-user fields; they render inside a collapsible section (see `CredentialTypeDefinition.advancedSection`).
|
|
19
|
+
* Defaults to `"default"` when omitted.
|
|
20
|
+
*/
|
|
21
|
+
visibility?: "default" | "advanced";
|
|
16
22
|
placeholder?: string;
|
|
17
23
|
helpText?: string;
|
|
18
24
|
/** When set, host resolves this field from process.env at runtime; env wins over stored values. */
|
|
@@ -89,12 +95,26 @@ export type CredentialOAuth2AuthDefinition = Readonly<
|
|
|
89
95
|
|
|
90
96
|
export type CredentialAuthDefinition = CredentialOAuth2AuthDefinition;
|
|
91
97
|
|
|
98
|
+
export type CredentialAdvancedSectionPresentation = Readonly<{
|
|
99
|
+
/** Collapsible section title (default: "Advanced"). */
|
|
100
|
+
title?: string;
|
|
101
|
+
/** Optional short helper text shown inside the section (above the fields). */
|
|
102
|
+
description?: string;
|
|
103
|
+
/** When true, the advanced section starts expanded. Default: false (collapsed). */
|
|
104
|
+
defaultOpen?: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
|
|
92
107
|
export type CredentialTypeDefinition = Readonly<{
|
|
93
108
|
typeId: CredentialTypeId;
|
|
94
109
|
displayName: string;
|
|
95
110
|
description?: string;
|
|
96
111
|
publicFields?: ReadonlyArray<CredentialFieldSchema>;
|
|
97
112
|
secretFields?: ReadonlyArray<CredentialFieldSchema>;
|
|
113
|
+
/**
|
|
114
|
+
* Optional labels for the collapsible block that contains every field with `visibility: "advanced"`.
|
|
115
|
+
* If omitted, the UI still shows that block with defaults (title "Advanced", collapsed).
|
|
116
|
+
*/
|
|
117
|
+
advancedSection?: CredentialAdvancedSectionPresentation;
|
|
98
118
|
supportedSourceKinds?: ReadonlyArray<CredentialMaterialSourceKind>;
|
|
99
119
|
auth?: CredentialAuthDefinition;
|
|
100
120
|
}>;
|
package/src/contracts/index.ts
CHANGED
|
@@ -2,7 +2,8 @@ export * from "./credentialTypes";
|
|
|
2
2
|
export * from "./emitPorts";
|
|
3
3
|
export * from "./executionPersistenceContracts";
|
|
4
4
|
export * from "./itemMeta";
|
|
5
|
-
export * from "./
|
|
5
|
+
export * from "./params";
|
|
6
|
+
export * from "./itemExpr";
|
|
6
7
|
export * from "./runtimeTypes";
|
|
7
8
|
export * from "./runFinishedAtFactory";
|
|
8
9
|
export * from "./runTypes";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { NodeExecutionContext } from "./runtimeTypes";
|
|
2
2
|
import type { Item, Items, NodeActivationId, NodeId, RunDataSnapshot, RunId, WorkflowId } from "./workflowTypes";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const ITEM_EXPR_BRAND = Symbol.for("codemation.itemExpr");
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type ItemExprResolvedContext = Readonly<{
|
|
7
7
|
runId: RunId;
|
|
8
8
|
workflowId: WorkflowId;
|
|
9
9
|
nodeId: NodeId;
|
|
@@ -14,52 +14,51 @@ export type ItemValueResolvedContext = Readonly<{
|
|
|
14
14
|
/**
|
|
15
15
|
* Context aligned with former {@link ItemInputMapperContext} — use **`data`** to read any completed upstream node.
|
|
16
16
|
*/
|
|
17
|
-
export type
|
|
17
|
+
export type ItemExprContext = ItemExprResolvedContext;
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type ItemExprArgs<TItemJson = unknown> = Readonly<{
|
|
20
20
|
item: Item<TItemJson>;
|
|
21
21
|
itemIndex: number;
|
|
22
22
|
items: Items<TItemJson>;
|
|
23
|
-
ctx:
|
|
23
|
+
ctx: ItemExprContext;
|
|
24
24
|
}>;
|
|
25
25
|
|
|
26
|
-
export type
|
|
26
|
+
export type ItemExprCallback<T, TItemJson = unknown> = (args: ItemExprArgs<TItemJson>) => T | Promise<T>;
|
|
27
27
|
|
|
28
|
-
export type
|
|
29
|
-
readonly [
|
|
30
|
-
readonly fn:
|
|
28
|
+
export type ItemExpr<T, TItemJson = unknown> = Readonly<{
|
|
29
|
+
readonly [ITEM_EXPR_BRAND]: true;
|
|
30
|
+
readonly fn: ItemExprCallback<T, TItemJson>;
|
|
31
31
|
}>;
|
|
32
32
|
|
|
33
|
-
export function
|
|
34
|
-
return { [
|
|
33
|
+
export function itemExpr<T, TItemJson = unknown>(fn: ItemExprCallback<T, TItemJson>): ItemExpr<T, TItemJson> {
|
|
34
|
+
return { [ITEM_EXPR_BRAND]: true, fn };
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export function
|
|
37
|
+
export function isItemExpr<T, TItemJson = unknown>(value: unknown): value is ItemExpr<T, TItemJson> {
|
|
38
38
|
if (typeof value !== "object" || value === null) {
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
41
|
const v = value as Record<PropertyKey, unknown>;
|
|
42
|
-
if (v[
|
|
42
|
+
if (v[ITEM_EXPR_BRAND] === true) {
|
|
43
43
|
return true;
|
|
44
44
|
}
|
|
45
|
-
// Support snapshot-hydrated
|
|
45
|
+
// Support snapshot-hydrated itemExpr wrappers where the symbol brand was lost but the callback survived.
|
|
46
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
|
|
47
|
+
// We treat the minimal `{ fn: Function }` shape as an itemExpr wrapper to keep runnable configs working.
|
|
48
48
|
const keys = Object.keys(v);
|
|
49
49
|
if (keys.length === 1 && keys[0] === "fn" && typeof (v as { fn?: unknown }).fn === "function") {
|
|
50
50
|
return true;
|
|
51
51
|
}
|
|
52
|
-
// Support legacy module-local Symbol("codemation.itemValue") brands (e.g. duplicate module graphs).
|
|
53
52
|
for (const sym of Object.getOwnPropertySymbols(v)) {
|
|
54
|
-
if (sym.description === "codemation.
|
|
53
|
+
if (sym.description === "codemation.itemExpr" && v[sym] === true) {
|
|
55
54
|
return true;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
return false;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
function
|
|
62
|
-
if (
|
|
60
|
+
function containsItemExprInUnknown(value: unknown, seen: WeakSet<object> = new WeakSet()): boolean {
|
|
61
|
+
if (isItemExpr(value)) {
|
|
63
62
|
return true;
|
|
64
63
|
}
|
|
65
64
|
if (value === null || typeof value !== "object") {
|
|
@@ -70,10 +69,10 @@ function containsItemValueInUnknown(value: unknown, seen: WeakSet<object> = new
|
|
|
70
69
|
}
|
|
71
70
|
seen.add(value as object);
|
|
72
71
|
if (Array.isArray(value)) {
|
|
73
|
-
return value.some((entry) =>
|
|
72
|
+
return value.some((entry) => containsItemExprInUnknown(entry, seen));
|
|
74
73
|
}
|
|
75
74
|
for (const entry of Object.values(value as Record<string, unknown>)) {
|
|
76
|
-
if (
|
|
75
|
+
if (containsItemExprInUnknown(entry, seen)) {
|
|
77
76
|
return true;
|
|
78
77
|
}
|
|
79
78
|
}
|
|
@@ -81,14 +80,14 @@ function containsItemValueInUnknown(value: unknown, seen: WeakSet<object> = new
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
|
-
* Deep-resolves {@link
|
|
83
|
+
* Deep-resolves {@link itemExpr} leaves. Returns a new graph (does not mutate the original config object).
|
|
85
84
|
*/
|
|
86
|
-
export async function
|
|
85
|
+
export async function resolveItemExprsInUnknown(
|
|
87
86
|
value: unknown,
|
|
88
|
-
args:
|
|
87
|
+
args: ItemExprArgs,
|
|
89
88
|
seen: WeakSet<object> = new WeakSet(),
|
|
90
89
|
): Promise<unknown> {
|
|
91
|
-
if (
|
|
90
|
+
if (isItemExpr(value)) {
|
|
92
91
|
return await Promise.resolve(value.fn(args));
|
|
93
92
|
}
|
|
94
93
|
if (value === null || typeof value !== "object") {
|
|
@@ -101,7 +100,7 @@ export async function resolveItemValuesInUnknown(
|
|
|
101
100
|
if (Array.isArray(value)) {
|
|
102
101
|
const out: unknown[] = [];
|
|
103
102
|
for (let i = 0; i < value.length; i++) {
|
|
104
|
-
out.push(await
|
|
103
|
+
out.push(await resolveItemExprsInUnknown(value[i], args, seen));
|
|
105
104
|
}
|
|
106
105
|
return out;
|
|
107
106
|
}
|
|
@@ -113,22 +112,22 @@ export async function resolveItemValuesInUnknown(
|
|
|
113
112
|
}
|
|
114
113
|
const out = Object.create(proto) as Record<string, unknown>;
|
|
115
114
|
for (const [k, v] of entries) {
|
|
116
|
-
out[k] = await
|
|
115
|
+
out[k] = await resolveItemExprsInUnknown(v, args, seen);
|
|
117
116
|
}
|
|
118
117
|
return out;
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
/**
|
|
122
|
-
* Clones runnable config (best-effort) so per-item {@link
|
|
121
|
+
* Clones runnable config (best-effort) so per-item {@link itemExpr} resolution never mutates shared instances.
|
|
123
122
|
*/
|
|
124
|
-
export async function
|
|
123
|
+
export async function resolveItemExprsForExecution(
|
|
125
124
|
config: unknown,
|
|
126
125
|
nodeCtx: NodeExecutionContext,
|
|
127
126
|
item: Item,
|
|
128
127
|
itemIndex: number,
|
|
129
128
|
items: Items,
|
|
130
129
|
): Promise<unknown | undefined> {
|
|
131
|
-
const
|
|
130
|
+
const exprArgs: ItemExprArgs = {
|
|
132
131
|
item,
|
|
133
132
|
itemIndex,
|
|
134
133
|
items,
|
|
@@ -140,8 +139,8 @@ export async function resolveItemValuesForExecution(
|
|
|
140
139
|
data: nodeCtx.data,
|
|
141
140
|
},
|
|
142
141
|
};
|
|
143
|
-
if (!
|
|
142
|
+
if (!containsItemExprInUnknown(config)) {
|
|
144
143
|
return undefined;
|
|
145
144
|
}
|
|
146
|
-
return await
|
|
145
|
+
return await resolveItemExprsInUnknown(config, exprArgs);
|
|
147
146
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ItemExpr } from "./itemExpr";
|
|
2
|
+
|
|
3
|
+
export type Expr<T, TItemJson = unknown> = ItemExpr<T, TItemJson>;
|
|
4
|
+
|
|
5
|
+
export type Param<T, TItemJson = unknown> = T | Expr<T, TItemJson>;
|
|
6
|
+
|
|
7
|
+
export type ParamDeep<T, TItemJson = unknown> =
|
|
8
|
+
| Expr<T, TItemJson>
|
|
9
|
+
| (T extends readonly (infer U)[] ? ReadonlyArray<ParamDeep<U, TItemJson>> : never)
|
|
10
|
+
| (T extends object ? { [K in keyof T]: ParamDeep<T[K], TItemJson> } : T);
|
|
@@ -223,8 +223,10 @@ export interface EngineHost {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
* Per-item runnable node: return JSON, an array to fan-out on `main`, or {@link emitPorts}
|
|
227
|
-
* Engine applies `inputSchema.parse(item.json)` and passes the result as `args.input`
|
|
226
|
+
* Per-item runnable node: return JSON, an array to fan-out on `main`, an explicit `Item`, or {@link emitPorts}
|
|
227
|
+
* for multi-port emission. Engine applies `inputSchema.parse(item.json)` and passes the result as `args.input`
|
|
228
|
+
* (wire `item.json` is unchanged). Transform helpers may opt into binary preservation, while routers and
|
|
229
|
+
* pass-through nodes should return explicit items when they need to preserve full item state.
|
|
228
230
|
*/
|
|
229
231
|
export interface RunnableNodeExecuteArgs<
|
|
230
232
|
TConfig extends RunnableNodeConfig<any, any> = RunnableNodeConfig<any, any>,
|
|
@@ -6,6 +6,7 @@ import type { RetryPolicySpec } from "./retryPolicySpec.types";
|
|
|
6
6
|
|
|
7
7
|
export type WorkflowId = string;
|
|
8
8
|
export type NodeId = string;
|
|
9
|
+
export type NodeIdRef<TJson = unknown> = NodeId & Readonly<{ __codemationNodeJson?: TJson }>;
|
|
9
10
|
export type OutputPortKey = string;
|
|
10
11
|
export type InputPortKey = string;
|
|
11
12
|
export type PersistedTokenId = string;
|
|
@@ -96,8 +97,6 @@ export declare const runnableNodeInputType: unique symbol;
|
|
|
96
97
|
export declare const runnableNodeOutputType: unique symbol;
|
|
97
98
|
export declare const triggerNodeOutputType: unique symbol;
|
|
98
99
|
|
|
99
|
-
export type LineageCarryPolicy = "emitOnly" | "carryThrough";
|
|
100
|
-
|
|
101
100
|
/**
|
|
102
101
|
* Runnable node: **`TInputJson`** is what **`inputSchema`** validates on **`item.json`** (the wire payload).
|
|
103
102
|
* **`TOutputJson`** is emitted `item.json` on outputs.
|
|
@@ -111,11 +110,6 @@ export interface RunnableNodeConfig<TInputJson = unknown, TOutputJson = unknown>
|
|
|
111
110
|
* Resolution order: node instance `inputSchema`, then config `inputSchema`, then `z.unknown()`.
|
|
112
111
|
*/
|
|
113
112
|
readonly inputSchema?: ZodType<TInputJson>;
|
|
114
|
-
/**
|
|
115
|
-
* Overrides default lineage propagation for `execute` outputs (binary/meta/paired).
|
|
116
|
-
* Routers with multiple {@link RunnableNode#outputPorts} default to **`carryThrough`**; others default to **`emitOnly`**.
|
|
117
|
-
*/
|
|
118
|
-
readonly lineageCarry?: LineageCarryPolicy;
|
|
119
113
|
/**
|
|
120
114
|
* When an activation receives **zero** input items, the engine normally runs `execute` zero times.
|
|
121
115
|
* Set to **`runOnce`** to run `execute` once with an empty `items` batch (and a synthetic wire item for schema parsing).
|
|
@@ -161,6 +155,10 @@ export interface NodeRef {
|
|
|
161
155
|
name?: string;
|
|
162
156
|
}
|
|
163
157
|
|
|
158
|
+
export function nodeRef<TJson>(nodeId: NodeId): NodeIdRef<TJson> {
|
|
159
|
+
return nodeId as NodeIdRef<TJson>;
|
|
160
|
+
}
|
|
161
|
+
|
|
164
162
|
export type PairedItemRef = Readonly<{ nodeId: NodeId; output: OutputPortKey; itemIndex: number }>;
|
|
165
163
|
|
|
166
164
|
export type BinaryPreviewKind = "image" | "audio" | "video" | "download";
|
|
@@ -211,8 +209,12 @@ export interface ParentExecutionRef {
|
|
|
211
209
|
|
|
212
210
|
export interface RunDataSnapshot {
|
|
213
211
|
getOutputs(nodeId: NodeId): NodeOutputs | undefined;
|
|
214
|
-
getOutputItems(nodeId: NodeId
|
|
215
|
-
getOutputItem
|
|
212
|
+
getOutputItems<TJson = unknown>(nodeId: NodeId | NodeIdRef<TJson>, output?: OutputPortKey): Items<TJson>;
|
|
213
|
+
getOutputItem<TJson = unknown>(
|
|
214
|
+
nodeId: NodeId | NodeIdRef<TJson>,
|
|
215
|
+
itemIndex: number,
|
|
216
|
+
output?: OutputPortKey,
|
|
217
|
+
): Item<TJson> | undefined;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
export interface MutableRunData extends RunDataSnapshot {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveItemExprsForExecution } from "../contracts/itemExpr";
|
|
2
2
|
import type { Item, NodeExecutionContext, RunnableNodeConfig } from "../types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Resolves {@link import("../contracts/
|
|
5
|
+
* Resolves {@link import("../contracts/itemExpr").ItemExpr} leaves on runnable config before {@link RunnableNode.execute}.
|
|
6
6
|
*/
|
|
7
|
-
export class
|
|
7
|
+
export class ItemExprResolver {
|
|
8
8
|
async resolveConfigForItem<TConfig extends RunnableNodeConfig<any, any>>(
|
|
9
9
|
ctx: NodeExecutionContext<TConfig>,
|
|
10
10
|
item: Item,
|
|
@@ -12,9 +12,9 @@ export class ItemValueResolver {
|
|
|
12
12
|
items: ReadonlyArray<Item>,
|
|
13
13
|
): Promise<NodeExecutionContext<TConfig>> {
|
|
14
14
|
if (!ctx) {
|
|
15
|
-
throw new Error("
|
|
15
|
+
throw new Error("ItemExprResolver.resolveConfigForItem: ctx is required");
|
|
16
16
|
}
|
|
17
|
-
const resolvedConfig = await
|
|
17
|
+
const resolvedConfig = await resolveItemExprsForExecution(ctx.config, ctx, item, itemIndex, items);
|
|
18
18
|
const merged = resolvedConfig !== undefined && resolvedConfig !== null ? resolvedConfig : ctx.config;
|
|
19
19
|
if (merged === undefined || merged === null) {
|
|
20
20
|
return ctx;
|
|
@@ -3,10 +3,8 @@ import { isPortsEmission, isUnbrandedPortsEmissionShape } from "../contracts/emi
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
Item,
|
|
6
|
-
LineageCarryPolicy,
|
|
7
6
|
MultiInputNode,
|
|
8
7
|
NodeActivationRequest,
|
|
9
|
-
NodeConfigBase,
|
|
10
8
|
NodeExecutionContext,
|
|
11
9
|
NodeOutputs,
|
|
12
10
|
RunnableNode,
|
|
@@ -17,21 +15,25 @@ import type {
|
|
|
17
15
|
} from "../types";
|
|
18
16
|
|
|
19
17
|
import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
|
|
20
|
-
import {
|
|
18
|
+
import { ItemExprResolver } from "./ItemExprResolver";
|
|
21
19
|
import { InProcessRetryRunner } from "./InProcessRetryRunner";
|
|
22
20
|
import { NodeOutputNormalizer } from "./NodeOutputNormalizer";
|
|
21
|
+
import { RunnableOutputBehaviorResolver } from "./RunnableOutputBehaviorResolver";
|
|
23
22
|
|
|
24
23
|
export class NodeExecutor {
|
|
25
24
|
private readonly fanInMerger = new FanInMergeByOriginMerger();
|
|
26
25
|
private readonly outputNormalizer = new NodeOutputNormalizer();
|
|
27
|
-
private readonly
|
|
26
|
+
private readonly itemExprResolver: ItemExprResolver;
|
|
27
|
+
private readonly outputBehaviorResolver: RunnableOutputBehaviorResolver;
|
|
28
28
|
|
|
29
29
|
constructor(
|
|
30
30
|
private readonly nodeInstanceFactory: WorkflowNodeInstanceFactory,
|
|
31
31
|
private readonly retryRunner: InProcessRetryRunner,
|
|
32
|
-
|
|
32
|
+
itemExprResolver?: ItemExprResolver,
|
|
33
|
+
outputBehaviorResolver?: RunnableOutputBehaviorResolver,
|
|
33
34
|
) {
|
|
34
|
-
this.
|
|
35
|
+
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
36
|
+
this.outputBehaviorResolver = outputBehaviorResolver ?? new RunnableOutputBehaviorResolver();
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async execute(request: NodeActivationRequest): Promise<NodeOutputs> {
|
|
@@ -126,14 +128,14 @@ export class NodeExecutor {
|
|
|
126
128
|
node: RunnableNode,
|
|
127
129
|
): Promise<NodeOutputs> {
|
|
128
130
|
const runnableConfig = request.ctx.config as RunnableNodeConfig;
|
|
129
|
-
const
|
|
131
|
+
const behavior = this.outputBehaviorResolver.resolve(runnableConfig);
|
|
130
132
|
const inputSchema = this.resolveInputSchema(node, runnableConfig);
|
|
131
133
|
const inputBatch = request.input ?? [];
|
|
132
134
|
if (inputBatch.length === 0 && runnableConfig.emptyBatchExecution === "runOnce") {
|
|
133
135
|
const syntheticItem: Item = { json: {} };
|
|
134
136
|
const parsed = inputSchema.parse(syntheticItem.json);
|
|
135
137
|
const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
|
|
136
|
-
const resolvedCtx = await this.
|
|
138
|
+
const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, syntheticItem, 0, inputBatch);
|
|
137
139
|
const ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
|
|
138
140
|
const args: RunnableNodeExecuteArgs = {
|
|
139
141
|
input: parsed,
|
|
@@ -146,7 +148,7 @@ export class NodeExecutor {
|
|
|
146
148
|
return this.outputNormalizer.normalizeExecuteResult({
|
|
147
149
|
baseItem: syntheticItem,
|
|
148
150
|
raw,
|
|
149
|
-
|
|
151
|
+
behavior,
|
|
150
152
|
}) as NodeOutputs;
|
|
151
153
|
}
|
|
152
154
|
const byPort: Partial<Record<string, Item[]>> = {};
|
|
@@ -155,7 +157,7 @@ export class NodeExecutor {
|
|
|
155
157
|
this.assertItemJsonNotTopLevelArray(request.nodeId, item);
|
|
156
158
|
const parsed = inputSchema.parse(item.json);
|
|
157
159
|
const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
|
|
158
|
-
const resolvedCtx = await this.
|
|
160
|
+
const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
|
|
159
161
|
const ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
|
|
160
162
|
const args: RunnableNodeExecuteArgs = {
|
|
161
163
|
input: parsed,
|
|
@@ -168,7 +170,7 @@ export class NodeExecutor {
|
|
|
168
170
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
169
171
|
baseItem: item,
|
|
170
172
|
raw,
|
|
171
|
-
|
|
173
|
+
behavior,
|
|
172
174
|
});
|
|
173
175
|
for (const [port, batch] of Object.entries(normalized)) {
|
|
174
176
|
if (!batch || batch.length === 0) {
|
|
@@ -226,24 +228,4 @@ export class NodeExecutor {
|
|
|
226
228
|
);
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
|
-
|
|
230
|
-
private resolveLineageCarry(node: unknown, config: RunnableNodeConfig): LineageCarryPolicy {
|
|
231
|
-
if (config.lineageCarry) {
|
|
232
|
-
return config.lineageCarry;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const base = config as NodeConfigBase;
|
|
236
|
-
const declared = base.declaredOutputPorts;
|
|
237
|
-
const ports =
|
|
238
|
-
declared && declared.length > 0
|
|
239
|
-
? [...new Set([...(declared as readonly string[]), ...(base.nodeErrorHandler ? ["error"] : [])])]
|
|
240
|
-
: base.nodeErrorHandler
|
|
241
|
-
? (["main", "error"] as const)
|
|
242
|
-
: (["main"] as const);
|
|
243
|
-
|
|
244
|
-
if (ports.length > 1) {
|
|
245
|
-
return "carryThrough";
|
|
246
|
-
}
|
|
247
|
-
return "emitOnly";
|
|
248
|
-
}
|
|
249
231
|
}
|
|
@@ -2,9 +2,14 @@ import type { WorkflowNodeInstanceFactory } from "../types";
|
|
|
2
2
|
|
|
3
3
|
import { InProcessRetryRunner } from "./InProcessRetryRunner";
|
|
4
4
|
import { NodeExecutor } from "./NodeExecutor";
|
|
5
|
+
import { RunnableOutputBehaviorResolver } from "./RunnableOutputBehaviorResolver";
|
|
5
6
|
|
|
6
7
|
export class NodeExecutorFactory {
|
|
7
|
-
create(
|
|
8
|
-
|
|
8
|
+
create(
|
|
9
|
+
workflowNodeInstanceFactory: WorkflowNodeInstanceFactory,
|
|
10
|
+
retryRunner: InProcessRetryRunner,
|
|
11
|
+
outputBehaviorResolver: RunnableOutputBehaviorResolver,
|
|
12
|
+
): NodeExecutor {
|
|
13
|
+
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, undefined, outputBehaviorResolver);
|
|
9
14
|
}
|
|
10
15
|
}
|