@codemation/core-nodes 0.13.0 → 0.14.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 +13 -0
- package/dist/index.cjs +34 -433
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -1312
- package/dist/index.d.ts +95 -1312
- package/dist/index.js +35 -435
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +1 -1
- package/package.json +2 -2
- package/src/authoring/defineRestNode.types.ts +0 -84
- package/src/canvasIconName.ts +0 -7
- package/src/chatModels/CodemationChatModelConfig.ts +0 -10
- package/src/chatModels/CodemationChatModelFactory.ts +0 -7
- package/src/chatModels/ManagedHmacSignerFactory.types.ts +0 -35
- package/src/chatModels/OpenAIChatModelFactory.ts +0 -2
- package/src/chatModels/OpenAiChatModelPresetsFactory.ts +0 -5
- package/src/chatModels/OpenAiCredentialSession.ts +0 -1
- package/src/chatModels/OpenAiStrictJsonSchemaFactory.ts +0 -21
- package/src/credentials/ApiKeyCredentialType.ts +0 -3
- package/src/credentials/BasicAuthCredentialType.ts +0 -4
- package/src/credentials/BearerTokenCredentialType.ts +0 -4
- package/src/credentials/OAuth2ClientCredentialsTypeFactory.ts +0 -19
- package/src/credentials/OAuth2TokenExchangeFactory.ts +0 -7
- package/src/http/HttpBodyBuilder.ts +0 -16
- package/src/http/HttpRequestExecutor.ts +0 -35
- package/src/http/HttpUrlBuilder.ts +0 -4
- package/src/http/SSRFBlockedError.ts +0 -4
- package/src/http/SsrfGuard.ts +10 -50
- package/src/http/httpRequest.types.ts +0 -49
- package/src/index.ts +1 -0
- package/src/nodes/AIAgentConfig.ts +0 -44
- package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
- package/src/nodes/AIAgentNode.ts +0 -132
- package/src/nodes/AgentBinaryContentFactory.ts +0 -12
- package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
- package/src/nodes/AgentMessageFactory.ts +0 -18
- package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
- package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
- package/src/nodes/AgentToolResultContentFactory.ts +0 -29
- package/src/nodes/AssertionNode.ts +0 -14
- package/src/nodes/BM25Index.ts +0 -14
- package/src/nodes/ConnectionCredentialExecutionContextFactory.ts +0 -5
- package/src/nodes/ConnectionCredentialNode.ts +0 -4
- package/src/nodes/CronTriggerFactory.ts +0 -9
- package/src/nodes/DeferredMetaToolStrategy.ts +0 -18
- package/src/nodes/DeferredMetaToolStrategyFactory.ts +0 -5
- package/src/nodes/HttpRequestNodeFactory.ts +0 -14
- package/src/nodes/InboxApprovalNode.types.ts +0 -16
- package/src/nodes/IsTestRunNode.ts +0 -8
- package/src/nodes/ManualTriggerFactory.ts +0 -3
- package/src/nodes/ManualTriggerNode.ts +0 -4
- package/src/nodes/MergeNode.ts +0 -1
- package/src/nodes/NodeBackedToolRuntime.ts +0 -14
- package/src/nodes/SubWorkflowNode.ts +0 -3
- package/src/nodes/SwitchNode.ts +0 -3
- package/src/nodes/TestTriggerNode.ts +0 -9
- package/src/nodes/aiAgentSupport.types.ts +0 -16
- package/src/nodes/assertion.ts +0 -10
- package/src/nodes/codemationDocumentScannerNode.ts +0 -18
- package/src/nodes/collections/collectionListNode.types.ts +0 -1
- package/src/nodes/httpRequest.ts +0 -68
- package/src/nodes/isTestRun.ts +0 -4
- package/src/nodes/mapData.ts +0 -1
- package/src/nodes/merge.ts +0 -4
- package/src/nodes/mergeExecutionUtils.types.ts +0 -3
- package/src/nodes/nodeOptions.types.ts +0 -8
- package/src/nodes/schedulePollingTrigger.ts +37 -0
- package/src/nodes/split.ts +0 -4
- package/src/nodes/testTrigger.ts +0 -21
- package/src/nodes/wait.ts +0 -1
- package/src/nodes/webhookTriggerNode.ts +0 -5
- package/src/register.types.ts +0 -10
- package/src/workflows/AIAgentConnectionWorkflowExpander.ts +0 -3
|
@@ -3,10 +3,6 @@ import { node } from "@codemation/core";
|
|
|
3
3
|
|
|
4
4
|
import type { ConnectionCredentialNodeConfig } from "./ConnectionCredentialNodeConfig";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Placeholder runnable node for connection-owned workflow nodes (LLM/tool slots).
|
|
8
|
-
* The engine does not schedule these; they exist for credentials, tokens, and UI identity.
|
|
9
|
-
*/
|
|
10
6
|
@node({ packageName: "@codemation/core-nodes" })
|
|
11
7
|
export class ConnectionCredentialNode implements RunnableNode<ConnectionCredentialNodeConfig> {
|
|
12
8
|
kind = "node" as const;
|
|
@@ -8,15 +8,6 @@ import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
|
8
8
|
|
|
9
9
|
export type CronTickJson = { firedAt: string; scheduledFor: string };
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Schedules a workflow on a standard cron expression.
|
|
13
|
-
*
|
|
14
|
-
* Each tick emits one item: `{ firedAt: string, scheduledFor: string }` — both ISO-8601 timestamps.
|
|
15
|
-
* `firedAt` is the wall-clock moment the callback ran; `scheduledFor` is the cron-computed
|
|
16
|
-
* firing instant (these differ when the job was delayed).
|
|
17
|
-
*
|
|
18
|
-
* Timezone defaults to UTC when omitted — cron without an explicit TZ is a DST footgun.
|
|
19
|
-
*/
|
|
20
11
|
export class CronTrigger implements TriggerNodeConfig<CronTickJson> {
|
|
21
12
|
readonly kind = "trigger" as const;
|
|
22
13
|
readonly type: TypeToken<unknown> = CronTriggerNode;
|
|
@@ -21,26 +21,12 @@ interface McpToolEntry {
|
|
|
21
21
|
readonly toolDef: ToolSet[string];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
* Default tool-loading strategy: BM25-indexed MCP tool deferral via a `find_tools` meta-tool.
|
|
26
|
-
*
|
|
27
|
-
* - Node-backed tools and pinned MCP tools are always included in every turn.
|
|
28
|
-
* - `find_tools(query, limit?)` is added to the tool set when MCP tools are indexed.
|
|
29
|
-
* - Tools surfaced by `find_tools` are included in subsequent turns.
|
|
30
|
-
*
|
|
31
|
-
* Not DI-managed; instantiated per agent execution by DeferredMetaToolStrategyFactory.
|
|
32
|
-
*/
|
|
33
24
|
export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
34
25
|
private nodeBackedTools: ToolSet = {};
|
|
35
26
|
private pinnedTools: ToolSet = {};
|
|
36
27
|
private mcpEntries: McpToolEntry[] = [];
|
|
37
28
|
private toolsByServerId = new Map<string, Map<string, ToolSet[string]>>();
|
|
38
29
|
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
30
|
private jsonSchema!: typeof import("ai").jsonSchema;
|
|
45
31
|
|
|
46
32
|
constructor(
|
|
@@ -124,8 +110,6 @@ export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
|
124
110
|
|
|
125
111
|
ownsToolName(toolName: string): boolean {
|
|
126
112
|
if (toolName === FIND_TOOLS_NAME) return true;
|
|
127
|
-
// Any tool that came from an MCP server is strategy-owned so the coordinator
|
|
128
|
-
// does not attempt to dispatch it as a node-backed tool.
|
|
129
113
|
for (const serverMap of this.toolsByServerId.values()) {
|
|
130
114
|
if (serverMap.has(toolName)) return true;
|
|
131
115
|
}
|
|
@@ -149,8 +133,6 @@ export class DeferredMetaToolStrategy implements ToolLoadingStrategy {
|
|
|
149
133
|
return results;
|
|
150
134
|
}
|
|
151
135
|
|
|
152
|
-
// Route to the MCP tool's execute callback (injected by AgentMcpIntegrationImpl with
|
|
153
|
-
// telemetry + 403 detection wrapping).
|
|
154
136
|
for (const serverMap of this.toolsByServerId.values()) {
|
|
155
137
|
const toolDef = serverMap.get(toolName);
|
|
156
138
|
if (toolDef) {
|
|
@@ -3,11 +3,6 @@ import { BM25Index } from "./BM25Index";
|
|
|
3
3
|
import { DeferredMetaToolStrategy } from "./DeferredMetaToolStrategy";
|
|
4
4
|
import type { ToolLoadingStrategy, ToolLoadingStrategyInitInput } from "./ToolLoadingStrategy";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Factory for creating and initializing a DeferredMetaToolStrategy per agent execution.
|
|
8
|
-
* Injected into AIAgentNode; each agent call creates its own initialized strategy instance.
|
|
9
|
-
* BM25Index is constructed here (this file is a composition root via the Factory suffix).
|
|
10
|
-
*/
|
|
11
6
|
@injectable()
|
|
12
7
|
export class DeferredMetaToolStrategyFactory {
|
|
13
8
|
async create(input: ToolLoadingStrategyInitInput): Promise<ToolLoadingStrategy> {
|
|
@@ -39,9 +39,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
39
39
|
ctx: ctx as unknown as HttpRequestSpec["ctx"],
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
// Build the request (headers, body encoding, URL query merge) once,
|
|
43
|
-
// then make a SINGLE fetch call and decide what to do with the response.
|
|
44
|
-
// This avoids a double-fetch regression for auto-mode binary responses.
|
|
45
42
|
const executor = new HttpRequestExecutor(
|
|
46
43
|
globalThis.fetch,
|
|
47
44
|
new HttpBodyBuilder(),
|
|
@@ -55,7 +52,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
55
52
|
const headers = this.readHeaders(response.headers);
|
|
56
53
|
const mimeType = this.resolveMimeType(headers);
|
|
57
54
|
|
|
58
|
-
// New explicit responseFormat="binary" path — takes precedence over downloadMode.
|
|
59
55
|
if (ctx.config.responseFormat === "binary") {
|
|
60
56
|
return await this.handleBinaryResponse(response, resolvedUrl, headers, mimeType, ctx);
|
|
61
57
|
}
|
|
@@ -90,7 +86,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
90
86
|
return outputItem;
|
|
91
87
|
}
|
|
92
88
|
|
|
93
|
-
// Non-binary path: parse JSON or read text.
|
|
94
89
|
const isJson = this.isJsonMimeType(mimeType);
|
|
95
90
|
let json: unknown | undefined;
|
|
96
91
|
let text: string | undefined;
|
|
@@ -130,7 +125,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
130
125
|
const slotName = ctx.config.responseBinarySlot;
|
|
131
126
|
const sizeCap = ctx.config.responseSizeCapBytes;
|
|
132
127
|
|
|
133
|
-
// Check Content-Length against size cap before allocating.
|
|
134
128
|
const contentLengthHeader = headers["content-length"];
|
|
135
129
|
if (contentLengthHeader) {
|
|
136
130
|
const declaredSize = parseInt(contentLengthHeader, 10);
|
|
@@ -144,11 +138,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
144
138
|
|
|
145
139
|
const filename = this.resolveFilename(resolvedUrl, headers);
|
|
146
140
|
|
|
147
|
-
// Stream response.body straight into binary storage — never load the
|
|
148
|
-
// whole payload into memory. ctx.binary.attach accepts ReadableStream
|
|
149
|
-
// natively. Falls back to arrayBuffer only when response.body is null
|
|
150
|
-
// (rare; 204/304-style responses where the cap-check above already
|
|
151
|
-
// covers the meaningful size case).
|
|
152
141
|
const attachment = await ctx.binary.attach({
|
|
153
142
|
name: slotName,
|
|
154
143
|
body: response.body
|
|
@@ -168,7 +157,6 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
168
157
|
headers,
|
|
169
158
|
binarySlot: slotName,
|
|
170
159
|
contentType: mimeType,
|
|
171
|
-
// Reported by the binary storage adapter after streaming completes.
|
|
172
160
|
size: attachment.size,
|
|
173
161
|
...(filename !== undefined ? { filename } : {}),
|
|
174
162
|
};
|
|
@@ -189,13 +177,11 @@ export class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
|
|
|
189
177
|
try {
|
|
190
178
|
return await ctx.getCredential<CredentialSession>(slotKey);
|
|
191
179
|
} catch {
|
|
192
|
-
// Credential slot configured but not bound — treat as no credential.
|
|
193
180
|
return undefined;
|
|
194
181
|
}
|
|
195
182
|
}
|
|
196
183
|
|
|
197
184
|
private resolveUrl(item: Item, ctx: NodeExecutionContext<HttpRequest<any, any>>): string {
|
|
198
|
-
// Literal URL in args takes precedence over the legacy urlField approach.
|
|
199
185
|
const literalUrl = ctx.config.args.url;
|
|
200
186
|
if (literalUrl && literalUrl.trim().length > 0) {
|
|
201
187
|
return literalUrl.trim();
|
|
@@ -3,28 +3,12 @@ import { defineHumanApprovalNode } from "@codemation/core";
|
|
|
3
3
|
import type { Item, JsonValue } from "@codemation/core";
|
|
4
4
|
import { InboxChannelResolverToken } from "@codemation/core";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* A subject field (title / body) for an inbox approval. Either a static string
|
|
8
|
-
* or a contextual callback that builds the string from the item using ordinary
|
|
9
|
-
* JavaScript template literals — e.g. `({ item }) => `Approve ${item.json.vendor}``.
|
|
10
|
-
* Code-first: no template DSL, just functions.
|
|
11
|
-
*/
|
|
12
6
|
type InboxSubjectField = string | ((args: { item: Item }) => string);
|
|
13
7
|
|
|
14
8
|
function resolveSubjectField(field: InboxSubjectField, item: Item): string {
|
|
15
9
|
return typeof field === "function" ? field({ item }) : field;
|
|
16
10
|
}
|
|
17
11
|
|
|
18
|
-
/**
|
|
19
|
-
* Auto-detecting inbox approval node.
|
|
20
|
-
*
|
|
21
|
-
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
22
|
-
* at runtime:
|
|
23
|
-
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
24
|
-
* - Otherwise: routes to the local inbox.
|
|
25
|
-
*
|
|
26
|
-
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
27
|
-
*/
|
|
28
12
|
export const inboxApproval = defineHumanApprovalNode({
|
|
29
13
|
key: "inbox.approval",
|
|
30
14
|
title: "Inbox Approval",
|
|
@@ -3,14 +3,6 @@ import { emitPorts, node } from "@codemation/core";
|
|
|
3
3
|
|
|
4
4
|
import { IsTestRun } from "./isTestRun";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Routes each item to the `true` port if `ctx.testContext` is set (the run was started by the
|
|
8
|
-
* TestSuiteOrchestrator), else to `false`. Lets workflow authors guard real side-effects:
|
|
9
|
-
*
|
|
10
|
-
* GmailTrigger / TestTrigger → ClassifyAgent → IsTestRun
|
|
11
|
-
* ├── true → AssertionNode
|
|
12
|
-
* └── false → SendReply
|
|
13
|
-
*/
|
|
14
6
|
@node({ packageName: "@codemation/core-nodes" })
|
|
15
7
|
export class IsTestRunNode implements RunnableNode<IsTestRun<unknown>> {
|
|
16
8
|
kind = "node" as const;
|
|
@@ -15,7 +15,6 @@ export class ManualTrigger<TOutputJson = unknown> implements TriggerNodeConfig<T
|
|
|
15
15
|
readonly defaultItems?: Items<TOutputJson>;
|
|
16
16
|
readonly id?: string;
|
|
17
17
|
readonly description?: string;
|
|
18
|
-
/** Manual runs often emit an empty batch; still schedule downstream by default. */
|
|
19
18
|
readonly continueWhenEmptyOutput = true as const;
|
|
20
19
|
|
|
21
20
|
constructor(name?: string, idOrOptions?: string | NodeBaseOptions);
|
|
@@ -29,8 +28,6 @@ export class ManualTrigger<TOutputJson = unknown> implements TriggerNodeConfig<T
|
|
|
29
28
|
defaultItemsOrId?: ManualTriggerDefaultValue<TOutputJson> | string,
|
|
30
29
|
idOrOptions?: string | NodeBaseOptions,
|
|
31
30
|
) {
|
|
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.
|
|
34
31
|
this.defaultItems = ManualTrigger.resolveDefaultItems(defaultItemsOrId);
|
|
35
32
|
const trailing = idOrOptions ?? (typeof defaultItemsOrId === "string" ? defaultItemsOrId : undefined);
|
|
36
33
|
const options = typeof trailing === "string" ? { id: trailing } : (trailing ?? {});
|
|
@@ -11,10 +11,6 @@ import { node } from "@codemation/core";
|
|
|
11
11
|
|
|
12
12
|
import { ManualTrigger } from "./ManualTriggerFactory";
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Setup is intentionally a no-op: the engine host can run workflows manually
|
|
16
|
-
* by calling `engine.runWorkflow(workflow, triggerNodeId, items)`.
|
|
17
|
-
*/
|
|
18
14
|
@node({ packageName: "@codemation/core-nodes" })
|
|
19
15
|
export class ManualTriggerNode implements TestableTriggerNode<ManualTrigger<any>> {
|
|
20
16
|
kind = "trigger" as const;
|
package/src/nodes/MergeNode.ts
CHANGED
|
@@ -48,7 +48,6 @@ export class MergeNode implements MultiInputNode<Merge<any, any>> {
|
|
|
48
48
|
return { main: out };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// passThrough (default): for each origin index, take first available input (deterministic input precedence).
|
|
52
51
|
const chosenByOrigin = new Map<number, Item>();
|
|
53
52
|
const fallback: Item[] = [];
|
|
54
53
|
|
|
@@ -63,20 +63,6 @@ export class NodeBackedToolRuntime {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* Returns a re-rooted child ctx for nested-agent tools (so their LLM/tool connection ids derive
|
|
68
|
-
* from the tool connection node, telemetry parents under the tool-call span, and connection
|
|
69
|
-
* invocations carry `parentInvocationId`). Plain runnable tools (non-agent) keep the orchestrator
|
|
70
|
-
* ctx with only `config` swapped — no nesting concern.
|
|
71
|
-
*
|
|
72
|
-
* The caller (`AIAgentNode.createItemScopedTools`) already wraps the orchestrator ctx via
|
|
73
|
-
* `ConnectionCredentialExecutionContextFactory.forConnectionNode`, so `args.ctx.nodeId` is the
|
|
74
|
-
* tool's own connection node id (e.g. `AIAgentNode:2__conn__tool__searchInMail`). We pass that
|
|
75
|
-
* through as the sub-agent's `nodeId`; deriving another `toolConnectionNodeId(args.ctx.nodeId,
|
|
76
|
-
* config.name)` here would prepend a duplicate `__conn__tool__<name>` segment and exponentially
|
|
77
|
-
* deepen ids on each invocation, which also breaks credential resolution because user-provided
|
|
78
|
-
* bindings sit on the single-level connection node id.
|
|
79
|
-
*/
|
|
80
66
|
private resolveNodeCtx(
|
|
81
67
|
config: NodeBackedToolConfig<any, ZodSchemaAny, ZodSchemaAny>,
|
|
82
68
|
args: ToolExecuteArgs,
|
|
@@ -37,9 +37,6 @@ export class SubWorkflowNode implements RunnableNode<SubWorkflow<any, any>> {
|
|
|
37
37
|
engineMaxSubworkflowDepth: args.ctx.engineMaxSubworkflowDepth,
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
|
-
// Annotate the parent node's snapshot with the child run id so the UI can deep-link to
|
|
41
|
-
// the specific child execution. The engine's subsequent markCompleted preserves this via
|
|
42
|
-
// NodeExecutionSnapshotFactory.completed which carries forward previous.childRunId.
|
|
43
40
|
await args.ctx.nodeState?.setChildRunId?.({ nodeId: args.ctx.nodeId, childRunId: result.runId });
|
|
44
41
|
if (result.status !== "completed") {
|
|
45
42
|
throw new Error(`Subworkflow ${args.ctx.config.workflowId} did not complete (status=${result.status})`);
|
package/src/nodes/SwitchNode.ts
CHANGED
|
@@ -4,9 +4,6 @@ import { emitPorts, node } from "@codemation/core";
|
|
|
4
4
|
import type { Switch } from "./switch";
|
|
5
5
|
import { tagItemForRouterFanIn } from "./mergeExecutionUtils.types";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Routes each item to exactly one output port. Port names must match workflow edges (see {@link Switch} config).
|
|
9
|
-
*/
|
|
10
7
|
@node({ packageName: "@codemation/core-nodes" })
|
|
11
8
|
export class SwitchNode implements RunnableNode<Switch<any>> {
|
|
12
9
|
kind = "node" as const;
|
|
@@ -9,15 +9,6 @@ import type {
|
|
|
9
9
|
|
|
10
10
|
import { node } from "@codemation/core";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Author-defined test-fixture trigger. Live activation skips this trigger (filtered by
|
|
14
|
-
* `triggerKind === "test"` in `TriggerRuntimeService`); the `TestSuiteOrchestrator` drives its
|
|
15
|
-
* `generateItems` callback during a TestSuiteRun and dispatches one workflow run per yielded item.
|
|
16
|
-
*
|
|
17
|
-
* `setup` is intentionally a no-op for symmetry with other trigger nodes — the real work happens
|
|
18
|
-
* in the orchestrator. `execute` is a passthrough so items provided to `engine.runWorkflow(...)`
|
|
19
|
-
* (one per case) flow downstream unchanged on `main`.
|
|
20
|
-
*/
|
|
21
12
|
@node({ packageName: "@codemation/core-nodes" })
|
|
22
13
|
export class TestTriggerNode implements TriggerNode<TestTriggerNodeConfig<any>> {
|
|
23
14
|
kind = "trigger" as const;
|
|
@@ -24,30 +24,15 @@ export type ResolvedTool = Readonly<{
|
|
|
24
24
|
}>;
|
|
25
25
|
}>;
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Per-item binding of a tool: the user config plus the resolved runtime and a snapshot of the
|
|
29
|
-
* original Zod `inputSchema`.
|
|
30
|
-
*
|
|
31
|
-
* `execute` accepts optional `hooks` so the agent coordinator can pass the live `agent.tool.call`
|
|
32
|
-
* span and the planned tool-call's `invocationId`. Node-backed sub-agent tools use these hooks
|
|
33
|
-
* via {@link ChildExecutionScopeFactory} to re-root their runtime ctx under the tool-call boundary
|
|
34
|
-
* (fresh activationId, telemetry parented at the tool-call span, `parentInvocationId` set).
|
|
35
|
-
*
|
|
36
|
-
* `humanApproval` is present only when the tool was created via `defineHumanApprovalNode`
|
|
37
|
-
* (via its marker) — detected during `resolveTools` in `AIAgentNode`.
|
|
38
|
-
*/
|
|
39
27
|
export type ItemScopedToolBinding = Readonly<{
|
|
40
28
|
config: ToolConfig;
|
|
41
29
|
inputSchema: ZodSchemaAny;
|
|
42
30
|
execute(input: unknown, hooks?: ItemScopedToolCallHooks): Promise<unknown>;
|
|
43
|
-
/** Present when this binding is backed by a HITL-approval node (story 10). */
|
|
44
31
|
humanApproval?: Readonly<{ onRejected: "halt" | "return" }>;
|
|
45
32
|
}>;
|
|
46
33
|
|
|
47
34
|
export type ItemScopedToolCallHooks = Readonly<{
|
|
48
|
-
/** Live agent.tool.call span (used to parent sub-agent telemetry). */
|
|
49
35
|
parentSpan?: TelemetrySpanScope;
|
|
50
|
-
/** invocationId of the parent tool call (used to thread `parentInvocationId` through ctx). */
|
|
51
36
|
parentInvocationId?: ConnectionInvocationId;
|
|
52
37
|
}>;
|
|
53
38
|
|
|
@@ -56,7 +41,6 @@ export type PlannedToolCall = Readonly<{
|
|
|
56
41
|
toolCall: AgentToolCall;
|
|
57
42
|
invocationIndex: number;
|
|
58
43
|
nodeId: string;
|
|
59
|
-
/** Stable id reused across queued / running / completed connection invocation rows for this tool call. */
|
|
60
44
|
invocationId: string;
|
|
61
45
|
}>;
|
|
62
46
|
|
package/src/nodes/assertion.ts
CHANGED
|
@@ -14,22 +14,12 @@ export interface AssertionOptions<TInputJson> {
|
|
|
14
14
|
readonly id?: string;
|
|
15
15
|
readonly icon?: string;
|
|
16
16
|
readonly description?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Author callback. Returns one or more {@link AssertionResult}s per input item. Each becomes
|
|
19
|
-
* one emitted output item — useful for per-row reporting in the Tests tab. Return `[]` to
|
|
20
|
-
* emit nothing for this case (rare; usually you want at least a "no-op" pass).
|
|
21
|
-
*/
|
|
22
17
|
assertions(
|
|
23
18
|
item: Item<TInputJson>,
|
|
24
19
|
ctx: NodeExecutionContext<Assertion<TInputJson>>,
|
|
25
20
|
): Promise<ReadonlyArray<AssertionResult>> | ReadonlyArray<AssertionResult>;
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
/**
|
|
29
|
-
* Generic assertion node — the "callback" form. For declarative shorthands (StringEquals,
|
|
30
|
-
* JudgeByAgent) compose this with helpers added in later phases. Sets `emitsAssertions: true`
|
|
31
|
-
* so host-side persisters know to record its outputs as `TestAssertion` rows.
|
|
32
|
-
*/
|
|
33
23
|
export class Assertion<TInputJson = unknown> implements RunnableNodeConfig<TInputJson, AssertionResult> {
|
|
34
24
|
readonly kind = "node" as const;
|
|
35
25
|
readonly type: TypeToken<unknown> = AssertionNode;
|
|
@@ -5,30 +5,21 @@ import { managedHmacFetchFactory } from "../chatModels/ManagedHmacSignerFactory.
|
|
|
5
5
|
const ANALYZER_TYPES = ["document", "invoice", "image", "auto"] as const;
|
|
6
6
|
export type DocScannerAnalyzerType = (typeof ANALYZER_TYPES)[number];
|
|
7
7
|
|
|
8
|
-
/** Per-field value/confidence shape as returned by apps/doc-scanner. */
|
|
9
8
|
export type DocScannerField = Readonly<{
|
|
10
9
|
value: unknown;
|
|
11
10
|
confidence: number | null;
|
|
12
11
|
}>;
|
|
13
12
|
|
|
14
|
-
/** Output shape of CodemationDocumentScanner — identical to the service wire response. */
|
|
15
13
|
export type DocScannerOutput = Readonly<{
|
|
16
14
|
markdown: string;
|
|
17
15
|
fields: Readonly<Record<string, DocScannerField>>;
|
|
18
16
|
}>;
|
|
19
17
|
|
|
20
18
|
export type CodemationDocumentScannerConfig = Readonly<{
|
|
21
|
-
/** Key on `item.binary` that holds the document. Default: "data". */
|
|
22
19
|
binaryField?: string;
|
|
23
|
-
/** Analyzer type. Default: "auto" (routes on mime type on the service side). */
|
|
24
20
|
analyzerType?: DocScannerAnalyzerType;
|
|
25
|
-
/** MIME type override. Falls back to attachment.mimeType. */
|
|
26
21
|
contentType?: string;
|
|
27
|
-
/** Include per-field confidence scores (0–1). Default: false.
|
|
28
|
-
* Enabling this roughly doubles contextualization tokens for document analyzers.
|
|
29
|
-
* Image and auto-to-image requests silently ignore this flag (confidence stays null). */
|
|
30
22
|
includeConfidence?: boolean;
|
|
31
|
-
/** Max bytes checked before any read. Default: 50 MiB (same cap as OCR nodes, LD10). */
|
|
32
23
|
maxBytes?: number;
|
|
33
24
|
}>;
|
|
34
25
|
|
|
@@ -54,9 +45,6 @@ export const codemationDocumentScannerNode = defineNode({
|
|
|
54
45
|
includeConfidence: z.boolean().optional(),
|
|
55
46
|
maxBytes: z.number().int().positive().optional(),
|
|
56
47
|
}),
|
|
57
|
-
// keepBinaries is omitted (false) — the output replaces the item payload.
|
|
58
|
-
// To carry the source binary forward, set keepBinaries: true at the call site
|
|
59
|
-
// or use a downstream step that reads item.binary.
|
|
60
48
|
inspectorSummary({ config }) {
|
|
61
49
|
const cfg = config as unknown as CodemationDocumentScannerConfig;
|
|
62
50
|
const rows: Array<{ label: string; value: string }> = [
|
|
@@ -79,9 +67,6 @@ export const codemationDocumentScannerNode = defineNode({
|
|
|
79
67
|
);
|
|
80
68
|
}
|
|
81
69
|
|
|
82
|
-
// Workspace pairing identity — injected by the CP provisioner (mirrors
|
|
83
|
-
// CodemationChatModelFactory). Passed to the signer; the HMAC binds THIS
|
|
84
|
-
// workspace (LD4) and does not sign the file body (LD11).
|
|
85
70
|
// eslint-disable-next-line no-restricted-properties -- WORKSPACE_ID is injected by the provisioner; read at execute time.
|
|
86
71
|
const workspaceId = process.env["WORKSPACE_ID"];
|
|
87
72
|
// eslint-disable-next-line no-restricted-properties -- WORKSPACE_PAIRING_SECRET is injected by the provisioner; read at execute time.
|
|
@@ -104,9 +89,6 @@ export const codemationDocumentScannerNode = defineNode({
|
|
|
104
89
|
const confidenceSuffix = includeConfidence ? "&confidence=true" : "";
|
|
105
90
|
const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
|
|
106
91
|
|
|
107
|
-
// signBody: false (LD11): the HMAC binds the workspace, not the file body.
|
|
108
|
-
// The signer forwards the bytes untouched and signs an empty-body hash, so
|
|
109
|
-
// we never re-read or normalise the binary payload.
|
|
110
92
|
const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false });
|
|
111
93
|
|
|
112
94
|
const response = await hmacFetch(url, {
|
package/src/nodes/httpRequest.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { HttpRequestNode } from "./HttpRequestNodeFactory";
|
|
|
18
18
|
|
|
19
19
|
export type HttpRequestDownloadMode = "auto" | "always" | "never";
|
|
20
20
|
|
|
21
|
-
/** JSON emitted by {@link HttpRequest} — response metadata only (input item fields are not passed through). */
|
|
22
21
|
export type HttpRequestOutputJson = Readonly<{
|
|
23
22
|
url: string;
|
|
24
23
|
method: string;
|
|
@@ -30,20 +29,12 @@ export type HttpRequestOutputJson = Readonly<{
|
|
|
30
29
|
json?: unknown;
|
|
31
30
|
text?: string;
|
|
32
31
|
bodyBinaryName?: string;
|
|
33
|
-
/** Set when `responseFormat === "binary"`. Name of the binary slot the response was stored in. */
|
|
34
32
|
binarySlot?: string;
|
|
35
|
-
/** Set when `responseFormat === "binary"`. MIME type of the stored response. */
|
|
36
33
|
contentType?: string;
|
|
37
|
-
/** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
|
|
38
34
|
size?: number;
|
|
39
|
-
/** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
|
|
40
35
|
filename?: string;
|
|
41
36
|
}>;
|
|
42
37
|
|
|
43
|
-
/**
|
|
44
|
-
* The built-in HTTP request credential type IDs accepted by the `HttpRequest` node.
|
|
45
|
-
* These match the four generic credential types shipped with `@codemation/core-nodes`.
|
|
46
|
-
*/
|
|
47
38
|
export const HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES: ReadonlyArray<string> = [
|
|
48
39
|
bearerTokenCredentialType.definition.typeId,
|
|
49
40
|
apiKeyCredentialType.definition.typeId,
|
|
@@ -51,7 +42,6 @@ export const HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES: ReadonlyArray<string> = [
|
|
|
51
42
|
oauth2ClientCredentialsType.definition.typeId,
|
|
52
43
|
] as const;
|
|
53
44
|
|
|
54
|
-
/** Default maximum response size for binary mode: 100 MiB. */
|
|
55
45
|
const DEFAULT_RESPONSE_SIZE_CAP_BYTES = 100 * 1024 * 1024;
|
|
56
46
|
|
|
57
47
|
export class HttpRequest<
|
|
@@ -67,77 +57,20 @@ export class HttpRequest<
|
|
|
67
57
|
constructor(
|
|
68
58
|
public readonly name: string,
|
|
69
59
|
public readonly args: Readonly<{
|
|
70
|
-
/** HTTP method (default: GET). */
|
|
71
60
|
method?: string;
|
|
72
|
-
/**
|
|
73
|
-
* Legacy: field name on item.json to read the URL from.
|
|
74
|
-
* Use `url` for a literal/templated URL instead.
|
|
75
|
-
*/
|
|
76
61
|
urlField?: string;
|
|
77
|
-
/** Literal or templated URL. When present, takes precedence over `urlField`. */
|
|
78
62
|
url?: string;
|
|
79
|
-
/** Extra headers to add to every request. */
|
|
80
63
|
headers?: Readonly<Record<string, string>>;
|
|
81
|
-
/** Query parameters to append to the URL. */
|
|
82
64
|
query?: Readonly<Record<string, string>>;
|
|
83
|
-
/** Request body specification. For `kind:"json"`, pass the object directly in `body.data` — it is JSON-encoded exactly once, so never a pre-stringified string. */
|
|
84
65
|
body?: HttpBodySpec;
|
|
85
|
-
/**
|
|
86
|
-
* Credential slot.
|
|
87
|
-
*
|
|
88
|
-
* **String shorthand** (existing): `credentialSlot: "auth"` — the slot accepts all four
|
|
89
|
-
* default HTTP credential types (bearer, API-key, basic, OAuth2).
|
|
90
|
-
*
|
|
91
|
-
* **Object form** (new): narrows the accepted types to the caller-supplied list, useful
|
|
92
|
-
* when only a subset of credential types makes sense for a specific endpoint.
|
|
93
|
-
* ```ts
|
|
94
|
-
* credentialSlot: { name: "auth", acceptedTypes: [bearerTokenCredentialType] }
|
|
95
|
-
* ```
|
|
96
|
-
* The slot must be declared in `getCredentialRequirements()`, which is wired automatically.
|
|
97
|
-
*/
|
|
98
66
|
credentialSlot?: string | Readonly<{ name: string; acceptedTypes?: ReadonlyArray<AnyCredentialType> }>;
|
|
99
67
|
binaryName?: string;
|
|
100
68
|
downloadMode?: HttpRequestDownloadMode;
|
|
101
|
-
/**
|
|
102
|
-
* Controls how the response body is handled.
|
|
103
|
-
* - `"json"` / `"text"`: existing behaviour (parse + emit on `item.json`).
|
|
104
|
-
* - `"binary"`: read the response as raw bytes and store via `ctx.binary.attach`.
|
|
105
|
-
* The output JSON contains `{ status, headers, binarySlot, contentType, size, filename }`
|
|
106
|
-
* but NOT the raw bytes. Use `responseBinarySlot` to name the slot (default `"response"`).
|
|
107
|
-
*
|
|
108
|
-
* When omitted, the existing `downloadMode` logic applies (backward-compatible).
|
|
109
|
-
*/
|
|
110
69
|
responseFormat?: "json" | "text" | "binary";
|
|
111
|
-
/**
|
|
112
|
-
* Name of the binary slot to write the response body into when `responseFormat === "binary"`.
|
|
113
|
-
* Defaults to `"response"`.
|
|
114
|
-
*/
|
|
115
70
|
responseBinarySlot?: string;
|
|
116
|
-
/**
|
|
117
|
-
* Maximum response size in bytes for binary mode. Checked against the `Content-Length`
|
|
118
|
-
* response header before allocating memory. Defaults to 100 MiB (104857600).
|
|
119
|
-
* Requests whose `Content-Length` exceeds this cap are rejected before the body is read.
|
|
120
|
-
*/
|
|
121
71
|
responseSizeCapBytes?: number;
|
|
122
|
-
/**
|
|
123
|
-
* Operator-configurable outbound host allowlist.
|
|
124
|
-
*
|
|
125
|
-
* When set, every HTTP request target must match an entry in this list before the
|
|
126
|
-
* request is made — requests to any other host are rejected with {@link SSRFBlockedError}.
|
|
127
|
-
* Supports exact hostnames (`api.example.com`) and wildcard subdomain patterns
|
|
128
|
-
* (`*.example.com` matches `sub.example.com` but not `example.com` itself).
|
|
129
|
-
*
|
|
130
|
-
* When unset (default), the existing SSRF private-network guard applies:
|
|
131
|
-
* public hosts are allowed and private/loopback ranges are blocked.
|
|
132
|
-
*
|
|
133
|
-
* **Production warning**: when `NODE_ENV === "production"` and this is unset, a one-time
|
|
134
|
-
* warning is logged at workflow startup.
|
|
135
|
-
*
|
|
136
|
-
* Setting this to an empty array `[]` is equivalent to "block everything".
|
|
137
|
-
*/
|
|
138
72
|
allowedOutboundHosts?: ReadonlyArray<string>;
|
|
139
73
|
id?: string;
|
|
140
|
-
/** Plain-language explanation surfaced in the node sidebar. */
|
|
141
74
|
description?: string;
|
|
142
75
|
}> = {},
|
|
143
76
|
public readonly retryPolicy: RetryPolicySpec = RetryPolicy.defaultForHttp,
|
|
@@ -197,7 +130,6 @@ export class HttpRequest<
|
|
|
197
130
|
},
|
|
198
131
|
];
|
|
199
132
|
}
|
|
200
|
-
// Object form: use caller-supplied acceptedTypes (mapped to typeIds), falling back to all defaults.
|
|
201
133
|
const acceptedTypes =
|
|
202
134
|
slot.acceptedTypes && slot.acceptedTypes.length > 0
|
|
203
135
|
? slot.acceptedTypes.map((ct) => ct.definition.typeId)
|
package/src/nodes/isTestRun.ts
CHANGED
|
@@ -3,10 +3,6 @@ import type { RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
|
3
3
|
import { IsTestRunNode } from "./IsTestRunNode";
|
|
4
4
|
import type { NodeBaseOptions } from "./nodeOptions.types";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Branches per-item on whether the current run is a test run. Output ports: `true`, `false`.
|
|
8
|
-
* The wire payload is unchanged — this is a router, not a transform.
|
|
9
|
-
*/
|
|
10
6
|
export class IsTestRun<TInputJson = unknown> implements RunnableNodeConfig<TInputJson, TInputJson> {
|
|
11
7
|
readonly kind = "node" as const;
|
|
12
8
|
readonly type: TypeToken<unknown> = IsTestRunNode;
|
package/src/nodes/mapData.ts
CHANGED
|
@@ -19,7 +19,6 @@ export class MapData<TInputJson = unknown, TOutputJson = unknown> implements Run
|
|
|
19
19
|
readonly kind = "node" as const;
|
|
20
20
|
readonly type: TypeToken<unknown> = MapDataNode;
|
|
21
21
|
readonly execution = { hint: "local" } as const;
|
|
22
|
-
/** Zero mapped items should still allow downstream nodes to run. */
|
|
23
22
|
readonly continueWhenEmptyOutput = true as const;
|
|
24
23
|
readonly icon = "lucide:square-pen" as const;
|
|
25
24
|
readonly keepBinaries: boolean;
|
package/src/nodes/merge.ts
CHANGED
|
@@ -18,10 +18,6 @@ export class Merge<TInputJson = unknown, TOutputJson = TInputJson> implements Ru
|
|
|
18
18
|
public readonly name: string,
|
|
19
19
|
public readonly cfg: Readonly<{
|
|
20
20
|
mode: MergeMode;
|
|
21
|
-
/**
|
|
22
|
-
* Deterministic input precedence order (only used for passThrough/append).
|
|
23
|
-
* Any inputs not listed are appended in lexicographic order.
|
|
24
|
-
*/
|
|
25
21
|
prefer?: ReadonlyArray<InputPortKey>;
|
|
26
22
|
}> = { mode: "passThrough" },
|
|
27
23
|
idOrOptions?: string | NodeBaseOptions,
|
|
@@ -5,9 +5,6 @@ export function getOriginIndex(item: Item): number | undefined {
|
|
|
5
5
|
return getOriginIndexFromItem(item);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Tags items routed to fan-in merge-by-origin (same contract as {@link IfNode} / {@link SwitchNode}).
|
|
10
|
-
*/
|
|
11
8
|
export function tagItemForRouterFanIn<TJson>(
|
|
12
9
|
args: Readonly<{
|
|
13
10
|
item: Item<TJson>;
|
|
@@ -1,11 +1,3 @@
|
|
|
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
1
|
export interface NodeBaseOptions {
|
|
10
2
|
readonly id?: string;
|
|
11
3
|
readonly description?: string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { definePollingTrigger } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
type ScheduleItem = {
|
|
4
|
+
firedAt: string;
|
|
5
|
+
tick: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type ScheduleState = {
|
|
9
|
+
tick: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const schedulePollingTrigger = definePollingTrigger({
|
|
13
|
+
key: "schedule.interval",
|
|
14
|
+
packageName: "@codemation/core-nodes",
|
|
15
|
+
title: "Run on schedule",
|
|
16
|
+
description: "Emit one tick item on every poll cycle.",
|
|
17
|
+
icon: "lucide:clock",
|
|
18
|
+
pollIntervalMs: 60_000,
|
|
19
|
+
initialState(): ScheduleState {
|
|
20
|
+
return { tick: 0 };
|
|
21
|
+
},
|
|
22
|
+
poll({ state }) {
|
|
23
|
+
const currentState = (state ?? { tick: 0 }) as ScheduleState;
|
|
24
|
+
const tick = currentState.tick + 1;
|
|
25
|
+
const item: { json: ScheduleItem } = { json: { firedAt: new Date().toISOString(), tick } };
|
|
26
|
+
return {
|
|
27
|
+
items: [item],
|
|
28
|
+
nextState: { tick },
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
execute(items) {
|
|
32
|
+
return { main: items };
|
|
33
|
+
},
|
|
34
|
+
testItems() {
|
|
35
|
+
return [{ json: { firedAt: new Date().toISOString(), tick: 0 } }];
|
|
36
|
+
},
|
|
37
|
+
});
|