@codemation/core-nodes 0.12.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.cjs +180 -413
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +99 -1303
  5. package/dist/index.d.ts +99 -1303
  6. package/dist/index.js +181 -415
  7. package/dist/index.js.map +1 -1
  8. package/dist/metadata.json +1 -1
  9. package/package.json +2 -2
  10. package/src/authoring/defineRestNode.types.ts +0 -84
  11. package/src/canvasIconName.ts +0 -7
  12. package/src/chatModels/CodemationChatModelConfig.ts +0 -10
  13. package/src/chatModels/CodemationChatModelFactory.ts +0 -7
  14. package/src/chatModels/ManagedHmacSignerFactory.types.ts +0 -35
  15. package/src/chatModels/OpenAIChatModelFactory.ts +0 -2
  16. package/src/chatModels/OpenAiChatModelPresetsFactory.ts +0 -5
  17. package/src/chatModels/OpenAiCredentialSession.ts +0 -1
  18. package/src/chatModels/OpenAiStrictJsonSchemaFactory.ts +0 -21
  19. package/src/credentials/ApiKeyCredentialType.ts +0 -3
  20. package/src/credentials/BasicAuthCredentialType.ts +0 -4
  21. package/src/credentials/BearerTokenCredentialType.ts +0 -4
  22. package/src/credentials/OAuth2ClientCredentialsTypeFactory.ts +0 -19
  23. package/src/credentials/OAuth2TokenExchangeFactory.ts +0 -7
  24. package/src/http/HttpBodyBuilder.ts +0 -16
  25. package/src/http/HttpRequestExecutor.ts +0 -35
  26. package/src/http/HttpUrlBuilder.ts +0 -4
  27. package/src/http/SSRFBlockedError.ts +0 -4
  28. package/src/http/SsrfGuard.ts +10 -50
  29. package/src/http/httpRequest.types.ts +0 -49
  30. package/src/index.ts +1 -0
  31. package/src/nodes/AIAgentConfig.ts +3 -39
  32. package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
  33. package/src/nodes/AIAgentNode.ts +4 -134
  34. package/src/nodes/AgentBinaryContentFactory.ts +0 -12
  35. package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
  36. package/src/nodes/AgentMessageFactory.ts +17 -19
  37. package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
  38. package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
  39. package/src/nodes/AgentToolResultContentFactory.ts +126 -0
  40. package/src/nodes/AssertionNode.ts +0 -14
  41. package/src/nodes/BM25Index.ts +0 -14
  42. package/src/nodes/ConnectionCredentialExecutionContextFactory.ts +0 -5
  43. package/src/nodes/ConnectionCredentialNode.ts +0 -4
  44. package/src/nodes/CronTriggerFactory.ts +0 -9
  45. package/src/nodes/DeferredMetaToolStrategy.ts +0 -18
  46. package/src/nodes/DeferredMetaToolStrategyFactory.ts +0 -5
  47. package/src/nodes/HttpRequestNodeFactory.ts +0 -14
  48. package/src/nodes/InboxApprovalNode.types.ts +0 -16
  49. package/src/nodes/IsTestRunNode.ts +0 -8
  50. package/src/nodes/ManualTriggerFactory.ts +0 -3
  51. package/src/nodes/ManualTriggerNode.ts +0 -4
  52. package/src/nodes/MergeNode.ts +0 -1
  53. package/src/nodes/NodeBackedToolRuntime.ts +0 -14
  54. package/src/nodes/SubWorkflowNode.ts +0 -3
  55. package/src/nodes/SwitchNode.ts +0 -3
  56. package/src/nodes/TestTriggerNode.ts +0 -9
  57. package/src/nodes/aiAgentSupport.types.ts +0 -16
  58. package/src/nodes/assertion.ts +0 -10
  59. package/src/nodes/codemationDocumentScannerNode.ts +0 -18
  60. package/src/nodes/collections/collectionListNode.types.ts +0 -1
  61. package/src/nodes/httpRequest.ts +0 -68
  62. package/src/nodes/isTestRun.ts +0 -4
  63. package/src/nodes/mapData.ts +0 -1
  64. package/src/nodes/merge.ts +0 -4
  65. package/src/nodes/mergeExecutionUtils.types.ts +0 -3
  66. package/src/nodes/nodeOptions.types.ts +0 -8
  67. package/src/nodes/schedulePollingTrigger.ts +37 -0
  68. package/src/nodes/split.ts +0 -4
  69. package/src/nodes/testTrigger.ts +0 -21
  70. package/src/nodes/wait.ts +0 -1
  71. package/src/nodes/webhookTriggerNode.ts +0 -5
  72. package/src/register.types.ts +0 -10
  73. package/src/workflows/AIAgentConnectionWorkflowExpander.ts +0 -3
@@ -0,0 +1,126 @@
1
+ import type { ToolResultPart } from "ai";
2
+
3
+ type ContentOutputValue = Extract<ToolResultPart["output"], { type: "content" }>["value"];
4
+ type ContentOutputPart = ContentOutputValue[number];
5
+
6
+ const MAX_INLINE_BYTES = 8 * 1024 * 1024;
7
+
8
+ const KNOWN_MCP_BLOCK_TYPES: ReadonlySet<string> = new Set(["text", "image", "audio", "resource", "resource_link"]);
9
+
10
+ type McpContentBlock = Readonly<{
11
+ type?: unknown;
12
+ text?: unknown;
13
+ data?: unknown;
14
+ mimeType?: unknown;
15
+ uri?: unknown;
16
+ name?: unknown;
17
+ resource?: Readonly<{ blob?: unknown; text?: unknown; mimeType?: unknown; uri?: unknown; name?: unknown }>;
18
+ }>;
19
+
20
+ export class AgentToolResultContentFactory {
21
+ static tryMapToContentOutput(result: unknown): ContentOutputValue | undefined {
22
+ const blocks = AgentToolResultContentFactory.contentBlocks(result);
23
+ if (blocks === undefined) return undefined;
24
+
25
+ const parts: ContentOutputPart[] = [];
26
+ let inlinedBytes = 0;
27
+ for (const block of blocks) {
28
+ const mapped = AgentToolResultContentFactory.mapBlock(block, inlinedBytes);
29
+ parts.push(mapped.part);
30
+ inlinedBytes += mapped.bytes;
31
+ }
32
+ return parts;
33
+ }
34
+
35
+ private static contentBlocks(result: unknown): ReadonlyArray<McpContentBlock> | undefined {
36
+ if (result === null || typeof result !== "object") return undefined;
37
+ const content = (result as { content?: unknown }).content;
38
+ if (!Array.isArray(content) || content.length === 0) return undefined;
39
+ const allTyped = content.every(
40
+ (block) => block !== null && typeof block === "object" && typeof (block as { type?: unknown }).type === "string",
41
+ );
42
+ if (!allTyped) return undefined;
43
+ const looksMcp = content.some((block) => KNOWN_MCP_BLOCK_TYPES.has((block as { type: string }).type));
44
+ return looksMcp ? (content as ReadonlyArray<McpContentBlock>) : undefined;
45
+ }
46
+
47
+ private static mapBlock(
48
+ block: McpContentBlock,
49
+ inlinedBytesSoFar: number,
50
+ ): Readonly<{ part: ContentOutputPart; bytes: number }> {
51
+ const type = block.type;
52
+ if (type === "text" && typeof block.text === "string") {
53
+ return { part: { type: "text", text: block.text }, bytes: 0 };
54
+ }
55
+ if (type === "image" && typeof block.data === "string" && typeof block.mimeType === "string") {
56
+ return AgentToolResultContentFactory.mapBinary({
57
+ base64: block.data,
58
+ mediaType: block.mimeType,
59
+ inlinedBytesSoFar,
60
+ });
61
+ }
62
+ if (type === "resource" && block.resource) {
63
+ return AgentToolResultContentFactory.mapEmbeddedResource(block.resource, inlinedBytesSoFar);
64
+ }
65
+ if (type === "resource_link" && typeof block.uri === "string") {
66
+ const mime = typeof block.mimeType === "string" ? ` (${block.mimeType})` : "";
67
+ return { part: { type: "text", text: `[linked resource: ${block.uri}${mime}]` }, bytes: 0 };
68
+ }
69
+ return { part: { type: "text", text: `[unsupported tool content block: ${String(type)}]` }, bytes: 0 };
70
+ }
71
+
72
+ private static mapEmbeddedResource(
73
+ resource: NonNullable<McpContentBlock["resource"]>,
74
+ inlinedBytesSoFar: number,
75
+ ): Readonly<{ part: ContentOutputPart; bytes: number }> {
76
+ if (typeof resource.text === "string") {
77
+ return { part: { type: "text", text: resource.text }, bytes: 0 };
78
+ }
79
+ if (typeof resource.blob === "string" && typeof resource.mimeType === "string") {
80
+ return AgentToolResultContentFactory.mapBinary({
81
+ base64: resource.blob,
82
+ mediaType: resource.mimeType,
83
+ filename: typeof resource.name === "string" ? resource.name : undefined,
84
+ inlinedBytesSoFar,
85
+ });
86
+ }
87
+ const uri = typeof resource.uri === "string" ? resource.uri : "unknown";
88
+ return { part: { type: "text", text: `[embedded resource: ${uri}]` }, bytes: 0 };
89
+ }
90
+
91
+ private static mapBinary(
92
+ args: Readonly<{ base64: string; mediaType: string; filename?: string; inlinedBytesSoFar: number }>,
93
+ ): Readonly<{ part: ContentOutputPart; bytes: number }> {
94
+ const rawBytes = Math.floor((args.base64.length * 3) / 4);
95
+ if (args.inlinedBytesSoFar + rawBytes > MAX_INLINE_BYTES) {
96
+ const name = args.filename ? ` "${args.filename}"` : "";
97
+ const kb = Math.round(rawBytes / 1024);
98
+ return {
99
+ part: {
100
+ type: "text",
101
+ text: `[binary${name} (${args.mediaType}, ~${kb} KB) omitted: exceeds the per-tool-result inline limit]`,
102
+ },
103
+ bytes: 0,
104
+ };
105
+ }
106
+ if (args.mediaType.startsWith("image/")) {
107
+ return { part: { type: "image-data", data: args.base64, mediaType: args.mediaType }, bytes: rawBytes };
108
+ }
109
+ if (args.mediaType === "application/pdf") {
110
+ return {
111
+ part: {
112
+ type: "file-data",
113
+ data: args.base64,
114
+ mediaType: args.mediaType,
115
+ ...(args.filename ? { filename: args.filename } : {}),
116
+ },
117
+ bytes: rawBytes,
118
+ };
119
+ }
120
+ const name = args.filename ? ` "${args.filename}"` : "";
121
+ return {
122
+ part: { type: "text", text: `[binary${name} (${args.mediaType}) not inlined: unsupported by the model]` },
123
+ bytes: 0,
124
+ };
125
+ }
126
+ }
@@ -3,17 +3,6 @@ import { node } from "@codemation/core";
3
3
 
4
4
  import { Assertion } from "./assertion";
5
5
 
6
- /**
7
- * Runs the author's `assertions` callback for each input item and emits one workflow `Item` per
8
- * returned {@link AssertionResult} on `main`. Persistence is handled by a host-side subscriber
9
- * to `nodeCompleted` events that filters on `config.emitsAssertions === true`; this node does
10
- * not write to any store on its own.
11
- *
12
- * If the author callback throws, we emit a single synthetic AssertionResult with `errored: true`
13
- * and `score: 0`. Without this catch the whole node would fail and no assertion row would be
14
- * persisted — making the rollup blind to "the assertion code itself is broken." The synthetic
15
- * row keeps `failedAssertionsByRunId` consistent and gives the UI something to surface.
16
- */
17
6
  @node({ packageName: "@codemation/core-nodes" })
18
7
  export class AssertionNode implements RunnableNode<Assertion<any>> {
19
8
  kind = "node" as const;
@@ -24,9 +13,6 @@ export class AssertionNode implements RunnableNode<Assertion<any>> {
24
13
  const config = ctx.config;
25
14
  try {
26
15
  const results: ReadonlyArray<AssertionResult> = await config.assertions(args.item, ctx);
27
- // Engine "array → fan-out on main, each element is item.json" — returning the plain results
28
- // makes downstream `item.json` exactly an AssertionResult. Wrapping in `{ json: result }`
29
- // would double-wrap (engine would see `Item`-shaped values but treat them as JSON values).
30
16
  return [...results];
31
17
  } catch (err) {
32
18
  const message = err instanceof Error ? err.message : String(err);
@@ -1,9 +1,3 @@
1
- /**
2
- * Minimal BM25 (Okapi BM25) implementation for indexing MCP tool descriptions.
3
- *
4
- * Parameters: k1=1.5, b=0.75 (standard defaults).
5
- * Tokenisation: lowercase, split on non-alphanumerics, filter empties.
6
- */
7
1
  export class BM25Index {
8
2
  private readonly k1 = 1.5;
9
3
  private readonly b = 0.75;
@@ -12,10 +6,6 @@ export class BM25Index {
12
6
  private readonly df = new Map<string, number>();
13
7
  private avgDocLen = 0;
14
8
 
15
- /**
16
- * Add all documents at once. After calling this, search is available.
17
- * Documents are indexed in insertion order; search returns their indices.
18
- */
19
9
  add(docs: ReadonlyArray<string>): void {
20
10
  const docTerms = docs.map((d) => this.tokenize(d));
21
11
 
@@ -34,10 +24,6 @@ export class BM25Index {
34
24
  this.avgDocLen = docTerms.length > 0 ? totalLen / docTerms.length : 0;
35
25
  }
36
26
 
37
- /**
38
- * Returns up to `limit` document indices ranked by BM25 score (highest first).
39
- * Returns an empty array if the index is empty or the query matches nothing.
40
- */
41
27
  search(query: string, limit: number): ReadonlyArray<number> {
42
28
  const n = this.tf.length;
43
29
  if (n === 0) return [];
@@ -8,11 +8,6 @@ import type {
8
8
 
9
9
  import { CredentialResolverFactory } from "@codemation/core/bootstrap";
10
10
 
11
- /**
12
- * Builds a {@link NodeExecutionContext} whose identity for credential binding and `getCredential`
13
- * is a **connection-owned** workflow node id (`ConnectionNodeIdFactory` in `@codemation/core`),
14
- * not the executing parent node. Use for LLM slots, tool slots, or any connection-scoped owner.
15
- */
16
11
  export class ConnectionCredentialExecutionContextFactory {
17
12
  private readonly credentialResolverFactory: CredentialResolverFactory;
18
13
 
@@ -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;
@@ -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})`);
@@ -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
 
@@ -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, {
@@ -30,7 +30,6 @@ export const collectionListNode = defineNode({
30
30
  offset: config.offset,
31
31
  where: config.where,
32
32
  });
33
- // Emit one item per row per AGENTS.md engine/node contract.
34
33
  return [...rows];
35
34
  },
36
35
  });