@codemation/core-nodes 0.12.0 → 0.13.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/dist/index.d.cts CHANGED
@@ -2388,7 +2388,13 @@ declare class AgentMessageFactory {
2388
2388
  * Builds the `{ role: "tool", content: [{ type: "tool-result", ... }, ...] }` message returned
2389
2389
  * to the model after each tool round.
2390
2390
  */
2391
- static createToolResultsMessage(executedToolCalls: ReadonlyArray<ExecutedToolCall>): ToolModelMessage;
2391
+ static createToolResultsMessage(executedToolCalls: ReadonlyArray<ExecutedToolCall>, passToolBinariesToModel?: boolean): ToolModelMessage;
2392
+ /**
2393
+ * Routes a tool result to a native multimodal `{ type: "content" }` output when it is
2394
+ * content-block-shaped (an MCP `CallToolResult`) and binary passdown is enabled; otherwise keeps
2395
+ * the inert `{ type: "json" }` path.
2396
+ */
2397
+ private static toToolResultOutput;
2392
2398
  private static toToolResultJson;
2393
2399
  private static createPromptMessage;
2394
2400
  }
@@ -2547,6 +2553,12 @@ interface AIAgentOptions<TInputJson$1 = unknown, _TOutputJson = unknown> {
2547
2553
  * entirely (the node then behaves as if no binaries were present).
2548
2554
  */
2549
2555
  readonly passBinariesToModel?: boolean;
2556
+ /**
2557
+ * Whether binaries returned by a tool (e.g. an MCP tool returning a PDF or image) are passed to
2558
+ * the chat model as native multimodal tool-result blocks. Defaults to `true`. Set to `false` to
2559
+ * keep tool results as inert JSON text (the model then never "sees" the document).
2560
+ */
2561
+ readonly passToolBinariesToModel?: boolean;
2550
2562
  /**
2551
2563
  * Explicit binaries to pass to the chat model, instead of the ones on the current item.
2552
2564
  * Either a static array or a function resolved per item (so an author can forward binaries
@@ -2582,6 +2594,7 @@ declare class AIAgent<TInputJson$1 = unknown, TOutputJson$1 = unknown> implement
2582
2594
  readonly pinnedMcpTools?: readonly string[];
2583
2595
  readonly untrustedSources?: ReadonlyArray<string>;
2584
2596
  readonly passBinariesToModel?: boolean;
2597
+ readonly passToolBinariesToModel?: boolean;
2585
2598
  readonly binaries?: ReadonlyArray<BinaryAttachment> | ((args: AgentMessageBuildArgs<TInputJson$1>) => ReadonlyArray<BinaryAttachment>);
2586
2599
  constructor(options: AIAgentOptions<TInputJson$1, TOutputJson$1>);
2587
2600
  inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow>;
package/dist/index.d.ts CHANGED
@@ -2388,7 +2388,13 @@ declare class AgentMessageFactory {
2388
2388
  * Builds the `{ role: "tool", content: [{ type: "tool-result", ... }, ...] }` message returned
2389
2389
  * to the model after each tool round.
2390
2390
  */
2391
- static createToolResultsMessage(executedToolCalls: ReadonlyArray<ExecutedToolCall>): ToolModelMessage;
2391
+ static createToolResultsMessage(executedToolCalls: ReadonlyArray<ExecutedToolCall>, passToolBinariesToModel?: boolean): ToolModelMessage;
2392
+ /**
2393
+ * Routes a tool result to a native multimodal `{ type: "content" }` output when it is
2394
+ * content-block-shaped (an MCP `CallToolResult`) and binary passdown is enabled; otherwise keeps
2395
+ * the inert `{ type: "json" }` path.
2396
+ */
2397
+ private static toToolResultOutput;
2392
2398
  private static toToolResultJson;
2393
2399
  private static createPromptMessage;
2394
2400
  }
@@ -2547,6 +2553,12 @@ interface AIAgentOptions<TInputJson$1 = unknown, _TOutputJson = unknown> {
2547
2553
  * entirely (the node then behaves as if no binaries were present).
2548
2554
  */
2549
2555
  readonly passBinariesToModel?: boolean;
2556
+ /**
2557
+ * Whether binaries returned by a tool (e.g. an MCP tool returning a PDF or image) are passed to
2558
+ * the chat model as native multimodal tool-result blocks. Defaults to `true`. Set to `false` to
2559
+ * keep tool results as inert JSON text (the model then never "sees" the document).
2560
+ */
2561
+ readonly passToolBinariesToModel?: boolean;
2550
2562
  /**
2551
2563
  * Explicit binaries to pass to the chat model, instead of the ones on the current item.
2552
2564
  * Either a static array or a function resolved per item (so an author can forward binaries
@@ -2582,6 +2594,7 @@ declare class AIAgent<TInputJson$1 = unknown, TOutputJson$1 = unknown> implement
2582
2594
  readonly pinnedMcpTools?: readonly string[];
2583
2595
  readonly untrustedSources?: ReadonlyArray<string>;
2584
2596
  readonly passBinariesToModel?: boolean;
2597
+ readonly passToolBinariesToModel?: boolean;
2585
2598
  readonly binaries?: ReadonlyArray<BinaryAttachment> | ((args: AgentMessageBuildArgs<TInputJson$1>) => ReadonlyArray<BinaryAttachment>);
2586
2599
  constructor(options: AIAgentOptions<TInputJson$1, TOutputJson$1>);
2587
2600
  inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow>;
package/dist/index.js CHANGED
@@ -4458,6 +4458,155 @@ var CodemationChatModelConfig = class {
4458
4458
  }
4459
4459
  };
4460
4460
 
4461
+ //#endregion
4462
+ //#region src/nodes/AgentToolResultContentFactory.ts
4463
+ /**
4464
+ * Cap on raw (pre-base64) bytes inlined from a single tool result. Base64 inflates ~33% and every
4465
+ * inlined byte eats model context, so oversize binaries are replaced with a text placeholder.
4466
+ */
4467
+ const MAX_INLINE_BYTES = 8 * 1024 * 1024;
4468
+ /** MCP content-block discriminators. At least one must appear for a result to be treated as MCP-shaped. */
4469
+ const KNOWN_MCP_BLOCK_TYPES = new Set([
4470
+ "text",
4471
+ "image",
4472
+ "audio",
4473
+ "resource",
4474
+ "resource_link"
4475
+ ]);
4476
+ /**
4477
+ * Maps a tool result that is **content-block-shaped** (an MCP `CallToolResult` with a `content`
4478
+ * array) into AI SDK `{ type: "content" }` tool-result output, so binaries reach the chat model as
4479
+ * native multimodal tool-result blocks instead of being flattened to inert JSON text.
4480
+ *
4481
+ * The `@ai-sdk/anthropic` provider maps a `content`-output part as follows:
4482
+ * `text` → text block, `image-data` → image block, `file-data` (only `application/pdf`) → document
4483
+ * block. Non-PDF `file-data` is dropped by the provider, so this factory emits `image-data` for
4484
+ * images, `file-data` only for PDFs, and a text marker for every other binary type.
4485
+ *
4486
+ * Returns `undefined` when the result is not content-block-shaped — callers keep the existing
4487
+ * `{ type: "json" }` path, so plain string/object tool results are unaffected.
4488
+ */
4489
+ var AgentToolResultContentFactory = class AgentToolResultContentFactory {
4490
+ static tryMapToContentOutput(result) {
4491
+ const blocks = AgentToolResultContentFactory.contentBlocks(result);
4492
+ if (blocks === void 0) return void 0;
4493
+ const parts = [];
4494
+ let inlinedBytes = 0;
4495
+ for (const block of blocks) {
4496
+ const mapped = AgentToolResultContentFactory.mapBlock(block, inlinedBytes);
4497
+ parts.push(mapped.part);
4498
+ inlinedBytes += mapped.bytes;
4499
+ }
4500
+ return parts;
4501
+ }
4502
+ /**
4503
+ * Returns the `content` array iff `result` is an object whose `content` is an array of typed
4504
+ * blocks AND at least one block carries a known MCP discriminator. A plain JSON result that merely
4505
+ * has a `content` key of some other shape (e.g. Notion/Slack rich-text blocks) is rejected,
4506
+ * preserving the `{ type: "json" }` path so its payload is never lost.
4507
+ */
4508
+ static contentBlocks(result) {
4509
+ if (result === null || typeof result !== "object") return void 0;
4510
+ const content = result.content;
4511
+ if (!Array.isArray(content) || content.length === 0) return void 0;
4512
+ if (!content.every((block) => block !== null && typeof block === "object" && typeof block.type === "string")) return void 0;
4513
+ return content.some((block) => KNOWN_MCP_BLOCK_TYPES.has(block.type)) ? content : void 0;
4514
+ }
4515
+ static mapBlock(block, inlinedBytesSoFar) {
4516
+ const type = block.type;
4517
+ if (type === "text" && typeof block.text === "string") return {
4518
+ part: {
4519
+ type: "text",
4520
+ text: block.text
4521
+ },
4522
+ bytes: 0
4523
+ };
4524
+ if (type === "image" && typeof block.data === "string" && typeof block.mimeType === "string") return AgentToolResultContentFactory.mapBinary({
4525
+ base64: block.data,
4526
+ mediaType: block.mimeType,
4527
+ inlinedBytesSoFar
4528
+ });
4529
+ if (type === "resource" && block.resource) return AgentToolResultContentFactory.mapEmbeddedResource(block.resource, inlinedBytesSoFar);
4530
+ if (type === "resource_link" && typeof block.uri === "string") {
4531
+ const mime = typeof block.mimeType === "string" ? ` (${block.mimeType})` : "";
4532
+ return {
4533
+ part: {
4534
+ type: "text",
4535
+ text: `[linked resource: ${block.uri}${mime}]`
4536
+ },
4537
+ bytes: 0
4538
+ };
4539
+ }
4540
+ return {
4541
+ part: {
4542
+ type: "text",
4543
+ text: `[unsupported tool content block: ${String(type)}]`
4544
+ },
4545
+ bytes: 0
4546
+ };
4547
+ }
4548
+ static mapEmbeddedResource(resource, inlinedBytesSoFar) {
4549
+ if (typeof resource.text === "string") return {
4550
+ part: {
4551
+ type: "text",
4552
+ text: resource.text
4553
+ },
4554
+ bytes: 0
4555
+ };
4556
+ if (typeof resource.blob === "string" && typeof resource.mimeType === "string") return AgentToolResultContentFactory.mapBinary({
4557
+ base64: resource.blob,
4558
+ mediaType: resource.mimeType,
4559
+ filename: typeof resource.name === "string" ? resource.name : void 0,
4560
+ inlinedBytesSoFar
4561
+ });
4562
+ return {
4563
+ part: {
4564
+ type: "text",
4565
+ text: `[embedded resource: ${typeof resource.uri === "string" ? resource.uri : "unknown"}]`
4566
+ },
4567
+ bytes: 0
4568
+ };
4569
+ }
4570
+ static mapBinary(args) {
4571
+ const rawBytes = Math.floor(args.base64.length * 3 / 4);
4572
+ if (args.inlinedBytesSoFar + rawBytes > MAX_INLINE_BYTES) {
4573
+ const name = args.filename ? ` "${args.filename}"` : "";
4574
+ const kb = Math.round(rawBytes / 1024);
4575
+ return {
4576
+ part: {
4577
+ type: "text",
4578
+ text: `[binary${name} (${args.mediaType}, ~${kb} KB) omitted: exceeds the per-tool-result inline limit]`
4579
+ },
4580
+ bytes: 0
4581
+ };
4582
+ }
4583
+ if (args.mediaType.startsWith("image/")) return {
4584
+ part: {
4585
+ type: "image-data",
4586
+ data: args.base64,
4587
+ mediaType: args.mediaType
4588
+ },
4589
+ bytes: rawBytes
4590
+ };
4591
+ if (args.mediaType === "application/pdf") return {
4592
+ part: {
4593
+ type: "file-data",
4594
+ data: args.base64,
4595
+ mediaType: args.mediaType,
4596
+ ...args.filename ? { filename: args.filename } : {}
4597
+ },
4598
+ bytes: rawBytes
4599
+ };
4600
+ return {
4601
+ part: {
4602
+ type: "text",
4603
+ text: `[binary${args.filename ? ` "${args.filename}"` : ""} (${args.mediaType}) not inlined: unsupported by the model]`
4604
+ },
4605
+ bytes: 0
4606
+ };
4607
+ }
4608
+ };
4609
+
4461
4610
  //#endregion
4462
4611
  //#region src/nodes/AgentMessageFactory.ts
4463
4612
  /**
@@ -4494,20 +4643,35 @@ var AgentMessageFactory = class AgentMessageFactory {
4494
4643
  * Builds the `{ role: "tool", content: [{ type: "tool-result", ... }, ...] }` message returned
4495
4644
  * to the model after each tool round.
4496
4645
  */
4497
- static createToolResultsMessage(executedToolCalls) {
4646
+ static createToolResultsMessage(executedToolCalls, passToolBinariesToModel = true) {
4498
4647
  return {
4499
4648
  role: "tool",
4500
4649
  content: executedToolCalls.map((executed) => ({
4501
4650
  type: "tool-result",
4502
4651
  toolCallId: executed.toolCallId,
4503
4652
  toolName: executed.toolName,
4504
- output: {
4505
- type: "json",
4506
- value: AgentMessageFactory.toToolResultJson(executed.result)
4507
- }
4653
+ output: AgentMessageFactory.toToolResultOutput(executed.result, passToolBinariesToModel)
4508
4654
  }))
4509
4655
  };
4510
4656
  }
4657
+ /**
4658
+ * Routes a tool result to a native multimodal `{ type: "content" }` output when it is
4659
+ * content-block-shaped (an MCP `CallToolResult`) and binary passdown is enabled; otherwise keeps
4660
+ * the inert `{ type: "json" }` path.
4661
+ */
4662
+ static toToolResultOutput(result, passToolBinariesToModel) {
4663
+ if (passToolBinariesToModel) {
4664
+ const content = AgentToolResultContentFactory.tryMapToContentOutput(result);
4665
+ if (content !== void 0) return {
4666
+ type: "content",
4667
+ value: content
4668
+ };
4669
+ }
4670
+ return {
4671
+ type: "json",
4672
+ value: AgentMessageFactory.toToolResultJson(result)
4673
+ };
4674
+ }
4511
4675
  static toToolResultJson(value) {
4512
4676
  if (value === void 0) return null;
4513
4677
  try {
@@ -6296,7 +6460,7 @@ let AIAgentNode = class AIAgentNode$1 {
6296
6460
  result: decision,
6297
6461
  serialized: JSON.stringify(decision)
6298
6462
  };
6299
- const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
6463
+ const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry], ctx.config.passToolBinariesToModel !== false)];
6300
6464
  const loopResult = await this.runTurnLoopUntilFinalAnswer({
6301
6465
  prepared,
6302
6466
  itemInputsByPort,
@@ -6516,7 +6680,7 @@ let AIAgentNode = class AIAgentNode$1 {
6516
6680
  coordinatorExecutedCalls.push(...executed);
6517
6681
  }
6518
6682
  const allExecutedCalls = [...strategyExecutedCalls, ...coordinatorExecutedCalls];
6519
- this.appendAssistantAndToolMessages(conversation, result.assistantMessage, result.text, result.toolCalls, allExecutedCalls);
6683
+ this.appendAssistantAndToolMessages(conversation, result.assistantMessage, result.text, result.toolCalls, allExecutedCalls, ctx.config.passToolBinariesToModel !== false);
6520
6684
  }
6521
6685
  return {
6522
6686
  finalText,
@@ -6531,8 +6695,8 @@ let AIAgentNode = class AIAgentNode$1 {
6531
6695
  if (guardrails.onTurnLimitReached === "respondWithLastMessage") return;
6532
6696
  throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" reached maxTurns=${guardrails.maxTurns} before producing a final response.`);
6533
6697
  }
6534
- appendAssistantAndToolMessages(conversation, assistantMessage, text, toolCalls, executedToolCalls) {
6535
- conversation.push(assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(text, toolCalls), AgentMessageFactory.createToolResultsMessage(executedToolCalls));
6698
+ appendAssistantAndToolMessages(conversation, assistantMessage, text, toolCalls, executedToolCalls, passToolBinariesToModel) {
6699
+ conversation.push(assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(text, toolCalls), AgentMessageFactory.createToolResultsMessage(executedToolCalls, passToolBinariesToModel));
6536
6700
  }
6537
6701
  async resolveFinalOutputJson(prepared, itemInputsByPort, conversation, finalText, wasToolEnabledRun) {
6538
6702
  if (!prepared.ctx.config.outputSchema) return AgentOutputFactory.fromAgentContent(finalText);
@@ -7296,6 +7460,7 @@ var AIAgent = class {
7296
7460
  pinnedMcpTools;
7297
7461
  untrustedSources;
7298
7462
  passBinariesToModel;
7463
+ passToolBinariesToModel;
7299
7464
  binaries;
7300
7465
  constructor(options) {
7301
7466
  this.name = options.name;
@@ -7312,6 +7477,7 @@ var AIAgent = class {
7312
7477
  this.pinnedMcpTools = options.pinnedMcpTools;
7313
7478
  this.untrustedSources = options.untrustedSources;
7314
7479
  this.passBinariesToModel = options.passBinariesToModel;
7480
+ this.passToolBinariesToModel = options.passToolBinariesToModel;
7315
7481
  this.binaries = options.binaries;
7316
7482
  }
7317
7483
  inspectorSummary() {