@codemation/core-nodes-ocr 0.2.2 → 0.2.4
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 +14 -0
- package/dist/{analyzeInvoiceNode-uVwe3GHD.js → analyzeInvoiceNode-BqZsN8iL.js} +4 -36
- package/dist/analyzeInvoiceNode-BqZsN8iL.js.map +1 -0
- package/dist/{analyzeInvoiceNode-BIw8j_Zb.cjs → analyzeInvoiceNode-CmMsifbw.cjs} +4 -36
- package/dist/analyzeInvoiceNode-CmMsifbw.cjs.map +1 -0
- package/dist/codemation.plugin.cjs +392 -56
- package/dist/codemation.plugin.cjs.map +1 -1
- package/dist/codemation.plugin.d.cts +1 -3
- package/dist/codemation.plugin.d.ts +1 -3
- package/dist/codemation.plugin.js +393 -57
- package/dist/codemation.plugin.js.map +1 -1
- package/dist/{index-C2KJPzqN.d.ts → index-DF2ht42F.d.ts} +409 -321
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/metadata.json +1 -1
- package/dist/{runtimeTypes-ffl603pJ.d.cts → runtimeTypes-WCvsnJMY.d.cts} +281 -193
- package/package.json +2 -2
- package/src/nodes/analyzeDocumentNode.ts +1 -2
- package/src/nodes/analyzeImageNode.ts +1 -2
- package/src/nodes/analyzeInvoiceNode.ts +1 -2
- package/dist/analyzeInvoiceNode-BIw8j_Zb.cjs.map +0 -1
- package/dist/analyzeInvoiceNode-uVwe3GHD.js.map +0 -1
- package/src/lib/readBinaryBody.ts +0 -51
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-BaqVhFee.cjs');
|
|
2
|
-
const require_analyzeInvoiceNode = require('./analyzeInvoiceNode-
|
|
2
|
+
const require_analyzeInvoiceNode = require('./analyzeInvoiceNode-CmMsifbw.cjs');
|
|
3
3
|
const require_token_util$1 = require('./token-util-B2kSJtEV.cjs');
|
|
4
4
|
let __codemation_core = require("@codemation/core");
|
|
5
5
|
__codemation_core = require_chunk.__toESM(__codemation_core);
|
|
@@ -8289,6 +8289,59 @@ var OpenAiChatModelPresets = class {
|
|
|
8289
8289
|
};
|
|
8290
8290
|
const openAiChatModelPresets = new OpenAiChatModelPresets();
|
|
8291
8291
|
|
|
8292
|
+
//#endregion
|
|
8293
|
+
//#region ../core-nodes/src/chatModels/ManagedHmacSignerFactory.types.ts
|
|
8294
|
+
/**
|
|
8295
|
+
* Creates an HMAC-signing fetch wrapper that authenticates requests to
|
|
8296
|
+
* Codemation managed services (LLM broker, doc-scanner) with the
|
|
8297
|
+
* Codemation-Hmac v=1 scheme.
|
|
8298
|
+
*
|
|
8299
|
+
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
8300
|
+
* that package (which would create a circular dependency since @codemation/host
|
|
8301
|
+
* depends on @codemation/core-nodes).
|
|
8302
|
+
*
|
|
8303
|
+
* @param workspaceId - Workspace identifier injected by the CP provisioner.
|
|
8304
|
+
* @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
|
|
8305
|
+
* @param options - Optional behaviour flags and test seams.
|
|
8306
|
+
*/
|
|
8307
|
+
function managedHmacFetchFactory(workspaceId, pairingSecret, options) {
|
|
8308
|
+
const signBody = options?.signBody ?? true;
|
|
8309
|
+
return async (input, init) => {
|
|
8310
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
8311
|
+
const method = init?.method ?? "POST";
|
|
8312
|
+
let bodyForSigning = "";
|
|
8313
|
+
if (signBody && init?.body !== void 0 && init.body !== null) bodyForSigning = typeof init.body === "string" ? init.body : await new Response(init.body).text();
|
|
8314
|
+
const authHeader = buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyForSigning, options);
|
|
8315
|
+
const headers = new Headers(init?.headers);
|
|
8316
|
+
headers.set("Authorization", authHeader);
|
|
8317
|
+
const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
|
|
8318
|
+
return fetch(input, {
|
|
8319
|
+
...init,
|
|
8320
|
+
body: outgoingBody,
|
|
8321
|
+
headers
|
|
8322
|
+
});
|
|
8323
|
+
};
|
|
8324
|
+
}
|
|
8325
|
+
/**
|
|
8326
|
+
* Produces a Codemation-Hmac v=1 Authorization header value.
|
|
8327
|
+
* Algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
8328
|
+
*/
|
|
8329
|
+
function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, overrides) {
|
|
8330
|
+
const ts = overrides?.now ? overrides.now() : Math.floor(Date.now() / 1e3);
|
|
8331
|
+
const nonce = overrides?.nonce ? overrides.nonce() : (0, node_crypto.randomBytes)(16).toString("base64");
|
|
8332
|
+
const parsed = new URL(url);
|
|
8333
|
+
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
8334
|
+
const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
|
|
8335
|
+
const baseString = [
|
|
8336
|
+
method.toUpperCase(),
|
|
8337
|
+
path,
|
|
8338
|
+
ts,
|
|
8339
|
+
nonce,
|
|
8340
|
+
bodyHash
|
|
8341
|
+
].join("\n");
|
|
8342
|
+
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")}`;
|
|
8343
|
+
}
|
|
8344
|
+
|
|
8292
8345
|
//#endregion
|
|
8293
8346
|
//#region ../core-nodes/src/chatModels/CodemationChatModelFactory.ts
|
|
8294
8347
|
let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
@@ -8298,7 +8351,7 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
8298
8351
|
const workspaceId = process.env["WORKSPACE_ID"];
|
|
8299
8352
|
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
8300
8353
|
if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
|
|
8301
|
-
const hmacFetch =
|
|
8354
|
+
const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
|
|
8302
8355
|
const languageModel = createOpenAI({
|
|
8303
8356
|
baseURL: `${gatewayUrl}/v1`,
|
|
8304
8357
|
apiKey: "codemation-managed",
|
|
@@ -8314,52 +8367,6 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
8314
8367
|
}
|
|
8315
8368
|
});
|
|
8316
8369
|
}
|
|
8317
|
-
/**
|
|
8318
|
-
* Creates an HMAC-signed fetch wrapper for use with AI SDK's createOpenAI.
|
|
8319
|
-
* Each call signs the request body with the workspace pairing secret so the
|
|
8320
|
-
* LLM broker can authenticate the workspace without a user-managed API key.
|
|
8321
|
-
*
|
|
8322
|
-
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
8323
|
-
* that package (which would create a circular dependency since @codemation/host
|
|
8324
|
-
* depends on @codemation/core-nodes).
|
|
8325
|
-
*/
|
|
8326
|
-
buildHmacSignedFetch(workspaceId, pairingSecret) {
|
|
8327
|
-
return async (input, init) => {
|
|
8328
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
8329
|
-
const method = init?.method ?? "POST";
|
|
8330
|
-
let bodyString = "";
|
|
8331
|
-
if (init?.body !== void 0 && init.body !== null) if (typeof init.body === "string") bodyString = init.body;
|
|
8332
|
-
else bodyString = await new Response(init.body).text();
|
|
8333
|
-
const authHeader = this.buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyString);
|
|
8334
|
-
const headers = new Headers(init?.headers);
|
|
8335
|
-
headers.set("Authorization", authHeader);
|
|
8336
|
-
const effectiveBody = bodyString || init?.body;
|
|
8337
|
-
return fetch(input, {
|
|
8338
|
-
...init,
|
|
8339
|
-
body: effectiveBody,
|
|
8340
|
-
headers
|
|
8341
|
-
});
|
|
8342
|
-
};
|
|
8343
|
-
}
|
|
8344
|
-
/**
|
|
8345
|
-
* Produces a Codemation-Hmac v1 Authorization header value.
|
|
8346
|
-
* The algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
8347
|
-
*/
|
|
8348
|
-
buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body) {
|
|
8349
|
-
const ts = Math.floor(Date.now() / 1e3);
|
|
8350
|
-
const nonce = (0, node_crypto.randomBytes)(16).toString("base64");
|
|
8351
|
-
const parsed = new URL(url);
|
|
8352
|
-
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
8353
|
-
const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
|
|
8354
|
-
const baseString = [
|
|
8355
|
-
method.toUpperCase(),
|
|
8356
|
-
path,
|
|
8357
|
-
ts,
|
|
8358
|
-
nonce,
|
|
8359
|
-
bodyHash
|
|
8360
|
-
].join("\n");
|
|
8361
|
-
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")}`;
|
|
8362
|
-
}
|
|
8363
8370
|
};
|
|
8364
8371
|
CodemationChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
|
|
8365
8372
|
|
|
@@ -8738,12 +8745,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
8738
8745
|
this.repairPolicy = repairPolicy;
|
|
8739
8746
|
}
|
|
8740
8747
|
async execute(args) {
|
|
8748
|
+
if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
|
|
8749
|
+
toolName: c.binding.config.name,
|
|
8750
|
+
toolCallId: c.toolCall.id ?? c.binding.config.name,
|
|
8751
|
+
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.` },
|
|
8752
|
+
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.` })
|
|
8753
|
+
}));
|
|
8741
8754
|
const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
|
|
8742
8755
|
...args,
|
|
8743
8756
|
plannedToolCall
|
|
8744
8757
|
})));
|
|
8745
8758
|
const rejected = results.find((result) => result.status === "rejected");
|
|
8746
|
-
if (rejected?.status === "rejected")
|
|
8759
|
+
if (rejected?.status === "rejected") {
|
|
8760
|
+
const reason = rejected.reason;
|
|
8761
|
+
if (reason instanceof __codemation_core.SuspensionRequest) throw reason;
|
|
8762
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
8763
|
+
}
|
|
8747
8764
|
return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
8748
8765
|
}
|
|
8749
8766
|
async executePlannedToolCall(args) {
|
|
@@ -8828,6 +8845,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
8828
8845
|
result
|
|
8829
8846
|
};
|
|
8830
8847
|
} catch (error) {
|
|
8848
|
+
if (error instanceof __codemation_core.SuspensionRequest) {
|
|
8849
|
+
const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
|
|
8850
|
+
const checkpoint = {
|
|
8851
|
+
conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
|
|
8852
|
+
turnCount: args.turnCount ?? 0,
|
|
8853
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
8854
|
+
pendingToolCallId,
|
|
8855
|
+
agentName: args.agentName,
|
|
8856
|
+
modelId: args.modelId ?? ""
|
|
8857
|
+
};
|
|
8858
|
+
const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
|
|
8859
|
+
const augmented = new __codemation_core.SuspensionRequest({
|
|
8860
|
+
...error.request,
|
|
8861
|
+
metadata: {
|
|
8862
|
+
...error.request.metadata,
|
|
8863
|
+
agentCheckpoint: checkpoint,
|
|
8864
|
+
pendingToolCallId,
|
|
8865
|
+
agentReasoning,
|
|
8866
|
+
onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
|
|
8867
|
+
}
|
|
8868
|
+
});
|
|
8869
|
+
await span.end({
|
|
8870
|
+
status: "error",
|
|
8871
|
+
statusMessage: "suspended",
|
|
8872
|
+
endedAt: /* @__PURE__ */ new Date()
|
|
8873
|
+
});
|
|
8874
|
+
throw augmented;
|
|
8875
|
+
}
|
|
8831
8876
|
const classification = this.errorClassifier.classify({
|
|
8832
8877
|
error,
|
|
8833
8878
|
toolName: plannedToolCall.binding.config.name,
|
|
@@ -9003,6 +9048,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
9003
9048
|
extractErrorDetails(error) {
|
|
9004
9049
|
return error.details;
|
|
9005
9050
|
}
|
|
9051
|
+
/**
|
|
9052
|
+
* Extracts the text content from the last assistant message in the conversation snapshot.
|
|
9053
|
+
* Used to populate `agentReasoning` in the HITL suspension metadata.
|
|
9054
|
+
*/
|
|
9055
|
+
extractLastAssistantText(conversation) {
|
|
9056
|
+
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
9057
|
+
const msg = conversation[i];
|
|
9058
|
+
if (msg?.role !== "assistant") continue;
|
|
9059
|
+
const content = msg.content;
|
|
9060
|
+
if (typeof content === "string") return content;
|
|
9061
|
+
if (Array.isArray(content)) {
|
|
9062
|
+
const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
|
|
9063
|
+
if (textParts.length > 0) return textParts.join("");
|
|
9064
|
+
}
|
|
9065
|
+
break;
|
|
9066
|
+
}
|
|
9067
|
+
return "";
|
|
9068
|
+
}
|
|
9006
9069
|
serializeIssue(issue) {
|
|
9007
9070
|
const result = {
|
|
9008
9071
|
path: [...issue.path],
|
|
@@ -15333,6 +15396,7 @@ var AgentItemPortMap = class {
|
|
|
15333
15396
|
//#endregion
|
|
15334
15397
|
//#region ../core-nodes/src/nodes/AIAgentNode.ts
|
|
15335
15398
|
var _ref, _ref2, _ref3, _ref4, _ref5;
|
|
15399
|
+
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.";
|
|
15336
15400
|
let AIAgentNode = class AIAgentNode$1 {
|
|
15337
15401
|
kind = "node";
|
|
15338
15402
|
outputPorts = ["main"];
|
|
@@ -15350,13 +15414,90 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15350
15414
|
this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
15351
15415
|
}
|
|
15352
15416
|
async execute(args) {
|
|
15353
|
-
const
|
|
15417
|
+
const { ctx } = args;
|
|
15418
|
+
if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
|
|
15419
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
15354
15420
|
const itemWithMappedJson = {
|
|
15355
15421
|
...args.item,
|
|
15356
15422
|
json: args.input
|
|
15357
15423
|
};
|
|
15358
15424
|
return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
15359
15425
|
}
|
|
15426
|
+
/**
|
|
15427
|
+
* Resume path: re-enters the agent loop after a HITL suspension.
|
|
15428
|
+
* Reconstructs the conversation from the checkpoint, injects the human decision
|
|
15429
|
+
* as a tool_result, and continues the loop from where it suspended.
|
|
15430
|
+
*/
|
|
15431
|
+
async executeResumed(args, resumeContext) {
|
|
15432
|
+
const { ctx } = args;
|
|
15433
|
+
const taskMetadata = resumeContext.task.metadata ?? {};
|
|
15434
|
+
const checkpoint = taskMetadata["agentCheckpoint"];
|
|
15435
|
+
const onRejected = taskMetadata["onRejected"] ?? "return";
|
|
15436
|
+
if (!checkpoint) {
|
|
15437
|
+
const prepared$1 = await this.getOrPrepareExecution(ctx);
|
|
15438
|
+
const itemWithMappedJson = {
|
|
15439
|
+
...args.item,
|
|
15440
|
+
json: args.input
|
|
15441
|
+
};
|
|
15442
|
+
return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
15443
|
+
}
|
|
15444
|
+
if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
|
|
15445
|
+
const decision = this.normalizeDecision(resumeContext);
|
|
15446
|
+
if (decision.status === "rejected" && onRejected === "halt") return;
|
|
15447
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
15448
|
+
const item = args.item;
|
|
15449
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
15450
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
|
|
15451
|
+
const toolResultEntry = {
|
|
15452
|
+
toolName: checkpoint.pendingToolCallId,
|
|
15453
|
+
toolCallId: checkpoint.pendingToolCallId,
|
|
15454
|
+
result: decision,
|
|
15455
|
+
serialized: JSON.stringify(decision)
|
|
15456
|
+
};
|
|
15457
|
+
const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
|
|
15458
|
+
const loopResult = await this.runTurnLoopUntilFinalAnswer({
|
|
15459
|
+
prepared,
|
|
15460
|
+
itemInputsByPort,
|
|
15461
|
+
itemScopedTools,
|
|
15462
|
+
conversation,
|
|
15463
|
+
resumedTurnCount: checkpoint.turnCount,
|
|
15464
|
+
resumedToolCallCount: checkpoint.toolCallCount
|
|
15465
|
+
});
|
|
15466
|
+
await ctx.telemetry.recordMetric({
|
|
15467
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentTurns,
|
|
15468
|
+
value: loopResult.turnCount
|
|
15469
|
+
});
|
|
15470
|
+
await ctx.telemetry.recordMetric({
|
|
15471
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentToolCalls,
|
|
15472
|
+
value: loopResult.toolCallCount
|
|
15473
|
+
});
|
|
15474
|
+
const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
|
|
15475
|
+
return this.buildOutputItem(item, outputJson).json;
|
|
15476
|
+
}
|
|
15477
|
+
/**
|
|
15478
|
+
* Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
|
|
15479
|
+
* suitable for injection as a tool_result content.
|
|
15480
|
+
*/
|
|
15481
|
+
normalizeDecision(resumeContext) {
|
|
15482
|
+
const { decision } = resumeContext;
|
|
15483
|
+
if (decision.kind === "decided") {
|
|
15484
|
+
const value = decision.value;
|
|
15485
|
+
return {
|
|
15486
|
+
status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
|
|
15487
|
+
value: decision.value,
|
|
15488
|
+
actor: decision.actor,
|
|
15489
|
+
decidedAt: decision.decidedAt.toISOString()
|
|
15490
|
+
};
|
|
15491
|
+
}
|
|
15492
|
+
if (decision.kind === "timed_out") return {
|
|
15493
|
+
status: "timed_out",
|
|
15494
|
+
at: decision.at.toISOString()
|
|
15495
|
+
};
|
|
15496
|
+
return {
|
|
15497
|
+
status: "auto_accepted",
|
|
15498
|
+
at: decision.at.toISOString()
|
|
15499
|
+
};
|
|
15500
|
+
}
|
|
15360
15501
|
async getOrPrepareExecution(ctx) {
|
|
15361
15502
|
let pending = this.preparedByExecutionContext.get(ctx);
|
|
15362
15503
|
if (!pending) {
|
|
@@ -15477,7 +15618,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15477
15618
|
const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
|
|
15478
15619
|
const { ctx, guardrails, toolLoadingStrategy } = prepared;
|
|
15479
15620
|
let finalText = "";
|
|
15480
|
-
let toolCallCount = 0;
|
|
15621
|
+
let toolCallCount = args.resumedToolCallCount ?? 0;
|
|
15481
15622
|
let turnCount = 0;
|
|
15482
15623
|
const repairAttemptsByToolName = /* @__PURE__ */ new Map();
|
|
15483
15624
|
/** Tool IDs surfaced by find_tools across all prior turns in this item run. */
|
|
@@ -15518,11 +15659,17 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15518
15659
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
|
|
15519
15660
|
toolCallCount += plannedToolCalls.length;
|
|
15520
15661
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
15662
|
+
const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
|
|
15663
|
+
const conversationWithAssistant = [...conversation, assistantMsg];
|
|
15521
15664
|
const executed = await this.toolExecutionCoordinator.execute({
|
|
15522
15665
|
plannedToolCalls,
|
|
15523
15666
|
ctx,
|
|
15524
15667
|
agentName: this.getAgentDisplayName(ctx),
|
|
15525
|
-
repairAttemptsByToolName
|
|
15668
|
+
repairAttemptsByToolName,
|
|
15669
|
+
conversationSnapshot: conversationWithAssistant,
|
|
15670
|
+
turnCount,
|
|
15671
|
+
toolCallCount,
|
|
15672
|
+
modelId: this.resolveChatModelName(ctx.config.chatModel)
|
|
15526
15673
|
});
|
|
15527
15674
|
coordinatorExecutedCalls.push(...executed);
|
|
15528
15675
|
}
|
|
@@ -15584,6 +15731,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15584
15731
|
connectionNodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
15585
15732
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
|
|
15586
15733
|
});
|
|
15734
|
+
const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
|
|
15587
15735
|
return {
|
|
15588
15736
|
config: entry.config,
|
|
15589
15737
|
inputSchema: entry.runtime.inputSchema,
|
|
@@ -15598,11 +15746,22 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15598
15746
|
items,
|
|
15599
15747
|
hooks
|
|
15600
15748
|
});
|
|
15601
|
-
}
|
|
15749
|
+
},
|
|
15750
|
+
...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
|
|
15602
15751
|
};
|
|
15603
15752
|
});
|
|
15604
15753
|
}
|
|
15605
15754
|
/**
|
|
15755
|
+
* Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
|
|
15756
|
+
* and returns the HITL behavior config, or `undefined` when not a HITL tool.
|
|
15757
|
+
*/
|
|
15758
|
+
resolveHumanApprovalBehavior(config) {
|
|
15759
|
+
if (!this.isNodeBackedToolConfig(config)) return void 0;
|
|
15760
|
+
const marker$3 = config.node.humanApprovalToolBehavior;
|
|
15761
|
+
if (marker$3 === void 0) return void 0;
|
|
15762
|
+
return { onRejected: marker$3.onRejected ?? "return" };
|
|
15763
|
+
}
|
|
15764
|
+
/**
|
|
15606
15765
|
* Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
|
|
15607
15766
|
* and strategy tools (find_tools + discovered MCP tools).
|
|
15608
15767
|
* Strategy tools take precedence for names that overlap.
|
|
@@ -15632,6 +15791,8 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15632
15791
|
/**
|
|
15633
15792
|
* Builds a ToolSet from resolved tools for strategy initialization.
|
|
15634
15793
|
* The strategy uses this for its "always-included" node-backed tool descriptions.
|
|
15794
|
+
* HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
|
|
15795
|
+
* appended to their description.
|
|
15635
15796
|
*/
|
|
15636
15797
|
buildToolSetFromResolved(resolvedTools) {
|
|
15637
15798
|
if (resolvedTools.length === 0) return {};
|
|
@@ -15641,8 +15802,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15641
15802
|
schemaName: entry.config.name,
|
|
15642
15803
|
requireObjectRoot: true
|
|
15643
15804
|
});
|
|
15805
|
+
const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
|
|
15806
|
+
const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
|
|
15644
15807
|
toolSet[entry.config.name] = {
|
|
15645
|
-
description
|
|
15808
|
+
description,
|
|
15646
15809
|
inputSchema: jsonSchema(schemaRecord)
|
|
15647
15810
|
};
|
|
15648
15811
|
}
|
|
@@ -15670,8 +15833,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15670
15833
|
schemaName: entry.config.name,
|
|
15671
15834
|
requireObjectRoot: true
|
|
15672
15835
|
});
|
|
15836
|
+
const baseDescription = entry.config.description;
|
|
15837
|
+
const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
|
|
15673
15838
|
toolSet[entry.config.name] = {
|
|
15674
|
-
description
|
|
15839
|
+
description,
|
|
15675
15840
|
inputSchema: jsonSchema(schemaRecord)
|
|
15676
15841
|
};
|
|
15677
15842
|
}
|
|
@@ -15839,7 +16004,11 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15839
16004
|
});
|
|
15840
16005
|
try {
|
|
15841
16006
|
const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
|
|
15842
|
-
const
|
|
16007
|
+
const schemaRecord = this.isZodSchema(schema) ? this.executionHelpers.createJsonSchemaRecord(schema, {
|
|
16008
|
+
schemaName: structuredOptions?.schemaName ?? "structured_output",
|
|
16009
|
+
requireObjectRoot: true
|
|
16010
|
+
}) : schema;
|
|
16011
|
+
const outputSchema$1 = output_exports.object({ schema: jsonSchema(schemaRecord) });
|
|
15843
16012
|
const result = await generateText({
|
|
15844
16013
|
model: model.languageModel,
|
|
15845
16014
|
messages: [...messages],
|
|
@@ -17763,6 +17932,173 @@ const collectionDeleteNode = (0, __codemation_core.defineNode)({
|
|
|
17763
17932
|
}
|
|
17764
17933
|
});
|
|
17765
17934
|
|
|
17935
|
+
//#endregion
|
|
17936
|
+
//#region ../core-nodes/src/nodes/InboxApprovalNode.types.ts
|
|
17937
|
+
function resolveSubjectField(field, item) {
|
|
17938
|
+
return typeof field === "function" ? field({ item }) : field;
|
|
17939
|
+
}
|
|
17940
|
+
/**
|
|
17941
|
+
* Auto-detecting inbox approval node.
|
|
17942
|
+
*
|
|
17943
|
+
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
17944
|
+
* at runtime:
|
|
17945
|
+
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
17946
|
+
* - Otherwise: routes to the local inbox.
|
|
17947
|
+
*
|
|
17948
|
+
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
17949
|
+
*/
|
|
17950
|
+
const inboxApproval = (0, __codemation_core.defineHumanApprovalNode)({
|
|
17951
|
+
key: "inbox.approval",
|
|
17952
|
+
title: "Inbox Approval",
|
|
17953
|
+
description: "Suspend and wait for a human reviewer to approve or reject.",
|
|
17954
|
+
icon: "lucide:inbox",
|
|
17955
|
+
channel: "inbox",
|
|
17956
|
+
configSchema: zod.z.object({
|
|
17957
|
+
title: zod.z.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
|
|
17958
|
+
body: zod.z.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
|
|
17959
|
+
priority: zod.z.enum([
|
|
17960
|
+
"low",
|
|
17961
|
+
"normal",
|
|
17962
|
+
"high"
|
|
17963
|
+
]).default("normal"),
|
|
17964
|
+
timeout: zod.z.string().default("24h"),
|
|
17965
|
+
onTimeout: zod.z.enum(["halt", "auto-accept"]).default("halt")
|
|
17966
|
+
}),
|
|
17967
|
+
decisionSchema: zod.z.object({
|
|
17968
|
+
approved: zod.z.boolean(),
|
|
17969
|
+
note: zod.z.string().optional()
|
|
17970
|
+
}),
|
|
17971
|
+
defaultTimeout: "24h",
|
|
17972
|
+
defaultOnTimeout: "halt",
|
|
17973
|
+
async deliver({ task, config, item }, ctx) {
|
|
17974
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
17975
|
+
if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
|
|
17976
|
+
const { channel, workspaceId } = resolver.resolve();
|
|
17977
|
+
const subject = {
|
|
17978
|
+
title: resolveSubjectField(config.title, item),
|
|
17979
|
+
summary: resolveSubjectField(config.body, item),
|
|
17980
|
+
attributes: {
|
|
17981
|
+
workflowId: ctx.workflowId,
|
|
17982
|
+
item: item.json
|
|
17983
|
+
}
|
|
17984
|
+
};
|
|
17985
|
+
const delivery = await channel.deliver({
|
|
17986
|
+
task,
|
|
17987
|
+
subject,
|
|
17988
|
+
priority: config.priority,
|
|
17989
|
+
item,
|
|
17990
|
+
workspaceId
|
|
17991
|
+
});
|
|
17992
|
+
ctx.telemetry.addSpanEvent({
|
|
17993
|
+
name: "hitl.task.delivered",
|
|
17994
|
+
attributes: {
|
|
17995
|
+
taskId: task.taskId,
|
|
17996
|
+
channel: channel.kind
|
|
17997
|
+
}
|
|
17998
|
+
});
|
|
17999
|
+
return delivery;
|
|
18000
|
+
},
|
|
18001
|
+
async onDecision({ decision, actor, delivery }, ctx) {
|
|
18002
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
18003
|
+
if (!resolver) return;
|
|
18004
|
+
const { channel } = resolver.resolve();
|
|
18005
|
+
await channel.updateOnDecision?.({
|
|
18006
|
+
delivery,
|
|
18007
|
+
decision,
|
|
18008
|
+
actor
|
|
18009
|
+
});
|
|
18010
|
+
},
|
|
18011
|
+
async onTimeout({ delivery, policy }, ctx) {
|
|
18012
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
18013
|
+
if (!resolver) return;
|
|
18014
|
+
const { channel } = resolver.resolve();
|
|
18015
|
+
await channel.updateOnTimeout?.({
|
|
18016
|
+
delivery,
|
|
18017
|
+
policy
|
|
18018
|
+
});
|
|
18019
|
+
}
|
|
18020
|
+
});
|
|
18021
|
+
|
|
18022
|
+
//#endregion
|
|
18023
|
+
//#region ../core-nodes/src/nodes/codemationDocumentScannerNode.ts
|
|
18024
|
+
const ANALYZER_TYPES = [
|
|
18025
|
+
"document",
|
|
18026
|
+
"invoice",
|
|
18027
|
+
"image",
|
|
18028
|
+
"auto"
|
|
18029
|
+
];
|
|
18030
|
+
const codemationDocumentScannerNode = (0, __codemation_core.defineNode)({
|
|
18031
|
+
key: "codemation.document-scanner",
|
|
18032
|
+
title: "Codemation Document Scanner",
|
|
18033
|
+
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).",
|
|
18034
|
+
icon: "lucide:scan-text",
|
|
18035
|
+
input: {
|
|
18036
|
+
binaryField: "data",
|
|
18037
|
+
analyzerType: "auto",
|
|
18038
|
+
contentType: void 0,
|
|
18039
|
+
includeConfidence: false,
|
|
18040
|
+
maxBytes: void 0
|
|
18041
|
+
},
|
|
18042
|
+
configSchema: zod.z.object({
|
|
18043
|
+
binaryField: zod.z.string().optional(),
|
|
18044
|
+
analyzerType: zod.z.enum(ANALYZER_TYPES).optional(),
|
|
18045
|
+
contentType: zod.z.string().optional(),
|
|
18046
|
+
includeConfidence: zod.z.boolean().optional(),
|
|
18047
|
+
maxBytes: zod.z.number().int().positive().optional()
|
|
18048
|
+
}),
|
|
18049
|
+
inspectorSummary({ config }) {
|
|
18050
|
+
const cfg = config;
|
|
18051
|
+
const rows = [{
|
|
18052
|
+
label: "Analyzer type",
|
|
18053
|
+
value: cfg.analyzerType ?? "auto"
|
|
18054
|
+
}];
|
|
18055
|
+
const binaryField = cfg.binaryField ?? "data";
|
|
18056
|
+
if (binaryField !== "data") rows.push({
|
|
18057
|
+
label: "Binary field",
|
|
18058
|
+
value: binaryField
|
|
18059
|
+
});
|
|
18060
|
+
if (cfg.includeConfidence) rows.push({
|
|
18061
|
+
label: "Confidence",
|
|
18062
|
+
value: "enabled"
|
|
18063
|
+
});
|
|
18064
|
+
if (cfg.contentType) rows.push({
|
|
18065
|
+
label: "Content type",
|
|
18066
|
+
value: cfg.contentType
|
|
18067
|
+
});
|
|
18068
|
+
return rows;
|
|
18069
|
+
},
|
|
18070
|
+
async execute({ item, ctx }, { config: rawConfig }) {
|
|
18071
|
+
const config = rawConfig;
|
|
18072
|
+
const gatewayUrl = process.env["DOC_SCANNER_GATEWAY_URL"];
|
|
18073
|
+
if (!gatewayUrl) throw new Error("Codemation Document Scanner not available in this environment (DOC_SCANNER_GATEWAY_URL is not set).");
|
|
18074
|
+
const workspaceId = process.env["WORKSPACE_ID"];
|
|
18075
|
+
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
18076
|
+
if (!workspaceId || !pairingSecret) throw new Error("Codemation Document Scanner not available (workspace pairing is not configured).");
|
|
18077
|
+
const binaryField = config.binaryField ?? "data";
|
|
18078
|
+
const attachment = item.binary?.[binaryField];
|
|
18079
|
+
if (!attachment) throw new Error(`Codemation Document Scanner: no binary attachment at key "${binaryField}".`);
|
|
18080
|
+
const body = await ctx.binary.getBytes(attachment, config.maxBytes);
|
|
18081
|
+
const contentType = config.contentType ?? attachment.mimeType ?? "application/octet-stream";
|
|
18082
|
+
const analyzerType = config.analyzerType ?? "auto";
|
|
18083
|
+
const confidenceSuffix = config.includeConfidence ?? false ? "&confidence=true" : "";
|
|
18084
|
+
const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
|
|
18085
|
+
const response = await managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false })(url, {
|
|
18086
|
+
method: "POST",
|
|
18087
|
+
body: body.buffer,
|
|
18088
|
+
headers: {
|
|
18089
|
+
"Content-Type": contentType,
|
|
18090
|
+
"Content-Length": String(body.byteLength),
|
|
18091
|
+
"X-Codemation-Caller": "workflow-node"
|
|
18092
|
+
}
|
|
18093
|
+
});
|
|
18094
|
+
if (!response.ok) {
|
|
18095
|
+
const text$1 = await response.text().catch(() => "(unreadable)");
|
|
18096
|
+
throw new Error(`Codemation Document Scanner: service responded ${response.status} ${response.statusText} — ${text$1}`);
|
|
18097
|
+
}
|
|
18098
|
+
return await response.json();
|
|
18099
|
+
}
|
|
18100
|
+
});
|
|
18101
|
+
|
|
17766
18102
|
//#endregion
|
|
17767
18103
|
//#region ../host/src/presentation/config/CodemationAuthoring.types.ts
|
|
17768
18104
|
var CodemationAuthoringConfigFactory = class {
|