@codemation/core-nodes 0.8.0 → 0.9.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 +34 -0
- package/LICENSE +37 -1
- package/dist/index.cjs +301 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +904 -692
- package/dist/index.d.ts +904 -692
- package/dist/index.js +302 -9
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +7 -7
- package/package.json +3 -3
- package/src/chatModels/CodemationChatModelConfig.ts +1 -1
- package/src/index.ts +1 -0
- package/src/nodes/AIAgentNode.ts +159 -7
- package/src/nodes/AgentLoopCheckpoint.types.ts +23 -0
- package/src/nodes/AgentToolExecutionCoordinator.ts +91 -2
- package/src/nodes/InboxApprovalNode.types.ts +87 -0
- package/src/nodes/aiAgentSupport.types.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentConfigInspector, AgentConnectionNodeCollector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, DefinedNodeRegistry, GenAiTelemetryAttributeNames, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RetryPolicy, RunnableOutputBehaviorResolver, WorkflowBuilder, chatModel, defineCredential, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
|
|
1
|
+
import { AgentConfigInspector, AgentConnectionNodeCollector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, DefinedNodeRegistry, GenAiTelemetryAttributeNames, InboxChannelResolverToken, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RetryPolicy, RunnableOutputBehaviorResolver, SuspensionRequest, WorkflowBuilder, chatModel, defineCredential, defineHumanApprovalNode, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
|
|
2
2
|
import dns from "node:dns/promises";
|
|
3
3
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
4
4
|
import { CredentialResolverFactory } from "@codemation/core/bootstrap";
|
|
@@ -1279,6 +1279,7 @@ const string$1 = (params) => {
|
|
|
1279
1279
|
};
|
|
1280
1280
|
const integer = /^-?\d+$/;
|
|
1281
1281
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
1282
|
+
const boolean$1 = /^(?:true|false)$/i;
|
|
1282
1283
|
const lowercase = /^[^A-Z]*$/;
|
|
1283
1284
|
const uppercase = /^[^a-z]*$/;
|
|
1284
1285
|
|
|
@@ -2057,6 +2058,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
|
|
|
2057
2058
|
$ZodCheckNumberFormat.init(inst, def);
|
|
2058
2059
|
$ZodNumber.init(inst, def);
|
|
2059
2060
|
});
|
|
2061
|
+
const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
2062
|
+
$ZodType.init(inst, def);
|
|
2063
|
+
inst._zod.pattern = boolean$1;
|
|
2064
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
2065
|
+
if (def.coerce) try {
|
|
2066
|
+
payload.value = Boolean(payload.value);
|
|
2067
|
+
} catch (_) {}
|
|
2068
|
+
const input = payload.value;
|
|
2069
|
+
if (typeof input === "boolean") return payload;
|
|
2070
|
+
payload.issues.push({
|
|
2071
|
+
expected: "boolean",
|
|
2072
|
+
code: "invalid_type",
|
|
2073
|
+
input,
|
|
2074
|
+
inst
|
|
2075
|
+
});
|
|
2076
|
+
return payload;
|
|
2077
|
+
};
|
|
2078
|
+
});
|
|
2060
2079
|
const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
2061
2080
|
$ZodType.init(inst, def);
|
|
2062
2081
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3133,6 +3152,13 @@ function _int(Class, params) {
|
|
|
3133
3152
|
});
|
|
3134
3153
|
}
|
|
3135
3154
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3155
|
+
function _boolean(Class, params) {
|
|
3156
|
+
return new Class({
|
|
3157
|
+
type: "boolean",
|
|
3158
|
+
...normalizeParams(params)
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3136
3162
|
function _unknown(Class) {
|
|
3137
3163
|
return new Class({ type: "unknown" });
|
|
3138
3164
|
}
|
|
@@ -3299,6 +3325,17 @@ function _array(Class, element, params) {
|
|
|
3299
3325
|
});
|
|
3300
3326
|
}
|
|
3301
3327
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3328
|
+
function _custom(Class, fn, _params) {
|
|
3329
|
+
const norm = normalizeParams(_params);
|
|
3330
|
+
norm.abort ?? (norm.abort = true);
|
|
3331
|
+
return new Class({
|
|
3332
|
+
type: "custom",
|
|
3333
|
+
check: "custom",
|
|
3334
|
+
fn,
|
|
3335
|
+
...norm
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3302
3339
|
function _refine(Class, fn, _params) {
|
|
3303
3340
|
return new Class({
|
|
3304
3341
|
type: "custom",
|
|
@@ -4895,6 +4932,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
|
|
|
4895
4932
|
function int(params) {
|
|
4896
4933
|
return _int(ZodNumberFormat, params);
|
|
4897
4934
|
}
|
|
4935
|
+
const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
4936
|
+
$ZodBoolean.init(inst, def);
|
|
4937
|
+
ZodType.init(inst, def);
|
|
4938
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4939
|
+
});
|
|
4940
|
+
function boolean(params) {
|
|
4941
|
+
return _boolean(ZodBoolean, params);
|
|
4942
|
+
}
|
|
4898
4943
|
const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
4899
4944
|
$ZodUnknown.init(inst, def);
|
|
4900
4945
|
ZodType.init(inst, def);
|
|
@@ -5206,6 +5251,9 @@ const ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
5206
5251
|
ZodType.init(inst, def);
|
|
5207
5252
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
5208
5253
|
});
|
|
5254
|
+
function custom(fn, _params) {
|
|
5255
|
+
return _custom(ZodCustom, fn ?? (() => true), _params);
|
|
5256
|
+
}
|
|
5209
5257
|
function refine(fn, _params = {}) {
|
|
5210
5258
|
return _refine(ZodCustom, fn, _params);
|
|
5211
5259
|
}
|
|
@@ -5455,12 +5503,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5455
5503
|
this.repairPolicy = repairPolicy;
|
|
5456
5504
|
}
|
|
5457
5505
|
async execute(args) {
|
|
5506
|
+
if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
|
|
5507
|
+
toolName: c.binding.config.name,
|
|
5508
|
+
toolCallId: c.toolCall.id ?? c.binding.config.name,
|
|
5509
|
+
result: { error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` },
|
|
5510
|
+
serialized: JSON.stringify({ error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` })
|
|
5511
|
+
}));
|
|
5458
5512
|
const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
|
|
5459
5513
|
...args,
|
|
5460
5514
|
plannedToolCall
|
|
5461
5515
|
})));
|
|
5462
5516
|
const rejected = results.find((result) => result.status === "rejected");
|
|
5463
|
-
if (rejected?.status === "rejected")
|
|
5517
|
+
if (rejected?.status === "rejected") {
|
|
5518
|
+
const reason = rejected.reason;
|
|
5519
|
+
if (reason instanceof SuspensionRequest) throw reason;
|
|
5520
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
5521
|
+
}
|
|
5464
5522
|
return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
5465
5523
|
}
|
|
5466
5524
|
async executePlannedToolCall(args) {
|
|
@@ -5545,6 +5603,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5545
5603
|
result
|
|
5546
5604
|
};
|
|
5547
5605
|
} catch (error) {
|
|
5606
|
+
if (error instanceof SuspensionRequest) {
|
|
5607
|
+
const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
|
|
5608
|
+
const checkpoint = {
|
|
5609
|
+
conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
|
|
5610
|
+
turnCount: args.turnCount ?? 0,
|
|
5611
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
5612
|
+
pendingToolCallId,
|
|
5613
|
+
agentName: args.agentName,
|
|
5614
|
+
modelId: args.modelId ?? ""
|
|
5615
|
+
};
|
|
5616
|
+
const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
|
|
5617
|
+
const augmented = new SuspensionRequest({
|
|
5618
|
+
...error.request,
|
|
5619
|
+
metadata: {
|
|
5620
|
+
...error.request.metadata,
|
|
5621
|
+
agentCheckpoint: checkpoint,
|
|
5622
|
+
pendingToolCallId,
|
|
5623
|
+
agentReasoning,
|
|
5624
|
+
onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
|
|
5625
|
+
}
|
|
5626
|
+
});
|
|
5627
|
+
await span.end({
|
|
5628
|
+
status: "error",
|
|
5629
|
+
statusMessage: "suspended",
|
|
5630
|
+
endedAt: /* @__PURE__ */ new Date()
|
|
5631
|
+
});
|
|
5632
|
+
throw augmented;
|
|
5633
|
+
}
|
|
5548
5634
|
const classification = this.errorClassifier.classify({
|
|
5549
5635
|
error,
|
|
5550
5636
|
toolName: plannedToolCall.binding.config.name,
|
|
@@ -5720,6 +5806,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5720
5806
|
extractErrorDetails(error) {
|
|
5721
5807
|
return error.details;
|
|
5722
5808
|
}
|
|
5809
|
+
/**
|
|
5810
|
+
* Extracts the text content from the last assistant message in the conversation snapshot.
|
|
5811
|
+
* Used to populate `agentReasoning` in the HITL suspension metadata.
|
|
5812
|
+
*/
|
|
5813
|
+
extractLastAssistantText(conversation) {
|
|
5814
|
+
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
5815
|
+
const msg = conversation[i];
|
|
5816
|
+
if (msg?.role !== "assistant") continue;
|
|
5817
|
+
const content = msg.content;
|
|
5818
|
+
if (typeof content === "string") return content;
|
|
5819
|
+
if (Array.isArray(content)) {
|
|
5820
|
+
const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
|
|
5821
|
+
if (textParts.length > 0) return textParts.join("");
|
|
5822
|
+
}
|
|
5823
|
+
break;
|
|
5824
|
+
}
|
|
5825
|
+
return "";
|
|
5826
|
+
}
|
|
5723
5827
|
serializeIssue(issue$1) {
|
|
5724
5828
|
const result = {
|
|
5725
5829
|
path: [...issue$1.path],
|
|
@@ -6073,6 +6177,7 @@ var AgentItemPortMap = class {
|
|
|
6073
6177
|
//#endregion
|
|
6074
6178
|
//#region src/nodes/AIAgentNode.ts
|
|
6075
6179
|
var _ref, _ref2, _ref3, _ref4, _ref5;
|
|
6180
|
+
const HITL_SOLO_CONSTRAINT_SENTENCE = "This tool requires human approval and may take time. Call it alone — do not invoke other tools in the same turn. Your turn will be paused until a decision is made.";
|
|
6076
6181
|
let AIAgentNode = class AIAgentNode$1 {
|
|
6077
6182
|
kind = "node";
|
|
6078
6183
|
outputPorts = ["main"];
|
|
@@ -6090,13 +6195,90 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6090
6195
|
this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
6091
6196
|
}
|
|
6092
6197
|
async execute(args) {
|
|
6093
|
-
const
|
|
6198
|
+
const { ctx } = args;
|
|
6199
|
+
if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
|
|
6200
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6094
6201
|
const itemWithMappedJson = {
|
|
6095
6202
|
...args.item,
|
|
6096
6203
|
json: args.input
|
|
6097
6204
|
};
|
|
6098
6205
|
return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6099
6206
|
}
|
|
6207
|
+
/**
|
|
6208
|
+
* Resume path: re-enters the agent loop after a HITL suspension.
|
|
6209
|
+
* Reconstructs the conversation from the checkpoint, injects the human decision
|
|
6210
|
+
* as a tool_result, and continues the loop from where it suspended.
|
|
6211
|
+
*/
|
|
6212
|
+
async executeResumed(args, resumeContext) {
|
|
6213
|
+
const { ctx } = args;
|
|
6214
|
+
const taskMetadata = resumeContext.task.metadata ?? {};
|
|
6215
|
+
const checkpoint = taskMetadata["agentCheckpoint"];
|
|
6216
|
+
const onRejected = taskMetadata["onRejected"] ?? "return";
|
|
6217
|
+
if (!checkpoint) {
|
|
6218
|
+
const prepared$1 = await this.getOrPrepareExecution(ctx);
|
|
6219
|
+
const itemWithMappedJson = {
|
|
6220
|
+
...args.item,
|
|
6221
|
+
json: args.input
|
|
6222
|
+
};
|
|
6223
|
+
return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6224
|
+
}
|
|
6225
|
+
if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
|
|
6226
|
+
const decision = this.normalizeDecision(resumeContext);
|
|
6227
|
+
if (decision.status === "rejected" && onRejected === "halt") return;
|
|
6228
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6229
|
+
const item = args.item;
|
|
6230
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
6231
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
|
|
6232
|
+
const toolResultEntry = {
|
|
6233
|
+
toolName: checkpoint.pendingToolCallId,
|
|
6234
|
+
toolCallId: checkpoint.pendingToolCallId,
|
|
6235
|
+
result: decision,
|
|
6236
|
+
serialized: JSON.stringify(decision)
|
|
6237
|
+
};
|
|
6238
|
+
const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
|
|
6239
|
+
const loopResult = await this.runTurnLoopUntilFinalAnswer({
|
|
6240
|
+
prepared,
|
|
6241
|
+
itemInputsByPort,
|
|
6242
|
+
itemScopedTools,
|
|
6243
|
+
conversation,
|
|
6244
|
+
resumedTurnCount: checkpoint.turnCount,
|
|
6245
|
+
resumedToolCallCount: checkpoint.toolCallCount
|
|
6246
|
+
});
|
|
6247
|
+
await ctx.telemetry.recordMetric({
|
|
6248
|
+
name: CodemationTelemetryMetricNames.agentTurns,
|
|
6249
|
+
value: loopResult.turnCount
|
|
6250
|
+
});
|
|
6251
|
+
await ctx.telemetry.recordMetric({
|
|
6252
|
+
name: CodemationTelemetryMetricNames.agentToolCalls,
|
|
6253
|
+
value: loopResult.toolCallCount
|
|
6254
|
+
});
|
|
6255
|
+
const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
|
|
6256
|
+
return this.buildOutputItem(item, outputJson).json;
|
|
6257
|
+
}
|
|
6258
|
+
/**
|
|
6259
|
+
* Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
|
|
6260
|
+
* suitable for injection as a tool_result content.
|
|
6261
|
+
*/
|
|
6262
|
+
normalizeDecision(resumeContext) {
|
|
6263
|
+
const { decision } = resumeContext;
|
|
6264
|
+
if (decision.kind === "decided") {
|
|
6265
|
+
const value = decision.value;
|
|
6266
|
+
return {
|
|
6267
|
+
status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
|
|
6268
|
+
value: decision.value,
|
|
6269
|
+
actor: decision.actor,
|
|
6270
|
+
decidedAt: decision.decidedAt.toISOString()
|
|
6271
|
+
};
|
|
6272
|
+
}
|
|
6273
|
+
if (decision.kind === "timed_out") return {
|
|
6274
|
+
status: "timed_out",
|
|
6275
|
+
at: decision.at.toISOString()
|
|
6276
|
+
};
|
|
6277
|
+
return {
|
|
6278
|
+
status: "auto_accepted",
|
|
6279
|
+
at: decision.at.toISOString()
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6100
6282
|
async getOrPrepareExecution(ctx) {
|
|
6101
6283
|
let pending = this.preparedByExecutionContext.get(ctx);
|
|
6102
6284
|
if (!pending) {
|
|
@@ -6217,7 +6399,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6217
6399
|
const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
|
|
6218
6400
|
const { ctx, guardrails, toolLoadingStrategy } = prepared;
|
|
6219
6401
|
let finalText = "";
|
|
6220
|
-
let toolCallCount = 0;
|
|
6402
|
+
let toolCallCount = args.resumedToolCallCount ?? 0;
|
|
6221
6403
|
let turnCount = 0;
|
|
6222
6404
|
const repairAttemptsByToolName = /* @__PURE__ */ new Map();
|
|
6223
6405
|
/** Tool IDs surfaced by find_tools across all prior turns in this item run. */
|
|
@@ -6258,11 +6440,17 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6258
6440
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
|
|
6259
6441
|
toolCallCount += plannedToolCalls.length;
|
|
6260
6442
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
6443
|
+
const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
|
|
6444
|
+
const conversationWithAssistant = [...conversation, assistantMsg];
|
|
6261
6445
|
const executed = await this.toolExecutionCoordinator.execute({
|
|
6262
6446
|
plannedToolCalls,
|
|
6263
6447
|
ctx,
|
|
6264
6448
|
agentName: this.getAgentDisplayName(ctx),
|
|
6265
|
-
repairAttemptsByToolName
|
|
6449
|
+
repairAttemptsByToolName,
|
|
6450
|
+
conversationSnapshot: conversationWithAssistant,
|
|
6451
|
+
turnCount,
|
|
6452
|
+
toolCallCount,
|
|
6453
|
+
modelId: this.resolveChatModelName(ctx.config.chatModel)
|
|
6266
6454
|
});
|
|
6267
6455
|
coordinatorExecutedCalls.push(...executed);
|
|
6268
6456
|
}
|
|
@@ -6324,6 +6512,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6324
6512
|
connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
6325
6513
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
|
|
6326
6514
|
});
|
|
6515
|
+
const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
|
|
6327
6516
|
return {
|
|
6328
6517
|
config: entry.config,
|
|
6329
6518
|
inputSchema: entry.runtime.inputSchema,
|
|
@@ -6338,11 +6527,22 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6338
6527
|
items,
|
|
6339
6528
|
hooks
|
|
6340
6529
|
});
|
|
6341
|
-
}
|
|
6530
|
+
},
|
|
6531
|
+
...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
|
|
6342
6532
|
};
|
|
6343
6533
|
});
|
|
6344
6534
|
}
|
|
6345
6535
|
/**
|
|
6536
|
+
* Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
|
|
6537
|
+
* and returns the HITL behavior config, or `undefined` when not a HITL tool.
|
|
6538
|
+
*/
|
|
6539
|
+
resolveHumanApprovalBehavior(config$1) {
|
|
6540
|
+
if (!this.isNodeBackedToolConfig(config$1)) return void 0;
|
|
6541
|
+
const marker = config$1.node.humanApprovalToolBehavior;
|
|
6542
|
+
if (marker === void 0) return void 0;
|
|
6543
|
+
return { onRejected: marker.onRejected ?? "return" };
|
|
6544
|
+
}
|
|
6545
|
+
/**
|
|
6346
6546
|
* Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
|
|
6347
6547
|
* and strategy tools (find_tools + discovered MCP tools).
|
|
6348
6548
|
* Strategy tools take precedence for names that overlap.
|
|
@@ -6372,6 +6572,8 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6372
6572
|
/**
|
|
6373
6573
|
* Builds a ToolSet from resolved tools for strategy initialization.
|
|
6374
6574
|
* The strategy uses this for its "always-included" node-backed tool descriptions.
|
|
6575
|
+
* HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
|
|
6576
|
+
* appended to their description.
|
|
6375
6577
|
*/
|
|
6376
6578
|
buildToolSetFromResolved(resolvedTools) {
|
|
6377
6579
|
if (resolvedTools.length === 0) return {};
|
|
@@ -6381,8 +6583,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6381
6583
|
schemaName: entry.config.name,
|
|
6382
6584
|
requireObjectRoot: true
|
|
6383
6585
|
});
|
|
6586
|
+
const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
|
|
6587
|
+
const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
|
|
6384
6588
|
toolSet[entry.config.name] = {
|
|
6385
|
-
description
|
|
6589
|
+
description,
|
|
6386
6590
|
inputSchema: jsonSchema(schemaRecord)
|
|
6387
6591
|
};
|
|
6388
6592
|
}
|
|
@@ -6410,8 +6614,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6410
6614
|
schemaName: entry.config.name,
|
|
6411
6615
|
requireObjectRoot: true
|
|
6412
6616
|
});
|
|
6617
|
+
const baseDescription = entry.config.description;
|
|
6618
|
+
const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
|
|
6413
6619
|
toolSet[entry.config.name] = {
|
|
6414
|
-
description
|
|
6620
|
+
description,
|
|
6415
6621
|
inputSchema: jsonSchema(schemaRecord)
|
|
6416
6622
|
};
|
|
6417
6623
|
}
|
|
@@ -8729,5 +8935,92 @@ const collectionDeleteNode = defineNode({
|
|
|
8729
8935
|
});
|
|
8730
8936
|
|
|
8731
8937
|
//#endregion
|
|
8732
|
-
|
|
8938
|
+
//#region src/nodes/InboxApprovalNode.types.ts
|
|
8939
|
+
function resolveSubjectField(field, item) {
|
|
8940
|
+
return typeof field === "function" ? field({ item }) : field;
|
|
8941
|
+
}
|
|
8942
|
+
/**
|
|
8943
|
+
* Auto-detecting inbox approval node.
|
|
8944
|
+
*
|
|
8945
|
+
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
8946
|
+
* at runtime:
|
|
8947
|
+
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
8948
|
+
* - Otherwise: routes to the local inbox.
|
|
8949
|
+
*
|
|
8950
|
+
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
8951
|
+
*/
|
|
8952
|
+
const inboxApproval = defineHumanApprovalNode({
|
|
8953
|
+
key: "inbox.approval",
|
|
8954
|
+
title: "Inbox Approval",
|
|
8955
|
+
description: "Suspend and wait for a human reviewer to approve or reject.",
|
|
8956
|
+
icon: "lucide:inbox",
|
|
8957
|
+
channel: "inbox",
|
|
8958
|
+
configSchema: object({
|
|
8959
|
+
title: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
8960
|
+
body: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
8961
|
+
priority: _enum([
|
|
8962
|
+
"low",
|
|
8963
|
+
"normal",
|
|
8964
|
+
"high"
|
|
8965
|
+
]).default("normal"),
|
|
8966
|
+
timeout: string().default("24h"),
|
|
8967
|
+
onTimeout: _enum(["halt", "auto-accept"]).default("halt")
|
|
8968
|
+
}),
|
|
8969
|
+
decisionSchema: object({
|
|
8970
|
+
approved: boolean(),
|
|
8971
|
+
note: string().optional()
|
|
8972
|
+
}),
|
|
8973
|
+
defaultTimeout: "24h",
|
|
8974
|
+
defaultOnTimeout: "halt",
|
|
8975
|
+
async deliver({ task, config: config$1, item }, ctx) {
|
|
8976
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
8977
|
+
if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
|
|
8978
|
+
const { channel, workspaceId } = resolver.resolve();
|
|
8979
|
+
const subject = {
|
|
8980
|
+
title: resolveSubjectField(config$1.title, item),
|
|
8981
|
+
summary: resolveSubjectField(config$1.body, item),
|
|
8982
|
+
attributes: {
|
|
8983
|
+
workflowId: ctx.workflowId,
|
|
8984
|
+
item: item.json
|
|
8985
|
+
}
|
|
8986
|
+
};
|
|
8987
|
+
const delivery = await channel.deliver({
|
|
8988
|
+
task,
|
|
8989
|
+
subject,
|
|
8990
|
+
priority: config$1.priority,
|
|
8991
|
+
item,
|
|
8992
|
+
workspaceId
|
|
8993
|
+
});
|
|
8994
|
+
ctx.telemetry.addSpanEvent({
|
|
8995
|
+
name: "hitl.task.delivered",
|
|
8996
|
+
attributes: {
|
|
8997
|
+
taskId: task.taskId,
|
|
8998
|
+
channel: channel.kind
|
|
8999
|
+
}
|
|
9000
|
+
});
|
|
9001
|
+
return delivery;
|
|
9002
|
+
},
|
|
9003
|
+
async onDecision({ decision, actor, delivery }, ctx) {
|
|
9004
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
9005
|
+
if (!resolver) return;
|
|
9006
|
+
const { channel } = resolver.resolve();
|
|
9007
|
+
await channel.updateOnDecision?.({
|
|
9008
|
+
delivery,
|
|
9009
|
+
decision,
|
|
9010
|
+
actor
|
|
9011
|
+
});
|
|
9012
|
+
},
|
|
9013
|
+
async onTimeout({ delivery, policy }, ctx) {
|
|
9014
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
9015
|
+
if (!resolver) return;
|
|
9016
|
+
const { channel } = resolver.resolve();
|
|
9017
|
+
await channel.updateOnTimeout?.({
|
|
9018
|
+
delivery,
|
|
9019
|
+
policy
|
|
9020
|
+
});
|
|
9021
|
+
}
|
|
9022
|
+
});
|
|
9023
|
+
|
|
9024
|
+
//#endregion
|
|
9025
|
+
export { AIAgent, AIAgentConnectionWorkflowExpander, AIAgentExecutionHelpersFactory, AIAgentNode, AgentItemPortMap, AgentMessageFactory, AgentOutputFactory, AgentStructuredOutputRepairPromptFactory, AgentStructuredOutputRunner, AgentToolCallPortMap, AgentToolErrorClassifier, AgentToolExecutionCoordinator, AgentToolRepairExhaustedError, AgentToolRepairPolicy, Aggregate, AggregateNode, Assertion, AssertionNode, BM25Index, Callback, CallbackNode, CallbackResultNormalizer, CodemationChatModelConfig, CodemationChatModelFactory, ConnectionCredentialExecutionContextFactory, ConnectionCredentialNode, ConnectionCredentialNodeConfig, ConnectionCredentialNodeConfigFactory, CronTrigger, CronTriggerNode, DeferredMetaToolStrategy, DeferredMetaToolStrategyFactory, Filter, FilterNode, HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES, HttpRequest, HttpRequestNode, If, IfNode, IsTestRun, IsTestRunNode, ManagedModelFetcher, ManualTrigger, ManualTriggerNode, MapData, MapDataNode, Merge, MergeNode, NoOp, NoOpNode, OpenAIChatModelConfig, OpenAIChatModelFactory, OpenAiChatModelPresets, OpenAiStrictJsonSchemaFactory, SSRFBlockedError, Split, SplitNode, SsrfGuard, SubWorkflow, SubWorkflowNode, Switch, SwitchNode, TestTrigger, TestTriggerNode, Wait, WaitDuration, WaitNode, WebhookRespondNowAndContinueError, WebhookRespondNowError, WebhookTrigger, WebhookTriggerNode, WorkflowAuthoringBuilder, WorkflowBranchBuilder, WorkflowChain, apiKeyCredentialType, basicAuthCredentialType, bearerTokenCredentialType, collectionDeleteNode, collectionFindOneNode, collectionGetNode, collectionInsertNode, collectionListNode, collectionUpdateNode, createWorkflowBuilder, defineRestNode, inboxApproval, oauth2ClientCredentialsType, openAiChatModelPresets, registerCoreNodes, workflow };
|
|
8733
9026
|
//# sourceMappingURL=index.js.map
|