@codemation/core-nodes 0.10.2 → 0.13.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 +122 -0
- package/dist/index.cjs +427 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -67
- package/dist/index.d.ts +206 -68
- package/dist/index.js +427 -99
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +1 -1
- package/package.json +3 -2
- package/src/chatModels/CodemationChatModelConfig.ts +9 -21
- package/src/chatModels/CodemationChatModelFactory.ts +12 -9
- package/src/chatModels/OpenAIChatModelFactory.ts +3 -2
- package/src/index.ts +1 -1
- package/src/nodes/AIAgentConfig.ts +36 -0
- package/src/nodes/AIAgentNode.ts +81 -15
- package/src/nodes/AgentBinaryContentFactory.ts +74 -0
- package/src/nodes/AgentMessageFactory.ts +22 -6
- package/src/nodes/AgentToolResultContentFactory.ts +155 -0
- package/src/nodes/CallbackNodeFactory.ts +9 -6
- package/src/nodes/CronTriggerFactory.ts +6 -2
- package/src/nodes/DeferredMetaToolStrategy.ts +8 -2
- package/src/nodes/ManualTriggerFactory.ts +15 -11
- package/src/nodes/WebhookTriggerFactory.ts +9 -2
- package/src/nodes/aggregate.ts +9 -2
- package/src/nodes/assertion.ts +3 -0
- package/src/nodes/filter.ts +9 -2
- package/src/nodes/httpRequest.ts +6 -1
- package/src/nodes/if.ts +9 -2
- package/src/nodes/isTestRun.ts +6 -2
- package/src/nodes/mapData.ts +4 -2
- package/src/nodes/merge.ts +9 -2
- package/src/nodes/noOp.ts +9 -2
- package/src/nodes/nodeOptions.types.ts +12 -0
- package/src/nodes/split.ts +9 -2
- package/src/nodes/subWorkflow.ts +9 -2
- package/src/nodes/switch.ts +7 -1
- package/src/nodes/wait.ts +9 -2
- package/src/workflowAuthoring/WorkflowChatModelFactory.types.ts +8 -2
- package/src/chatModels/ManagedModelFetcher.ts +0 -23
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ToolResultPart } from "ai";
|
|
2
|
+
|
|
3
|
+
/** The `value` array of a `{ type: "content" }` tool-result output, as accepted by the AI SDK. */
|
|
4
|
+
type ContentOutputValue = Extract<ToolResultPart["output"], { type: "content" }>["value"];
|
|
5
|
+
type ContentOutputPart = ContentOutputValue[number];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cap on raw (pre-base64) bytes inlined from a single tool result. Base64 inflates ~33% and every
|
|
9
|
+
* inlined byte eats model context, so oversize binaries are replaced with a text placeholder.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_INLINE_BYTES = 8 * 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
/** MCP content-block discriminators. At least one must appear for a result to be treated as MCP-shaped. */
|
|
14
|
+
const KNOWN_MCP_BLOCK_TYPES: ReadonlySet<string> = new Set(["text", "image", "audio", "resource", "resource_link"]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* MCP-style content block, as returned verbatim by a `CallToolResult` (`@ai-sdk/mcp` tool `execute`).
|
|
18
|
+
* Only the fields this factory reads are modelled; unknown shapes fall through to a text marker.
|
|
19
|
+
*/
|
|
20
|
+
type McpContentBlock = Readonly<{
|
|
21
|
+
type?: unknown;
|
|
22
|
+
text?: unknown;
|
|
23
|
+
data?: unknown;
|
|
24
|
+
mimeType?: unknown;
|
|
25
|
+
uri?: unknown;
|
|
26
|
+
name?: unknown;
|
|
27
|
+
resource?: Readonly<{ blob?: unknown; text?: unknown; mimeType?: unknown; uri?: unknown; name?: unknown }>;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Maps a tool result that is **content-block-shaped** (an MCP `CallToolResult` with a `content`
|
|
32
|
+
* array) into AI SDK `{ type: "content" }` tool-result output, so binaries reach the chat model as
|
|
33
|
+
* native multimodal tool-result blocks instead of being flattened to inert JSON text.
|
|
34
|
+
*
|
|
35
|
+
* The `@ai-sdk/anthropic` provider maps a `content`-output part as follows:
|
|
36
|
+
* `text` → text block, `image-data` → image block, `file-data` (only `application/pdf`) → document
|
|
37
|
+
* block. Non-PDF `file-data` is dropped by the provider, so this factory emits `image-data` for
|
|
38
|
+
* images, `file-data` only for PDFs, and a text marker for every other binary type.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` when the result is not content-block-shaped — callers keep the existing
|
|
41
|
+
* `{ type: "json" }` path, so plain string/object tool results are unaffected.
|
|
42
|
+
*/
|
|
43
|
+
export class AgentToolResultContentFactory {
|
|
44
|
+
static tryMapToContentOutput(result: unknown): ContentOutputValue | undefined {
|
|
45
|
+
const blocks = AgentToolResultContentFactory.contentBlocks(result);
|
|
46
|
+
if (blocks === undefined) return undefined;
|
|
47
|
+
|
|
48
|
+
const parts: ContentOutputPart[] = [];
|
|
49
|
+
let inlinedBytes = 0;
|
|
50
|
+
for (const block of blocks) {
|
|
51
|
+
const mapped = AgentToolResultContentFactory.mapBlock(block, inlinedBytes);
|
|
52
|
+
parts.push(mapped.part);
|
|
53
|
+
inlinedBytes += mapped.bytes;
|
|
54
|
+
}
|
|
55
|
+
return parts;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the `content` array iff `result` is an object whose `content` is an array of typed
|
|
60
|
+
* blocks AND at least one block carries a known MCP discriminator. A plain JSON result that merely
|
|
61
|
+
* has a `content` key of some other shape (e.g. Notion/Slack rich-text blocks) is rejected,
|
|
62
|
+
* preserving the `{ type: "json" }` path so its payload is never lost.
|
|
63
|
+
*/
|
|
64
|
+
private static contentBlocks(result: unknown): ReadonlyArray<McpContentBlock> | undefined {
|
|
65
|
+
if (result === null || typeof result !== "object") return undefined;
|
|
66
|
+
const content = (result as { content?: unknown }).content;
|
|
67
|
+
if (!Array.isArray(content) || content.length === 0) return undefined;
|
|
68
|
+
const allTyped = content.every(
|
|
69
|
+
(block) => block !== null && typeof block === "object" && typeof (block as { type?: unknown }).type === "string",
|
|
70
|
+
);
|
|
71
|
+
if (!allTyped) return undefined;
|
|
72
|
+
const looksMcp = content.some((block) => KNOWN_MCP_BLOCK_TYPES.has((block as { type: string }).type));
|
|
73
|
+
return looksMcp ? (content as ReadonlyArray<McpContentBlock>) : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private static mapBlock(
|
|
77
|
+
block: McpContentBlock,
|
|
78
|
+
inlinedBytesSoFar: number,
|
|
79
|
+
): Readonly<{ part: ContentOutputPart; bytes: number }> {
|
|
80
|
+
const type = block.type;
|
|
81
|
+
if (type === "text" && typeof block.text === "string") {
|
|
82
|
+
return { part: { type: "text", text: block.text }, bytes: 0 };
|
|
83
|
+
}
|
|
84
|
+
if (type === "image" && typeof block.data === "string" && typeof block.mimeType === "string") {
|
|
85
|
+
return AgentToolResultContentFactory.mapBinary({
|
|
86
|
+
base64: block.data,
|
|
87
|
+
mediaType: block.mimeType,
|
|
88
|
+
inlinedBytesSoFar,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (type === "resource" && block.resource) {
|
|
92
|
+
return AgentToolResultContentFactory.mapEmbeddedResource(block.resource, inlinedBytesSoFar);
|
|
93
|
+
}
|
|
94
|
+
if (type === "resource_link" && typeof block.uri === "string") {
|
|
95
|
+
const mime = typeof block.mimeType === "string" ? ` (${block.mimeType})` : "";
|
|
96
|
+
return { part: { type: "text", text: `[linked resource: ${block.uri}${mime}]` }, bytes: 0 };
|
|
97
|
+
}
|
|
98
|
+
return { part: { type: "text", text: `[unsupported tool content block: ${String(type)}]` }, bytes: 0 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static mapEmbeddedResource(
|
|
102
|
+
resource: NonNullable<McpContentBlock["resource"]>,
|
|
103
|
+
inlinedBytesSoFar: number,
|
|
104
|
+
): Readonly<{ part: ContentOutputPart; bytes: number }> {
|
|
105
|
+
if (typeof resource.text === "string") {
|
|
106
|
+
return { part: { type: "text", text: resource.text }, bytes: 0 };
|
|
107
|
+
}
|
|
108
|
+
if (typeof resource.blob === "string" && typeof resource.mimeType === "string") {
|
|
109
|
+
return AgentToolResultContentFactory.mapBinary({
|
|
110
|
+
base64: resource.blob,
|
|
111
|
+
mediaType: resource.mimeType,
|
|
112
|
+
filename: typeof resource.name === "string" ? resource.name : undefined,
|
|
113
|
+
inlinedBytesSoFar,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const uri = typeof resource.uri === "string" ? resource.uri : "unknown";
|
|
117
|
+
return { part: { type: "text", text: `[embedded resource: ${uri}]` }, bytes: 0 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static mapBinary(
|
|
121
|
+
args: Readonly<{ base64: string; mediaType: string; filename?: string; inlinedBytesSoFar: number }>,
|
|
122
|
+
): Readonly<{ part: ContentOutputPart; bytes: number }> {
|
|
123
|
+
const rawBytes = Math.floor((args.base64.length * 3) / 4);
|
|
124
|
+
if (args.inlinedBytesSoFar + rawBytes > MAX_INLINE_BYTES) {
|
|
125
|
+
const name = args.filename ? ` "${args.filename}"` : "";
|
|
126
|
+
const kb = Math.round(rawBytes / 1024);
|
|
127
|
+
return {
|
|
128
|
+
part: {
|
|
129
|
+
type: "text",
|
|
130
|
+
text: `[binary${name} (${args.mediaType}, ~${kb} KB) omitted: exceeds the per-tool-result inline limit]`,
|
|
131
|
+
},
|
|
132
|
+
bytes: 0,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (args.mediaType.startsWith("image/")) {
|
|
136
|
+
return { part: { type: "image-data", data: args.base64, mediaType: args.mediaType }, bytes: rawBytes };
|
|
137
|
+
}
|
|
138
|
+
if (args.mediaType === "application/pdf") {
|
|
139
|
+
return {
|
|
140
|
+
part: {
|
|
141
|
+
type: "file-data",
|
|
142
|
+
data: args.base64,
|
|
143
|
+
mediaType: args.mediaType,
|
|
144
|
+
...(args.filename ? { filename: args.filename } : {}),
|
|
145
|
+
},
|
|
146
|
+
bytes: rawBytes,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const name = args.filename ? ` "${args.filename}"` : "";
|
|
150
|
+
return {
|
|
151
|
+
part: { type: "text", text: `[binary${name} (${args.mediaType}) not inlined: unsupported by the model]` },
|
|
152
|
+
bytes: 0,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
} from "@codemation/core";
|
|
11
11
|
|
|
12
12
|
import { CallbackNode } from "./CallbackNode";
|
|
13
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
13
14
|
|
|
14
15
|
export type CallbackHandler<
|
|
15
16
|
TInputJson = unknown,
|
|
@@ -20,12 +21,12 @@ export type CallbackHandler<
|
|
|
20
21
|
ctx: NodeExecutionContext<TConfig>,
|
|
21
22
|
) => Promise<Items<TOutputJson> | PortsEmission | void> | Items<TOutputJson> | PortsEmission | void;
|
|
22
23
|
|
|
23
|
-
export type CallbackOptions =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}>;
|
|
24
|
+
export type CallbackOptions = NodeBaseOptions &
|
|
25
|
+
Readonly<{
|
|
26
|
+
retryPolicy?: RetryPolicySpec;
|
|
27
|
+
nodeErrorHandler?: NodeErrorHandlerSpec;
|
|
28
|
+
declaredOutputPorts?: ReadonlyArray<string>;
|
|
29
|
+
}>;
|
|
29
30
|
|
|
30
31
|
export class Callback<TInputJson = unknown, TOutputJson = TInputJson> implements RunnableNodeConfig<
|
|
31
32
|
TInputJson,
|
|
@@ -37,6 +38,7 @@ export class Callback<TInputJson = unknown, TOutputJson = TInputJson> implements
|
|
|
37
38
|
readonly icon = "lucide:braces" as const;
|
|
38
39
|
readonly emptyBatchExecution = "runOnce" as const;
|
|
39
40
|
readonly id?: string;
|
|
41
|
+
readonly description?: string;
|
|
40
42
|
readonly retryPolicy?: RetryPolicySpec;
|
|
41
43
|
readonly nodeErrorHandler?: NodeErrorHandlerSpec;
|
|
42
44
|
readonly declaredOutputPorts?: ReadonlyArray<string>;
|
|
@@ -52,6 +54,7 @@ export class Callback<TInputJson = unknown, TOutputJson = TInputJson> implements
|
|
|
52
54
|
) {
|
|
53
55
|
const resolvedOptions = typeof idOrOptions === "string" ? { ...options, id: idOrOptions } : idOrOptions;
|
|
54
56
|
this.id = resolvedOptions?.id;
|
|
57
|
+
this.description = resolvedOptions?.description;
|
|
55
58
|
this.retryPolicy = resolvedOptions?.retryPolicy;
|
|
56
59
|
this.nodeErrorHandler = resolvedOptions?.nodeErrorHandler;
|
|
57
60
|
this.declaredOutputPorts = resolvedOptions?.declaredOutputPorts;
|
|
@@ -4,6 +4,7 @@ import { Cron } from "croner";
|
|
|
4
4
|
import type { CronCallback } from "croner";
|
|
5
5
|
|
|
6
6
|
import { CronTriggerNode } from "./CronTriggerNode";
|
|
7
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
7
8
|
|
|
8
9
|
export type CronTickJson = { firedAt: string; scheduledFor: string };
|
|
9
10
|
|
|
@@ -21,14 +22,17 @@ export class CronTrigger implements TriggerNodeConfig<CronTickJson> {
|
|
|
21
22
|
readonly type: TypeToken<unknown> = CronTriggerNode;
|
|
22
23
|
readonly icon = "lucide:clock" as const;
|
|
23
24
|
readonly id?: string;
|
|
25
|
+
readonly description?: string;
|
|
24
26
|
|
|
25
27
|
constructor(
|
|
26
28
|
public readonly name: string,
|
|
27
29
|
private readonly args: Readonly<{ schedule: string; timezone?: string }>,
|
|
28
|
-
|
|
30
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
29
31
|
) {
|
|
30
32
|
new Cron(args.schedule, { paused: true, timezone: args.timezone });
|
|
31
|
-
|
|
33
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
34
|
+
this.id = options?.id;
|
|
35
|
+
this.description = options?.description;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
get schedule(): string {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ToolSet } from "ai";
|
|
2
|
-
import { jsonSchema } from "ai";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import type { BM25Index } from "./BM25Index";
|
|
5
4
|
import type {
|
|
@@ -37,6 +36,12 @@ export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
|
37
36
|
private mcpEntries: McpToolEntry[] = [];
|
|
38
37
|
private toolsByServerId = new Map<string, Map<string, ToolSet[string]>>();
|
|
39
38
|
private foundToolIds = new Set<string>();
|
|
39
|
+
/**
|
|
40
|
+
* `jsonSchema` from the `ai` SDK, loaded lazily in {@link initialize} so the SDK
|
|
41
|
+
* (~28MB RSS) stays off the boot path. `initialize` always runs before the sync
|
|
42
|
+
* `getToolsForTurn` → `buildFindToolsDefinition` path, so this is set before use.
|
|
43
|
+
*/
|
|
44
|
+
private jsonSchema!: typeof import("ai").jsonSchema;
|
|
40
45
|
|
|
41
46
|
constructor(
|
|
42
47
|
private readonly bm25: BM25Index,
|
|
@@ -44,6 +49,7 @@ export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
|
44
49
|
) {}
|
|
45
50
|
|
|
46
51
|
async initialize(input: ToolLoadingStrategyInitInput): Promise<void> {
|
|
52
|
+
this.jsonSchema = (await import("ai")).jsonSchema;
|
|
47
53
|
this.nodeBackedTools = { ...input.nodeBackedTools };
|
|
48
54
|
|
|
49
55
|
const pinnedIds = input.pinnedMcpTools ?? [];
|
|
@@ -194,7 +200,7 @@ export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
|
194
200
|
"After this call, the tools listed in the result will be callable on your very next turn. " +
|
|
195
201
|
"Use this when you need a capability not visible in your current tool list. " +
|
|
196
202
|
"Do not attempt to call a tool name you have not seen yet — use find_tools to discover it first.",
|
|
197
|
-
inputSchema: jsonSchema(inputSchemaRecord),
|
|
203
|
+
inputSchema: this.jsonSchema(inputSchemaRecord),
|
|
198
204
|
} as unknown as ToolSet[string];
|
|
199
205
|
}
|
|
200
206
|
}
|
|
@@ -3,6 +3,7 @@ import type { Items, NodeInspectorSummaryRow, TriggerNodeConfig, TypeToken } fro
|
|
|
3
3
|
import { ItemsInputNormalizer } from "@codemation/core";
|
|
4
4
|
|
|
5
5
|
import { ManualTriggerNode } from "./ManualTriggerNode";
|
|
6
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
6
7
|
|
|
7
8
|
type ManualTriggerDefaultValue<TOutputJson> = Items<TOutputJson> | ReadonlyArray<TOutputJson> | TOutputJson;
|
|
8
9
|
|
|
@@ -13,18 +14,28 @@ export class ManualTrigger<TOutputJson = unknown> implements TriggerNodeConfig<T
|
|
|
13
14
|
readonly icon = "lucide:play" as const;
|
|
14
15
|
readonly defaultItems?: Items<TOutputJson>;
|
|
15
16
|
readonly id?: string;
|
|
17
|
+
readonly description?: string;
|
|
16
18
|
/** Manual runs often emit an empty batch; still schedule downstream by default. */
|
|
17
19
|
readonly continueWhenEmptyOutput = true as const;
|
|
18
20
|
|
|
19
|
-
constructor(name?: string,
|
|
20
|
-
constructor(
|
|
21
|
+
constructor(name?: string, idOrOptions?: string | NodeBaseOptions);
|
|
22
|
+
constructor(
|
|
23
|
+
name: string,
|
|
24
|
+
defaultItems: ManualTriggerDefaultValue<TOutputJson> | undefined,
|
|
25
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
26
|
+
);
|
|
21
27
|
constructor(
|
|
22
28
|
public readonly name: string = "Manual trigger",
|
|
23
29
|
defaultItemsOrId?: ManualTriggerDefaultValue<TOutputJson> | string,
|
|
24
|
-
|
|
30
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
25
31
|
) {
|
|
32
|
+
// Position 2 keeps its existing string-vs-object meaning (string→id, object/array→default items).
|
|
33
|
+
// Options (id/description) live in the trailing slot, or position 2 when it's a bare string id.
|
|
26
34
|
this.defaultItems = ManualTrigger.resolveDefaultItems(defaultItemsOrId);
|
|
27
|
-
|
|
35
|
+
const trailing = idOrOptions ?? (typeof defaultItemsOrId === "string" ? defaultItemsOrId : undefined);
|
|
36
|
+
const options = typeof trailing === "string" ? { id: trailing } : (trailing ?? {});
|
|
37
|
+
this.id = options.id;
|
|
38
|
+
this.description = options.description;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
private static resolveDefaultItems<TOutputJson>(
|
|
@@ -36,13 +47,6 @@ export class ManualTrigger<TOutputJson = unknown> implements TriggerNodeConfig<T
|
|
|
36
47
|
return this.itemsInputNormalizer.normalize(value) as Items<TOutputJson>;
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
private static resolveId<TOutputJson>(
|
|
40
|
-
value: ManualTriggerDefaultValue<TOutputJson> | string | undefined,
|
|
41
|
-
id: string | undefined,
|
|
42
|
-
): string | undefined {
|
|
43
|
-
return typeof value === "string" ? value : id;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
50
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> {
|
|
47
51
|
const rows: NodeInspectorSummaryRow[] = [{ label: "Trigger", value: "manual" }];
|
|
48
52
|
if (this.defaultItems && this.defaultItems.length > 0) {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "@codemation/core";
|
|
9
9
|
import type { ZodType } from "zod";
|
|
10
10
|
import { WebhookTriggerNode } from "./webhookTriggerNode";
|
|
11
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
11
12
|
|
|
12
13
|
type WebhookInputSchema = ZodType<any, any, any>;
|
|
13
14
|
type WebhookTriggerHandler<TConfig extends WebhookTrigger<any> = WebhookTrigger<any>> = (
|
|
@@ -21,6 +22,8 @@ export class WebhookTrigger<
|
|
|
21
22
|
readonly kind = "trigger" as const;
|
|
22
23
|
readonly type: TypeToken<unknown> = WebhookTriggerNode;
|
|
23
24
|
readonly icon = "lucide:globe";
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
readonly description?: string;
|
|
24
27
|
|
|
25
28
|
constructor(
|
|
26
29
|
public readonly name: string,
|
|
@@ -32,8 +35,12 @@ export class WebhookTrigger<
|
|
|
32
35
|
public readonly handler: WebhookTriggerHandler<
|
|
33
36
|
WebhookTrigger<TSchema>
|
|
34
37
|
> = WebhookTrigger.defaultHandler as WebhookTriggerHandler<WebhookTrigger<TSchema>>,
|
|
35
|
-
|
|
36
|
-
) {
|
|
38
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
39
|
+
) {
|
|
40
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
41
|
+
this.id = options?.id;
|
|
42
|
+
this.description = options?.description;
|
|
43
|
+
}
|
|
37
44
|
|
|
38
45
|
get endpointKey(): string {
|
|
39
46
|
return this.args.endpointKey;
|
package/src/nodes/aggregate.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
TypeToken,
|
|
7
7
|
} from "@codemation/core";
|
|
8
8
|
import { AggregateNode } from "./AggregateNode";
|
|
9
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
9
10
|
|
|
10
11
|
export class Aggregate<TIn = unknown, TOut = unknown> implements RunnableNodeConfig<TIn, TOut> {
|
|
11
12
|
readonly kind = "node" as const;
|
|
@@ -13,6 +14,8 @@ export class Aggregate<TIn = unknown, TOut = unknown> implements RunnableNodeCon
|
|
|
13
14
|
readonly execution = { hint: "local" } as const;
|
|
14
15
|
readonly keepBinaries = true as const;
|
|
15
16
|
readonly icon = "builtin:aggregate-rows" as const;
|
|
17
|
+
readonly id?: string;
|
|
18
|
+
readonly description?: string;
|
|
16
19
|
|
|
17
20
|
constructor(
|
|
18
21
|
public readonly name: string,
|
|
@@ -20,8 +23,12 @@ export class Aggregate<TIn = unknown, TOut = unknown> implements RunnableNodeCon
|
|
|
20
23
|
items: Items<TIn>,
|
|
21
24
|
ctx: NodeExecutionContext<Aggregate<TIn, TOut>>,
|
|
22
25
|
) => TOut | Promise<TOut>,
|
|
23
|
-
|
|
24
|
-
) {
|
|
26
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
27
|
+
) {
|
|
28
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
29
|
+
this.id = options?.id;
|
|
30
|
+
this.description = options?.description;
|
|
31
|
+
}
|
|
25
32
|
|
|
26
33
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
|
|
27
34
|
const fnName = this.aggregate.name;
|
package/src/nodes/assertion.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface AssertionOptions<TInputJson> {
|
|
|
13
13
|
readonly name?: string;
|
|
14
14
|
readonly id?: string;
|
|
15
15
|
readonly icon?: string;
|
|
16
|
+
readonly description?: string;
|
|
16
17
|
/**
|
|
17
18
|
* Author callback. Returns one or more {@link AssertionResult}s per input item. Each becomes
|
|
18
19
|
* one emitted output item — useful for per-row reporting in the Tests tab. Return `[]` to
|
|
@@ -35,6 +36,7 @@ export class Assertion<TInputJson = unknown> implements RunnableNodeConfig<TInpu
|
|
|
35
36
|
readonly icon: string;
|
|
36
37
|
readonly name: string;
|
|
37
38
|
readonly id?: string;
|
|
39
|
+
readonly description?: string;
|
|
38
40
|
readonly emitsAssertions = true as const;
|
|
39
41
|
readonly assertions: AssertionOptions<TInputJson>["assertions"];
|
|
40
42
|
|
|
@@ -42,6 +44,7 @@ export class Assertion<TInputJson = unknown> implements RunnableNodeConfig<TInpu
|
|
|
42
44
|
this.name = options.name ?? "Assertion";
|
|
43
45
|
this.id = options.id;
|
|
44
46
|
this.icon = options.icon ?? "lucide:check-circle";
|
|
47
|
+
this.description = options.description;
|
|
45
48
|
this.assertions = options.assertions;
|
|
46
49
|
}
|
|
47
50
|
|
package/src/nodes/filter.ts
CHANGED
|
@@ -7,12 +7,15 @@ import type {
|
|
|
7
7
|
TypeToken,
|
|
8
8
|
} from "@codemation/core";
|
|
9
9
|
import { FilterNode } from "./FilterNode";
|
|
10
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
10
11
|
|
|
11
12
|
export class Filter<TIn = unknown> implements RunnableNodeConfig<TIn, TIn> {
|
|
12
13
|
readonly kind = "node" as const;
|
|
13
14
|
readonly type: TypeToken<unknown> = FilterNode;
|
|
14
15
|
readonly execution = { hint: "local" } as const;
|
|
15
16
|
readonly icon = "lucide:filter" as const;
|
|
17
|
+
readonly id?: string;
|
|
18
|
+
readonly description?: string;
|
|
16
19
|
|
|
17
20
|
constructor(
|
|
18
21
|
public readonly name: string,
|
|
@@ -22,8 +25,12 @@ export class Filter<TIn = unknown> implements RunnableNodeConfig<TIn, TIn> {
|
|
|
22
25
|
items: Items<TIn>,
|
|
23
26
|
ctx: NodeExecutionContext<Filter<TIn>>,
|
|
24
27
|
) => boolean,
|
|
25
|
-
|
|
26
|
-
) {
|
|
28
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
29
|
+
) {
|
|
30
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
31
|
+
this.id = options?.id;
|
|
32
|
+
this.description = options?.description;
|
|
33
|
+
}
|
|
27
34
|
|
|
28
35
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
|
|
29
36
|
const fnName = this.predicate.name;
|
package/src/nodes/httpRequest.ts
CHANGED
|
@@ -62,6 +62,7 @@ export class HttpRequest<
|
|
|
62
62
|
readonly type: TypeToken<unknown> = HttpRequestNode;
|
|
63
63
|
readonly execution = { hint: "local" } as const;
|
|
64
64
|
readonly icon = "lucide:globe" as const;
|
|
65
|
+
readonly description?: string;
|
|
65
66
|
|
|
66
67
|
constructor(
|
|
67
68
|
public readonly name: string,
|
|
@@ -136,9 +137,13 @@ export class HttpRequest<
|
|
|
136
137
|
*/
|
|
137
138
|
allowedOutboundHosts?: ReadonlyArray<string>;
|
|
138
139
|
id?: string;
|
|
140
|
+
/** Plain-language explanation surfaced in the node sidebar. */
|
|
141
|
+
description?: string;
|
|
139
142
|
}> = {},
|
|
140
143
|
public readonly retryPolicy: RetryPolicySpec = RetryPolicy.defaultForHttp,
|
|
141
|
-
) {
|
|
144
|
+
) {
|
|
145
|
+
this.description = args.description;
|
|
146
|
+
}
|
|
142
147
|
|
|
143
148
|
get id(): string | undefined {
|
|
144
149
|
return this.args.id;
|
package/src/nodes/if.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
TypeToken,
|
|
8
8
|
} from "@codemation/core";
|
|
9
9
|
import { IfNode } from "./IfNode";
|
|
10
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
10
11
|
|
|
11
12
|
export class If<TInputJson = unknown> implements RunnableNodeConfig<TInputJson, TInputJson> {
|
|
12
13
|
readonly kind = "node" as const;
|
|
@@ -14,6 +15,8 @@ export class If<TInputJson = unknown> implements RunnableNodeConfig<TInputJson,
|
|
|
14
15
|
readonly execution = { hint: "local" } as const;
|
|
15
16
|
readonly icon = "lucide:split@rot=90" as const;
|
|
16
17
|
readonly declaredOutputPorts = ["true", "false"] as const;
|
|
18
|
+
readonly id?: string;
|
|
19
|
+
readonly description?: string;
|
|
17
20
|
constructor(
|
|
18
21
|
public readonly name: string,
|
|
19
22
|
public readonly predicate: (
|
|
@@ -22,8 +25,12 @@ export class If<TInputJson = unknown> implements RunnableNodeConfig<TInputJson,
|
|
|
22
25
|
items: Items<TInputJson>,
|
|
23
26
|
ctx: NodeExecutionContext<If<TInputJson>>,
|
|
24
27
|
) => boolean,
|
|
25
|
-
|
|
26
|
-
) {
|
|
28
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
29
|
+
) {
|
|
30
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
31
|
+
this.id = options?.id;
|
|
32
|
+
this.description = options?.description;
|
|
33
|
+
}
|
|
27
34
|
|
|
28
35
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
|
|
29
36
|
const fnName = this.predicate.name;
|
package/src/nodes/isTestRun.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
2
2
|
|
|
3
3
|
import { IsTestRunNode } from "./IsTestRunNode";
|
|
4
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Branches per-item on whether the current run is a test run. Output ports: `true`, `false`.
|
|
@@ -14,10 +15,13 @@ export class IsTestRun<TInputJson = unknown> implements RunnableNodeConfig<TInpu
|
|
|
14
15
|
readonly declaredOutputPorts = ["true", "false"] as const;
|
|
15
16
|
readonly name: string;
|
|
16
17
|
readonly id?: string;
|
|
18
|
+
readonly description?: string;
|
|
17
19
|
|
|
18
|
-
constructor(name: string = "Is test run?",
|
|
20
|
+
constructor(name: string = "Is test run?", idOrOptions?: string | NodeBaseOptions) {
|
|
19
21
|
this.name = name;
|
|
20
|
-
|
|
22
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
23
|
+
this.id = options?.id;
|
|
24
|
+
this.description = options?.description;
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
package/src/nodes/mapData.ts
CHANGED
|
@@ -6,9 +6,9 @@ import type {
|
|
|
6
6
|
TypeToken,
|
|
7
7
|
} from "@codemation/core";
|
|
8
8
|
import { MapDataNode } from "./MapDataNode";
|
|
9
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
9
10
|
|
|
10
|
-
export interface MapDataOptions {
|
|
11
|
-
readonly id?: string;
|
|
11
|
+
export interface MapDataOptions extends NodeBaseOptions {
|
|
12
12
|
readonly keepBinaries?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -23,6 +23,7 @@ export class MapData<TInputJson = unknown, TOutputJson = unknown> implements Run
|
|
|
23
23
|
readonly continueWhenEmptyOutput = true as const;
|
|
24
24
|
readonly icon = "lucide:square-pen" as const;
|
|
25
25
|
readonly keepBinaries: boolean;
|
|
26
|
+
readonly description?: string;
|
|
26
27
|
|
|
27
28
|
constructor(
|
|
28
29
|
public readonly name: string,
|
|
@@ -33,6 +34,7 @@ export class MapData<TInputJson = unknown, TOutputJson = unknown> implements Run
|
|
|
33
34
|
private readonly options: MapDataOptions = {},
|
|
34
35
|
) {
|
|
35
36
|
this.keepBinaries = options.keepBinaries ?? true;
|
|
37
|
+
this.description = options.description;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
get id(): string | undefined {
|
package/src/nodes/merge.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InputPortKey, NodeInspectorSummaryRow, RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
2
2
|
import { MergeNode } from "./MergeNode";
|
|
3
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
3
4
|
|
|
4
5
|
export type MergeMode = "passThrough" | "append" | "mergeByPosition";
|
|
5
6
|
|
|
@@ -10,6 +11,8 @@ export class Merge<TInputJson = unknown, TOutputJson = TInputJson> implements Ru
|
|
|
10
11
|
readonly kind = "node" as const;
|
|
11
12
|
readonly type: TypeToken<unknown> = MergeNode;
|
|
12
13
|
readonly icon = "lucide:merge@rot=90" as const;
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly description?: string;
|
|
13
16
|
|
|
14
17
|
constructor(
|
|
15
18
|
public readonly name: string,
|
|
@@ -21,8 +24,12 @@ export class Merge<TInputJson = unknown, TOutputJson = TInputJson> implements Ru
|
|
|
21
24
|
*/
|
|
22
25
|
prefer?: ReadonlyArray<InputPortKey>;
|
|
23
26
|
}> = { mode: "passThrough" },
|
|
24
|
-
|
|
25
|
-
) {
|
|
27
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
28
|
+
) {
|
|
29
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
30
|
+
this.id = options?.id;
|
|
31
|
+
this.description = options?.description;
|
|
32
|
+
}
|
|
26
33
|
|
|
27
34
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> {
|
|
28
35
|
const rows: NodeInspectorSummaryRow[] = [{ label: "Mode", value: this.cfg.mode }];
|
package/src/nodes/noOp.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import type { RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
2
2
|
import { NoOpNode } from "./NoOpNode";
|
|
3
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
3
4
|
|
|
4
5
|
export class NoOp<TItemJson = unknown> implements RunnableNodeConfig<TItemJson, TItemJson> {
|
|
5
6
|
readonly kind = "node" as const;
|
|
6
7
|
readonly type: TypeToken<unknown> = NoOpNode;
|
|
7
8
|
readonly execution = { hint: "local" } as const;
|
|
8
9
|
readonly icon = "lucide:circle-dashed" as const;
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly description?: string;
|
|
9
12
|
|
|
10
13
|
constructor(
|
|
11
14
|
public readonly name: string = "NoOp",
|
|
12
|
-
|
|
13
|
-
) {
|
|
15
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
16
|
+
) {
|
|
17
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
18
|
+
this.id = options?.id;
|
|
19
|
+
this.description = options?.description;
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
export { NoOpNode } from "./NoOpNode";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options shared by every authorable built-in node: a stable `id` and a plain-language
|
|
3
|
+
* `description` (the non-technical "what does this node do" line surfaced in the node sidebar).
|
|
4
|
+
*
|
|
5
|
+
* `description` is a first-class config option — passed inline in the node's options, exactly like
|
|
6
|
+
* `id` — and is threaded onto the config instance so it flows into the persisted workflow snapshot
|
|
7
|
+
* the host / canvas mappers read. Node-specific option types extend this.
|
|
8
|
+
*/
|
|
9
|
+
export interface NodeBaseOptions {
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly description?: string;
|
|
12
|
+
}
|
package/src/nodes/split.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
TypeToken,
|
|
7
7
|
} from "@codemation/core";
|
|
8
8
|
import { SplitNode } from "./SplitNode";
|
|
9
|
+
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
9
10
|
|
|
10
11
|
export class Split<TIn = unknown, TElem = unknown> implements RunnableNodeConfig<TIn, TElem> {
|
|
11
12
|
readonly kind = "node" as const;
|
|
@@ -18,12 +19,18 @@ export class Split<TIn = unknown, TElem = unknown> implements RunnableNodeConfig
|
|
|
18
19
|
*/
|
|
19
20
|
readonly continueWhenEmptyOutput = true as const;
|
|
20
21
|
readonly icon = "builtin:split-rows" as const;
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
readonly description?: string;
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
23
26
|
public readonly name: string,
|
|
24
27
|
public readonly getElements: (item: Item<TIn>, ctx: NodeExecutionContext<Split<TIn, TElem>>) => readonly TElem[],
|
|
25
|
-
|
|
26
|
-
) {
|
|
28
|
+
idOrOptions?: string | NodeBaseOptions,
|
|
29
|
+
) {
|
|
30
|
+
const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
31
|
+
this.id = options?.id;
|
|
32
|
+
this.description = options?.description;
|
|
33
|
+
}
|
|
27
34
|
|
|
28
35
|
inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
|
|
29
36
|
const fnName = this.getElements.name;
|