@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.cjs +34 -433
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +95 -1312
  5. package/dist/index.d.ts +95 -1312
  6. package/dist/index.js +35 -435
  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 +0 -44
  32. package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
  33. package/src/nodes/AIAgentNode.ts +0 -132
  34. package/src/nodes/AgentBinaryContentFactory.ts +0 -12
  35. package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
  36. package/src/nodes/AgentMessageFactory.ts +0 -18
  37. package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
  38. package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
  39. package/src/nodes/AgentToolResultContentFactory.ts +0 -29
  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
@@ -3,27 +3,8 @@ import { SSRFBlockedError } from "./SSRFBlockedError";
3
3
 
4
4
  export { SSRFBlockedError } from "./SSRFBlockedError";
5
5
 
6
- /** Emitted once per process when NODE_ENV=production and no allowedOutboundHosts is set. */
7
6
  let _productionNoAllowlistWarned = false;
8
7
 
9
- /**
10
- * Guards HTTP requests against Server-Side Request Forgery (SSRF) by
11
- * DNS-resolving the target host and rejecting private/link-local/loopback
12
- * addresses.
13
- *
14
- * Blocked ranges:
15
- * - RFC-1918: 10/8, 172.16/12, 192.168/16
16
- * - Link-local: 169.254/16
17
- * - Loopback: 127/8, ::1
18
- *
19
- * When `allowedOutboundHosts` is set, every resolved DNS target must match
20
- * at least one entry in the list (exact hostname or `*.example.com` wildcard).
21
- * When unset, existing behaviour applies: private ranges blocked, public allowed.
22
- *
23
- * Call {@link check} before making any outbound HTTP request.
24
- * Pass `allowPrivate: true` to bypass the private-network guard for trusted workflows
25
- * (allowedOutboundHosts allowlist is still applied when set).
26
- */
27
8
  export class SsrfGuard {
28
9
  constructor(private readonly allowedOutboundHosts?: ReadonlyArray<string>) {
29
10
  if (
@@ -40,15 +21,6 @@ export class SsrfGuard {
40
21
  }
41
22
  }
42
23
 
43
- /**
44
- * Resolves the host of `url` via DNS and throws {@link SSRFBlockedError}
45
- * if any resolved address falls in a blocked range, or if the host does not
46
- * match the operator-configured allowlist (when set).
47
- *
48
- * @param url - Fully-qualified URL of the intended request target.
49
- * @param allowPrivate - When `true`, the private-network check is skipped.
50
- * The allowedOutboundHosts check is still applied when set.
51
- */
52
24
  async check(url: string, allowPrivate: boolean): Promise<void> {
53
25
  if (allowPrivate && !this.allowedOutboundHosts?.length) return;
54
26
 
@@ -56,26 +28,20 @@ export class SsrfGuard {
56
28
  try {
57
29
  host = new URL(url).hostname;
58
30
  } catch {
59
- // Malformed URL — let the fetch call surface the error.
60
31
  return;
61
32
  }
62
33
 
63
- // Check allowedOutboundHosts allowlist first (hostname match, no DNS needed).
64
34
  if (this.allowedOutboundHosts?.length) {
65
35
  if (!this.isHostAllowed(host)) {
66
36
  throw new SSRFBlockedError(host, host);
67
37
  }
68
- // Host is in the allowlist — skip private-network checks (host is trusted).
69
38
  return;
70
39
  }
71
40
 
72
- // No allowlist: apply the standard private-network SSRF guard.
73
41
  if (allowPrivate) return;
74
42
 
75
- // Strip IPv6 brackets for the check below.
76
43
  const bareHost = host.startsWith("[") ? host.slice(1, -1) : host;
77
44
 
78
- // If the host is already a bare IP address, check directly without DNS.
79
45
  if (this.isPrivateAddress(bareHost)) {
80
46
  throw new SSRFBlockedError(host, bareHost);
81
47
  }
@@ -84,7 +50,6 @@ export class SsrfGuard {
84
50
  try {
85
51
  addresses = (await dns.lookup(host, { all: true })) as ReadonlyArray<{ address: string; family: number }>;
86
52
  } catch {
87
- // DNS failure — let the fetch call surface the real error (ENOTFOUND etc.).
88
53
  return;
89
54
  }
90
55
 
@@ -95,15 +60,10 @@ export class SsrfGuard {
95
60
  }
96
61
  }
97
62
 
98
- /**
99
- * Returns true when `host` matches at least one entry in `allowedOutboundHosts`.
100
- * Supports exact hostnames (`api.example.com`) and wildcard prefixes (`*.example.com`).
101
- */
102
63
  private isHostAllowed(host: string): boolean {
103
64
  for (const allowed of this.allowedOutboundHosts ?? []) {
104
65
  if (allowed.startsWith("*.")) {
105
- // Wildcard: *.example.com matches sub.example.com but NOT example.com itself.
106
- const suffix = allowed.slice(1); // ".example.com"
66
+ const suffix = allowed.slice(1);
107
67
  if (host.endsWith(suffix) && host.length > suffix.length) return true;
108
68
  } else {
109
69
  if (host === allowed) return true;
@@ -122,20 +82,20 @@ export class SsrfGuard {
122
82
  return false;
123
83
  }
124
84
  const [a, b] = parts as [number, number, number, number];
125
- if (a === 127) return true; // loopback 127/8
126
- if (a === 10) return true; // RFC-1918 10/8
127
- if (a === 172 && b >= 16 && b <= 31) return true; // RFC-1918 172.16/12
128
- if (a === 192 && b === 168) return true; // RFC-1918 192.168/16
129
- if (a === 169 && b === 254) return true; // link-local 169.254/16
130
- if (a === 100 && b >= 64 && b <= 127) return true; // CGN 100.64.0.0/10
85
+ if (a === 127) return true;
86
+ if (a === 10) return true;
87
+ if (a === 172 && b >= 16 && b <= 31) return true;
88
+ if (a === 192 && b === 168) return true;
89
+ if (a === 169 && b === 254) return true;
90
+ if (a === 100 && b >= 64 && b <= 127) return true;
131
91
  return false;
132
92
  }
133
93
 
134
94
  private isPrivateIPv6(ip: string): boolean {
135
95
  const lower = ip.toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
136
- if (lower === "::1" || lower === "0:0:0:0:0:0:0:1") return true; // loopback
137
- if (lower.startsWith("fc") || lower.startsWith("fd")) return true; // fc00::/7 ULA
138
- if (lower.startsWith("fe80")) return true; // fe80::/10 link-local
96
+ if (lower === "::1" || lower === "0:0:0:0:0:0:0:1") return true;
97
+ if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
98
+ if (lower.startsWith("fe80")) return true;
139
99
  return false;
140
100
  }
141
101
  }
@@ -1,24 +1,12 @@
1
1
  import type { NodeExecutionContext } from "@codemation/core";
2
2
  import type { RunnableNodeConfig } from "@codemation/core";
3
3
 
4
- /**
5
- * Binary reference key into `item.binary`.
6
- */
7
4
  export type BinaryRef = string;
8
5
 
9
- /**
10
- * Discriminated union for the HTTP request body.
11
- */
12
6
  export type HttpBodySpec =
13
7
  | Readonly<{ kind: "none" }>
14
8
  | Readonly<{
15
9
  kind: "json";
16
- /**
17
- * Serializable object/array to encode as the JSON body. Encoded exactly once via
18
- * `JSON.stringify`, so pass the value directly (e.g. `{ a: 1 }`) — never a
19
- * pre-stringified string (`JSON.stringify(...)`), which would double-encode it.
20
- * Typed as `object` so a bare string/primitive is a compile-time error.
21
- */
22
10
  data: object;
23
11
  }>
24
12
  | Readonly<{ kind: "form"; data: Readonly<Record<string, string>> }>
@@ -28,37 +16,19 @@ export type HttpBodySpec =
28
16
  binaries?: Readonly<Record<string, BinaryRef>>;
29
17
  }>
30
18
  | Readonly<{
31
- /**
32
- * Send raw bytes from a binary slot as the request body.
33
- * The binary attachment's `mimeType` is used as `Content-Type` unless
34
- * the request `headers` map already contains `content-type`.
35
- */
36
19
  kind: "binary";
37
- /** Key into `item.binary` to read the request body bytes from. */
38
20
  slot: string;
39
21
  }>;
40
22
 
41
- /**
42
- * Session interface that credential types implement.
43
- * Returns header/query deltas so the executor can merge them without
44
- * mutating the immutable HttpRequestSpec.
45
- */
46
23
  export interface CredentialSession {
47
24
  applyToRequest(spec: HttpRequestSpec): HttpCredentialDelta;
48
25
  }
49
26
 
50
- /**
51
- * Mutations the credential session wants to apply to the outgoing request.
52
- */
53
27
  export type HttpCredentialDelta = Readonly<{
54
28
  headers?: Readonly<Record<string, string>>;
55
29
  query?: Readonly<Record<string, string>>;
56
30
  }>;
57
31
 
58
- /**
59
- * Full specification of one HTTP request. All URLs are fully resolved before
60
- * being passed here (template substitution already applied by the caller).
61
- */
62
32
  export type HttpRequestSpec = Readonly<{
63
33
  url: string;
64
34
  method: string;
@@ -67,28 +37,13 @@ export type HttpRequestSpec = Readonly<{
67
37
  body?: HttpBodySpec;
68
38
  credential?: CredentialSession;
69
39
  download?: Readonly<{ mode: "auto" | "always" | "never"; binaryName: string }>;
70
- /**
71
- * When set to `"binary"`, the response body is written to a binary slot
72
- * instead of being parsed as JSON/text. Overrides `download` mode.
73
- */
74
40
  responseFormat?: "json" | "text" | "binary";
75
- /** Binary slot name for the response body when `responseFormat === "binary"`. Defaults to `"response"`. */
76
41
  responseBinarySlot?: string;
77
- /** Maximum allowed response size in bytes (checked against Content-Length before allocating). Defaults to 100 MiB. */
78
42
  responseSizeCapBytes?: number;
79
- /**
80
- * When `false` (default), requests whose target host resolves to an RFC-1918,
81
- * link-local (169.254/16), or loopback address are blocked to prevent SSRF attacks.
82
- * Set to `true` only for workflows that intentionally reach private infrastructure.
83
- */
84
43
  allowPrivateNetworkTargets?: boolean;
85
- /** Execution context — needed for binary attach. */
86
44
  ctx: NodeExecutionContext<RunnableNodeConfig<unknown, unknown>>;
87
45
  }>;
88
46
 
89
- /**
90
- * Result of executing an HTTP request.
91
- */
92
47
  export type HttpRequestResult = Readonly<{
93
48
  url: string;
94
49
  method: string;
@@ -100,12 +55,8 @@ export type HttpRequestResult = Readonly<{
100
55
  json?: unknown;
101
56
  text?: string;
102
57
  bodyBinaryName?: string;
103
- /** Set when `responseFormat === "binary"`. Name of the binary slot the response body was written to. */
104
58
  binarySlot?: string;
105
- /** Set when `responseFormat === "binary"`. The MIME type of the stored response. */
106
59
  contentType?: string;
107
- /** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
108
60
  size?: number;
109
- /** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
110
61
  filename?: string;
111
62
  }>;
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export * from "./nodes/webhookRespondNowAndContinueError";
34
34
  export * from "./nodes/webhookRespondNowError";
35
35
  export * from "./nodes/WebhookTriggerFactory";
36
36
  export * from "./nodes/webhookTriggerNode";
37
+ export { schedulePollingTrigger } from "./nodes/schedulePollingTrigger";
37
38
  export * from "./register.types";
38
39
  export * from "./workflowBuilder.types";
39
40
  export * from "./workflowAuthoring.types";
@@ -25,62 +25,18 @@ export interface AIAgentOptions<TInputJson = unknown, _TOutputJson = unknown> {
25
25
  readonly description?: string;
26
26
  readonly retryPolicy?: RetryPolicySpec;
27
27
  readonly guardrails?: AgentGuardrailConfig;
28
- /** Engine applies with {@link RunnableNodeConfig.inputSchema} before {@link AIAgentNode.execute}. */
29
28
  readonly inputSchema?: ZodType<TInputJson>;
30
29
  readonly outputSchema?: ZodType<_TOutputJson>;
31
- /**
32
- * MCP servers to connect for this agent run. Each entry is the server id from
33
- * the MCP catalog (e.g. `"gmail"`). Credential instances are bound via the
34
- * standard credential-binding flow — each server materializes an MCP connection
35
- * node and the slot lives on that node, keyed by
36
- * `(workflowId, mcpConnectionNodeId, "credential")` (same shape as ChatModel and
37
- * Tool connection nodes). There is no inline credential field; bind through the
38
- * canvas credential dropdown before activation.
39
- */
40
30
  readonly mcpServers?: ReadonlyArray<string>;
41
- /**
42
- * Tool ids to always include without going through `find_tools`.
43
- * Format: `"serverId:toolName"` (e.g. `"gmail:send_message"`). Max 16.
44
- */
45
31
  readonly pinnedMcpTools?: readonly string[];
46
- /**
47
- * Source identifiers that should be treated as untrusted external content.
48
- * When an incoming `Item.json.__source` matches one of these values, every
49
- * user-role message is wrapped with an untrusted-source preamble so the LLM
50
- * treats the content as data rather than instructions (prompt-injection defense).
51
- *
52
- * Defaults to `["gmail", "ocr", "webhook"]` when unset.
53
- */
54
32
  readonly untrustedSources?: ReadonlyArray<string>;
55
- /**
56
- * Whether file binaries are automatically passed to the chat model as native inline
57
- * multimodal blocks. Defaults to `true`. Set to `false` to skip the binary-passdown step
58
- * entirely (the node then behaves as if no binaries were present).
59
- */
60
33
  readonly passBinariesToModel?: boolean;
61
- /**
62
- * Whether binaries returned by a tool (e.g. an MCP tool returning a PDF or image) are passed to
63
- * the chat model as native multimodal tool-result blocks. Defaults to `true`. Set to `false` to
64
- * keep tool results as inert JSON text (the model then never "sees" the document).
65
- */
66
34
  readonly passToolBinariesToModel?: boolean;
67
- /**
68
- * Explicit binaries to pass to the chat model, instead of the ones on the current item.
69
- * Either a static array or a function resolved per item (so an author can forward binaries
70
- * produced by an earlier node further back in the workflow). When provided, these replace
71
- * `item.binary` as the passdown source. Ignored when {@link passBinariesToModel} is `false`.
72
- * Every binary is passed (images as image blocks, all other types as file blocks); the
73
- * provider surfaces an error at runtime if it doesn't support a given file type.
74
- */
75
35
  readonly binaries?:
76
36
  | ReadonlyArray<BinaryAttachment>
77
37
  | ((args: AgentMessageBuildArgs<TInputJson>) => ReadonlyArray<BinaryAttachment>);
78
38
  }
79
39
 
80
- /**
81
- * AI agent: credential bindings are keyed to connection-owned LLM/tool node ids (ConnectionNodeIdFactory),
82
- * not to the agent workflow node id.
83
- */
84
40
  export class AIAgent<TInputJson = unknown, TOutputJson = unknown>
85
41
  implements RunnableNodeConfig<TInputJson, TOutputJson>, AgentNodeConfig<TInputJson, TOutputJson>
86
42
  {
@@ -5,27 +5,8 @@ import { toJSONSchema as frameworkToJSONSchema } from "zod/v4/core";
5
5
 
6
6
  import { ConnectionCredentialExecutionContextFactory } from "./ConnectionCredentialExecutionContextFactory";
7
7
 
8
- /**
9
- * Shape of the instance-level `toJSONSchema` method that Zod v4 schemas expose. Conversions must go
10
- * through this instance method (see {@link AIAgentExecutionHelpersFactory#createJsonSchemaRecord})
11
- * rather than the module-level `toJSONSchema` import because the consumer's workflow-loader (see
12
- * `CodemationConsumerConfigLoader.toNamespace`) can load Zod under a separate tsx namespace. That
13
- * produces two runtime copies of Zod whose internal class / symbol identities don't overlap, so the
14
- * framework-side module-level `toJSONSchema` throws "Cannot read properties of undefined (reading
15
- * 'def')" on consumer-created schemas. The instance method is bound inside the schema's own module
16
- * and therefore uses the matching Zod internals.
17
- */
18
8
  type ZodInstanceToJsonSchema = (params?: Readonly<{ target: "draft-07" | "draft-7" | "draft-2020-12" }>) => unknown;
19
9
 
20
- /**
21
- * Helper utilities shared by {@link AIAgentNode} and supporting runners.
22
- *
23
- * Responsibilities:
24
- * - {@link #createConnectionCredentialExecutionContextFactory} centralizes credential-context wiring.
25
- * - {@link #createJsonSchemaRecord} is a pure Zod → draft-07 converter used by both
26
- * `OpenAiStrictJsonSchemaFactory` (to feed OpenAI-strict structured output) and the
27
- * `AgentStructuredOutputRepairPromptFactory` (to show a required-schema reminder).
28
- */
29
10
  @injectable()
30
11
  export class AIAgentExecutionHelpersFactory {
31
12
  createConnectionCredentialExecutionContextFactory(
@@ -34,15 +15,6 @@ export class AIAgentExecutionHelpersFactory {
34
15
  return new ConnectionCredentialExecutionContextFactory(credentialSessions);
35
16
  }
36
17
 
37
- /**
38
- * Produces a plain JSON Schema object (`draft-07`) from a Zod schema, as needed by
39
- * OpenAI tool-parameter schemas and the structured-output repair prompt.
40
- * - Prefers the schema's **instance** `toJSONSchema(...)` method so we stay inside the Zod
41
- * instance that created the schema (works across consumer/framework tsx namespaces — see
42
- * {@link ZodInstanceToJsonSchema}). Falls back to the framework-imported module function.
43
- * - Strips root `$schema` (OpenAI ignores it).
44
- * - Sanitizes `required` for cfworker json-schema compatibility (must be a string array or absent).
45
- */
46
18
  createJsonSchemaRecord(
47
19
  inputSchema: ZodSchemaAny,
48
20
  options: Readonly<{
@@ -75,12 +47,6 @@ export class AIAgentExecutionHelpersFactory {
75
47
  return rest;
76
48
  }
77
49
 
78
- /**
79
- * Runs Zod's `toJSONSchema` via the schema's own instance method when available, so consumer
80
- * schemas loaded under a different tsx namespace still convert correctly. If the caller handed us
81
- * a payload that lacks that method (e.g. a plain JSON Schema record or a Zod instance whose
82
- * prototype was stripped), we fall back to the framework-bundled module function.
83
- */
84
50
  private convertZodSchemaToJsonSchema(inputSchema: ZodSchemaAny, params: Readonly<{ target: "draft-07" }>): unknown {
85
51
  const candidate = (inputSchema as unknown as { toJSONSchema?: ZodInstanceToJsonSchema }).toJSONSchema;
86
52
  if (typeof candidate === "function") {
@@ -89,9 +55,6 @@ export class AIAgentExecutionHelpersFactory {
89
55
  return frameworkToJSONSchema(inputSchema as unknown as Parameters<typeof frameworkToJSONSchema>[0], params);
90
56
  }
91
57
 
92
- /**
93
- * `@cfworker/json-schema` iterates `schema.required` with `for...of`; it must be a string array or absent.
94
- */
95
58
  private sanitizeJsonSchemaRequiredKeywordsForCfworker(node: unknown): void {
96
59
  if (!node || typeof node !== "object" || Array.isArray(node)) {
97
60
  return;