@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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as Container, l as TypeToken, s as WorkflowDefinition, u as AnyCredentialType } from "./runtimeTypes-WCvsnJMY.cjs";
|
|
2
2
|
import { BetterAuthOptions } from "better-auth";
|
|
3
3
|
|
|
4
4
|
//#region ../core/src/contracts/mcpTypes.d.ts
|
|
@@ -303,8 +303,6 @@ interface AppConfig {
|
|
|
303
303
|
}>;
|
|
304
304
|
readonly auth?: CodemationAuthConfig;
|
|
305
305
|
readonly whitelabel: CodemationWhitelabelConfig;
|
|
306
|
-
readonly webSocketPort: number;
|
|
307
|
-
readonly webSocketBindHost: string;
|
|
308
306
|
}
|
|
309
307
|
//#endregion
|
|
310
308
|
//#region ../host/src/presentation/config/CodemationAppContext.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as TypeToken, c as
|
|
1
|
+
import { a as TypeToken, c as McpServerDeclaration, i as Container, l as AnyCredentialType, n as DefinedCollection, o as EngineExecutionLimitsPolicyConfig, r as WorkflowDefinition, t as CollectionDefinition } from "./index-DF2ht42F.js";
|
|
2
2
|
import { ZodType, z } from "zod";
|
|
3
3
|
import { BetterAuthOptions } from "better-auth";
|
|
4
4
|
|
|
@@ -243,8 +243,6 @@ interface AppConfig {
|
|
|
243
243
|
}>;
|
|
244
244
|
readonly auth?: CodemationAuthConfig;
|
|
245
245
|
readonly whitelabel: CodemationWhitelabelConfig;
|
|
246
|
-
readonly webSocketPort: number;
|
|
247
|
-
readonly webSocketBindHost: string;
|
|
248
246
|
}
|
|
249
247
|
//#endregion
|
|
250
248
|
//#region ../host/src/presentation/config/CodemationAppContext.d.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as __toDynamicImportESM, i as __commonJS, n as require_auth_errors, o as __toESM, r as require_token_error, t as require_token_util } from "./token-util-EUxa8JtH.js";
|
|
2
|
-
import { n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-
|
|
3
|
-
import { AgentConfigInspector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, GenAiTelemetryAttributeNames, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RunnableOutputBehaviorResolver, chatModel, defineCredential, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
|
|
2
|
+
import { n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-BqZsN8iL.js";
|
|
3
|
+
import { AgentConfigInspector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, GenAiTelemetryAttributeNames, InboxChannelResolverToken, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RunnableOutputBehaviorResolver, SuspensionRequest, chatModel, defineCredential, defineHumanApprovalNode, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
|
|
4
4
|
import dns from "node:dns/promises";
|
|
5
5
|
import * as z4 from "zod/v4";
|
|
6
6
|
import { z } from "zod/v4";
|
|
@@ -8281,6 +8281,59 @@ var OpenAiChatModelPresets = class {
|
|
|
8281
8281
|
};
|
|
8282
8282
|
const openAiChatModelPresets = new OpenAiChatModelPresets();
|
|
8283
8283
|
|
|
8284
|
+
//#endregion
|
|
8285
|
+
//#region ../core-nodes/src/chatModels/ManagedHmacSignerFactory.types.ts
|
|
8286
|
+
/**
|
|
8287
|
+
* Creates an HMAC-signing fetch wrapper that authenticates requests to
|
|
8288
|
+
* Codemation managed services (LLM broker, doc-scanner) with the
|
|
8289
|
+
* Codemation-Hmac v=1 scheme.
|
|
8290
|
+
*
|
|
8291
|
+
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
8292
|
+
* that package (which would create a circular dependency since @codemation/host
|
|
8293
|
+
* depends on @codemation/core-nodes).
|
|
8294
|
+
*
|
|
8295
|
+
* @param workspaceId - Workspace identifier injected by the CP provisioner.
|
|
8296
|
+
* @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
|
|
8297
|
+
* @param options - Optional behaviour flags and test seams.
|
|
8298
|
+
*/
|
|
8299
|
+
function managedHmacFetchFactory(workspaceId, pairingSecret, options) {
|
|
8300
|
+
const signBody = options?.signBody ?? true;
|
|
8301
|
+
return async (input, init) => {
|
|
8302
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
8303
|
+
const method = init?.method ?? "POST";
|
|
8304
|
+
let bodyForSigning = "";
|
|
8305
|
+
if (signBody && init?.body !== void 0 && init.body !== null) bodyForSigning = typeof init.body === "string" ? init.body : await new Response(init.body).text();
|
|
8306
|
+
const authHeader = buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyForSigning, options);
|
|
8307
|
+
const headers = new Headers(init?.headers);
|
|
8308
|
+
headers.set("Authorization", authHeader);
|
|
8309
|
+
const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
|
|
8310
|
+
return fetch(input, {
|
|
8311
|
+
...init,
|
|
8312
|
+
body: outgoingBody,
|
|
8313
|
+
headers
|
|
8314
|
+
});
|
|
8315
|
+
};
|
|
8316
|
+
}
|
|
8317
|
+
/**
|
|
8318
|
+
* Produces a Codemation-Hmac v=1 Authorization header value.
|
|
8319
|
+
* Algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
8320
|
+
*/
|
|
8321
|
+
function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, overrides) {
|
|
8322
|
+
const ts = overrides?.now ? overrides.now() : Math.floor(Date.now() / 1e3);
|
|
8323
|
+
const nonce = overrides?.nonce ? overrides.nonce() : randomBytes(16).toString("base64");
|
|
8324
|
+
const parsed = new URL(url);
|
|
8325
|
+
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
8326
|
+
const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
|
|
8327
|
+
const baseString = [
|
|
8328
|
+
method.toUpperCase(),
|
|
8329
|
+
path,
|
|
8330
|
+
ts,
|
|
8331
|
+
nonce,
|
|
8332
|
+
bodyHash
|
|
8333
|
+
].join("\n");
|
|
8334
|
+
return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${createHmac("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
|
|
8335
|
+
}
|
|
8336
|
+
|
|
8284
8337
|
//#endregion
|
|
8285
8338
|
//#region ../core-nodes/src/chatModels/CodemationChatModelFactory.ts
|
|
8286
8339
|
let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
@@ -8290,7 +8343,7 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
8290
8343
|
const workspaceId = process.env["WORKSPACE_ID"];
|
|
8291
8344
|
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
8292
8345
|
if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
|
|
8293
|
-
const hmacFetch =
|
|
8346
|
+
const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
|
|
8294
8347
|
const languageModel = createOpenAI({
|
|
8295
8348
|
baseURL: `${gatewayUrl}/v1`,
|
|
8296
8349
|
apiKey: "codemation-managed",
|
|
@@ -8306,52 +8359,6 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
|
|
|
8306
8359
|
}
|
|
8307
8360
|
});
|
|
8308
8361
|
}
|
|
8309
|
-
/**
|
|
8310
|
-
* Creates an HMAC-signed fetch wrapper for use with AI SDK's createOpenAI.
|
|
8311
|
-
* Each call signs the request body with the workspace pairing secret so the
|
|
8312
|
-
* LLM broker can authenticate the workspace without a user-managed API key.
|
|
8313
|
-
*
|
|
8314
|
-
* Mirrors HmacRequestSigner from @codemation/host/pairing without importing
|
|
8315
|
-
* that package (which would create a circular dependency since @codemation/host
|
|
8316
|
-
* depends on @codemation/core-nodes).
|
|
8317
|
-
*/
|
|
8318
|
-
buildHmacSignedFetch(workspaceId, pairingSecret) {
|
|
8319
|
-
return async (input, init) => {
|
|
8320
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
8321
|
-
const method = init?.method ?? "POST";
|
|
8322
|
-
let bodyString = "";
|
|
8323
|
-
if (init?.body !== void 0 && init.body !== null) if (typeof init.body === "string") bodyString = init.body;
|
|
8324
|
-
else bodyString = await new Response(init.body).text();
|
|
8325
|
-
const authHeader = this.buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyString);
|
|
8326
|
-
const headers = new Headers(init?.headers);
|
|
8327
|
-
headers.set("Authorization", authHeader);
|
|
8328
|
-
const effectiveBody = bodyString || init?.body;
|
|
8329
|
-
return fetch(input, {
|
|
8330
|
-
...init,
|
|
8331
|
-
body: effectiveBody,
|
|
8332
|
-
headers
|
|
8333
|
-
});
|
|
8334
|
-
};
|
|
8335
|
-
}
|
|
8336
|
-
/**
|
|
8337
|
-
* Produces a Codemation-Hmac v1 Authorization header value.
|
|
8338
|
-
* The algorithm must match HmacVerifier.computeSignature() in the control-plane.
|
|
8339
|
-
*/
|
|
8340
|
-
buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body) {
|
|
8341
|
-
const ts = Math.floor(Date.now() / 1e3);
|
|
8342
|
-
const nonce = randomBytes(16).toString("base64");
|
|
8343
|
-
const parsed = new URL(url);
|
|
8344
|
-
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
8345
|
-
const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
|
|
8346
|
-
const baseString = [
|
|
8347
|
-
method.toUpperCase(),
|
|
8348
|
-
path,
|
|
8349
|
-
ts,
|
|
8350
|
-
nonce,
|
|
8351
|
-
bodyHash
|
|
8352
|
-
].join("\n");
|
|
8353
|
-
return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${createHmac("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
|
|
8354
|
-
}
|
|
8355
8362
|
};
|
|
8356
8363
|
CodemationChatModelFactory = __decorate([chatModel({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
|
|
8357
8364
|
|
|
@@ -8730,12 +8737,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
8730
8737
|
this.repairPolicy = repairPolicy;
|
|
8731
8738
|
}
|
|
8732
8739
|
async execute(args) {
|
|
8740
|
+
if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
|
|
8741
|
+
toolName: c.binding.config.name,
|
|
8742
|
+
toolCallId: c.toolCall.id ?? c.binding.config.name,
|
|
8743
|
+
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.` },
|
|
8744
|
+
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.` })
|
|
8745
|
+
}));
|
|
8733
8746
|
const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
|
|
8734
8747
|
...args,
|
|
8735
8748
|
plannedToolCall
|
|
8736
8749
|
})));
|
|
8737
8750
|
const rejected = results.find((result) => result.status === "rejected");
|
|
8738
|
-
if (rejected?.status === "rejected")
|
|
8751
|
+
if (rejected?.status === "rejected") {
|
|
8752
|
+
const reason = rejected.reason;
|
|
8753
|
+
if (reason instanceof SuspensionRequest) throw reason;
|
|
8754
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
8755
|
+
}
|
|
8739
8756
|
return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
8740
8757
|
}
|
|
8741
8758
|
async executePlannedToolCall(args) {
|
|
@@ -8820,6 +8837,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
8820
8837
|
result
|
|
8821
8838
|
};
|
|
8822
8839
|
} catch (error) {
|
|
8840
|
+
if (error instanceof SuspensionRequest) {
|
|
8841
|
+
const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
|
|
8842
|
+
const checkpoint = {
|
|
8843
|
+
conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
|
|
8844
|
+
turnCount: args.turnCount ?? 0,
|
|
8845
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
8846
|
+
pendingToolCallId,
|
|
8847
|
+
agentName: args.agentName,
|
|
8848
|
+
modelId: args.modelId ?? ""
|
|
8849
|
+
};
|
|
8850
|
+
const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
|
|
8851
|
+
const augmented = new SuspensionRequest({
|
|
8852
|
+
...error.request,
|
|
8853
|
+
metadata: {
|
|
8854
|
+
...error.request.metadata,
|
|
8855
|
+
agentCheckpoint: checkpoint,
|
|
8856
|
+
pendingToolCallId,
|
|
8857
|
+
agentReasoning,
|
|
8858
|
+
onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
|
|
8859
|
+
}
|
|
8860
|
+
});
|
|
8861
|
+
await span.end({
|
|
8862
|
+
status: "error",
|
|
8863
|
+
statusMessage: "suspended",
|
|
8864
|
+
endedAt: /* @__PURE__ */ new Date()
|
|
8865
|
+
});
|
|
8866
|
+
throw augmented;
|
|
8867
|
+
}
|
|
8823
8868
|
const classification = this.errorClassifier.classify({
|
|
8824
8869
|
error,
|
|
8825
8870
|
toolName: plannedToolCall.binding.config.name,
|
|
@@ -8995,6 +9040,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
8995
9040
|
extractErrorDetails(error) {
|
|
8996
9041
|
return error.details;
|
|
8997
9042
|
}
|
|
9043
|
+
/**
|
|
9044
|
+
* Extracts the text content from the last assistant message in the conversation snapshot.
|
|
9045
|
+
* Used to populate `agentReasoning` in the HITL suspension metadata.
|
|
9046
|
+
*/
|
|
9047
|
+
extractLastAssistantText(conversation) {
|
|
9048
|
+
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
9049
|
+
const msg = conversation[i];
|
|
9050
|
+
if (msg?.role !== "assistant") continue;
|
|
9051
|
+
const content = msg.content;
|
|
9052
|
+
if (typeof content === "string") return content;
|
|
9053
|
+
if (Array.isArray(content)) {
|
|
9054
|
+
const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
|
|
9055
|
+
if (textParts.length > 0) return textParts.join("");
|
|
9056
|
+
}
|
|
9057
|
+
break;
|
|
9058
|
+
}
|
|
9059
|
+
return "";
|
|
9060
|
+
}
|
|
8998
9061
|
serializeIssue(issue) {
|
|
8999
9062
|
const result = {
|
|
9000
9063
|
path: [...issue.path],
|
|
@@ -15325,6 +15388,7 @@ var AgentItemPortMap = class {
|
|
|
15325
15388
|
//#endregion
|
|
15326
15389
|
//#region ../core-nodes/src/nodes/AIAgentNode.ts
|
|
15327
15390
|
var _ref, _ref2, _ref3, _ref4, _ref5;
|
|
15391
|
+
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.";
|
|
15328
15392
|
let AIAgentNode = class AIAgentNode$1 {
|
|
15329
15393
|
kind = "node";
|
|
15330
15394
|
outputPorts = ["main"];
|
|
@@ -15342,13 +15406,90 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15342
15406
|
this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
15343
15407
|
}
|
|
15344
15408
|
async execute(args) {
|
|
15345
|
-
const
|
|
15409
|
+
const { ctx } = args;
|
|
15410
|
+
if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
|
|
15411
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
15346
15412
|
const itemWithMappedJson = {
|
|
15347
15413
|
...args.item,
|
|
15348
15414
|
json: args.input
|
|
15349
15415
|
};
|
|
15350
15416
|
return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
15351
15417
|
}
|
|
15418
|
+
/**
|
|
15419
|
+
* Resume path: re-enters the agent loop after a HITL suspension.
|
|
15420
|
+
* Reconstructs the conversation from the checkpoint, injects the human decision
|
|
15421
|
+
* as a tool_result, and continues the loop from where it suspended.
|
|
15422
|
+
*/
|
|
15423
|
+
async executeResumed(args, resumeContext) {
|
|
15424
|
+
const { ctx } = args;
|
|
15425
|
+
const taskMetadata = resumeContext.task.metadata ?? {};
|
|
15426
|
+
const checkpoint = taskMetadata["agentCheckpoint"];
|
|
15427
|
+
const onRejected = taskMetadata["onRejected"] ?? "return";
|
|
15428
|
+
if (!checkpoint) {
|
|
15429
|
+
const prepared$1 = await this.getOrPrepareExecution(ctx);
|
|
15430
|
+
const itemWithMappedJson = {
|
|
15431
|
+
...args.item,
|
|
15432
|
+
json: args.input
|
|
15433
|
+
};
|
|
15434
|
+
return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
15435
|
+
}
|
|
15436
|
+
if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
|
|
15437
|
+
const decision = this.normalizeDecision(resumeContext);
|
|
15438
|
+
if (decision.status === "rejected" && onRejected === "halt") return;
|
|
15439
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
15440
|
+
const item = args.item;
|
|
15441
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
15442
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
|
|
15443
|
+
const toolResultEntry = {
|
|
15444
|
+
toolName: checkpoint.pendingToolCallId,
|
|
15445
|
+
toolCallId: checkpoint.pendingToolCallId,
|
|
15446
|
+
result: decision,
|
|
15447
|
+
serialized: JSON.stringify(decision)
|
|
15448
|
+
};
|
|
15449
|
+
const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
|
|
15450
|
+
const loopResult = await this.runTurnLoopUntilFinalAnswer({
|
|
15451
|
+
prepared,
|
|
15452
|
+
itemInputsByPort,
|
|
15453
|
+
itemScopedTools,
|
|
15454
|
+
conversation,
|
|
15455
|
+
resumedTurnCount: checkpoint.turnCount,
|
|
15456
|
+
resumedToolCallCount: checkpoint.toolCallCount
|
|
15457
|
+
});
|
|
15458
|
+
await ctx.telemetry.recordMetric({
|
|
15459
|
+
name: CodemationTelemetryMetricNames.agentTurns,
|
|
15460
|
+
value: loopResult.turnCount
|
|
15461
|
+
});
|
|
15462
|
+
await ctx.telemetry.recordMetric({
|
|
15463
|
+
name: CodemationTelemetryMetricNames.agentToolCalls,
|
|
15464
|
+
value: loopResult.toolCallCount
|
|
15465
|
+
});
|
|
15466
|
+
const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
|
|
15467
|
+
return this.buildOutputItem(item, outputJson).json;
|
|
15468
|
+
}
|
|
15469
|
+
/**
|
|
15470
|
+
* Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
|
|
15471
|
+
* suitable for injection as a tool_result content.
|
|
15472
|
+
*/
|
|
15473
|
+
normalizeDecision(resumeContext) {
|
|
15474
|
+
const { decision } = resumeContext;
|
|
15475
|
+
if (decision.kind === "decided") {
|
|
15476
|
+
const value = decision.value;
|
|
15477
|
+
return {
|
|
15478
|
+
status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
|
|
15479
|
+
value: decision.value,
|
|
15480
|
+
actor: decision.actor,
|
|
15481
|
+
decidedAt: decision.decidedAt.toISOString()
|
|
15482
|
+
};
|
|
15483
|
+
}
|
|
15484
|
+
if (decision.kind === "timed_out") return {
|
|
15485
|
+
status: "timed_out",
|
|
15486
|
+
at: decision.at.toISOString()
|
|
15487
|
+
};
|
|
15488
|
+
return {
|
|
15489
|
+
status: "auto_accepted",
|
|
15490
|
+
at: decision.at.toISOString()
|
|
15491
|
+
};
|
|
15492
|
+
}
|
|
15352
15493
|
async getOrPrepareExecution(ctx) {
|
|
15353
15494
|
let pending = this.preparedByExecutionContext.get(ctx);
|
|
15354
15495
|
if (!pending) {
|
|
@@ -15469,7 +15610,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15469
15610
|
const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
|
|
15470
15611
|
const { ctx, guardrails, toolLoadingStrategy } = prepared;
|
|
15471
15612
|
let finalText = "";
|
|
15472
|
-
let toolCallCount = 0;
|
|
15613
|
+
let toolCallCount = args.resumedToolCallCount ?? 0;
|
|
15473
15614
|
let turnCount = 0;
|
|
15474
15615
|
const repairAttemptsByToolName = /* @__PURE__ */ new Map();
|
|
15475
15616
|
/** Tool IDs surfaced by find_tools across all prior turns in this item run. */
|
|
@@ -15510,11 +15651,17 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15510
15651
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
|
|
15511
15652
|
toolCallCount += plannedToolCalls.length;
|
|
15512
15653
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
15654
|
+
const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
|
|
15655
|
+
const conversationWithAssistant = [...conversation, assistantMsg];
|
|
15513
15656
|
const executed = await this.toolExecutionCoordinator.execute({
|
|
15514
15657
|
plannedToolCalls,
|
|
15515
15658
|
ctx,
|
|
15516
15659
|
agentName: this.getAgentDisplayName(ctx),
|
|
15517
|
-
repairAttemptsByToolName
|
|
15660
|
+
repairAttemptsByToolName,
|
|
15661
|
+
conversationSnapshot: conversationWithAssistant,
|
|
15662
|
+
turnCount,
|
|
15663
|
+
toolCallCount,
|
|
15664
|
+
modelId: this.resolveChatModelName(ctx.config.chatModel)
|
|
15518
15665
|
});
|
|
15519
15666
|
coordinatorExecutedCalls.push(...executed);
|
|
15520
15667
|
}
|
|
@@ -15576,6 +15723,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15576
15723
|
connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
15577
15724
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
|
|
15578
15725
|
});
|
|
15726
|
+
const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
|
|
15579
15727
|
return {
|
|
15580
15728
|
config: entry.config,
|
|
15581
15729
|
inputSchema: entry.runtime.inputSchema,
|
|
@@ -15590,11 +15738,22 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15590
15738
|
items,
|
|
15591
15739
|
hooks
|
|
15592
15740
|
});
|
|
15593
|
-
}
|
|
15741
|
+
},
|
|
15742
|
+
...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
|
|
15594
15743
|
};
|
|
15595
15744
|
});
|
|
15596
15745
|
}
|
|
15597
15746
|
/**
|
|
15747
|
+
* Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
|
|
15748
|
+
* and returns the HITL behavior config, or `undefined` when not a HITL tool.
|
|
15749
|
+
*/
|
|
15750
|
+
resolveHumanApprovalBehavior(config) {
|
|
15751
|
+
if (!this.isNodeBackedToolConfig(config)) return void 0;
|
|
15752
|
+
const marker$3 = config.node.humanApprovalToolBehavior;
|
|
15753
|
+
if (marker$3 === void 0) return void 0;
|
|
15754
|
+
return { onRejected: marker$3.onRejected ?? "return" };
|
|
15755
|
+
}
|
|
15756
|
+
/**
|
|
15598
15757
|
* Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
|
|
15599
15758
|
* and strategy tools (find_tools + discovered MCP tools).
|
|
15600
15759
|
* Strategy tools take precedence for names that overlap.
|
|
@@ -15624,6 +15783,8 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15624
15783
|
/**
|
|
15625
15784
|
* Builds a ToolSet from resolved tools for strategy initialization.
|
|
15626
15785
|
* The strategy uses this for its "always-included" node-backed tool descriptions.
|
|
15786
|
+
* HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
|
|
15787
|
+
* appended to their description.
|
|
15627
15788
|
*/
|
|
15628
15789
|
buildToolSetFromResolved(resolvedTools) {
|
|
15629
15790
|
if (resolvedTools.length === 0) return {};
|
|
@@ -15633,8 +15794,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15633
15794
|
schemaName: entry.config.name,
|
|
15634
15795
|
requireObjectRoot: true
|
|
15635
15796
|
});
|
|
15797
|
+
const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
|
|
15798
|
+
const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
|
|
15636
15799
|
toolSet[entry.config.name] = {
|
|
15637
|
-
description
|
|
15800
|
+
description,
|
|
15638
15801
|
inputSchema: jsonSchema(schemaRecord)
|
|
15639
15802
|
};
|
|
15640
15803
|
}
|
|
@@ -15662,8 +15825,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15662
15825
|
schemaName: entry.config.name,
|
|
15663
15826
|
requireObjectRoot: true
|
|
15664
15827
|
});
|
|
15828
|
+
const baseDescription = entry.config.description;
|
|
15829
|
+
const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
|
|
15665
15830
|
toolSet[entry.config.name] = {
|
|
15666
|
-
description
|
|
15831
|
+
description,
|
|
15667
15832
|
inputSchema: jsonSchema(schemaRecord)
|
|
15668
15833
|
};
|
|
15669
15834
|
}
|
|
@@ -15831,7 +15996,11 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
15831
15996
|
});
|
|
15832
15997
|
try {
|
|
15833
15998
|
const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
|
|
15834
|
-
const
|
|
15999
|
+
const schemaRecord = this.isZodSchema(schema) ? this.executionHelpers.createJsonSchemaRecord(schema, {
|
|
16000
|
+
schemaName: structuredOptions?.schemaName ?? "structured_output",
|
|
16001
|
+
requireObjectRoot: true
|
|
16002
|
+
}) : schema;
|
|
16003
|
+
const outputSchema$1 = output_exports.object({ schema: jsonSchema(schemaRecord) });
|
|
15835
16004
|
const result = await generateText({
|
|
15836
16005
|
model: model.languageModel,
|
|
15837
16006
|
messages: [...messages],
|
|
@@ -17755,6 +17924,173 @@ const collectionDeleteNode = defineNode({
|
|
|
17755
17924
|
}
|
|
17756
17925
|
});
|
|
17757
17926
|
|
|
17927
|
+
//#endregion
|
|
17928
|
+
//#region ../core-nodes/src/nodes/InboxApprovalNode.types.ts
|
|
17929
|
+
function resolveSubjectField(field, item) {
|
|
17930
|
+
return typeof field === "function" ? field({ item }) : field;
|
|
17931
|
+
}
|
|
17932
|
+
/**
|
|
17933
|
+
* Auto-detecting inbox approval node.
|
|
17934
|
+
*
|
|
17935
|
+
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
17936
|
+
* at runtime:
|
|
17937
|
+
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
17938
|
+
* - Otherwise: routes to the local inbox.
|
|
17939
|
+
*
|
|
17940
|
+
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
17941
|
+
*/
|
|
17942
|
+
const inboxApproval = defineHumanApprovalNode({
|
|
17943
|
+
key: "inbox.approval",
|
|
17944
|
+
title: "Inbox Approval",
|
|
17945
|
+
description: "Suspend and wait for a human reviewer to approve or reject.",
|
|
17946
|
+
icon: "lucide:inbox",
|
|
17947
|
+
channel: "inbox",
|
|
17948
|
+
configSchema: z$1.object({
|
|
17949
|
+
title: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
|
|
17950
|
+
body: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
|
|
17951
|
+
priority: z$1.enum([
|
|
17952
|
+
"low",
|
|
17953
|
+
"normal",
|
|
17954
|
+
"high"
|
|
17955
|
+
]).default("normal"),
|
|
17956
|
+
timeout: z$1.string().default("24h"),
|
|
17957
|
+
onTimeout: z$1.enum(["halt", "auto-accept"]).default("halt")
|
|
17958
|
+
}),
|
|
17959
|
+
decisionSchema: z$1.object({
|
|
17960
|
+
approved: z$1.boolean(),
|
|
17961
|
+
note: z$1.string().optional()
|
|
17962
|
+
}),
|
|
17963
|
+
defaultTimeout: "24h",
|
|
17964
|
+
defaultOnTimeout: "halt",
|
|
17965
|
+
async deliver({ task, config, item }, ctx) {
|
|
17966
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
17967
|
+
if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
|
|
17968
|
+
const { channel, workspaceId } = resolver.resolve();
|
|
17969
|
+
const subject = {
|
|
17970
|
+
title: resolveSubjectField(config.title, item),
|
|
17971
|
+
summary: resolveSubjectField(config.body, item),
|
|
17972
|
+
attributes: {
|
|
17973
|
+
workflowId: ctx.workflowId,
|
|
17974
|
+
item: item.json
|
|
17975
|
+
}
|
|
17976
|
+
};
|
|
17977
|
+
const delivery = await channel.deliver({
|
|
17978
|
+
task,
|
|
17979
|
+
subject,
|
|
17980
|
+
priority: config.priority,
|
|
17981
|
+
item,
|
|
17982
|
+
workspaceId
|
|
17983
|
+
});
|
|
17984
|
+
ctx.telemetry.addSpanEvent({
|
|
17985
|
+
name: "hitl.task.delivered",
|
|
17986
|
+
attributes: {
|
|
17987
|
+
taskId: task.taskId,
|
|
17988
|
+
channel: channel.kind
|
|
17989
|
+
}
|
|
17990
|
+
});
|
|
17991
|
+
return delivery;
|
|
17992
|
+
},
|
|
17993
|
+
async onDecision({ decision, actor, delivery }, ctx) {
|
|
17994
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
17995
|
+
if (!resolver) return;
|
|
17996
|
+
const { channel } = resolver.resolve();
|
|
17997
|
+
await channel.updateOnDecision?.({
|
|
17998
|
+
delivery,
|
|
17999
|
+
decision,
|
|
18000
|
+
actor
|
|
18001
|
+
});
|
|
18002
|
+
},
|
|
18003
|
+
async onTimeout({ delivery, policy }, ctx) {
|
|
18004
|
+
const resolver = ctx.resolve(InboxChannelResolverToken);
|
|
18005
|
+
if (!resolver) return;
|
|
18006
|
+
const { channel } = resolver.resolve();
|
|
18007
|
+
await channel.updateOnTimeout?.({
|
|
18008
|
+
delivery,
|
|
18009
|
+
policy
|
|
18010
|
+
});
|
|
18011
|
+
}
|
|
18012
|
+
});
|
|
18013
|
+
|
|
18014
|
+
//#endregion
|
|
18015
|
+
//#region ../core-nodes/src/nodes/codemationDocumentScannerNode.ts
|
|
18016
|
+
const ANALYZER_TYPES = [
|
|
18017
|
+
"document",
|
|
18018
|
+
"invoice",
|
|
18019
|
+
"image",
|
|
18020
|
+
"auto"
|
|
18021
|
+
];
|
|
18022
|
+
const codemationDocumentScannerNode = defineNode({
|
|
18023
|
+
key: "codemation.document-scanner",
|
|
18024
|
+
title: "Codemation Document Scanner",
|
|
18025
|
+
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).",
|
|
18026
|
+
icon: "lucide:scan-text",
|
|
18027
|
+
input: {
|
|
18028
|
+
binaryField: "data",
|
|
18029
|
+
analyzerType: "auto",
|
|
18030
|
+
contentType: void 0,
|
|
18031
|
+
includeConfidence: false,
|
|
18032
|
+
maxBytes: void 0
|
|
18033
|
+
},
|
|
18034
|
+
configSchema: z$1.object({
|
|
18035
|
+
binaryField: z$1.string().optional(),
|
|
18036
|
+
analyzerType: z$1.enum(ANALYZER_TYPES).optional(),
|
|
18037
|
+
contentType: z$1.string().optional(),
|
|
18038
|
+
includeConfidence: z$1.boolean().optional(),
|
|
18039
|
+
maxBytes: z$1.number().int().positive().optional()
|
|
18040
|
+
}),
|
|
18041
|
+
inspectorSummary({ config }) {
|
|
18042
|
+
const cfg = config;
|
|
18043
|
+
const rows = [{
|
|
18044
|
+
label: "Analyzer type",
|
|
18045
|
+
value: cfg.analyzerType ?? "auto"
|
|
18046
|
+
}];
|
|
18047
|
+
const binaryField = cfg.binaryField ?? "data";
|
|
18048
|
+
if (binaryField !== "data") rows.push({
|
|
18049
|
+
label: "Binary field",
|
|
18050
|
+
value: binaryField
|
|
18051
|
+
});
|
|
18052
|
+
if (cfg.includeConfidence) rows.push({
|
|
18053
|
+
label: "Confidence",
|
|
18054
|
+
value: "enabled"
|
|
18055
|
+
});
|
|
18056
|
+
if (cfg.contentType) rows.push({
|
|
18057
|
+
label: "Content type",
|
|
18058
|
+
value: cfg.contentType
|
|
18059
|
+
});
|
|
18060
|
+
return rows;
|
|
18061
|
+
},
|
|
18062
|
+
async execute({ item, ctx }, { config: rawConfig }) {
|
|
18063
|
+
const config = rawConfig;
|
|
18064
|
+
const gatewayUrl = process.env["DOC_SCANNER_GATEWAY_URL"];
|
|
18065
|
+
if (!gatewayUrl) throw new Error("Codemation Document Scanner not available in this environment (DOC_SCANNER_GATEWAY_URL is not set).");
|
|
18066
|
+
const workspaceId = process.env["WORKSPACE_ID"];
|
|
18067
|
+
const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
|
|
18068
|
+
if (!workspaceId || !pairingSecret) throw new Error("Codemation Document Scanner not available (workspace pairing is not configured).");
|
|
18069
|
+
const binaryField = config.binaryField ?? "data";
|
|
18070
|
+
const attachment = item.binary?.[binaryField];
|
|
18071
|
+
if (!attachment) throw new Error(`Codemation Document Scanner: no binary attachment at key "${binaryField}".`);
|
|
18072
|
+
const body = await ctx.binary.getBytes(attachment, config.maxBytes);
|
|
18073
|
+
const contentType = config.contentType ?? attachment.mimeType ?? "application/octet-stream";
|
|
18074
|
+
const analyzerType = config.analyzerType ?? "auto";
|
|
18075
|
+
const confidenceSuffix = config.includeConfidence ?? false ? "&confidence=true" : "";
|
|
18076
|
+
const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
|
|
18077
|
+
const response = await managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false })(url, {
|
|
18078
|
+
method: "POST",
|
|
18079
|
+
body: body.buffer,
|
|
18080
|
+
headers: {
|
|
18081
|
+
"Content-Type": contentType,
|
|
18082
|
+
"Content-Length": String(body.byteLength),
|
|
18083
|
+
"X-Codemation-Caller": "workflow-node"
|
|
18084
|
+
}
|
|
18085
|
+
});
|
|
18086
|
+
if (!response.ok) {
|
|
18087
|
+
const text$1 = await response.text().catch(() => "(unreadable)");
|
|
18088
|
+
throw new Error(`Codemation Document Scanner: service responded ${response.status} ${response.statusText} — ${text$1}`);
|
|
18089
|
+
}
|
|
18090
|
+
return await response.json();
|
|
18091
|
+
}
|
|
18092
|
+
});
|
|
18093
|
+
|
|
17758
18094
|
//#endregion
|
|
17759
18095
|
//#region ../host/src/presentation/config/CodemationAuthoring.types.ts
|
|
17760
18096
|
var CodemationAuthoringConfigFactory = class {
|