@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.
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require('./chunk-BaqVhFee.cjs');
2
- const require_analyzeInvoiceNode = require('./analyzeInvoiceNode-BIw8j_Zb.cjs');
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 = this.buildHmacSignedFetch(workspaceId, pairingSecret);
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") throw rejected.reason instanceof Error ? rejected.reason : new Error(String(rejected.reason));
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 prepared = await this.getOrPrepareExecution(args.ctx);
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: entry.config.description ?? entry.runtime.defaultDescription,
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: entry.config.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 outputSchema$1 = structuredOptions?.strict && !this.isZodSchema(schema) ? output_exports.object({ schema: jsonSchema(schema) }) : output_exports.object({ schema });
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 {