@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.
- package/CHANGELOG.md +30 -0
- package/dist/index.cjs +180 -413
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -1303
- package/dist/index.d.ts +99 -1303
- package/dist/index.js +181 -415
- 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 +3 -39
- package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
- package/src/nodes/AIAgentNode.ts +4 -134
- package/src/nodes/AgentBinaryContentFactory.ts +0 -12
- package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
- package/src/nodes/AgentMessageFactory.ts +17 -19
- package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
- package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
- package/src/nodes/AgentToolResultContentFactory.ts +126 -0
- 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
package/src/http/SsrfGuard.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
126
|
-
if (a === 10) return true;
|
|
127
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
128
|
-
if (a === 192 && b === 168) return true;
|
|
129
|
-
if (a === 169 && b === 254) return true;
|
|
130
|
-
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
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;
|
|
137
|
-
if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
|
|
138
|
-
if (lower.startsWith("fe80")) return true;
|
|
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,56 +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
|
-
* Explicit binaries to pass to the chat model, instead of the ones on the current item.
|
|
63
|
-
* Either a static array or a function resolved per item (so an author can forward binaries
|
|
64
|
-
* produced by an earlier node further back in the workflow). When provided, these replace
|
|
65
|
-
* `item.binary` as the passdown source. Ignored when {@link passBinariesToModel} is `false`.
|
|
66
|
-
* Every binary is passed (images as image blocks, all other types as file blocks); the
|
|
67
|
-
* provider surfaces an error at runtime if it doesn't support a given file type.
|
|
68
|
-
*/
|
|
34
|
+
readonly passToolBinariesToModel?: boolean;
|
|
69
35
|
readonly binaries?:
|
|
70
36
|
| ReadonlyArray<BinaryAttachment>
|
|
71
37
|
| ((args: AgentMessageBuildArgs<TInputJson>) => ReadonlyArray<BinaryAttachment>);
|
|
72
38
|
}
|
|
73
39
|
|
|
74
|
-
/**
|
|
75
|
-
* AI agent: credential bindings are keyed to connection-owned LLM/tool node ids (ConnectionNodeIdFactory),
|
|
76
|
-
* not to the agent workflow node id.
|
|
77
|
-
*/
|
|
78
40
|
export class AIAgent<TInputJson = unknown, TOutputJson = unknown>
|
|
79
41
|
implements RunnableNodeConfig<TInputJson, TOutputJson>, AgentNodeConfig<TInputJson, TOutputJson>
|
|
80
42
|
{
|
|
@@ -96,6 +58,7 @@ export class AIAgent<TInputJson = unknown, TOutputJson = unknown>
|
|
|
96
58
|
readonly pinnedMcpTools?: readonly string[];
|
|
97
59
|
readonly untrustedSources?: ReadonlyArray<string>;
|
|
98
60
|
readonly passBinariesToModel?: boolean;
|
|
61
|
+
readonly passToolBinariesToModel?: boolean;
|
|
99
62
|
readonly binaries?:
|
|
100
63
|
| ReadonlyArray<BinaryAttachment>
|
|
101
64
|
| ((args: AgentMessageBuildArgs<TInputJson>) => ReadonlyArray<BinaryAttachment>);
|
|
@@ -115,6 +78,7 @@ export class AIAgent<TInputJson = unknown, TOutputJson = unknown>
|
|
|
115
78
|
this.pinnedMcpTools = options.pinnedMcpTools;
|
|
116
79
|
this.untrustedSources = options.untrustedSources;
|
|
117
80
|
this.passBinariesToModel = options.passBinariesToModel;
|
|
81
|
+
this.passToolBinariesToModel = options.passToolBinariesToModel;
|
|
118
82
|
this.binaries = options.binaries;
|
|
119
83
|
}
|
|
120
84
|
|
|
@@ -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;
|