@codemation/core-nodes 0.10.1 → 0.12.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 (39) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/index.cjs +273 -108
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +212 -71
  5. package/dist/index.d.ts +213 -72
  6. package/dist/index.js +273 -105
  7. package/dist/index.js.map +1 -1
  8. package/dist/metadata.json +1 -1
  9. package/package.json +3 -2
  10. package/src/chatModels/CodemationChatModelConfig.ts +9 -21
  11. package/src/chatModels/CodemationChatModelFactory.ts +12 -9
  12. package/src/chatModels/OpenAIChatModelFactory.ts +3 -2
  13. package/src/http/HttpBodyBuilder.ts +9 -0
  14. package/src/http/httpRequest.types.ts +10 -1
  15. package/src/index.ts +1 -1
  16. package/src/nodes/AIAgentConfig.ts +28 -0
  17. package/src/nodes/AIAgentNode.ts +84 -17
  18. package/src/nodes/AgentBinaryContentFactory.ts +74 -0
  19. package/src/nodes/CallbackNodeFactory.ts +9 -6
  20. package/src/nodes/CronTriggerFactory.ts +6 -2
  21. package/src/nodes/DeferredMetaToolStrategy.ts +8 -2
  22. package/src/nodes/ManualTriggerFactory.ts +15 -11
  23. package/src/nodes/WebhookTriggerFactory.ts +9 -2
  24. package/src/nodes/aggregate.ts +9 -2
  25. package/src/nodes/assertion.ts +3 -0
  26. package/src/nodes/filter.ts +9 -2
  27. package/src/nodes/httpRequest.ts +7 -2
  28. package/src/nodes/if.ts +9 -2
  29. package/src/nodes/isTestRun.ts +6 -2
  30. package/src/nodes/mapData.ts +4 -2
  31. package/src/nodes/merge.ts +9 -2
  32. package/src/nodes/noOp.ts +9 -2
  33. package/src/nodes/nodeOptions.types.ts +12 -0
  34. package/src/nodes/split.ts +9 -2
  35. package/src/nodes/subWorkflow.ts +9 -2
  36. package/src/nodes/switch.ts +7 -1
  37. package/src/nodes/wait.ts +9 -2
  38. package/src/workflowAuthoring/WorkflowChatModelFactory.types.ts +8 -2
  39. package/src/chatModels/ManagedModelFetcher.ts +0 -23
package/dist/index.cjs CHANGED
@@ -25,14 +25,10 @@ let __codemation_core = require("@codemation/core");
25
25
  __codemation_core = __toESM(__codemation_core);
26
26
  let node_dns_promises = require("node:dns/promises");
27
27
  node_dns_promises = __toESM(node_dns_promises);
28
- let __ai_sdk_openai = require("@ai-sdk/openai");
29
- __ai_sdk_openai = __toESM(__ai_sdk_openai);
30
28
  let __codemation_core_bootstrap = require("@codemation/core/bootstrap");
31
29
  __codemation_core_bootstrap = __toESM(__codemation_core_bootstrap);
32
30
  let node_crypto = require("node:crypto");
33
31
  node_crypto = __toESM(node_crypto);
34
- let ai = require("ai");
35
- ai = __toESM(ai);
36
32
  let croner = require("croner");
37
33
  croner = __toESM(croner);
38
34
 
@@ -525,10 +521,13 @@ var HttpRequestExecutor = class {
525
521
  var HttpBodyBuilder = class {
526
522
  async build(spec, item, ctx) {
527
523
  if (!spec || spec.kind === "none") return;
528
- if (spec.kind === "json") return {
529
- body: JSON.stringify(spec.data),
530
- contentType: "application/json"
531
- };
524
+ if (spec.kind === "json") {
525
+ if (typeof spec.data === "string") throw new Error("HttpRequest body kind:\"json\" expects a serializable object for \"data\", but received a string. Pass the object directly (e.g. { a: 1 }), not a pre-stringified JSON string (JSON.stringify(...)).");
526
+ return {
527
+ body: JSON.stringify(spec.data),
528
+ contentType: "application/json"
529
+ };
530
+ }
532
531
  if (spec.kind === "form") {
533
532
  const params = new URLSearchParams();
534
533
  for (const [key, value] of Object.entries(spec.data)) params.append(key, value);
@@ -701,8 +700,9 @@ function __decorate(decorators, target, key, desc) {
701
700
  let OpenAIChatModelFactory = class OpenAIChatModelFactory$1 {
702
701
  async create(args) {
703
702
  const session = await args.ctx.getCredential(args.config.credentialSlotKey);
703
+ const { createOpenAI } = await import("@ai-sdk/openai");
704
704
  return {
705
- languageModel: (0, __ai_sdk_openai.createOpenAI)({
705
+ languageModel: createOpenAI({
706
706
  apiKey: session.apiKey,
707
707
  baseURL: session.baseUrl
708
708
  }).chat(args.config.model),
@@ -4445,27 +4445,24 @@ function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, over
4445
4445
  //#endregion
4446
4446
  //#region src/chatModels/CodemationChatModelFactory.ts
4447
4447
  let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
4448
- create(args) {
4448
+ async create(args) {
4449
4449
  const gatewayUrl = process.env["LLM_GATEWAY_URL"];
4450
4450
  if (!gatewayUrl) throw new Error("Codemation managed AI not available in this environment (LLM_GATEWAY_URL is not set).");
4451
4451
  const workspaceId = process.env["WORKSPACE_ID"];
4452
4452
  const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
4453
4453
  if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
4454
4454
  const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
4455
- const languageModel = (0, __ai_sdk_openai.createOpenAI)({
4456
- baseURL: `${gatewayUrl}/v1`,
4457
- apiKey: "codemation-managed",
4458
- fetch: hmacFetch
4459
- }).chat(args.config.model);
4460
- return Promise.resolve({
4461
- languageModel,
4462
- modelName: args.config.model,
4455
+ const { createAnthropic } = await import("@ai-sdk/anthropic");
4456
+ return {
4457
+ languageModel: createAnthropic({
4458
+ baseURL: `${gatewayUrl}/v1`,
4459
+ apiKey: "codemation-managed",
4460
+ fetch: hmacFetch
4461
+ })(args.config.complexity),
4462
+ modelName: args.config.complexity,
4463
4463
  provider: "codemation-managed",
4464
- defaultCallOptions: {
4465
- maxOutputTokens: args.config.options?.maxTokens,
4466
- temperature: args.config.options?.temperature
4467
- }
4468
- });
4464
+ defaultCallOptions: { maxOutputTokens: args.config.options?.maxTokens }
4465
+ };
4469
4466
  }
4470
4467
  };
4471
4468
  CodemationChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
@@ -4477,11 +4474,11 @@ var CodemationChatModelConfig = class {
4477
4474
  presentation;
4478
4475
  provider = "codemation-managed";
4479
4476
  modelName;
4480
- constructor(name, model, presentationIn, options) {
4477
+ constructor(name, complexity, presentationIn, options) {
4481
4478
  this.name = name;
4482
- this.model = model;
4479
+ this.complexity = complexity;
4483
4480
  this.options = options;
4484
- this.modelName = model;
4481
+ this.modelName = complexity;
4485
4482
  this.presentation = presentationIn ?? {
4486
4483
  icon: "lucide:bot",
4487
4484
  label: name
@@ -4489,28 +4486,6 @@ var CodemationChatModelConfig = class {
4489
4486
  }
4490
4487
  };
4491
4488
 
4492
- //#endregion
4493
- //#region src/chatModels/ManagedModelFetcher.ts
4494
- /**
4495
- * Fetches the active platform-managed model allowlist from the CP.
4496
- * Reads CONTROL_PLANE_URL from the workspace process env.
4497
- * Returns an empty array if the env var is absent or the fetch fails.
4498
- * Cache the result per session — the allowlist changes infrequently.
4499
- */
4500
- var ManagedModelFetcher = class {
4501
- async fetch() {
4502
- const cpUrl = process.env["CONTROL_PLANE_URL"];
4503
- if (!cpUrl) return [];
4504
- try {
4505
- const res = await globalThis.fetch(`${cpUrl}/api/llm/managed-models`);
4506
- if (!res.ok) return [];
4507
- return await res.json();
4508
- } catch {
4509
- return [];
4510
- }
4511
- }
4512
- };
4513
-
4514
4489
  //#endregion
4515
4490
  //#region src/nodes/AgentMessageFactory.ts
4516
4491
  /**
@@ -5879,6 +5854,63 @@ AgentToolExecutionCoordinator = __decorate([
5879
5854
  __decorateMetadata("design:paramtypes", [typeof (_ref$2 = typeof AgentToolErrorClassifier !== "undefined" && AgentToolErrorClassifier) === "function" ? _ref$2 : Object, typeof (_ref2$2 = typeof AgentToolRepairPolicy !== "undefined" && AgentToolRepairPolicy) === "function" ? _ref2$2 : Object])
5880
5855
  ], AgentToolExecutionCoordinator);
5881
5856
 
5857
+ //#endregion
5858
+ //#region src/nodes/AgentBinaryContentFactory.ts
5859
+ /**
5860
+ * Turns resolved file binaries into native AI SDK multimodal content parts and merges them into the
5861
+ * agent prompt. Images (`image/*`) become {@link ImagePart}s; every other type (PDFs, office docs,
5862
+ * CSV, JSON, …) becomes a {@link FilePart}. The provider maps these to its wire-level `image` /
5863
+ * `document` blocks; an unsupported file type surfaces as a provider error at runtime.
5864
+ *
5865
+ * Parts are appended to the LAST user message so the binary travels alongside the author's prompt
5866
+ * text (preserving any untrusted-source preamble that already wrapped that text). When no user
5867
+ * message exists, a new user message carrying only the binaries is appended.
5868
+ */
5869
+ var AgentBinaryContentFactory = class AgentBinaryContentFactory {
5870
+ static toContentPart(binary) {
5871
+ if (binary.mediaType.startsWith("image/")) return {
5872
+ type: "image",
5873
+ image: binary.base64,
5874
+ mediaType: binary.mediaType
5875
+ };
5876
+ return {
5877
+ type: "file",
5878
+ data: binary.base64,
5879
+ mediaType: binary.mediaType,
5880
+ ...binary.filename ? { filename: binary.filename } : {}
5881
+ };
5882
+ }
5883
+ static withBinaries(messages, binaries) {
5884
+ if (binaries.length === 0) return messages;
5885
+ const parts = binaries.map((binary) => AgentBinaryContentFactory.toContentPart(binary));
5886
+ const lastUserIndex = AgentBinaryContentFactory.lastUserMessageIndex(messages);
5887
+ if (lastUserIndex === -1) {
5888
+ const appended = {
5889
+ role: "user",
5890
+ content: parts
5891
+ };
5892
+ return [...messages, appended];
5893
+ }
5894
+ const next = [...messages];
5895
+ next[lastUserIndex] = AgentBinaryContentFactory.appendPartsToUserMessage(messages[lastUserIndex], parts);
5896
+ return next;
5897
+ }
5898
+ static lastUserMessageIndex(messages) {
5899
+ for (let index = messages.length - 1; index >= 0; index--) if (messages[index]?.role === "user") return index;
5900
+ return -1;
5901
+ }
5902
+ static appendPartsToUserMessage(message, parts) {
5903
+ const existing = typeof message.content === "string" ? message.content.length > 0 ? [{
5904
+ type: "text",
5905
+ text: message.content
5906
+ }] : [] : message.content;
5907
+ return {
5908
+ ...message,
5909
+ content: [...existing, ...parts]
5910
+ };
5911
+ }
5912
+ };
5913
+
5882
5914
  //#endregion
5883
5915
  //#region src/nodes/NodeBackedToolRuntime.ts
5884
5916
  var _ref$1, _ref2$1, _ref3$1, _ref4$1;
@@ -6077,11 +6109,18 @@ var DeferredMetaToolStrategy = class {
6077
6109
  mcpEntries = [];
6078
6110
  toolsByServerId = /* @__PURE__ */ new Map();
6079
6111
  foundToolIds = /* @__PURE__ */ new Set();
6112
+ /**
6113
+ * `jsonSchema` from the `ai` SDK, loaded lazily in {@link initialize} so the SDK
6114
+ * (~28MB RSS) stays off the boot path. `initialize` always runs before the sync
6115
+ * `getToolsForTurn` → `buildFindToolsDefinition` path, so this is set before use.
6116
+ */
6117
+ jsonSchema;
6080
6118
  constructor(bm25, warnFn) {
6081
6119
  this.bm25 = bm25;
6082
6120
  this.warnFn = warnFn;
6083
6121
  }
6084
6122
  async initialize(input) {
6123
+ this.jsonSchema = (await import("ai")).jsonSchema;
6085
6124
  this.nodeBackedTools = { ...input.nodeBackedTools };
6086
6125
  const pinnedIds = input.pinnedMcpTools ?? [];
6087
6126
  if (pinnedIds.length > PINNED_TOOLS_HARD_LIMIT) throw new Error(`Agent config error: pinnedMcpTools count (${pinnedIds.length}) exceeds hard limit of ${PINNED_TOOLS_HARD_LIMIT}.`);
@@ -6169,25 +6208,26 @@ var DeferredMetaToolStrategy = class {
6169
6208
  return [...this.foundToolIds];
6170
6209
  }
6171
6210
  buildFindToolsDefinition() {
6211
+ const inputSchemaRecord = {
6212
+ type: "object",
6213
+ properties: {
6214
+ query: {
6215
+ type: "string",
6216
+ description: "Natural language description of what you want to do."
6217
+ },
6218
+ limit: {
6219
+ type: "integer",
6220
+ minimum: 1,
6221
+ maximum: 10,
6222
+ description: `Maximum number of tools to return (default ${FIND_TOOLS_DEFAULT_LIMIT}).`
6223
+ }
6224
+ },
6225
+ required: ["query"],
6226
+ additionalProperties: false
6227
+ };
6172
6228
  return {
6173
6229
  description: "Search for tools available from connected MCP servers. After this call, the tools listed in the result will be callable on your very next turn. Use this when you need a capability not visible in your current tool list. Do not attempt to call a tool name you have not seen yet — use find_tools to discover it first.",
6174
- inputSchema: (0, ai.jsonSchema)({
6175
- type: "object",
6176
- properties: {
6177
- query: {
6178
- type: "string",
6179
- description: "Natural language description of what you want to do."
6180
- },
6181
- limit: {
6182
- type: "integer",
6183
- minimum: 1,
6184
- maximum: 10,
6185
- description: `Maximum number of tools to return (default ${FIND_TOOLS_DEFAULT_LIMIT}).`
6186
- }
6187
- },
6188
- required: ["query"],
6189
- additionalProperties: false
6190
- })
6230
+ inputSchema: this.jsonSchema(inputSchemaRecord)
6191
6231
  };
6192
6232
  }
6193
6233
  };
@@ -6221,6 +6261,13 @@ let AIAgentNode = class AIAgentNode$1 {
6221
6261
  inputSchema = unknown();
6222
6262
  connectionCredentialExecutionContextFactory;
6223
6263
  preparedByExecutionContext = /* @__PURE__ */ new WeakMap();
6264
+ /**
6265
+ * The `ai` SDK, loaded lazily in {@link execute} so the SDK (~28MB RSS) stays
6266
+ * off the boot path — non-AI workflows never load it. Every path runs through
6267
+ * `execute` → `ensureAiSdk` before any sync helper touches `this.aiSdk`.
6268
+ */
6269
+ aiSdk;
6270
+ aiSdkPromise = null;
6224
6271
  constructor(nodeResolver, credentialSessions, nodeBackedToolRuntime, executionHelpers, structuredOutputRunner, toolExecutionCoordinator, toolLoadingStrategyFactory, agentMcpIntegration) {
6225
6272
  this.nodeResolver = nodeResolver;
6226
6273
  this.nodeBackedToolRuntime = nodeBackedToolRuntime;
@@ -6233,6 +6280,7 @@ let AIAgentNode = class AIAgentNode$1 {
6233
6280
  }
6234
6281
  async execute(args) {
6235
6282
  const { ctx } = args;
6283
+ await this.ensureAiSdk();
6236
6284
  if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
6237
6285
  const prepared = await this.getOrPrepareExecution(ctx);
6238
6286
  const itemWithMappedJson = {
@@ -6241,6 +6289,10 @@ let AIAgentNode = class AIAgentNode$1 {
6241
6289
  };
6242
6290
  return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
6243
6291
  }
6292
+ /** Load the `ai` SDK once per node instance (cached promise guards concurrent items). */
6293
+ async ensureAiSdk() {
6294
+ this.aiSdk = await (this.aiSdkPromise ??= import("ai"));
6295
+ }
6244
6296
  /**
6245
6297
  * Resume path: re-enters the agent loop after a HITL suspension.
6246
6298
  * Reconstructs the conversation from the checkpoint, injects the human decision
@@ -6383,7 +6435,7 @@ let AIAgentNode = class AIAgentNode$1 {
6383
6435
  const { ctx } = prepared;
6384
6436
  const itemInputsByPort = AgentItemPortMap.fromItem(item);
6385
6437
  const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, itemIndex, items);
6386
- const conversation = [...this.createPromptMessages(item, itemIndex, items, ctx)];
6438
+ const conversation = [...await this.createPromptMessages(item, itemIndex, items, ctx)];
6387
6439
  if (ctx.config.outputSchema && itemScopedTools.length === 0) {
6388
6440
  const structuredOutput = await this.structuredOutputRunner.resolve({
6389
6441
  model: prepared.model,
@@ -6570,14 +6622,17 @@ let AIAgentNode = class AIAgentNode$1 {
6570
6622
  });
6571
6623
  }
6572
6624
  /**
6573
- * Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
6574
- * and returns the HITL behavior config, or `undefined` when not a HITL tool.
6625
+ * Resolves the HITL behavior for a tool binding, or `undefined` when it is not a HITL tool.
6626
+ * A binding is HITL if either the backing node carries a `defineHumanApprovalNode` marker or the
6627
+ * binding sets a per-binding `onRejected` via `asTool(..., { onRejected })`. The per-binding value
6628
+ * wins over the node marker, so two tools backed by the same node can reject differently.
6575
6629
  */
6576
6630
  resolveHumanApprovalBehavior(config$1) {
6577
6631
  if (!this.isNodeBackedToolConfig(config$1)) return void 0;
6578
6632
  const marker = config$1.node.humanApprovalToolBehavior;
6579
- if (marker === void 0) return void 0;
6580
- return { onRejected: marker.onRejected ?? "return" };
6633
+ const perBinding = config$1.onRejected;
6634
+ if (marker === void 0 && perBinding === void 0) return void 0;
6635
+ return { onRejected: perBinding ?? marker?.onRejected ?? "return" };
6581
6636
  }
6582
6637
  /**
6583
6638
  * Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
@@ -6624,7 +6679,7 @@ let AIAgentNode = class AIAgentNode$1 {
6624
6679
  const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
6625
6680
  toolSet[entry.config.name] = {
6626
6681
  description,
6627
- inputSchema: (0, ai.jsonSchema)(schemaRecord)
6682
+ inputSchema: this.aiSdk.jsonSchema(schemaRecord)
6628
6683
  };
6629
6684
  }
6630
6685
  return toolSet;
@@ -6655,7 +6710,7 @@ let AIAgentNode = class AIAgentNode$1 {
6655
6710
  const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
6656
6711
  toolSet[entry.config.name] = {
6657
6712
  description,
6658
- inputSchema: (0, ai.jsonSchema)(schemaRecord)
6713
+ inputSchema: this.aiSdk.jsonSchema(schemaRecord)
6659
6714
  };
6660
6715
  }
6661
6716
  return toolSet;
@@ -6707,7 +6762,7 @@ let AIAgentNode = class AIAgentNode$1 {
6707
6762
  });
6708
6763
  try {
6709
6764
  const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
6710
- const result = await (0, ai.generateText)({
6765
+ const result = await this.aiSdk.generateText({
6711
6766
  model: model.languageModel,
6712
6767
  messages: [...messages],
6713
6768
  tools,
@@ -6826,8 +6881,8 @@ let AIAgentNode = class AIAgentNode$1 {
6826
6881
  schemaName: structuredOptions?.schemaName ?? "structured_output",
6827
6882
  requireObjectRoot: true
6828
6883
  }) : schema;
6829
- const outputSchema = ai.Output.object({ schema: (0, ai.jsonSchema)(schemaRecord) });
6830
- const result = await (0, ai.generateText)({
6884
+ const outputSchema = this.aiSdk.Output.object({ schema: this.aiSdk.jsonSchema(schemaRecord) });
6885
+ const result = await this.aiSdk.generateText({
6831
6886
  model: model.languageModel,
6832
6887
  messages: [...messages],
6833
6888
  experimental_output: outputSchema,
@@ -7098,7 +7153,7 @@ let AIAgentNode = class AIAgentNode$1 {
7098
7153
  const json = JSON.stringify(value);
7099
7154
  return JSON.parse(json);
7100
7155
  }
7101
- createPromptMessages(item, itemIndex, items, ctx) {
7156
+ async createPromptMessages(item, itemIndex, items, ctx) {
7102
7157
  const messages = __codemation_core.AgentMessageConfigNormalizer.resolveFromInputOrConfig(item.json, ctx.config, {
7103
7158
  item,
7104
7159
  itemIndex,
@@ -7106,7 +7161,47 @@ let AIAgentNode = class AIAgentNode$1 {
7106
7161
  ctx
7107
7162
  });
7108
7163
  const wrapped = this.wrapUntrustedSourceMessages(messages, item, ctx.config);
7109
- return AgentMessageFactory.createPromptMessages(wrapped);
7164
+ const promptMessages = AgentMessageFactory.createPromptMessages(wrapped);
7165
+ if (ctx.config.passBinariesToModel === false) return promptMessages;
7166
+ const attachments = this.selectBinaryAttachments(item, itemIndex, items, ctx);
7167
+ const binaries = await this.resolveInlineBinaries(attachments, ctx);
7168
+ return AgentBinaryContentFactory.withBinaries(promptMessages, binaries);
7169
+ }
7170
+ /**
7171
+ * Picks which attachments feed the passdown. When the author supplies `config.binaries`
7172
+ * (a static array or a per-item function — e.g. to forward binaries from an earlier node),
7173
+ * those replace the current item's attachments; otherwise the current item's `item.binary`
7174
+ * is used.
7175
+ */
7176
+ selectBinaryAttachments(item, itemIndex, items, ctx) {
7177
+ const manual = ctx.config.binaries;
7178
+ if (manual !== void 0) return typeof manual === "function" ? manual({
7179
+ item,
7180
+ itemIndex,
7181
+ items,
7182
+ ctx
7183
+ }) : manual;
7184
+ return item.binary ? Object.values(item.binary) : [];
7185
+ }
7186
+ /**
7187
+ * Reads every attachment through `ctx.binary` (storage-backed, by reference — never base64 on
7188
+ * `item.json`) and resolves it to inline base64 so the agent can pass it to the chat model as a
7189
+ * native multimodal block. Images become image blocks; every other type (PDF, office docs, CSV,
7190
+ * JSON, …) becomes a file block — we don't filter by media type, so any binary can be fed to the
7191
+ * model. If the provider rejects an unsupported type the error surfaces at runtime, and the
7192
+ * workflow can filter the binary upstream.
7193
+ */
7194
+ async resolveInlineBinaries(attachments, ctx) {
7195
+ const resolved = [];
7196
+ for (const attachment of attachments) {
7197
+ const bytes = await ctx.binary.getBytes(attachment);
7198
+ resolved.push({
7199
+ mediaType: attachment.mimeType,
7200
+ base64: Buffer.from(bytes).toString("base64"),
7201
+ ...attachment.filename ? { filename: attachment.filename } : {}
7202
+ });
7203
+ }
7204
+ return resolved;
7110
7205
  }
7111
7206
  /**
7112
7207
  * When `item.json.__source` matches an entry in `config.untrustedSources`
@@ -7220,6 +7315,7 @@ var AIAgent = class {
7220
7315
  chatModel;
7221
7316
  tools;
7222
7317
  id;
7318
+ description;
7223
7319
  retryPolicy;
7224
7320
  guardrails;
7225
7321
  inputSchema;
@@ -7227,12 +7323,15 @@ var AIAgent = class {
7227
7323
  mcpServers;
7228
7324
  pinnedMcpTools;
7229
7325
  untrustedSources;
7326
+ passBinariesToModel;
7327
+ binaries;
7230
7328
  constructor(options) {
7231
7329
  this.name = options.name;
7232
7330
  this.messages = options.messages;
7233
7331
  this.chatModel = options.chatModel;
7234
7332
  this.tools = options.tools ?? [];
7235
7333
  this.id = options.id;
7334
+ this.description = options.description;
7236
7335
  this.retryPolicy = options.retryPolicy ?? __codemation_core.RetryPolicy.defaultForAiAgent;
7237
7336
  this.guardrails = options.guardrails;
7238
7337
  this.inputSchema = options.inputSchema;
@@ -7240,6 +7339,8 @@ var AIAgent = class {
7240
7339
  this.mcpServers = options.mcpServers;
7241
7340
  this.pinnedMcpTools = options.pinnedMcpTools;
7242
7341
  this.untrustedSources = options.untrustedSources;
7342
+ this.passBinariesToModel = options.passBinariesToModel;
7343
+ this.binaries = options.binaries;
7243
7344
  }
7244
7345
  inspectorSummary() {
7245
7346
  const rows = [];
@@ -7310,12 +7411,14 @@ var Assertion = class {
7310
7411
  icon;
7311
7412
  name;
7312
7413
  id;
7414
+ description;
7313
7415
  emitsAssertions = true;
7314
7416
  assertions;
7315
7417
  constructor(options) {
7316
7418
  this.name = options.name ?? "Assertion";
7317
7419
  this.id = options.id;
7318
7420
  this.icon = options.icon ?? "lucide:check-circle";
7421
+ this.description = options.description;
7319
7422
  this.assertions = options.assertions;
7320
7423
  }
7321
7424
  inspectorSummary() {
@@ -7367,6 +7470,7 @@ var Callback = class Callback {
7367
7470
  icon = "lucide:braces";
7368
7471
  emptyBatchExecution = "runOnce";
7369
7472
  id;
7473
+ description;
7370
7474
  retryPolicy;
7371
7475
  nodeErrorHandler;
7372
7476
  declaredOutputPorts;
@@ -7378,6 +7482,7 @@ var Callback = class Callback {
7378
7482
  id: idOrOptions
7379
7483
  } : idOrOptions;
7380
7484
  this.id = resolvedOptions?.id;
7485
+ this.description = resolvedOptions?.description;
7381
7486
  this.retryPolicy = resolvedOptions?.retryPolicy;
7382
7487
  this.nodeErrorHandler = resolvedOptions?.nodeErrorHandler;
7383
7488
  this.declaredOutputPorts = resolvedOptions?.declaredOutputPorts;
@@ -7579,10 +7684,12 @@ var HttpRequest = class {
7579
7684
  type = HttpRequestNode;
7580
7685
  execution = { hint: "local" };
7581
7686
  icon = "lucide:globe";
7687
+ description;
7582
7688
  constructor(name, args = {}, retryPolicy = __codemation_core.RetryPolicy.defaultForHttp) {
7583
7689
  this.name = name;
7584
7690
  this.args = args;
7585
7691
  this.retryPolicy = retryPolicy;
7692
+ this.description = args.description;
7586
7693
  }
7587
7694
  get id() {
7588
7695
  return this.args.id;
@@ -7677,10 +7784,14 @@ var Aggregate = class {
7677
7784
  execution = { hint: "local" };
7678
7785
  keepBinaries = true;
7679
7786
  icon = "builtin:aggregate-rows";
7680
- constructor(name, aggregate, id) {
7787
+ id;
7788
+ description;
7789
+ constructor(name, aggregate, idOrOptions) {
7681
7790
  this.name = name;
7682
7791
  this.aggregate = aggregate;
7683
- this.id = id;
7792
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
7793
+ this.id = options?.id;
7794
+ this.description = options?.description;
7684
7795
  }
7685
7796
  inspectorSummary() {
7686
7797
  const fnName = this.aggregate.name;
@@ -7711,10 +7822,14 @@ var Filter = class {
7711
7822
  type = FilterNode;
7712
7823
  execution = { hint: "local" };
7713
7824
  icon = "lucide:filter";
7714
- constructor(name, predicate, id) {
7825
+ id;
7826
+ description;
7827
+ constructor(name, predicate, idOrOptions) {
7715
7828
  this.name = name;
7716
7829
  this.predicate = predicate;
7717
- this.id = id;
7830
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
7831
+ this.id = options?.id;
7832
+ this.description = options?.description;
7718
7833
  }
7719
7834
  inspectorSummary() {
7720
7835
  const fnName = this.predicate.name;
@@ -7789,10 +7904,14 @@ var If = class {
7789
7904
  execution = { hint: "local" };
7790
7905
  icon = "lucide:split@rot=90";
7791
7906
  declaredOutputPorts = ["true", "false"];
7792
- constructor(name, predicate, id) {
7907
+ id;
7908
+ description;
7909
+ constructor(name, predicate, idOrOptions) {
7793
7910
  this.name = name;
7794
7911
  this.predicate = predicate;
7795
- this.id = id;
7912
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
7913
+ this.id = options?.id;
7914
+ this.description = options?.description;
7796
7915
  }
7797
7916
  inspectorSummary() {
7798
7917
  const fnName = this.predicate.name;
@@ -7832,9 +7951,12 @@ var IsTestRun = class {
7832
7951
  declaredOutputPorts = ["true", "false"];
7833
7952
  name;
7834
7953
  id;
7835
- constructor(name = "Is test run?", id) {
7954
+ description;
7955
+ constructor(name = "Is test run?", idOrOptions) {
7836
7956
  this.name = name;
7837
- this.id = id;
7957
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
7958
+ this.id = options?.id;
7959
+ this.description = options?.description;
7838
7960
  }
7839
7961
  };
7840
7962
 
@@ -7863,11 +7985,15 @@ var Switch = class {
7863
7985
  execution = { hint: "local" };
7864
7986
  icon = "lucide:git-branch-plus";
7865
7987
  declaredOutputPorts;
7866
- constructor(name, cfg, id) {
7988
+ id;
7989
+ description;
7990
+ constructor(name, cfg, idOrOptions) {
7867
7991
  this.name = name;
7868
7992
  this.cfg = cfg;
7869
- this.id = id;
7870
7993
  this.declaredOutputPorts = [...new Set([...cfg.cases, cfg.defaultCase])].sort();
7994
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
7995
+ this.id = options?.id;
7996
+ this.description = options?.description;
7871
7997
  }
7872
7998
  inspectorSummary() {
7873
7999
  const rows = [{
@@ -7906,10 +8032,14 @@ var Split = class {
7906
8032
  */
7907
8033
  continueWhenEmptyOutput = true;
7908
8034
  icon = "builtin:split-rows";
7909
- constructor(name, getElements, id) {
8035
+ id;
8036
+ description;
8037
+ constructor(name, getElements, idOrOptions) {
7910
8038
  this.name = name;
7911
8039
  this.getElements = getElements;
7912
- this.id = id;
8040
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8041
+ this.id = options?.id;
8042
+ this.description = options?.description;
7913
8043
  }
7914
8044
  inspectorSummary() {
7915
8045
  const fnName = this.getElements.name;
@@ -7967,14 +8097,17 @@ var CronTrigger = class {
7967
8097
  type = CronTriggerNode;
7968
8098
  icon = "lucide:clock";
7969
8099
  id;
7970
- constructor(name, args, id) {
8100
+ description;
8101
+ constructor(name, args, idOrOptions) {
7971
8102
  this.name = name;
7972
8103
  this.args = args;
7973
8104
  new croner.Cron(args.schedule, {
7974
8105
  paused: true,
7975
8106
  timezone: args.timezone
7976
8107
  });
7977
- this.id = id;
8108
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8109
+ this.id = options?.id;
8110
+ this.description = options?.description;
7978
8111
  }
7979
8112
  get schedule() {
7980
8113
  return this.args.schedule;
@@ -8028,20 +8161,21 @@ var ManualTrigger = class ManualTrigger {
8028
8161
  icon = "lucide:play";
8029
8162
  defaultItems;
8030
8163
  id;
8164
+ description;
8031
8165
  /** Manual runs often emit an empty batch; still schedule downstream by default. */
8032
8166
  continueWhenEmptyOutput = true;
8033
- constructor(name = "Manual trigger", defaultItemsOrId, id) {
8167
+ constructor(name = "Manual trigger", defaultItemsOrId, idOrOptions) {
8034
8168
  this.name = name;
8035
8169
  this.defaultItems = ManualTrigger.resolveDefaultItems(defaultItemsOrId);
8036
- this.id = ManualTrigger.resolveId(defaultItemsOrId, id);
8170
+ const trailing = idOrOptions ?? (typeof defaultItemsOrId === "string" ? defaultItemsOrId : void 0);
8171
+ const options = typeof trailing === "string" ? { id: trailing } : trailing ?? {};
8172
+ this.id = options.id;
8173
+ this.description = options.description;
8037
8174
  }
8038
8175
  static resolveDefaultItems(value) {
8039
8176
  if (typeof value === "string" || value === void 0) return;
8040
8177
  return this.itemsInputNormalizer.normalize(value);
8041
8178
  }
8042
- static resolveId(value, id) {
8043
- return typeof value === "string" ? value : id;
8044
- }
8045
8179
  inspectorSummary() {
8046
8180
  const rows = [{
8047
8181
  label: "Trigger",
@@ -8087,11 +8221,13 @@ var MapData = class {
8087
8221
  continueWhenEmptyOutput = true;
8088
8222
  icon = "lucide:square-pen";
8089
8223
  keepBinaries;
8224
+ description;
8090
8225
  constructor(name, map, options = {}) {
8091
8226
  this.name = name;
8092
8227
  this.map = map;
8093
8228
  this.options = options;
8094
8229
  this.keepBinaries = options.keepBinaries ?? true;
8230
+ this.description = options.description;
8095
8231
  }
8096
8232
  get id() {
8097
8233
  return this.options.id;
@@ -8164,10 +8300,14 @@ var Merge = class {
8164
8300
  kind = "node";
8165
8301
  type = MergeNode;
8166
8302
  icon = "lucide:merge@rot=90";
8167
- constructor(name, cfg = { mode: "passThrough" }, id) {
8303
+ id;
8304
+ description;
8305
+ constructor(name, cfg = { mode: "passThrough" }, idOrOptions) {
8168
8306
  this.name = name;
8169
8307
  this.cfg = cfg;
8170
- this.id = id;
8308
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8309
+ this.id = options?.id;
8310
+ this.description = options?.description;
8171
8311
  }
8172
8312
  inspectorSummary() {
8173
8313
  const rows = [{
@@ -8200,9 +8340,13 @@ var NoOp = class {
8200
8340
  type = NoOpNode;
8201
8341
  execution = { hint: "local" };
8202
8342
  icon = "lucide:circle-dashed";
8203
- constructor(name = "NoOp", id) {
8343
+ id;
8344
+ description;
8345
+ constructor(name = "NoOp", idOrOptions) {
8204
8346
  this.name = name;
8205
- this.id = id;
8347
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8348
+ this.id = options?.id;
8349
+ this.description = options?.description;
8206
8350
  }
8207
8351
  };
8208
8352
 
@@ -8268,12 +8412,16 @@ var SubWorkflow = class {
8268
8412
  kind = "node";
8269
8413
  type = SubWorkflowNode;
8270
8414
  icon = "lucide:workflow";
8271
- constructor(name, workflowId, upstreamRefs, startAt, id) {
8415
+ id;
8416
+ description;
8417
+ constructor(name, workflowId, upstreamRefs, startAt, idOrOptions) {
8272
8418
  this.name = name;
8273
8419
  this.workflowId = workflowId;
8274
8420
  this.upstreamRefs = upstreamRefs;
8275
8421
  this.startAt = startAt;
8276
- this.id = id;
8422
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8423
+ this.id = options?.id;
8424
+ this.description = options?.description;
8277
8425
  }
8278
8426
  inspectorSummary() {
8279
8427
  const rows = [{
@@ -8383,10 +8531,14 @@ var Wait = class {
8383
8531
  /** Pass-through empty batches should still advance to downstream nodes. */
8384
8532
  continueWhenEmptyOutput = true;
8385
8533
  icon = "lucide:hourglass";
8386
- constructor(name, milliseconds, id) {
8534
+ id;
8535
+ description;
8536
+ constructor(name, milliseconds, idOrOptions) {
8387
8537
  this.name = name;
8388
8538
  this.milliseconds = milliseconds;
8389
- this.id = id;
8539
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8540
+ this.id = options?.id;
8541
+ this.description = options?.description;
8390
8542
  }
8391
8543
  inspectorSummary() {
8392
8544
  const seconds = this.milliseconds / 1e3;
@@ -8441,11 +8593,15 @@ var WebhookTrigger = class WebhookTrigger {
8441
8593
  kind = "trigger";
8442
8594
  type = WebhookTriggerNode;
8443
8595
  icon = "lucide:globe";
8444
- constructor(name, args, handler = WebhookTrigger.defaultHandler, id) {
8596
+ id;
8597
+ description;
8598
+ constructor(name, args, handler = WebhookTrigger.defaultHandler, idOrOptions) {
8445
8599
  this.name = name;
8446
8600
  this.args = args;
8447
8601
  this.handler = handler;
8448
- this.id = id;
8602
+ const options = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
8603
+ this.id = options?.id;
8604
+ this.description = options?.description;
8449
8605
  }
8450
8606
  get endpointKey() {
8451
8607
  return this.args.endpointKey;
@@ -8502,11 +8658,21 @@ function createWorkflowBuilder(meta$2) {
8502
8658
 
8503
8659
  //#endregion
8504
8660
  //#region src/workflowAuthoring/WorkflowChatModelFactory.types.ts
8661
+ const VALID_COMPLEXITY = new Set([
8662
+ "low",
8663
+ "medium",
8664
+ "high",
8665
+ "xhigh"
8666
+ ]);
8505
8667
  var WorkflowChatModelFactory = class {
8506
8668
  static create(model) {
8507
8669
  if (typeof model !== "string") return model;
8508
8670
  const [provider, resolvedModel] = model.includes(":") ? model.split(":", 2) : ["openai", model];
8509
- if (provider === "codemation-managed") return new CodemationChatModelConfig("Codemation Managed", resolvedModel ?? "");
8671
+ if (provider === "codemation-managed") {
8672
+ const complexity = resolvedModel ?? "medium";
8673
+ if (!VALID_COMPLEXITY.has(complexity)) throw new Error(`Invalid managed complexity "${complexity}". Must be one of: low, medium, high, xhigh.`);
8674
+ return new CodemationChatModelConfig("Codemation Managed", complexity);
8675
+ }
8510
8676
  if (provider !== "openai") throw new Error(`Unsupported workflow().agent() model provider "${provider}".`);
8511
8677
  return new OpenAIChatModelConfig("OpenAI", resolvedModel);
8512
8678
  }
@@ -9274,7 +9440,6 @@ Object.defineProperty(exports, 'IsTestRunNode', {
9274
9440
  return IsTestRunNode;
9275
9441
  }
9276
9442
  });
9277
- exports.ManagedModelFetcher = ManagedModelFetcher;
9278
9443
  exports.ManualTrigger = ManualTrigger;
9279
9444
  Object.defineProperty(exports, 'ManualTriggerNode', {
9280
9445
  enumerable: true,