@codemation/core-nodes-ocr 0.2.2 → 0.2.3

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.
@@ -1,4 +1,4 @@
1
- import { l as WorkflowDefinition, n as TypeToken, t as Container, u as AnyCredentialType } from "./runtimeTypes-ffl603pJ.cjs";
1
+ import { c as Container, l as TypeToken, s as WorkflowDefinition, u as AnyCredentialType } from "./runtimeTypes-MQgcsey7.cjs";
2
2
  import { BetterAuthOptions } from "better-auth";
3
3
 
4
4
  //#region ../core/src/contracts/mcpTypes.d.ts
@@ -1,4 +1,4 @@
1
- import { a as TypeToken, c as WorkflowDefinition, i as Container, l as AnyCredentialType, n as DefinedCollection, o as EngineExecutionLimitsPolicyConfig, s as McpServerDeclaration, t as CollectionDefinition } from "./index-C2KJPzqN.js";
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-rc7dB1Ws.js";
2
2
  import { ZodType, z } from "zod";
3
3
  import { BetterAuthOptions } from "better-auth";
4
4
 
@@ -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
2
  import { n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-uVwe3GHD.js";
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";
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";
@@ -8730,12 +8730,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8730
8730
  this.repairPolicy = repairPolicy;
8731
8731
  }
8732
8732
  async execute(args) {
8733
+ if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
8734
+ toolName: c.binding.config.name,
8735
+ toolCallId: c.toolCall.id ?? c.binding.config.name,
8736
+ 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.` },
8737
+ 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.` })
8738
+ }));
8733
8739
  const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
8734
8740
  ...args,
8735
8741
  plannedToolCall
8736
8742
  })));
8737
8743
  const rejected = results.find((result) => result.status === "rejected");
8738
- if (rejected?.status === "rejected") throw rejected.reason instanceof Error ? rejected.reason : new Error(String(rejected.reason));
8744
+ if (rejected?.status === "rejected") {
8745
+ const reason = rejected.reason;
8746
+ if (reason instanceof SuspensionRequest) throw reason;
8747
+ throw reason instanceof Error ? reason : new Error(String(reason));
8748
+ }
8739
8749
  return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
8740
8750
  }
8741
8751
  async executePlannedToolCall(args) {
@@ -8820,6 +8830,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8820
8830
  result
8821
8831
  };
8822
8832
  } catch (error) {
8833
+ if (error instanceof SuspensionRequest) {
8834
+ const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
8835
+ const checkpoint = {
8836
+ conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
8837
+ turnCount: args.turnCount ?? 0,
8838
+ toolCallCount: args.toolCallCount ?? 0,
8839
+ pendingToolCallId,
8840
+ agentName: args.agentName,
8841
+ modelId: args.modelId ?? ""
8842
+ };
8843
+ const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
8844
+ const augmented = new SuspensionRequest({
8845
+ ...error.request,
8846
+ metadata: {
8847
+ ...error.request.metadata,
8848
+ agentCheckpoint: checkpoint,
8849
+ pendingToolCallId,
8850
+ agentReasoning,
8851
+ onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
8852
+ }
8853
+ });
8854
+ await span.end({
8855
+ status: "error",
8856
+ statusMessage: "suspended",
8857
+ endedAt: /* @__PURE__ */ new Date()
8858
+ });
8859
+ throw augmented;
8860
+ }
8823
8861
  const classification = this.errorClassifier.classify({
8824
8862
  error,
8825
8863
  toolName: plannedToolCall.binding.config.name,
@@ -8995,6 +9033,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8995
9033
  extractErrorDetails(error) {
8996
9034
  return error.details;
8997
9035
  }
9036
+ /**
9037
+ * Extracts the text content from the last assistant message in the conversation snapshot.
9038
+ * Used to populate `agentReasoning` in the HITL suspension metadata.
9039
+ */
9040
+ extractLastAssistantText(conversation) {
9041
+ for (let i = conversation.length - 1; i >= 0; i--) {
9042
+ const msg = conversation[i];
9043
+ if (msg?.role !== "assistant") continue;
9044
+ const content = msg.content;
9045
+ if (typeof content === "string") return content;
9046
+ if (Array.isArray(content)) {
9047
+ const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
9048
+ if (textParts.length > 0) return textParts.join("");
9049
+ }
9050
+ break;
9051
+ }
9052
+ return "";
9053
+ }
8998
9054
  serializeIssue(issue) {
8999
9055
  const result = {
9000
9056
  path: [...issue.path],
@@ -15325,6 +15381,7 @@ var AgentItemPortMap = class {
15325
15381
  //#endregion
15326
15382
  //#region ../core-nodes/src/nodes/AIAgentNode.ts
15327
15383
  var _ref, _ref2, _ref3, _ref4, _ref5;
15384
+ 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
15385
  let AIAgentNode = class AIAgentNode$1 {
15329
15386
  kind = "node";
15330
15387
  outputPorts = ["main"];
@@ -15342,13 +15399,90 @@ let AIAgentNode = class AIAgentNode$1 {
15342
15399
  this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
15343
15400
  }
15344
15401
  async execute(args) {
15345
- const prepared = await this.getOrPrepareExecution(args.ctx);
15402
+ const { ctx } = args;
15403
+ if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
15404
+ const prepared = await this.getOrPrepareExecution(ctx);
15346
15405
  const itemWithMappedJson = {
15347
15406
  ...args.item,
15348
15407
  json: args.input
15349
15408
  };
15350
15409
  return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
15351
15410
  }
15411
+ /**
15412
+ * Resume path: re-enters the agent loop after a HITL suspension.
15413
+ * Reconstructs the conversation from the checkpoint, injects the human decision
15414
+ * as a tool_result, and continues the loop from where it suspended.
15415
+ */
15416
+ async executeResumed(args, resumeContext) {
15417
+ const { ctx } = args;
15418
+ const taskMetadata = resumeContext.task.metadata ?? {};
15419
+ const checkpoint = taskMetadata["agentCheckpoint"];
15420
+ const onRejected = taskMetadata["onRejected"] ?? "return";
15421
+ if (!checkpoint) {
15422
+ const prepared$1 = await this.getOrPrepareExecution(ctx);
15423
+ const itemWithMappedJson = {
15424
+ ...args.item,
15425
+ json: args.input
15426
+ };
15427
+ return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
15428
+ }
15429
+ if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
15430
+ const decision = this.normalizeDecision(resumeContext);
15431
+ if (decision.status === "rejected" && onRejected === "halt") return;
15432
+ const prepared = await this.getOrPrepareExecution(ctx);
15433
+ const item = args.item;
15434
+ const itemInputsByPort = AgentItemPortMap.fromItem(item);
15435
+ const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
15436
+ const toolResultEntry = {
15437
+ toolName: checkpoint.pendingToolCallId,
15438
+ toolCallId: checkpoint.pendingToolCallId,
15439
+ result: decision,
15440
+ serialized: JSON.stringify(decision)
15441
+ };
15442
+ const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
15443
+ const loopResult = await this.runTurnLoopUntilFinalAnswer({
15444
+ prepared,
15445
+ itemInputsByPort,
15446
+ itemScopedTools,
15447
+ conversation,
15448
+ resumedTurnCount: checkpoint.turnCount,
15449
+ resumedToolCallCount: checkpoint.toolCallCount
15450
+ });
15451
+ await ctx.telemetry.recordMetric({
15452
+ name: CodemationTelemetryMetricNames.agentTurns,
15453
+ value: loopResult.turnCount
15454
+ });
15455
+ await ctx.telemetry.recordMetric({
15456
+ name: CodemationTelemetryMetricNames.agentToolCalls,
15457
+ value: loopResult.toolCallCount
15458
+ });
15459
+ const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
15460
+ return this.buildOutputItem(item, outputJson).json;
15461
+ }
15462
+ /**
15463
+ * Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
15464
+ * suitable for injection as a tool_result content.
15465
+ */
15466
+ normalizeDecision(resumeContext) {
15467
+ const { decision } = resumeContext;
15468
+ if (decision.kind === "decided") {
15469
+ const value = decision.value;
15470
+ return {
15471
+ status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
15472
+ value: decision.value,
15473
+ actor: decision.actor,
15474
+ decidedAt: decision.decidedAt.toISOString()
15475
+ };
15476
+ }
15477
+ if (decision.kind === "timed_out") return {
15478
+ status: "timed_out",
15479
+ at: decision.at.toISOString()
15480
+ };
15481
+ return {
15482
+ status: "auto_accepted",
15483
+ at: decision.at.toISOString()
15484
+ };
15485
+ }
15352
15486
  async getOrPrepareExecution(ctx) {
15353
15487
  let pending = this.preparedByExecutionContext.get(ctx);
15354
15488
  if (!pending) {
@@ -15469,7 +15603,7 @@ let AIAgentNode = class AIAgentNode$1 {
15469
15603
  const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
15470
15604
  const { ctx, guardrails, toolLoadingStrategy } = prepared;
15471
15605
  let finalText = "";
15472
- let toolCallCount = 0;
15606
+ let toolCallCount = args.resumedToolCallCount ?? 0;
15473
15607
  let turnCount = 0;
15474
15608
  const repairAttemptsByToolName = /* @__PURE__ */ new Map();
15475
15609
  /** Tool IDs surfaced by find_tools across all prior turns in this item run. */
@@ -15510,11 +15644,17 @@ let AIAgentNode = class AIAgentNode$1 {
15510
15644
  const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
15511
15645
  toolCallCount += plannedToolCalls.length;
15512
15646
  await this.markQueuedTools(plannedToolCalls, ctx);
15647
+ const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
15648
+ const conversationWithAssistant = [...conversation, assistantMsg];
15513
15649
  const executed = await this.toolExecutionCoordinator.execute({
15514
15650
  plannedToolCalls,
15515
15651
  ctx,
15516
15652
  agentName: this.getAgentDisplayName(ctx),
15517
- repairAttemptsByToolName
15653
+ repairAttemptsByToolName,
15654
+ conversationSnapshot: conversationWithAssistant,
15655
+ turnCount,
15656
+ toolCallCount,
15657
+ modelId: this.resolveChatModelName(ctx.config.chatModel)
15518
15658
  });
15519
15659
  coordinatorExecutedCalls.push(...executed);
15520
15660
  }
@@ -15576,6 +15716,7 @@ let AIAgentNode = class AIAgentNode$1 {
15576
15716
  connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
15577
15717
  getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
15578
15718
  });
15719
+ const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
15579
15720
  return {
15580
15721
  config: entry.config,
15581
15722
  inputSchema: entry.runtime.inputSchema,
@@ -15590,11 +15731,22 @@ let AIAgentNode = class AIAgentNode$1 {
15590
15731
  items,
15591
15732
  hooks
15592
15733
  });
15593
- }
15734
+ },
15735
+ ...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
15594
15736
  };
15595
15737
  });
15596
15738
  }
15597
15739
  /**
15740
+ * Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
15741
+ * and returns the HITL behavior config, or `undefined` when not a HITL tool.
15742
+ */
15743
+ resolveHumanApprovalBehavior(config) {
15744
+ if (!this.isNodeBackedToolConfig(config)) return void 0;
15745
+ const marker$3 = config.node.humanApprovalToolBehavior;
15746
+ if (marker$3 === void 0) return void 0;
15747
+ return { onRejected: marker$3.onRejected ?? "return" };
15748
+ }
15749
+ /**
15598
15750
  * Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
15599
15751
  * and strategy tools (find_tools + discovered MCP tools).
15600
15752
  * Strategy tools take precedence for names that overlap.
@@ -15624,6 +15776,8 @@ let AIAgentNode = class AIAgentNode$1 {
15624
15776
  /**
15625
15777
  * Builds a ToolSet from resolved tools for strategy initialization.
15626
15778
  * The strategy uses this for its "always-included" node-backed tool descriptions.
15779
+ * HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
15780
+ * appended to their description.
15627
15781
  */
15628
15782
  buildToolSetFromResolved(resolvedTools) {
15629
15783
  if (resolvedTools.length === 0) return {};
@@ -15633,8 +15787,10 @@ let AIAgentNode = class AIAgentNode$1 {
15633
15787
  schemaName: entry.config.name,
15634
15788
  requireObjectRoot: true
15635
15789
  });
15790
+ const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
15791
+ const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
15636
15792
  toolSet[entry.config.name] = {
15637
- description: entry.config.description ?? entry.runtime.defaultDescription,
15793
+ description,
15638
15794
  inputSchema: jsonSchema(schemaRecord)
15639
15795
  };
15640
15796
  }
@@ -15662,8 +15818,10 @@ let AIAgentNode = class AIAgentNode$1 {
15662
15818
  schemaName: entry.config.name,
15663
15819
  requireObjectRoot: true
15664
15820
  });
15821
+ const baseDescription = entry.config.description;
15822
+ const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
15665
15823
  toolSet[entry.config.name] = {
15666
- description: entry.config.description,
15824
+ description,
15667
15825
  inputSchema: jsonSchema(schemaRecord)
15668
15826
  };
15669
15827
  }
@@ -17755,6 +17913,93 @@ const collectionDeleteNode = defineNode({
17755
17913
  }
17756
17914
  });
17757
17915
 
17916
+ //#endregion
17917
+ //#region ../core-nodes/src/nodes/InboxApprovalNode.types.ts
17918
+ function resolveSubjectField(field, item) {
17919
+ return typeof field === "function" ? field({ item }) : field;
17920
+ }
17921
+ /**
17922
+ * Auto-detecting inbox approval node.
17923
+ *
17924
+ * Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
17925
+ * at runtime:
17926
+ * - In managed mode (PairingConfig present): routes to the control-plane inbox.
17927
+ * - Otherwise: routes to the local inbox.
17928
+ *
17929
+ * Authors use this node directly; no extra wiring needed per deployment mode.
17930
+ */
17931
+ const inboxApproval = defineHumanApprovalNode({
17932
+ key: "inbox.approval",
17933
+ title: "Inbox Approval",
17934
+ description: "Suspend and wait for a human reviewer to approve or reject.",
17935
+ icon: "lucide:inbox",
17936
+ channel: "inbox",
17937
+ configSchema: z$1.object({
17938
+ title: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
17939
+ body: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
17940
+ priority: z$1.enum([
17941
+ "low",
17942
+ "normal",
17943
+ "high"
17944
+ ]).default("normal"),
17945
+ timeout: z$1.string().default("24h"),
17946
+ onTimeout: z$1.enum(["halt", "auto-accept"]).default("halt")
17947
+ }),
17948
+ decisionSchema: z$1.object({
17949
+ approved: z$1.boolean(),
17950
+ note: z$1.string().optional()
17951
+ }),
17952
+ defaultTimeout: "24h",
17953
+ defaultOnTimeout: "halt",
17954
+ async deliver({ task, config, item }, ctx) {
17955
+ const resolver = ctx.resolve(InboxChannelResolverToken);
17956
+ if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
17957
+ const { channel, workspaceId } = resolver.resolve();
17958
+ const subject = {
17959
+ title: resolveSubjectField(config.title, item),
17960
+ summary: resolveSubjectField(config.body, item),
17961
+ attributes: {
17962
+ workflowId: ctx.workflowId,
17963
+ item: item.json
17964
+ }
17965
+ };
17966
+ const delivery = await channel.deliver({
17967
+ task,
17968
+ subject,
17969
+ priority: config.priority,
17970
+ item,
17971
+ workspaceId
17972
+ });
17973
+ ctx.telemetry.addSpanEvent({
17974
+ name: "hitl.task.delivered",
17975
+ attributes: {
17976
+ taskId: task.taskId,
17977
+ channel: channel.kind
17978
+ }
17979
+ });
17980
+ return delivery;
17981
+ },
17982
+ async onDecision({ decision, actor, delivery }, ctx) {
17983
+ const resolver = ctx.resolve(InboxChannelResolverToken);
17984
+ if (!resolver) return;
17985
+ const { channel } = resolver.resolve();
17986
+ await channel.updateOnDecision?.({
17987
+ delivery,
17988
+ decision,
17989
+ actor
17990
+ });
17991
+ },
17992
+ async onTimeout({ delivery, policy }, ctx) {
17993
+ const resolver = ctx.resolve(InboxChannelResolverToken);
17994
+ if (!resolver) return;
17995
+ const { channel } = resolver.resolve();
17996
+ await channel.updateOnTimeout?.({
17997
+ delivery,
17998
+ policy
17999
+ });
18000
+ }
18001
+ });
18002
+
17758
18003
  //#endregion
17759
18004
  //#region ../host/src/presentation/config/CodemationAuthoring.types.ts
17760
18005
  var CodemationAuthoringConfigFactory = class {