@codemation/core-nodes 0.9.0 → 0.10.1

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 CHANGED
@@ -1,5 +1,50 @@
1
1
  # @codemation/core-nodes
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`2fcb715`](https://github.com/MadeRelevant/codemation/commit/2fcb7153d9c732b2f846b8a8d1cc5626b4363fa6)]:
8
+ - @codemation/core@0.13.1
9
+
10
+ ## 0.10.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#184](https://github.com/MadeRelevant/codemation/pull/184) [`8c5b213`](https://github.com/MadeRelevant/codemation/commit/8c5b213092501bb48aa0c575b0489bbd36b46c79) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `CodemationDocumentScanner` node — managed document/image analysis returning `{ markdown, fields }` via the Codemation doc-scanner service, no Azure credential required.
15
+
16
+ ### Patch Changes
17
+
18
+ - [#187](https://github.com/MadeRelevant/codemation/pull/187) [`cf45e88`](https://github.com/MadeRelevant/codemation/commit/cf45e88c28dcb3ba87bcf14e34c550ae0ac036e8) Thanks [@cblokland90](https://github.com/cblokland90)! - fix(aiagent): managed structured output no longer throws "schema is not a function"
19
+
20
+ AIAgentNode passed a consumer-created Zod `outputSchema` straight to the AI SDK's
21
+ `Output.object` on the managed (non-OpenAI) path. Consumer workflows build that schema
22
+ with their own tsx-loaded Zod — a different runtime copy than the framework's Zod — so the
23
+ SDK threw "schema is not a function". Now the schema is converted to a plain JSON Schema via
24
+ the schema's own instance method (`createJsonSchemaRecord`, dual-zod safe) and wrapped with
25
+ `jsonSchema()` for both paths, so `Output.object` never receives a raw cross-namespace Zod.
26
+
27
+ - [#189](https://github.com/MadeRelevant/codemation/pull/189) [`c1b081f`](https://github.com/MadeRelevant/codemation/commit/c1b081ffc8b66b0c4593c94f1d57a1cdf5c41140) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): add `getBytes`, `getText`, and `getJson<T>` to the binary API
28
+
29
+ `ExecutionBinaryService` (and `NodeBinaryAttachmentService`) now expose three read helpers:
30
+ - `getBytes(attachment, maxBytes?)` — bounded read into `Uint8Array` (size-checked before allocation).
31
+ - `getText(attachment, maxBytes?)` — bounded read + UTF-8 decode.
32
+ - `getJson<T>(attachment, maxBytes?)` — bounded read + decode + `JSON.parse` with a clear `SyntaxError` on invalid JSON.
33
+
34
+ The duplicate `readBinaryBody` helpers previously copied across `core-nodes` and `core-nodes-ocr` are removed; all callers now use `ctx.binary.getBytes()`. `openReadStream` remains for true streaming use cases.
35
+
36
+ - [#184](https://github.com/MadeRelevant/codemation/pull/184) [`8c5b213`](https://github.com/MadeRelevant/codemation/commit/8c5b213092501bb48aa0c575b0489bbd36b46c79) Thanks [@cblokland90](https://github.com/cblokland90)! - Extract `managedHmacFetchFactory` from `CodemationChatModelFactory` into a shared
37
+ `ManagedHmacSignerFactory` module (story 05, doc-scanner sprint).
38
+
39
+ The helper is a package-internal refactor: not added to the public barrel export,
40
+ no public API surface change — patch is correct. It adds a `signBody` option
41
+ (default `true`, preserving LLM-chat behaviour) that implements LD11
42
+ empty-body-hash signing for the upcoming `CodemationDocumentScanner` node
43
+ (story 06).
44
+
45
+ - Updated dependencies [[`c1b081f`](https://github.com/MadeRelevant/codemation/commit/c1b081ffc8b66b0c4593c94f1d57a1cdf5c41140), [`be520d2`](https://github.com/MadeRelevant/codemation/commit/be520d2755144a3709ecc109019b84e2c502337e)]:
46
+ - @codemation/core@0.13.0
47
+
3
48
  ## 0.9.0
4
49
 
5
50
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -4389,6 +4389,59 @@ var OpenAiChatModelPresets = class {
4389
4389
  };
4390
4390
  const openAiChatModelPresets = new OpenAiChatModelPresets();
4391
4391
 
4392
+ //#endregion
4393
+ //#region src/chatModels/ManagedHmacSignerFactory.types.ts
4394
+ /**
4395
+ * Creates an HMAC-signing fetch wrapper that authenticates requests to
4396
+ * Codemation managed services (LLM broker, doc-scanner) with the
4397
+ * Codemation-Hmac v=1 scheme.
4398
+ *
4399
+ * Mirrors HmacRequestSigner from @codemation/host/pairing without importing
4400
+ * that package (which would create a circular dependency since @codemation/host
4401
+ * depends on @codemation/core-nodes).
4402
+ *
4403
+ * @param workspaceId - Workspace identifier injected by the CP provisioner.
4404
+ * @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
4405
+ * @param options - Optional behaviour flags and test seams.
4406
+ */
4407
+ function managedHmacFetchFactory(workspaceId, pairingSecret, options) {
4408
+ const signBody = options?.signBody ?? true;
4409
+ return async (input, init) => {
4410
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
4411
+ const method = init?.method ?? "POST";
4412
+ let bodyForSigning = "";
4413
+ if (signBody && init?.body !== void 0 && init.body !== null) bodyForSigning = typeof init.body === "string" ? init.body : await new Response(init.body).text();
4414
+ const authHeader = buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyForSigning, options);
4415
+ const headers = new Headers(init?.headers);
4416
+ headers.set("Authorization", authHeader);
4417
+ const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
4418
+ return fetch(input, {
4419
+ ...init,
4420
+ body: outgoingBody,
4421
+ headers
4422
+ });
4423
+ };
4424
+ }
4425
+ /**
4426
+ * Produces a Codemation-Hmac v=1 Authorization header value.
4427
+ * Algorithm must match HmacVerifier.computeSignature() in the control-plane.
4428
+ */
4429
+ function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, overrides) {
4430
+ const ts = overrides?.now ? overrides.now() : Math.floor(Date.now() / 1e3);
4431
+ const nonce = overrides?.nonce ? overrides.nonce() : (0, node_crypto.randomBytes)(16).toString("base64");
4432
+ const parsed = new URL(url);
4433
+ const path = (parsed.pathname + parsed.search).toLowerCase();
4434
+ const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
4435
+ const baseString = [
4436
+ method.toUpperCase(),
4437
+ path,
4438
+ ts,
4439
+ nonce,
4440
+ bodyHash
4441
+ ].join("\n");
4442
+ return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${(0, node_crypto.createHmac)("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
4443
+ }
4444
+
4392
4445
  //#endregion
4393
4446
  //#region src/chatModels/CodemationChatModelFactory.ts
4394
4447
  let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
@@ -4398,7 +4451,7 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
4398
4451
  const workspaceId = process.env["WORKSPACE_ID"];
4399
4452
  const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
4400
4453
  if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
4401
- const hmacFetch = this.buildHmacSignedFetch(workspaceId, pairingSecret);
4454
+ const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
4402
4455
  const languageModel = (0, __ai_sdk_openai.createOpenAI)({
4403
4456
  baseURL: `${gatewayUrl}/v1`,
4404
4457
  apiKey: "codemation-managed",
@@ -4414,52 +4467,6 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
4414
4467
  }
4415
4468
  });
4416
4469
  }
4417
- /**
4418
- * Creates an HMAC-signed fetch wrapper for use with AI SDK's createOpenAI.
4419
- * Each call signs the request body with the workspace pairing secret so the
4420
- * LLM broker can authenticate the workspace without a user-managed API key.
4421
- *
4422
- * Mirrors HmacRequestSigner from @codemation/host/pairing without importing
4423
- * that package (which would create a circular dependency since @codemation/host
4424
- * depends on @codemation/core-nodes).
4425
- */
4426
- buildHmacSignedFetch(workspaceId, pairingSecret) {
4427
- return async (input, init) => {
4428
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
4429
- const method = init?.method ?? "POST";
4430
- let bodyString = "";
4431
- if (init?.body !== void 0 && init.body !== null) if (typeof init.body === "string") bodyString = init.body;
4432
- else bodyString = await new Response(init.body).text();
4433
- const authHeader = this.buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyString);
4434
- const headers = new Headers(init?.headers);
4435
- headers.set("Authorization", authHeader);
4436
- const effectiveBody = bodyString || init?.body;
4437
- return fetch(input, {
4438
- ...init,
4439
- body: effectiveBody,
4440
- headers
4441
- });
4442
- };
4443
- }
4444
- /**
4445
- * Produces a Codemation-Hmac v1 Authorization header value.
4446
- * The algorithm must match HmacVerifier.computeSignature() in the control-plane.
4447
- */
4448
- buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body) {
4449
- const ts = Math.floor(Date.now() / 1e3);
4450
- const nonce = (0, node_crypto.randomBytes)(16).toString("base64");
4451
- const parsed = new URL(url);
4452
- const path = (parsed.pathname + parsed.search).toLowerCase();
4453
- const bodyHash = (0, node_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
4454
- const baseString = [
4455
- method.toUpperCase(),
4456
- path,
4457
- ts,
4458
- nonce,
4459
- bodyHash
4460
- ].join("\n");
4461
- 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")}`;
4462
- }
4463
4470
  };
4464
4471
  CodemationChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
4465
4472
 
@@ -6815,7 +6822,11 @@ let AIAgentNode = class AIAgentNode$1 {
6815
6822
  });
6816
6823
  try {
6817
6824
  const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
6818
- const outputSchema = structuredOptions?.strict && !this.isZodSchema(schema) ? ai.Output.object({ schema: (0, ai.jsonSchema)(schema) }) : ai.Output.object({ schema });
6825
+ const schemaRecord = this.isZodSchema(schema) ? this.executionHelpers.createJsonSchemaRecord(schema, {
6826
+ schemaName: structuredOptions?.schemaName ?? "structured_output",
6827
+ requireObjectRoot: true
6828
+ }) : schema;
6829
+ const outputSchema = ai.Output.object({ schema: (0, ai.jsonSchema)(schemaRecord) });
6819
6830
  const result = await (0, ai.generateText)({
6820
6831
  model: model.languageModel,
6821
6832
  messages: [...messages],
@@ -9051,6 +9062,86 @@ const inboxApproval = (0, __codemation_core.defineHumanApprovalNode)({
9051
9062
  }
9052
9063
  });
9053
9064
 
9065
+ //#endregion
9066
+ //#region src/nodes/codemationDocumentScannerNode.ts
9067
+ const ANALYZER_TYPES = [
9068
+ "document",
9069
+ "invoice",
9070
+ "image",
9071
+ "auto"
9072
+ ];
9073
+ const codemationDocumentScannerNode = (0, __codemation_core.defineNode)({
9074
+ key: "codemation.document-scanner",
9075
+ title: "Codemation Document Scanner",
9076
+ description: "Analyzes a binary attachment (document or image) via the managed Codemation document-scanning service and returns markdown text plus structured fields. No Azure credential required — auth uses the workspace pairing secret. Enable includeConfidence to get per-field confidence scores (0–1).",
9077
+ icon: "lucide:scan-text",
9078
+ input: {
9079
+ binaryField: "data",
9080
+ analyzerType: "auto",
9081
+ contentType: void 0,
9082
+ includeConfidence: false,
9083
+ maxBytes: void 0
9084
+ },
9085
+ configSchema: object({
9086
+ binaryField: string().optional(),
9087
+ analyzerType: _enum(ANALYZER_TYPES).optional(),
9088
+ contentType: string().optional(),
9089
+ includeConfidence: boolean().optional(),
9090
+ maxBytes: number().int().positive().optional()
9091
+ }),
9092
+ inspectorSummary({ config: config$1 }) {
9093
+ const cfg = config$1;
9094
+ const rows = [{
9095
+ label: "Analyzer type",
9096
+ value: cfg.analyzerType ?? "auto"
9097
+ }];
9098
+ const binaryField = cfg.binaryField ?? "data";
9099
+ if (binaryField !== "data") rows.push({
9100
+ label: "Binary field",
9101
+ value: binaryField
9102
+ });
9103
+ if (cfg.includeConfidence) rows.push({
9104
+ label: "Confidence",
9105
+ value: "enabled"
9106
+ });
9107
+ if (cfg.contentType) rows.push({
9108
+ label: "Content type",
9109
+ value: cfg.contentType
9110
+ });
9111
+ return rows;
9112
+ },
9113
+ async execute({ item, ctx }, { config: rawConfig }) {
9114
+ const config$1 = rawConfig;
9115
+ const gatewayUrl = process.env["DOC_SCANNER_GATEWAY_URL"];
9116
+ if (!gatewayUrl) throw new Error("Codemation Document Scanner not available in this environment (DOC_SCANNER_GATEWAY_URL is not set).");
9117
+ const workspaceId = process.env["WORKSPACE_ID"];
9118
+ const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
9119
+ if (!workspaceId || !pairingSecret) throw new Error("Codemation Document Scanner not available (workspace pairing is not configured).");
9120
+ const binaryField = config$1.binaryField ?? "data";
9121
+ const attachment = item.binary?.[binaryField];
9122
+ if (!attachment) throw new Error(`Codemation Document Scanner: no binary attachment at key "${binaryField}".`);
9123
+ const body = await ctx.binary.getBytes(attachment, config$1.maxBytes);
9124
+ const contentType = config$1.contentType ?? attachment.mimeType ?? "application/octet-stream";
9125
+ const analyzerType = config$1.analyzerType ?? "auto";
9126
+ const confidenceSuffix = config$1.includeConfidence ?? false ? "&confidence=true" : "";
9127
+ const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
9128
+ const response = await managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false })(url, {
9129
+ method: "POST",
9130
+ body: body.buffer,
9131
+ headers: {
9132
+ "Content-Type": contentType,
9133
+ "Content-Length": String(body.byteLength),
9134
+ "X-Codemation-Caller": "workflow-node"
9135
+ }
9136
+ });
9137
+ if (!response.ok) {
9138
+ const text = await response.text().catch(() => "(unreadable)");
9139
+ throw new Error(`Codemation Document Scanner: service responded ${response.status} ${response.statusText} — ${text}`);
9140
+ }
9141
+ return await response.json();
9142
+ }
9143
+ });
9144
+
9054
9145
  //#endregion
9055
9146
  exports.AIAgent = AIAgent;
9056
9147
  exports.AIAgentConnectionWorkflowExpander = AIAgentConnectionWorkflowExpander;
@@ -9279,6 +9370,7 @@ exports.WorkflowChain = WorkflowChain;
9279
9370
  exports.apiKeyCredentialType = apiKeyCredentialType;
9280
9371
  exports.basicAuthCredentialType = basicAuthCredentialType;
9281
9372
  exports.bearerTokenCredentialType = bearerTokenCredentialType;
9373
+ exports.codemationDocumentScannerNode = codemationDocumentScannerNode;
9282
9374
  exports.collectionDeleteNode = collectionDeleteNode;
9283
9375
  exports.collectionFindOneNode = collectionFindOneNode;
9284
9376
  exports.collectionGetNode = collectionGetNode;