@codemation/core-nodes 0.8.1 → 0.10.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 +49 -0
- package/dist/index.cjs +441 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +945 -694
- package/dist/index.d.ts +945 -694
- package/dist/index.js +441 -57
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +13 -1
- package/package.json +2 -2
- package/src/chatModels/CodemationChatModelConfig.ts +1 -1
- package/src/chatModels/CodemationChatModelFactory.ts +2 -61
- package/src/chatModels/ManagedHmacSignerFactory.types.ts +88 -0
- package/src/index.ts +2 -0
- package/src/nodes/AIAgentNode.ts +173 -11
- 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/src/nodes/codemationDocumentScannerNode.ts +131 -0
package/dist/index.cjs
CHANGED
|
@@ -1309,6 +1309,7 @@ const string$1 = (params) => {
|
|
|
1309
1309
|
};
|
|
1310
1310
|
const integer = /^-?\d+$/;
|
|
1311
1311
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
1312
|
+
const boolean$1 = /^(?:true|false)$/i;
|
|
1312
1313
|
const lowercase = /^[^A-Z]*$/;
|
|
1313
1314
|
const uppercase = /^[^a-z]*$/;
|
|
1314
1315
|
|
|
@@ -2087,6 +2088,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
|
|
|
2087
2088
|
$ZodCheckNumberFormat.init(inst, def);
|
|
2088
2089
|
$ZodNumber.init(inst, def);
|
|
2089
2090
|
});
|
|
2091
|
+
const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
2092
|
+
$ZodType.init(inst, def);
|
|
2093
|
+
inst._zod.pattern = boolean$1;
|
|
2094
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
2095
|
+
if (def.coerce) try {
|
|
2096
|
+
payload.value = Boolean(payload.value);
|
|
2097
|
+
} catch (_) {}
|
|
2098
|
+
const input = payload.value;
|
|
2099
|
+
if (typeof input === "boolean") return payload;
|
|
2100
|
+
payload.issues.push({
|
|
2101
|
+
expected: "boolean",
|
|
2102
|
+
code: "invalid_type",
|
|
2103
|
+
input,
|
|
2104
|
+
inst
|
|
2105
|
+
});
|
|
2106
|
+
return payload;
|
|
2107
|
+
};
|
|
2108
|
+
});
|
|
2090
2109
|
const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
2091
2110
|
$ZodType.init(inst, def);
|
|
2092
2111
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3163,6 +3182,13 @@ function _int(Class, params) {
|
|
|
3163
3182
|
});
|
|
3164
3183
|
}
|
|
3165
3184
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3185
|
+
function _boolean(Class, params) {
|
|
3186
|
+
return new Class({
|
|
3187
|
+
type: "boolean",
|
|
3188
|
+
...normalizeParams(params)
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3166
3192
|
function _unknown(Class) {
|
|
3167
3193
|
return new Class({ type: "unknown" });
|
|
3168
3194
|
}
|
|
@@ -3329,6 +3355,17 @@ function _array(Class, element, params) {
|
|
|
3329
3355
|
});
|
|
3330
3356
|
}
|
|
3331
3357
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3358
|
+
function _custom(Class, fn, _params) {
|
|
3359
|
+
const norm = normalizeParams(_params);
|
|
3360
|
+
norm.abort ?? (norm.abort = true);
|
|
3361
|
+
return new Class({
|
|
3362
|
+
type: "custom",
|
|
3363
|
+
check: "custom",
|
|
3364
|
+
fn,
|
|
3365
|
+
...norm
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3332
3369
|
function _refine(Class, fn, _params) {
|
|
3333
3370
|
return new Class({
|
|
3334
3371
|
type: "custom",
|
|
@@ -4352,6 +4389,59 @@ var OpenAiChatModelPresets = class {
|
|
|
4352
4389
|
};
|
|
4353
4390
|
const openAiChatModelPresets = new OpenAiChatModelPresets();
|
|
4354
4391
|
|
|
4392
|
+
//#endregion
|
|
4393
|
+
//#region src/chatModels/ManagedHmacSignerFactory.types.ts
|
|
4394
|
+
/**
|
|
4395
|
+
* Creates an HMAC-signing fetch wrapper that authenticates requests to
|
|
4396
|
+
* Codemation managed services (LLM broker, doc-scanner) with the
|
|
4397
|
+
* Codemation-Hmac v=1 scheme.
|
|
4398
|
+
*
|
|
4399
|
+
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
4400
|
+
* that package (which would create a circular dependency since @codemation/host
|
|
4401
|
+
* depends on @codemation/core-nodes).
|
|
4402
|
+
*
|
|
4403
|
+
* @param workspaceId - Workspace identifier injected by the CP provisioner.
|
|
4404
|
+
* @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
|
|
4405
|
+
* @param options - Optional behaviour flags and test seams.
|
|
4406
|
+
*/
|
|
4407
|
+
function managedHmacFetchFactory(workspaceId, pairingSecret, options) {
|
|
4408
|
+
const signBody = options?.signBody ?? true;
|
|
4409
|
+
return async (input, init) => {
|
|
4410
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
4411
|
+
const method = init?.method ?? "POST";
|
|
4412
|
+
let bodyForSigning = "";
|
|
4413
|
+
if (signBody && init?.body !== void 0 && init.body !== null) bodyForSigning = typeof init.body === "string" ? init.body : await new Response(init.body).text();
|
|
4414
|
+
const authHeader = buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyForSigning, options);
|
|
4415
|
+
const headers = new Headers(init?.headers);
|
|
4416
|
+
headers.set("Authorization", authHeader);
|
|
4417
|
+
const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
|
|
4418
|
+
return fetch(input, {
|
|
4419
|
+
...init,
|
|
4420
|
+
body: outgoingBody,
|
|
4421
|
+
headers
|
|
4422
|
+
});
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Produces a Codemation-Hmac v=1 Authorization header value.
|
|
4427
|
+
* Algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
4428
|
+
*/
|
|
4429
|
+
function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, overrides) {
|
|
4430
|
+
const ts = overrides?.now ? overrides.now() : Math.floor(Date.now() / 1e3);
|
|
4431
|
+
const nonce = overrides?.nonce ? overrides.nonce() : (0, node_crypto.randomBytes)(16).toString("base64");
|
|
4432
|
+
const parsed = new URL(url);
|
|
4433
|
+
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
4434
|
+
const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
|
|
4435
|
+
const baseString = [
|
|
4436
|
+
method.toUpperCase(),
|
|
4437
|
+
path,
|
|
4438
|
+
ts,
|
|
4439
|
+
nonce,
|
|
4440
|
+
bodyHash
|
|
4441
|
+
].join("\n");
|
|
4442
|
+
return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${(0, node_crypto.createHmac)("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4355
4445
|
//#endregion
|
|
4356
4446
|
//#region src/chatModels/CodemationChatModelFactory.ts
|
|
4357
4447
|
let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
@@ -4361,7 +4451,7 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
4361
4451
|
const workspaceId = process.env["WORKSPACE_ID"];
|
|
4362
4452
|
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
4363
4453
|
if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
|
|
4364
|
-
const hmacFetch =
|
|
4454
|
+
const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
|
|
4365
4455
|
const languageModel = (0, __ai_sdk_openai.createOpenAI)({
|
|
4366
4456
|
baseURL: `${gatewayUrl}/v1`,
|
|
4367
4457
|
apiKey: "codemation-managed",
|
|
@@ -4377,52 +4467,6 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
4377
4467
|
}
|
|
4378
4468
|
});
|
|
4379
4469
|
}
|
|
4380
|
-
/**
|
|
4381
|
-
* Creates an HMAC-signed fetch wrapper for use with AI SDK's createOpenAI.
|
|
4382
|
-
* Each call signs the request body with the workspace pairing secret so the
|
|
4383
|
-
* LLM broker can authenticate the workspace without a user-managed API key.
|
|
4384
|
-
*
|
|
4385
|
-
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
4386
|
-
* that package (which would create a circular dependency since @codemation/host
|
|
4387
|
-
* depends on @codemation/core-nodes).
|
|
4388
|
-
*/
|
|
4389
|
-
buildHmacSignedFetch(workspaceId, pairingSecret) {
|
|
4390
|
-
return async (input, init) => {
|
|
4391
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
4392
|
-
const method = init?.method ?? "POST";
|
|
4393
|
-
let bodyString = "";
|
|
4394
|
-
if (init?.body !== void 0 && init.body !== null) if (typeof init.body === "string") bodyString = init.body;
|
|
4395
|
-
else bodyString = await new Response(init.body).text();
|
|
4396
|
-
const authHeader = this.buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyString);
|
|
4397
|
-
const headers = new Headers(init?.headers);
|
|
4398
|
-
headers.set("Authorization", authHeader);
|
|
4399
|
-
const effectiveBody = bodyString || init?.body;
|
|
4400
|
-
return fetch(input, {
|
|
4401
|
-
...init,
|
|
4402
|
-
body: effectiveBody,
|
|
4403
|
-
headers
|
|
4404
|
-
});
|
|
4405
|
-
};
|
|
4406
|
-
}
|
|
4407
|
-
/**
|
|
4408
|
-
* Produces a Codemation-Hmac v1 Authorization header value.
|
|
4409
|
-
* The algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
4410
|
-
*/
|
|
4411
|
-
buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body) {
|
|
4412
|
-
const ts = Math.floor(Date.now() / 1e3);
|
|
4413
|
-
const nonce = (0, node_crypto.randomBytes)(16).toString("base64");
|
|
4414
|
-
const parsed = new URL(url);
|
|
4415
|
-
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
4416
|
-
const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
|
|
4417
|
-
const baseString = [
|
|
4418
|
-
method.toUpperCase(),
|
|
4419
|
-
path,
|
|
4420
|
-
ts,
|
|
4421
|
-
nonce,
|
|
4422
|
-
bodyHash
|
|
4423
|
-
].join("\n");
|
|
4424
|
-
return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${(0, node_crypto.createHmac)("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
|
|
4425
|
-
}
|
|
4426
4470
|
};
|
|
4427
4471
|
CodemationChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
|
|
4428
4472
|
|
|
@@ -4925,6 +4969,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
|
|
|
4925
4969
|
function int(params) {
|
|
4926
4970
|
return _int(ZodNumberFormat, params);
|
|
4927
4971
|
}
|
|
4972
|
+
const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
4973
|
+
$ZodBoolean.init(inst, def);
|
|
4974
|
+
ZodType.init(inst, def);
|
|
4975
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4976
|
+
});
|
|
4977
|
+
function boolean(params) {
|
|
4978
|
+
return _boolean(ZodBoolean, params);
|
|
4979
|
+
}
|
|
4928
4980
|
const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
4929
4981
|
$ZodUnknown.init(inst, def);
|
|
4930
4982
|
ZodType.init(inst, def);
|
|
@@ -5236,6 +5288,9 @@ const ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
5236
5288
|
ZodType.init(inst, def);
|
|
5237
5289
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
5238
5290
|
});
|
|
5291
|
+
function custom(fn, _params) {
|
|
5292
|
+
return _custom(ZodCustom, fn ?? (() => true), _params);
|
|
5293
|
+
}
|
|
5239
5294
|
function refine(fn, _params = {}) {
|
|
5240
5295
|
return _refine(ZodCustom, fn, _params);
|
|
5241
5296
|
}
|
|
@@ -5485,12 +5540,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5485
5540
|
this.repairPolicy = repairPolicy;
|
|
5486
5541
|
}
|
|
5487
5542
|
async execute(args) {
|
|
5543
|
+
if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
|
|
5544
|
+
toolName: c.binding.config.name,
|
|
5545
|
+
toolCallId: c.toolCall.id ?? c.binding.config.name,
|
|
5546
|
+
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.` },
|
|
5547
|
+
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.` })
|
|
5548
|
+
}));
|
|
5488
5549
|
const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
|
|
5489
5550
|
...args,
|
|
5490
5551
|
plannedToolCall
|
|
5491
5552
|
})));
|
|
5492
5553
|
const rejected = results.find((result) => result.status === "rejected");
|
|
5493
|
-
if (rejected?.status === "rejected")
|
|
5554
|
+
if (rejected?.status === "rejected") {
|
|
5555
|
+
const reason = rejected.reason;
|
|
5556
|
+
if (reason instanceof __codemation_core.SuspensionRequest) throw reason;
|
|
5557
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
5558
|
+
}
|
|
5494
5559
|
return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
5495
5560
|
}
|
|
5496
5561
|
async executePlannedToolCall(args) {
|
|
@@ -5575,6 +5640,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5575
5640
|
result
|
|
5576
5641
|
};
|
|
5577
5642
|
} catch (error) {
|
|
5643
|
+
if (error instanceof __codemation_core.SuspensionRequest) {
|
|
5644
|
+
const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
|
|
5645
|
+
const checkpoint = {
|
|
5646
|
+
conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
|
|
5647
|
+
turnCount: args.turnCount ?? 0,
|
|
5648
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
5649
|
+
pendingToolCallId,
|
|
5650
|
+
agentName: args.agentName,
|
|
5651
|
+
modelId: args.modelId ?? ""
|
|
5652
|
+
};
|
|
5653
|
+
const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
|
|
5654
|
+
const augmented = new __codemation_core.SuspensionRequest({
|
|
5655
|
+
...error.request,
|
|
5656
|
+
metadata: {
|
|
5657
|
+
...error.request.metadata,
|
|
5658
|
+
agentCheckpoint: checkpoint,
|
|
5659
|
+
pendingToolCallId,
|
|
5660
|
+
agentReasoning,
|
|
5661
|
+
onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
|
|
5662
|
+
}
|
|
5663
|
+
});
|
|
5664
|
+
await span.end({
|
|
5665
|
+
status: "error",
|
|
5666
|
+
statusMessage: "suspended",
|
|
5667
|
+
endedAt: /* @__PURE__ */ new Date()
|
|
5668
|
+
});
|
|
5669
|
+
throw augmented;
|
|
5670
|
+
}
|
|
5578
5671
|
const classification = this.errorClassifier.classify({
|
|
5579
5672
|
error,
|
|
5580
5673
|
toolName: plannedToolCall.binding.config.name,
|
|
@@ -5750,6 +5843,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5750
5843
|
extractErrorDetails(error) {
|
|
5751
5844
|
return error.details;
|
|
5752
5845
|
}
|
|
5846
|
+
/**
|
|
5847
|
+
* Extracts the text content from the last assistant message in the conversation snapshot.
|
|
5848
|
+
* Used to populate `agentReasoning` in the HITL suspension metadata.
|
|
5849
|
+
*/
|
|
5850
|
+
extractLastAssistantText(conversation) {
|
|
5851
|
+
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
5852
|
+
const msg = conversation[i];
|
|
5853
|
+
if (msg?.role !== "assistant") continue;
|
|
5854
|
+
const content = msg.content;
|
|
5855
|
+
if (typeof content === "string") return content;
|
|
5856
|
+
if (Array.isArray(content)) {
|
|
5857
|
+
const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
|
|
5858
|
+
if (textParts.length > 0) return textParts.join("");
|
|
5859
|
+
}
|
|
5860
|
+
break;
|
|
5861
|
+
}
|
|
5862
|
+
return "";
|
|
5863
|
+
}
|
|
5753
5864
|
serializeIssue(issue$1) {
|
|
5754
5865
|
const result = {
|
|
5755
5866
|
path: [...issue$1.path],
|
|
@@ -6103,6 +6214,7 @@ var AgentItemPortMap = class {
|
|
|
6103
6214
|
//#endregion
|
|
6104
6215
|
//#region src/nodes/AIAgentNode.ts
|
|
6105
6216
|
var _ref, _ref2, _ref3, _ref4, _ref5;
|
|
6217
|
+
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.";
|
|
6106
6218
|
let AIAgentNode = class AIAgentNode$1 {
|
|
6107
6219
|
kind = "node";
|
|
6108
6220
|
outputPorts = ["main"];
|
|
@@ -6120,13 +6232,90 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6120
6232
|
this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
6121
6233
|
}
|
|
6122
6234
|
async execute(args) {
|
|
6123
|
-
const
|
|
6235
|
+
const { ctx } = args;
|
|
6236
|
+
if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
|
|
6237
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6124
6238
|
const itemWithMappedJson = {
|
|
6125
6239
|
...args.item,
|
|
6126
6240
|
json: args.input
|
|
6127
6241
|
};
|
|
6128
6242
|
return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6129
6243
|
}
|
|
6244
|
+
/**
|
|
6245
|
+
* Resume path: re-enters the agent loop after a HITL suspension.
|
|
6246
|
+
* Reconstructs the conversation from the checkpoint, injects the human decision
|
|
6247
|
+
* as a tool_result, and continues the loop from where it suspended.
|
|
6248
|
+
*/
|
|
6249
|
+
async executeResumed(args, resumeContext) {
|
|
6250
|
+
const { ctx } = args;
|
|
6251
|
+
const taskMetadata = resumeContext.task.metadata ?? {};
|
|
6252
|
+
const checkpoint = taskMetadata["agentCheckpoint"];
|
|
6253
|
+
const onRejected = taskMetadata["onRejected"] ?? "return";
|
|
6254
|
+
if (!checkpoint) {
|
|
6255
|
+
const prepared$1 = await this.getOrPrepareExecution(ctx);
|
|
6256
|
+
const itemWithMappedJson = {
|
|
6257
|
+
...args.item,
|
|
6258
|
+
json: args.input
|
|
6259
|
+
};
|
|
6260
|
+
return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6261
|
+
}
|
|
6262
|
+
if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
|
|
6263
|
+
const decision = this.normalizeDecision(resumeContext);
|
|
6264
|
+
if (decision.status === "rejected" && onRejected === "halt") return;
|
|
6265
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6266
|
+
const item = args.item;
|
|
6267
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
6268
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
|
|
6269
|
+
const toolResultEntry = {
|
|
6270
|
+
toolName: checkpoint.pendingToolCallId,
|
|
6271
|
+
toolCallId: checkpoint.pendingToolCallId,
|
|
6272
|
+
result: decision,
|
|
6273
|
+
serialized: JSON.stringify(decision)
|
|
6274
|
+
};
|
|
6275
|
+
const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
|
|
6276
|
+
const loopResult = await this.runTurnLoopUntilFinalAnswer({
|
|
6277
|
+
prepared,
|
|
6278
|
+
itemInputsByPort,
|
|
6279
|
+
itemScopedTools,
|
|
6280
|
+
conversation,
|
|
6281
|
+
resumedTurnCount: checkpoint.turnCount,
|
|
6282
|
+
resumedToolCallCount: checkpoint.toolCallCount
|
|
6283
|
+
});
|
|
6284
|
+
await ctx.telemetry.recordMetric({
|
|
6285
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentTurns,
|
|
6286
|
+
value: loopResult.turnCount
|
|
6287
|
+
});
|
|
6288
|
+
await ctx.telemetry.recordMetric({
|
|
6289
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentToolCalls,
|
|
6290
|
+
value: loopResult.toolCallCount
|
|
6291
|
+
});
|
|
6292
|
+
const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
|
|
6293
|
+
return this.buildOutputItem(item, outputJson).json;
|
|
6294
|
+
}
|
|
6295
|
+
/**
|
|
6296
|
+
* Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
|
|
6297
|
+
* suitable for injection as a tool_result content.
|
|
6298
|
+
*/
|
|
6299
|
+
normalizeDecision(resumeContext) {
|
|
6300
|
+
const { decision } = resumeContext;
|
|
6301
|
+
if (decision.kind === "decided") {
|
|
6302
|
+
const value = decision.value;
|
|
6303
|
+
return {
|
|
6304
|
+
status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
|
|
6305
|
+
value: decision.value,
|
|
6306
|
+
actor: decision.actor,
|
|
6307
|
+
decidedAt: decision.decidedAt.toISOString()
|
|
6308
|
+
};
|
|
6309
|
+
}
|
|
6310
|
+
if (decision.kind === "timed_out") return {
|
|
6311
|
+
status: "timed_out",
|
|
6312
|
+
at: decision.at.toISOString()
|
|
6313
|
+
};
|
|
6314
|
+
return {
|
|
6315
|
+
status: "auto_accepted",
|
|
6316
|
+
at: decision.at.toISOString()
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6130
6319
|
async getOrPrepareExecution(ctx) {
|
|
6131
6320
|
let pending = this.preparedByExecutionContext.get(ctx);
|
|
6132
6321
|
if (!pending) {
|
|
@@ -6247,7 +6436,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6247
6436
|
const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
|
|
6248
6437
|
const { ctx, guardrails, toolLoadingStrategy } = prepared;
|
|
6249
6438
|
let finalText = "";
|
|
6250
|
-
let toolCallCount = 0;
|
|
6439
|
+
let toolCallCount = args.resumedToolCallCount ?? 0;
|
|
6251
6440
|
let turnCount = 0;
|
|
6252
6441
|
const repairAttemptsByToolName = /* @__PURE__ */ new Map();
|
|
6253
6442
|
/** Tool IDs surfaced by find_tools across all prior turns in this item run. */
|
|
@@ -6288,11 +6477,17 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6288
6477
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
|
|
6289
6478
|
toolCallCount += plannedToolCalls.length;
|
|
6290
6479
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
6480
|
+
const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
|
|
6481
|
+
const conversationWithAssistant = [...conversation, assistantMsg];
|
|
6291
6482
|
const executed = await this.toolExecutionCoordinator.execute({
|
|
6292
6483
|
plannedToolCalls,
|
|
6293
6484
|
ctx,
|
|
6294
6485
|
agentName: this.getAgentDisplayName(ctx),
|
|
6295
|
-
repairAttemptsByToolName
|
|
6486
|
+
repairAttemptsByToolName,
|
|
6487
|
+
conversationSnapshot: conversationWithAssistant,
|
|
6488
|
+
turnCount,
|
|
6489
|
+
toolCallCount,
|
|
6490
|
+
modelId: this.resolveChatModelName(ctx.config.chatModel)
|
|
6296
6491
|
});
|
|
6297
6492
|
coordinatorExecutedCalls.push(...executed);
|
|
6298
6493
|
}
|
|
@@ -6354,6 +6549,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6354
6549
|
connectionNodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
6355
6550
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
|
|
6356
6551
|
});
|
|
6552
|
+
const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
|
|
6357
6553
|
return {
|
|
6358
6554
|
config: entry.config,
|
|
6359
6555
|
inputSchema: entry.runtime.inputSchema,
|
|
@@ -6368,11 +6564,22 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6368
6564
|
items,
|
|
6369
6565
|
hooks
|
|
6370
6566
|
});
|
|
6371
|
-
}
|
|
6567
|
+
},
|
|
6568
|
+
...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
|
|
6372
6569
|
};
|
|
6373
6570
|
});
|
|
6374
6571
|
}
|
|
6375
6572
|
/**
|
|
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.
|
|
6575
|
+
*/
|
|
6576
|
+
resolveHumanApprovalBehavior(config$1) {
|
|
6577
|
+
if (!this.isNodeBackedToolConfig(config$1)) return void 0;
|
|
6578
|
+
const marker = config$1.node.humanApprovalToolBehavior;
|
|
6579
|
+
if (marker === void 0) return void 0;
|
|
6580
|
+
return { onRejected: marker.onRejected ?? "return" };
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6376
6583
|
* Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
|
|
6377
6584
|
* and strategy tools (find_tools + discovered MCP tools).
|
|
6378
6585
|
* Strategy tools take precedence for names that overlap.
|
|
@@ -6402,6 +6609,8 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6402
6609
|
/**
|
|
6403
6610
|
* Builds a ToolSet from resolved tools for strategy initialization.
|
|
6404
6611
|
* The strategy uses this for its "always-included" node-backed tool descriptions.
|
|
6612
|
+
* HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
|
|
6613
|
+
* appended to their description.
|
|
6405
6614
|
*/
|
|
6406
6615
|
buildToolSetFromResolved(resolvedTools) {
|
|
6407
6616
|
if (resolvedTools.length === 0) return {};
|
|
@@ -6411,8 +6620,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6411
6620
|
schemaName: entry.config.name,
|
|
6412
6621
|
requireObjectRoot: true
|
|
6413
6622
|
});
|
|
6623
|
+
const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
|
|
6624
|
+
const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
|
|
6414
6625
|
toolSet[entry.config.name] = {
|
|
6415
|
-
description
|
|
6626
|
+
description,
|
|
6416
6627
|
inputSchema: (0, ai.jsonSchema)(schemaRecord)
|
|
6417
6628
|
};
|
|
6418
6629
|
}
|
|
@@ -6440,8 +6651,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6440
6651
|
schemaName: entry.config.name,
|
|
6441
6652
|
requireObjectRoot: true
|
|
6442
6653
|
});
|
|
6654
|
+
const baseDescription = entry.config.description;
|
|
6655
|
+
const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
|
|
6443
6656
|
toolSet[entry.config.name] = {
|
|
6444
|
-
description
|
|
6657
|
+
description,
|
|
6445
6658
|
inputSchema: (0, ai.jsonSchema)(schemaRecord)
|
|
6446
6659
|
};
|
|
6447
6660
|
}
|
|
@@ -6609,7 +6822,11 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6609
6822
|
});
|
|
6610
6823
|
try {
|
|
6611
6824
|
const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
|
|
6612
|
-
const
|
|
6825
|
+
const schemaRecord = this.isZodSchema(schema) ? this.executionHelpers.createJsonSchemaRecord(schema, {
|
|
6826
|
+
schemaName: structuredOptions?.schemaName ?? "structured_output",
|
|
6827
|
+
requireObjectRoot: true
|
|
6828
|
+
}) : schema;
|
|
6829
|
+
const outputSchema = ai.Output.object({ schema: (0, ai.jsonSchema)(schemaRecord) });
|
|
6613
6830
|
const result = await (0, ai.generateText)({
|
|
6614
6831
|
model: model.languageModel,
|
|
6615
6832
|
messages: [...messages],
|
|
@@ -8758,6 +8975,173 @@ const collectionDeleteNode = (0, __codemation_core.defineNode)({
|
|
|
8758
8975
|
}
|
|
8759
8976
|
});
|
|
8760
8977
|
|
|
8978
|
+
//#endregion
|
|
8979
|
+
//#region src/nodes/InboxApprovalNode.types.ts
|
|
8980
|
+
function resolveSubjectField(field, item) {
|
|
8981
|
+
return typeof field === "function" ? field({ item }) : field;
|
|
8982
|
+
}
|
|
8983
|
+
/**
|
|
8984
|
+
* Auto-detecting inbox approval node.
|
|
8985
|
+
*
|
|
8986
|
+
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
8987
|
+
* at runtime:
|
|
8988
|
+
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
8989
|
+
* - Otherwise: routes to the local inbox.
|
|
8990
|
+
*
|
|
8991
|
+
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
8992
|
+
*/
|
|
8993
|
+
const inboxApproval = (0, __codemation_core.defineHumanApprovalNode)({
|
|
8994
|
+
key: "inbox.approval",
|
|
8995
|
+
title: "Inbox Approval",
|
|
8996
|
+
description: "Suspend and wait for a human reviewer to approve or reject.",
|
|
8997
|
+
icon: "lucide:inbox",
|
|
8998
|
+
channel: "inbox",
|
|
8999
|
+
configSchema: object({
|
|
9000
|
+
title: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
9001
|
+
body: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
9002
|
+
priority: _enum([
|
|
9003
|
+
"low",
|
|
9004
|
+
"normal",
|
|
9005
|
+
"high"
|
|
9006
|
+
]).default("normal"),
|
|
9007
|
+
timeout: string().default("24h"),
|
|
9008
|
+
onTimeout: _enum(["halt", "auto-accept"]).default("halt")
|
|
9009
|
+
}),
|
|
9010
|
+
decisionSchema: object({
|
|
9011
|
+
approved: boolean(),
|
|
9012
|
+
note: string().optional()
|
|
9013
|
+
}),
|
|
9014
|
+
defaultTimeout: "24h",
|
|
9015
|
+
defaultOnTimeout: "halt",
|
|
9016
|
+
async deliver({ task, config: config$1, item }, ctx) {
|
|
9017
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9018
|
+
if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
|
|
9019
|
+
const { channel, workspaceId } = resolver.resolve();
|
|
9020
|
+
const subject = {
|
|
9021
|
+
title: resolveSubjectField(config$1.title, item),
|
|
9022
|
+
summary: resolveSubjectField(config$1.body, item),
|
|
9023
|
+
attributes: {
|
|
9024
|
+
workflowId: ctx.workflowId,
|
|
9025
|
+
item: item.json
|
|
9026
|
+
}
|
|
9027
|
+
};
|
|
9028
|
+
const delivery = await channel.deliver({
|
|
9029
|
+
task,
|
|
9030
|
+
subject,
|
|
9031
|
+
priority: config$1.priority,
|
|
9032
|
+
item,
|
|
9033
|
+
workspaceId
|
|
9034
|
+
});
|
|
9035
|
+
ctx.telemetry.addSpanEvent({
|
|
9036
|
+
name: "hitl.task.delivered",
|
|
9037
|
+
attributes: {
|
|
9038
|
+
taskId: task.taskId,
|
|
9039
|
+
channel: channel.kind
|
|
9040
|
+
}
|
|
9041
|
+
});
|
|
9042
|
+
return delivery;
|
|
9043
|
+
},
|
|
9044
|
+
async onDecision({ decision, actor, delivery }, ctx) {
|
|
9045
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9046
|
+
if (!resolver) return;
|
|
9047
|
+
const { channel } = resolver.resolve();
|
|
9048
|
+
await channel.updateOnDecision?.({
|
|
9049
|
+
delivery,
|
|
9050
|
+
decision,
|
|
9051
|
+
actor
|
|
9052
|
+
});
|
|
9053
|
+
},
|
|
9054
|
+
async onTimeout({ delivery, policy }, ctx) {
|
|
9055
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9056
|
+
if (!resolver) return;
|
|
9057
|
+
const { channel } = resolver.resolve();
|
|
9058
|
+
await channel.updateOnTimeout?.({
|
|
9059
|
+
delivery,
|
|
9060
|
+
policy
|
|
9061
|
+
});
|
|
9062
|
+
}
|
|
9063
|
+
});
|
|
9064
|
+
|
|
9065
|
+
//#endregion
|
|
9066
|
+
//#region src/nodes/codemationDocumentScannerNode.ts
|
|
9067
|
+
const ANALYZER_TYPES = [
|
|
9068
|
+
"document",
|
|
9069
|
+
"invoice",
|
|
9070
|
+
"image",
|
|
9071
|
+
"auto"
|
|
9072
|
+
];
|
|
9073
|
+
const codemationDocumentScannerNode = (0, __codemation_core.defineNode)({
|
|
9074
|
+
key: "codemation.document-scanner",
|
|
9075
|
+
title: "Codemation Document Scanner",
|
|
9076
|
+
description: "Analyzes a binary attachment (document or image) via the managed Codemation document-scanning service and returns markdown text plus structured fields. No Azure credential required — auth uses the workspace pairing secret. Enable includeConfidence to get per-field confidence scores (0–1).",
|
|
9077
|
+
icon: "lucide:scan-text",
|
|
9078
|
+
input: {
|
|
9079
|
+
binaryField: "data",
|
|
9080
|
+
analyzerType: "auto",
|
|
9081
|
+
contentType: void 0,
|
|
9082
|
+
includeConfidence: false,
|
|
9083
|
+
maxBytes: void 0
|
|
9084
|
+
},
|
|
9085
|
+
configSchema: object({
|
|
9086
|
+
binaryField: string().optional(),
|
|
9087
|
+
analyzerType: _enum(ANALYZER_TYPES).optional(),
|
|
9088
|
+
contentType: string().optional(),
|
|
9089
|
+
includeConfidence: boolean().optional(),
|
|
9090
|
+
maxBytes: number().int().positive().optional()
|
|
9091
|
+
}),
|
|
9092
|
+
inspectorSummary({ config: config$1 }) {
|
|
9093
|
+
const cfg = config$1;
|
|
9094
|
+
const rows = [{
|
|
9095
|
+
label: "Analyzer type",
|
|
9096
|
+
value: cfg.analyzerType ?? "auto"
|
|
9097
|
+
}];
|
|
9098
|
+
const binaryField = cfg.binaryField ?? "data";
|
|
9099
|
+
if (binaryField !== "data") rows.push({
|
|
9100
|
+
label: "Binary field",
|
|
9101
|
+
value: binaryField
|
|
9102
|
+
});
|
|
9103
|
+
if (cfg.includeConfidence) rows.push({
|
|
9104
|
+
label: "Confidence",
|
|
9105
|
+
value: "enabled"
|
|
9106
|
+
});
|
|
9107
|
+
if (cfg.contentType) rows.push({
|
|
9108
|
+
label: "Content type",
|
|
9109
|
+
value: cfg.contentType
|
|
9110
|
+
});
|
|
9111
|
+
return rows;
|
|
9112
|
+
},
|
|
9113
|
+
async execute({ item, ctx }, { config: rawConfig }) {
|
|
9114
|
+
const config$1 = rawConfig;
|
|
9115
|
+
const gatewayUrl = process.env["DOC_SCANNER_GATEWAY_URL"];
|
|
9116
|
+
if (!gatewayUrl) throw new Error("Codemation Document Scanner not available in this environment (DOC_SCANNER_GATEWAY_URL is not set).");
|
|
9117
|
+
const workspaceId = process.env["WORKSPACE_ID"];
|
|
9118
|
+
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
9119
|
+
if (!workspaceId || !pairingSecret) throw new Error("Codemation Document Scanner not available (workspace pairing is not configured).");
|
|
9120
|
+
const binaryField = config$1.binaryField ?? "data";
|
|
9121
|
+
const attachment = item.binary?.[binaryField];
|
|
9122
|
+
if (!attachment) throw new Error(`Codemation Document Scanner: no binary attachment at key "${binaryField}".`);
|
|
9123
|
+
const body = await ctx.binary.getBytes(attachment, config$1.maxBytes);
|
|
9124
|
+
const contentType = config$1.contentType ?? attachment.mimeType ?? "application/octet-stream";
|
|
9125
|
+
const analyzerType = config$1.analyzerType ?? "auto";
|
|
9126
|
+
const confidenceSuffix = config$1.includeConfidence ?? false ? "&confidence=true" : "";
|
|
9127
|
+
const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
|
|
9128
|
+
const response = await managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false })(url, {
|
|
9129
|
+
method: "POST",
|
|
9130
|
+
body: body.buffer,
|
|
9131
|
+
headers: {
|
|
9132
|
+
"Content-Type": contentType,
|
|
9133
|
+
"Content-Length": String(body.byteLength),
|
|
9134
|
+
"X-Codemation-Caller": "workflow-node"
|
|
9135
|
+
}
|
|
9136
|
+
});
|
|
9137
|
+
if (!response.ok) {
|
|
9138
|
+
const text = await response.text().catch(() => "(unreadable)");
|
|
9139
|
+
throw new Error(`Codemation Document Scanner: service responded ${response.status} ${response.statusText} — ${text}`);
|
|
9140
|
+
}
|
|
9141
|
+
return await response.json();
|
|
9142
|
+
}
|
|
9143
|
+
});
|
|
9144
|
+
|
|
8761
9145
|
//#endregion
|
|
8762
9146
|
exports.AIAgent = AIAgent;
|
|
8763
9147
|
exports.AIAgentConnectionWorkflowExpander = AIAgentConnectionWorkflowExpander;
|
|
@@ -8986,6 +9370,7 @@ exports.WorkflowChain = WorkflowChain;
|
|
|
8986
9370
|
exports.apiKeyCredentialType = apiKeyCredentialType;
|
|
8987
9371
|
exports.basicAuthCredentialType = basicAuthCredentialType;
|
|
8988
9372
|
exports.bearerTokenCredentialType = bearerTokenCredentialType;
|
|
9373
|
+
exports.codemationDocumentScannerNode = codemationDocumentScannerNode;
|
|
8989
9374
|
exports.collectionDeleteNode = collectionDeleteNode;
|
|
8990
9375
|
exports.collectionFindOneNode = collectionFindOneNode;
|
|
8991
9376
|
exports.collectionGetNode = collectionGetNode;
|
|
@@ -8994,6 +9379,7 @@ exports.collectionListNode = collectionListNode;
|
|
|
8994
9379
|
exports.collectionUpdateNode = collectionUpdateNode;
|
|
8995
9380
|
exports.createWorkflowBuilder = createWorkflowBuilder;
|
|
8996
9381
|
exports.defineRestNode = defineRestNode;
|
|
9382
|
+
exports.inboxApproval = inboxApproval;
|
|
8997
9383
|
exports.oauth2ClientCredentialsType = oauth2ClientCredentialsType;
|
|
8998
9384
|
exports.openAiChatModelPresets = openAiChatModelPresets;
|
|
8999
9385
|
exports.registerCoreNodes = registerCoreNodes;
|