@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/CHANGELOG.md +17 -0
- package/dist/index.cjs +175 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +175 -9
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +1 -1
- package/package.json +1 -1
- package/src/nodes/AIAgentConfig.ts +8 -0
- package/src/nodes/AIAgentNode.ts +4 -2
- package/src/nodes/AgentMessageFactory.ts +22 -6
- package/src/nodes/AgentToolResultContentFactory.ts +155 -0
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
|
|
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
|
|
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() {
|