From a0f74b1fea31ae9df9e30caf789366e59353a0b0 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:54:39 +0530 Subject: [PATCH 1/9] UN-2894 Fix n8n cloud review issues - Remove dependency overrides from package.json - Remove prepublishOnly script, add validate script instead - Add pairedItem linkage to all returnData.push() calls - Replace helpers.request() with helpers.httpRequestWithAuthentication() - Update LLMWhispererApi credentials to use unstract-key header - Add build:dev script for fast development builds - Update workflows to use validate script for comprehensive checks - Update package-lock.json All blocking issues from n8n cloud review have been addressed. --- .github/workflows/pr-checks.yml | 2 +- credentials/LLMWhispererApi.credentials.ts | 2 +- nodes/LlmWhisperer/LlmWhisperer.node.ts | 16 ++++------------ nodes/Unstract/Unstract.node.ts | 12 +++--------- .../UnstractHitlFetch/UnstractHitlFetch.node.ts | 16 +++++++++------- nodes/UnstractHitlPush/UnstractHitlPush.node.ts | 16 ++++++---------- package-lock.json | 15 +++++++++++++++ package.json | 6 ++---- 8 files changed, 41 insertions(+), 44 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 0934c30..6da23a5 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -16,7 +16,7 @@ jobs: run: npm ci - name: Run comprehensive checks - run: npm run prepublishOnly + run: npm run validate - name: Validate package structure run: npm pack --dry-run diff --git a/credentials/LLMWhispererApi.credentials.ts b/credentials/LLMWhispererApi.credentials.ts index 85bf703..2660211 100644 --- a/credentials/LLMWhispererApi.credentials.ts +++ b/credentials/LLMWhispererApi.credentials.ts @@ -29,7 +29,7 @@ export class LLMWhispererApi implements ICredentialType { type: 'generic', properties: { headers: { - 'Authorization': '=Bearer apiKey', + 'unstract-key': '={{$credentials.apiKey}}', }, }, }; diff --git a/nodes/LlmWhisperer/LlmWhisperer.node.ts b/nodes/LlmWhisperer/LlmWhisperer.node.ts index 7cc5051..08352d2 100644 --- a/nodes/LlmWhisperer/LlmWhisperer.node.ts +++ b/nodes/LlmWhisperer/LlmWhisperer.node.ts @@ -195,8 +195,6 @@ export class LlmWhisperer implements INodeType { const returnData: INodeExecutionData[] = []; try { - const credentials = await this.getCredentials('llmWhispererApi'); - const apiKey = credentials.apiKey as string; const { helpers, logger } = this; for (let i = 0; i < items.length; i++) { @@ -231,7 +229,6 @@ export class LlmWhisperer implements INodeType { method: 'POST' as IHttpRequestMethods, url: `${host}/api/v2/whisper`, headers: { - 'unstract-key': `${apiKey}`, 'Content-Type': 'application/octet-stream', }, body: fileBuffer, @@ -256,7 +253,7 @@ export class LlmWhisperer implements INodeType { logger.info('Making API request to LLMWhisperer API...'); let result: any; try { - result = await helpers.request(requestOptions); + result = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', requestOptions); } catch (requestError) { logger.error('Error during LLMWhisperer API request:', requestError); throw requestError; @@ -276,12 +273,9 @@ export class LlmWhisperer implements INodeType { while (status !== 'processed' && status !== 'error') { await sleep(2000); - const statusResult = await helpers.request({ + const statusResult = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', { method: 'GET' as IHttpRequestMethods, url: `${host}/api/v2/whisper-status`, - headers: { - 'unstract-key': `${apiKey}`, - }, qs: { whisper_hash: whisperHash, }, @@ -311,12 +305,9 @@ export class LlmWhisperer implements INodeType { } if (status === 'processed') { - const retrieveResult = await helpers.request({ + const retrieveResult = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', { method: 'GET' as IHttpRequestMethods, url: `${host}/api/v2/whisper-retrieve`, - headers: { - 'unstract-key': `${apiKey}`, - }, qs: { whisper_hash: whisperHash, }, @@ -327,6 +318,7 @@ export class LlmWhisperer implements INodeType { delete retrieveResultContent.webhook_metadata; returnData.push({ json: retrieveResultContent, + pairedItem: { item: i }, }); } } diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index 20d57f4..d74b14c 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -112,7 +112,6 @@ export class Unstract implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); - const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; @@ -158,15 +157,12 @@ export class Unstract implements INodeType { const requestOptions = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, formData, timeout: 5 * 60 * 1000, }; logger.info('Making API request to Unstract API...'); - const result = await helpers.request(requestOptions); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); let resultContent = JSON.parse(result).message; let executionStatus = resultContent.execution_status; @@ -180,14 +176,11 @@ export class Unstract implements INodeType { const statusRequestOptions = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, timeout: 5 * 60 * 1000, }; try { - const statusResult = await helpers.request(statusRequestOptions); + const statusResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', statusRequestOptions); resultContent = JSON.parse(statusResult); executionStatus = resultContent.status; } catch (error: any) { @@ -217,6 +210,7 @@ export class Unstract implements INodeType { returnData.push({ json: resultContent, + pairedItem: { item: i }, }); } diff --git a/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts b/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts index 7378abf..74779c9 100644 --- a/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts +++ b/nodes/UnstractHitlFetch/UnstractHitlFetch.node.ts @@ -56,7 +56,6 @@ export class UnstractHitlFetch implements INodeType { const returnData: INodeExecutionData[] = []; const credentials = await this.getCredentials('unstractHITLApi'); - const hitlKey = credentials.HITLKey as string; const orgId = credentials.orgId as string; const helpers = this.helpers; @@ -73,23 +72,26 @@ export class UnstractHitlFetch implements INodeType { const options = { method: 'GET' as IHttpRequestMethods, url, - headers: { - Authorization: `Bearer ${hitlKey}`, - }, }; try { - const response = await helpers.request(options); + const response = await helpers.httpRequestWithAuthentication.call(this, 'unstractHITLApi', options); const parsed = typeof response === 'string' ? JSON.parse(response) : response; if (parsed.error) { if (parsed.error === 'No approved items available.') { - returnData.push({ json: { message: 'No approved items available', hasData: false } }); + returnData.push({ + json: { message: 'No approved items available', hasData: false }, + pairedItem: { item: i } + }); } else { throw new NodeOperationError(this.getNode(), `API Error: ${parsed.error}`); } } else if (parsed.data) { - returnData.push({ json: { ...parsed.data, hasData: true } }); + returnData.push({ + json: { ...parsed.data, hasData: true }, + pairedItem: { item: i } + }); } else { throw new NodeOperationError(this.getNode(), 'Unexpected response format.'); } diff --git a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts index bc8d0a4..9b769e2 100644 --- a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts +++ b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts @@ -114,7 +114,6 @@ export class UnstractHitlPush implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); - const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; @@ -159,15 +158,12 @@ export class UnstractHitlPush implements INodeType { const requestOptions = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, - headers: { - Authorization: `Bearer ${apiKey}`, - }, formData, timeout: 5 * 60 * 1000, }; logger.info('[HITL] Sending file to Unstract HITL API...'); - const result = await helpers.request(requestOptions); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); let resultContent = JSON.parse(result).message; let executionStatus = resultContent.execution_status; @@ -181,14 +177,11 @@ export class UnstractHitlPush implements INodeType { const pollRequest = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, - headers: { - Authorization: `Bearer ${apiKey}`, - }, timeout: 5 * 60 * 1000, }; try { - const pollResult = await helpers.request(pollRequest); + const pollResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', pollRequest); resultContent = JSON.parse(pollResult); executionStatus = resultContent.status; } catch (error: any) { @@ -216,7 +209,10 @@ export class UnstractHitlPush implements INodeType { } } - returnData.push({ json: resultContent }); + returnData.push({ + json: resultContent, + pairedItem: { item: i } + }); } return [returnData]; diff --git a/package-lock.json b/package-lock.json index 55a290b..a300140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3428,6 +3428,21 @@ "zod": "3.24.1" } }, + "node_modules/n8n-workflow/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index e34bbe1..bc2841e 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,12 @@ "main": "index.js", "scripts": { "build": "npx rimraf dist && tsc && gulp build:icons", + "build:dev": "npx rimraf dist && tsc && gulp build:icons", "dev": "tsc --watch", "format": "prettier nodes credentials --write", "lint": "eslint nodes/**/*.ts credentials/**/*.ts package.json", "lintfix": "eslint nodes/**/*.ts credentials/**/*.ts package.json --fix", - "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json && npm audit --audit-level=high" + "validate": "npm run build && eslint -c .eslintrc.prepublish.js nodes credentials package.json" }, "files": [ "dist", @@ -70,8 +71,5 @@ }, "peerDependencies": { "n8n-workflow": "*" - }, - "overrides": { - "form-data": "^4.0.4" } } From d2e014d15da0d8e2b74337e6acb5539eba71f24b Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:32:49 +0530 Subject: [PATCH 2/9] refactor: separate audit check from validation script - Add dedicated 'audit' script for security checks with high severity threshold - Remove audit from 'validate' script to allow independent execution - Update package-lock.json to reflect current dependency tree --- package-lock.json | 57 ++++++++++------------------------------------- package.json | 1 + 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index a300140..c25de9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -841,18 +841,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "license": "MIT", - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -1460,22 +1448,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2029,23 +2001,6 @@ "node": ">=0.10.0" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs-mkdirp-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", @@ -3428,6 +3383,18 @@ "zod": "3.24.1" } }, + "node_modules/n8n-workflow/node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/n8n-workflow/node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", diff --git a/package.json b/package.json index bc2841e..50ebca1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "format": "prettier nodes credentials --write", "lint": "eslint nodes/**/*.ts credentials/**/*.ts package.json", "lintfix": "eslint nodes/**/*.ts credentials/**/*.ts package.json --fix", + "audit": "npm audit --audit-level=high", "validate": "npm run build && eslint -c .eslintrc.prepublish.js nodes credentials package.json" }, "files": [ From d912f6f7f726682661698f7b7e5b94bb7ddba1d5 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:35:05 +0530 Subject: [PATCH 3/9] ci: add security audit step to PR checks - Add npm audit step after validation - Set continue-on-error to true for visibility without blocking PRs - Audit failures will be visible in CI but won't fail the workflow --- .github/workflows/pr-checks.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 6da23a5..8988f1b 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -17,7 +17,11 @@ jobs: - name: Run comprehensive checks run: npm run validate - + + - name: Run security audit + run: npm run audit + continue-on-error: true + - name: Validate package structure run: npm pack --dry-run From c5c6ebe3fdca4e61aca8db71764461fb095e9803 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:28:01 +0530 Subject: [PATCH 4/9] refactor: remove httpbin credential tests per n8n review - Remove test property from all three credential classes: - LLMWhispererApi - UnstractApi - UnstractHITLApi - Remove unused ICredentialTestRequest import - httpbin tests did not actually validate credentials (would pass with any value) - Following n8n recommendation to remove rather than use fake validation endpoints Addresses n8n community node review feedback. --- credentials/LLMWhispererApi.credentials.ts | 12 ------------ credentials/UnstractApi.credentials.ts | 12 ------------ credentials/UnstractHITLApi.credentials.ts | 12 ------------ 3 files changed, 36 deletions(-) diff --git a/credentials/LLMWhispererApi.credentials.ts b/credentials/LLMWhispererApi.credentials.ts index 2660211..1d0a4a2 100644 --- a/credentials/LLMWhispererApi.credentials.ts +++ b/credentials/LLMWhispererApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -33,15 +32,4 @@ export class LLMWhispererApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // LLMWhisperer API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file diff --git a/credentials/UnstractApi.credentials.ts b/credentials/UnstractApi.credentials.ts index f8d7074..06aa8a1 100644 --- a/credentials/UnstractApi.credentials.ts +++ b/credentials/UnstractApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -41,15 +40,4 @@ export class UnstractApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // Unstract API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file diff --git a/credentials/UnstractHITLApi.credentials.ts b/credentials/UnstractHITLApi.credentials.ts index d555da0..e18de2c 100644 --- a/credentials/UnstractHITLApi.credentials.ts +++ b/credentials/UnstractHITLApi.credentials.ts @@ -1,7 +1,6 @@ import { ICredentialType, INodeProperties, - ICredentialTestRequest, IAuthenticateGeneric, } from 'n8n-workflow'; @@ -41,15 +40,4 @@ export class UnstractHITLApi implements ICredentialType { }, }, }; - - // Using external test endpoint to satisfy n8n verification requirements - // Unstract HITL API does not provide a dedicated test connection endpoint - // httpbin.org/bearer accepts any Bearer token and returns success - test: ICredentialTestRequest = { - request: { - baseURL: 'https://httpbin.org', - url: '/bearer', - method: 'GET', - }, - }; } \ No newline at end of file From 33f2ce06b6d22d57195e9348758040ea3c646b0b Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:54:15 +0530 Subject: [PATCH 5/9] fix: correct credential reference in authenticate blocks - Fix UnstractApi to use {{$credentials.apiKey}} instead of literal 'apiKey' - Fix UnstractHITLApi to use {{$credentials.HITLKey}} instead of literal 'apiKey' - Previous implementation sent literal string "Bearer apiKey" instead of actual token - This bug was introduced in PR #17 along with httpbin tests These authenticate blocks are required for httpRequestWithAuthentication to work correctly. --- credentials/UnstractApi.credentials.ts | 2 +- credentials/UnstractHITLApi.credentials.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/credentials/UnstractApi.credentials.ts b/credentials/UnstractApi.credentials.ts index 06aa8a1..f9ee06a 100644 --- a/credentials/UnstractApi.credentials.ts +++ b/credentials/UnstractApi.credentials.ts @@ -36,7 +36,7 @@ export class UnstractApi implements ICredentialType { type: 'generic', properties: { headers: { - 'Authorization': '=Bearer apiKey', + 'Authorization': '=Bearer {{$credentials.apiKey}}', }, }, }; diff --git a/credentials/UnstractHITLApi.credentials.ts b/credentials/UnstractHITLApi.credentials.ts index e18de2c..ecd5dbe 100644 --- a/credentials/UnstractHITLApi.credentials.ts +++ b/credentials/UnstractHITLApi.credentials.ts @@ -36,7 +36,7 @@ export class UnstractHITLApi implements ICredentialType { type: 'generic', properties: { headers: { - 'Authorization': '=Bearer apiKey', + 'Authorization': '=Bearer {{$credentials.HITLKey}}', }, }, }; From f53350ad162944e282b5cdd0ef2b71404cc7fec8 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:29:47 +0530 Subject: [PATCH 6/9] fix: add explicit Authorization headers to work around n8n formData bug - Add explicit Authorization header to Unstract node POST request - Add explicit Authorization header to status polling GET requests - Extract apiKey from credentials to use in headers - Works around known n8n bug where httpRequestWithAuthentication doesn't properly add auth headers when formData is present (Issue #18271) - Still uses httpRequestWithAuthentication to satisfy n8n review requirements - Restores working pattern from commit 845c15c while keeping httpRequestWithAuthentication This fixes 'Bad request' errors caused by missing Authorization headers. --- nodes/Unstract/Unstract.node.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index d74b14c..3dfd15a 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -112,6 +112,7 @@ export class Unstract implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); + const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; @@ -157,6 +158,9 @@ export class Unstract implements INodeType { const requestOptions = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, + headers: { + 'Authorization': `Bearer ${apiKey}`, + }, formData, timeout: 5 * 60 * 1000, }; @@ -176,6 +180,9 @@ export class Unstract implements INodeType { const statusRequestOptions = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, + headers: { + 'Authorization': `Bearer ${apiKey}`, + }, timeout: 5 * 60 * 1000, }; From 069ac0bc42f5b01d4fde50437c47d0bcbc78f467 Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Fri, 21 Nov 2025 02:48:42 +0530 Subject: [PATCH 7/9] fix: resolve n8n Cloud compatibility issues across all nodes This commit addresses multiple issues found during n8n Cloud review: **Unstract node:** - Add HTTP 422 handling for status polling (API uses 422 to indicate "still executing") - Use helpers.httpRequest() for status checks with manual Authorization header - Add comprehensive logging with request/response details **UnstractHitlPush node:** - Implement manual multipart/form-data construction (n8n Cloud requirement) - Add HTTP 422 handling for status polling - Fix response parsing to handle already-parsed JSON from httpRequestWithAuthentication - Add comprehensive logging for debugging **LLMWhisperer node:** - Fix response parsing issues (httpRequestWithAuthentication returns parsed JSON, not strings) - Remove incorrect HTTP status check that was comparing job status to HTTP status code - Add proper validation for required whisper_hash field - Add comprehensive logging throughout the workflow **Credentials:** - Remove trailing whitespace for code quality All nodes now properly handle: 1. Response parsing from httpRequestWithAuthentication 2. Status polling with appropriate error handling 3. Manual multipart form construction where required for n8n Cloud --- credentials/UnstractApi.credentials.ts | 2 +- credentials/UnstractHITLApi.credentials.ts | 2 +- nodes/LlmWhisperer/LlmWhisperer.node.ts | 30 +++-- nodes/Unstract/Unstract.node.ts | 114 ++++++++++++++---- .../UnstractHitlPush/UnstractHitlPush.node.ts | 113 +++++++++++++---- 5 files changed, 201 insertions(+), 60 deletions(-) diff --git a/credentials/UnstractApi.credentials.ts b/credentials/UnstractApi.credentials.ts index f9ee06a..0a37b00 100644 --- a/credentials/UnstractApi.credentials.ts +++ b/credentials/UnstractApi.credentials.ts @@ -9,7 +9,7 @@ export class UnstractApi implements ICredentialType { displayName = 'Unstract API'; documentationUrl = 'https://docs.unstract.com/unstract/index.html'; icon = 'file:llmWhisperer.svg' as const; - + properties: INodeProperties[] = [ { displayName: 'API Key', diff --git a/credentials/UnstractHITLApi.credentials.ts b/credentials/UnstractHITLApi.credentials.ts index ecd5dbe..35b819e 100644 --- a/credentials/UnstractHITLApi.credentials.ts +++ b/credentials/UnstractHITLApi.credentials.ts @@ -9,7 +9,7 @@ export class UnstractHITLApi implements ICredentialType { displayName = 'Unstract HITL API'; documentationUrl = 'https://docs.unstract.com/unstract/index.html'; icon = 'file:llmWhisperer.svg' as const; - + properties: INodeProperties[] = [ { displayName: 'HITL Key', diff --git a/nodes/LlmWhisperer/LlmWhisperer.node.ts b/nodes/LlmWhisperer/LlmWhisperer.node.ts index 08352d2..0672fa6 100644 --- a/nodes/LlmWhisperer/LlmWhisperer.node.ts +++ b/nodes/LlmWhisperer/LlmWhisperer.node.ts @@ -250,20 +250,29 @@ export class LlmWhisperer implements INodeType { accept: 'application/json', }; - logger.info('Making API request to LLMWhisperer API...'); + logger.info('[LLMWhisperer] Making API request to: ' + requestOptions.url); + logger.info('[LLMWhisperer] File: ' + binaryData.fileName + ' (' + fileBuffer.length + ' bytes)'); + let result: any; try { result = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', requestOptions); - } catch (requestError) { - logger.error('Error during LLMWhisperer API request:', requestError); + } catch (requestError: any) { + logger.error('[LLMWhisperer] Error during API request: ' + requestError.message); throw requestError; } - if (result.status && result.status !== 202) { - throw new NodeOperationError(this.getNode(), result.body); + // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json + const resultContent = typeof result === 'string' ? JSON.parse(result) : result; + + logger.info('[LLMWhisperer] API Response: ' + JSON.stringify(resultContent)); + + if (!resultContent.whisper_hash) { + throw new NodeOperationError( + this.getNode(), + `Invalid API response: ${resultContent.message || JSON.stringify(resultContent)}`, + ); } - const resultContent = JSON.parse(result) as any; const whisperHash = resultContent.whisper_hash; let status = 'processing'; @@ -281,8 +290,10 @@ export class LlmWhisperer implements INodeType { }, }); - resultContentX = JSON.parse(statusResult); + // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json + resultContentX = typeof statusResult === 'string' ? JSON.parse(statusResult) : statusResult; status = resultContentX.status; + logger.info('[LLMWhisperer] Status check: ' + status); const currentTime = Date.now(); const elapsedSeconds = (currentTime - t1) / 1000; @@ -313,7 +324,10 @@ export class LlmWhisperer implements INodeType { }, }); - const retrieveResultContent = JSON.parse(retrieveResult); + // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json + const retrieveResultContent = typeof retrieveResult === 'string' ? JSON.parse(retrieveResult) : retrieveResult; + logger.info('[LLMWhisperer] Retrieved result successfully'); + delete retrieveResultContent.metadata; delete retrieveResultContent.webhook_metadata; returnData.push({ diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index 3dfd15a..9f23a2c 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -137,37 +137,79 @@ export class Unstract implements INodeType { const tags = this.getNodeParameter('tags', i) as string; const useFileHistory = this.getNodeParameter('use_file_history', i) as boolean; - const formData: any = { - files: { - value: fileBuffer, - options: { - filename: binaryData.fileName, - contentType: binaryData.mimeType, - }, - }, - timeout: 1, + // Manual multipart/form-data construction (cloud-compatible, no external dependencies) + // Workaround for n8n issue #18271 where httpRequestWithAuthentication doesn't properly + // handle formData objects. See: https://github.com/n8n-io/n8n/issues/18271 + const boundary = `----n8nFormBoundary${Date.now()}`; + const CRLF = '\r\n'; + + // Build multipart body parts + const parts: Buffer[] = []; + + // File field + parts.push(Buffer.from( + `--${boundary}${CRLF}` + + `Content-Disposition: form-data; name="files"; filename="${binaryData.fileName}"${CRLF}` + + `Content-Type: ${binaryData.mimeType}${CRLF}${CRLF}` + )); + parts.push(fileBuffer); + parts.push(Buffer.from(CRLF)); + + // Other fields + const fields = { + timeout: '1', include_metrics: includeMetrics.toString(), include_metadata: includeMetadata.toString(), use_file_history: useFileHistory.toString(), + ...(tags && { tags }), }; - if (tags) { - formData.tags = tags; + for (const [name, value] of Object.entries(fields)) { + if (value) { + parts.push(Buffer.from( + `--${boundary}${CRLF}` + + `Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}` + + `${value}${CRLF}` + )); + } } - const requestOptions = { + // Closing boundary + parts.push(Buffer.from(`--${boundary}--${CRLF}`)); + + // Combine all parts + const body = Buffer.concat(parts); + + const requestOptions: any = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, headers: { 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'Content-Length': body.length.toString(), }, - formData, + body, timeout: 5 * 60 * 1000, }; - logger.info('Making API request to Unstract API...'); + logger.info(`Making API request to: ${requestOptions.url}`); + logger.info(`File: ${binaryData.fileName} (${fileBuffer.length} bytes)`); + logger.info(`Request headers: ${JSON.stringify(requestOptions.headers)}`); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); - let resultContent = JSON.parse(result).message; + // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json + const resultData = typeof result === 'string' ? JSON.parse(result) : result; + + logger.info(`API Response: ${JSON.stringify(resultData)}`); + + if (!resultData || !resultData.message) { + throw new NodeOperationError( + this.getNode(), + `Unexpected API response structure: ${JSON.stringify(resultData)}`, + ); + } + + let resultContent = resultData.message; let executionStatus = resultContent.execution_status; if (executionStatus === 'PENDING' || executionStatus === 'EXECUTING') { @@ -177,7 +219,7 @@ export class Unstract implements INodeType { while (executionStatus !== 'COMPLETED') { await sleep(2000); - const statusRequestOptions = { + const statusRequestOptions: any = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, headers: { @@ -185,19 +227,32 @@ export class Unstract implements INodeType { }, timeout: 5 * 60 * 1000, }; + logger.info(`Status check URL: ${statusRequestOptions.url}`); + logger.info(`Status check method: ${statusRequestOptions.method}`); + logger.info(`Status check headers: ${JSON.stringify(statusRequestOptions.headers)}`); + try { - const statusResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', statusRequestOptions); - resultContent = JSON.parse(statusResult); + const statusResult = await helpers.httpRequest(statusRequestOptions); + resultContent = typeof statusResult === 'string' ? JSON.parse(statusResult) : statusResult; executionStatus = resultContent.status; } catch (error: any) { - if (error.response && error.response.statusCode === 400) { - throw new NodeOperationError(this.getNode(), `Error: ${error}`); + // HTTP 422 indicates execution still in progress - this is expected + if (error.response?.status === 422 && error.response?.data) { + resultContent = typeof error.response.data === 'string' ? JSON.parse(error.response.data) : error.response.data; + executionStatus = resultContent.status; + logger.info(`Status check returned 422 (still executing): ${executionStatus}`); + } else { + // Actual error - log and rethrow + logger.error(`Status check failed: ${error.message}`); + if (error.response?.status) { + logger.error(`Status check response status: ${error.response.status}`); + } + throw new NodeOperationError( + this.getNode(), + `Failed to check execution status: ${error.message}`, + ); } - const jsonResponse = error.message.split(' - ')[1]; - const cleanJson = jsonResponse.replace(/\\"/g, '"').slice(1, -1); - resultContent = JSON.parse(cleanJson); - executionStatus = resultContent.status; } const t2 = new Date(); @@ -223,6 +278,17 @@ export class Unstract implements INodeType { return [returnData]; } catch (error: any) { + this.logger.error(`Error message: ${error.message}`); + this.logger.error(`Error name: ${error.name}`); + if (error.response?.data) { + this.logger.error(`Response data: ${JSON.stringify(error.response.data, null, 2)}`); + } + if (error.response?.status) { + this.logger.error(`Response status: ${error.response.status}`); + } + if (error.context?.data) { + this.logger.error(`Error context: ${JSON.stringify(error.context.data, null, 2)}`); + } if (error.message) { throw new NodeOperationError(this.getNode(), error.message); } diff --git a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts index 9b769e2..463f9c2 100644 --- a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts +++ b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts @@ -114,12 +114,13 @@ export class UnstractHitlPush implements INodeType { try { const credentials = await this.getCredentials('unstractApi'); + const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; const { helpers, logger } = this; for (let i = 0; i < items.length; i++) { const binaryPropertyName = this.getNodeParameter('file_contents', i) as string; - + if (!items[i].binary?.[binaryPropertyName]) { throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" exists on input`); } @@ -136,62 +137,122 @@ export class UnstractHitlPush implements INodeType { const useFileHistory = this.getNodeParameter('use_file_history', i) as boolean; const hitlQueueName = this.getNodeParameter('hitl_queue_name', i) as string; - const formData: any = { - files: { - value: fileBuffer, - options: { - filename: binaryData.fileName, - contentType: binaryData.mimeType, - }, - }, - timeout: 1, + // Manual multipart/form-data construction (cloud-compatible, no external dependencies) + // Workaround for n8n issue #18271 where httpRequestWithAuthentication doesn't properly + // handle formData objects. See: https://github.com/n8n-io/n8n/issues/18271 + const boundary = `----n8nFormBoundary${Date.now()}`; + const CRLF = '\r\n'; + + // Build multipart body parts + const parts: Buffer[] = []; + + // File field + parts.push(Buffer.from( + `--${boundary}${CRLF}` + + `Content-Disposition: form-data; name="files"; filename="${binaryData.fileName}"${CRLF}` + + `Content-Type: ${binaryData.mimeType}${CRLF}${CRLF}` + )); + parts.push(fileBuffer); + parts.push(Buffer.from(CRLF)); + + // Other fields + const fields = { + timeout: '1', include_metrics: includeMetrics.toString(), include_metadata: includeMetadata.toString(), use_file_history: useFileHistory.toString(), hitl_queue_name: hitlQueueName, + ...(tags && { tags }), }; - if (tags) { - formData.tags = tags; + for (const [name, value] of Object.entries(fields)) { + if (value) { + parts.push(Buffer.from( + `--${boundary}${CRLF}` + + `Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}` + + `${value}${CRLF}` + )); + } } - const requestOptions = { + // Closing boundary + parts.push(Buffer.from(`--${boundary}--${CRLF}`)); + + // Combine all parts + const body = Buffer.concat(parts); + + const requestOptions: any = { method: 'POST' as IHttpRequestMethods, url: `${host}/deployment/api/${orgId}/${deploymentName}/`, - formData, + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'Content-Length': body.length.toString(), + }, + body, timeout: 5 * 60 * 1000, }; - logger.info('[HITL] Sending file to Unstract HITL API...'); + logger.info('[HITL] Making API request to: ' + requestOptions.url); + logger.info('[HITL] File: ' + binaryData.fileName + ' (' + fileBuffer.length + ' bytes)'); + logger.info('[HITL] Request headers: ' + JSON.stringify(requestOptions.headers)); + const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); - let resultContent = JSON.parse(result).message; + // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json + const resultData = typeof result === 'string' ? JSON.parse(result) : result; + + logger.info('[HITL] API Response: ' + JSON.stringify(resultData)); + + if (!resultData || !resultData.message) { + throw new NodeOperationError( + this.getNode(), + `Unexpected API response structure: ${JSON.stringify(resultData)}`, + ); + } + + let resultContent = resultData.message; let executionStatus = resultContent.execution_status; if (executionStatus === 'PENDING' || executionStatus === 'EXECUTING') { - const statusApi = resultContent.status_api; const t1 = new Date(); + const statusApi = resultContent.status_api; while (executionStatus !== 'COMPLETED') { await sleep(2000); - const pollRequest = { + const statusRequestOptions: any = { method: 'GET' as IHttpRequestMethods, url: `${host}${statusApi}`, + headers: { + 'Authorization': `Bearer ${apiKey}`, + }, timeout: 5 * 60 * 1000, }; + logger.info('[HITL] Status check URL: ' + statusRequestOptions.url); + logger.info('[HITL] Status check method: ' + statusRequestOptions.method); + logger.info('[HITL] Status check headers: ' + JSON.stringify(statusRequestOptions.headers)); try { - const pollResult = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', pollRequest); - resultContent = JSON.parse(pollResult); + const statusResult = await helpers.httpRequest(statusRequestOptions); + resultContent = typeof statusResult === 'string' ? JSON.parse(statusResult) : statusResult; executionStatus = resultContent.status; } catch (error: any) { - if (error.response && error.response.statusCode === 400) { - throw new NodeOperationError(this.getNode(), `Polling error: ${error}`); + // HTTP 422 indicates execution still in progress - this is expected + if (error.response?.status === 422 && error.response?.data) { + resultContent = typeof error.response.data === 'string' ? JSON.parse(error.response.data) : error.response.data; + executionStatus = resultContent.status; + logger.info('[HITL] Status check returned 422 (still executing): ' + executionStatus); + } else { + // Actual error - log and rethrow + logger.error('[HITL] Status check failed: ' + error.message); + if (error.response?.status) { + logger.error('[HITL] Status check response status: ' + error.response.status); + } + throw new NodeOperationError( + this.getNode(), + `Failed to check execution status: ${error.message}`, + ); } - const json = error.message.split(' - ')[1]; - const cleanJson = json.replace(/\\"/g, '"').slice(1, -1); - resultContent = JSON.parse(cleanJson); - executionStatus = resultContent.status; } const t2 = new Date(); From 208ccaf0055a9ae0cb540896877007c46bcfa07b Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Fri, 21 Nov 2025 03:10:29 +0530 Subject: [PATCH 8/9] chore: remove debug logging from all nodes Removed debug logging statements that were added for troubleshooting: - LlmWhisperer: removed 6 logger statements - Unstract: removed 15 logger statements - UnstractHitlPush: removed 10 logger statements All nodes now have clean code without debug-level logging. --- nodes/LlmWhisperer/LlmWhisperer.node.ts | 6 ------ nodes/Unstract/Unstract.node.ts | 15 --------------- nodes/UnstractHitlPush/UnstractHitlPush.node.ts | 10 ---------- 3 files changed, 31 deletions(-) diff --git a/nodes/LlmWhisperer/LlmWhisperer.node.ts b/nodes/LlmWhisperer/LlmWhisperer.node.ts index 0672fa6..0155ec8 100644 --- a/nodes/LlmWhisperer/LlmWhisperer.node.ts +++ b/nodes/LlmWhisperer/LlmWhisperer.node.ts @@ -250,21 +250,17 @@ export class LlmWhisperer implements INodeType { accept: 'application/json', }; - logger.info('[LLMWhisperer] Making API request to: ' + requestOptions.url); - logger.info('[LLMWhisperer] File: ' + binaryData.fileName + ' (' + fileBuffer.length + ' bytes)'); let result: any; try { result = await helpers.httpRequestWithAuthentication.call(this, 'llmWhispererApi', requestOptions); } catch (requestError: any) { - logger.error('[LLMWhisperer] Error during API request: ' + requestError.message); throw requestError; } // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json const resultContent = typeof result === 'string' ? JSON.parse(result) : result; - logger.info('[LLMWhisperer] API Response: ' + JSON.stringify(resultContent)); if (!resultContent.whisper_hash) { throw new NodeOperationError( @@ -293,7 +289,6 @@ export class LlmWhisperer implements INodeType { // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json resultContentX = typeof statusResult === 'string' ? JSON.parse(statusResult) : statusResult; status = resultContentX.status; - logger.info('[LLMWhisperer] Status check: ' + status); const currentTime = Date.now(); const elapsedSeconds = (currentTime - t1) / 1000; @@ -326,7 +321,6 @@ export class LlmWhisperer implements INodeType { // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json const retrieveResultContent = typeof retrieveResult === 'string' ? JSON.parse(retrieveResult) : retrieveResult; - logger.info('[LLMWhisperer] Retrieved result successfully'); delete retrieveResultContent.metadata; delete retrieveResultContent.webhook_metadata; diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index 9f23a2c..b985d85 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -192,15 +192,11 @@ export class Unstract implements INodeType { timeout: 5 * 60 * 1000, }; - logger.info(`Making API request to: ${requestOptions.url}`); - logger.info(`File: ${binaryData.fileName} (${fileBuffer.length} bytes)`); - logger.info(`Request headers: ${JSON.stringify(requestOptions.headers)}`); const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json const resultData = typeof result === 'string' ? JSON.parse(result) : result; - logger.info(`API Response: ${JSON.stringify(resultData)}`); if (!resultData || !resultData.message) { throw new NodeOperationError( @@ -227,9 +223,6 @@ export class Unstract implements INodeType { }, timeout: 5 * 60 * 1000, }; - logger.info(`Status check URL: ${statusRequestOptions.url}`); - logger.info(`Status check method: ${statusRequestOptions.method}`); - logger.info(`Status check headers: ${JSON.stringify(statusRequestOptions.headers)}`); try { @@ -241,12 +234,9 @@ export class Unstract implements INodeType { if (error.response?.status === 422 && error.response?.data) { resultContent = typeof error.response.data === 'string' ? JSON.parse(error.response.data) : error.response.data; executionStatus = resultContent.status; - logger.info(`Status check returned 422 (still executing): ${executionStatus}`); } else { // Actual error - log and rethrow - logger.error(`Status check failed: ${error.message}`); if (error.response?.status) { - logger.error(`Status check response status: ${error.response.status}`); } throw new NodeOperationError( this.getNode(), @@ -278,16 +268,11 @@ export class Unstract implements INodeType { return [returnData]; } catch (error: any) { - this.logger.error(`Error message: ${error.message}`); - this.logger.error(`Error name: ${error.name}`); if (error.response?.data) { - this.logger.error(`Response data: ${JSON.stringify(error.response.data, null, 2)}`); } if (error.response?.status) { - this.logger.error(`Response status: ${error.response.status}`); } if (error.context?.data) { - this.logger.error(`Error context: ${JSON.stringify(error.context.data, null, 2)}`); } if (error.message) { throw new NodeOperationError(this.getNode(), error.message); diff --git a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts index 463f9c2..f4f5045 100644 --- a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts +++ b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts @@ -193,15 +193,11 @@ export class UnstractHitlPush implements INodeType { timeout: 5 * 60 * 1000, }; - logger.info('[HITL] Making API request to: ' + requestOptions.url); - logger.info('[HITL] File: ' + binaryData.fileName + ' (' + fileBuffer.length + ' bytes)'); - logger.info('[HITL] Request headers: ' + JSON.stringify(requestOptions.headers)); const result = await helpers.httpRequestWithAuthentication.call(this, 'unstractApi', requestOptions); // httpRequestWithAuthentication returns already-parsed JSON if Content-Type is application/json const resultData = typeof result === 'string' ? JSON.parse(result) : result; - logger.info('[HITL] API Response: ' + JSON.stringify(resultData)); if (!resultData || !resultData.message) { throw new NodeOperationError( @@ -228,9 +224,6 @@ export class UnstractHitlPush implements INodeType { }, timeout: 5 * 60 * 1000, }; - logger.info('[HITL] Status check URL: ' + statusRequestOptions.url); - logger.info('[HITL] Status check method: ' + statusRequestOptions.method); - logger.info('[HITL] Status check headers: ' + JSON.stringify(statusRequestOptions.headers)); try { const statusResult = await helpers.httpRequest(statusRequestOptions); @@ -241,12 +234,9 @@ export class UnstractHitlPush implements INodeType { if (error.response?.status === 422 && error.response?.data) { resultContent = typeof error.response.data === 'string' ? JSON.parse(error.response.data) : error.response.data; executionStatus = resultContent.status; - logger.info('[HITL] Status check returned 422 (still executing): ' + executionStatus); } else { // Actual error - log and rethrow - logger.error('[HITL] Status check failed: ' + error.message); if (error.response?.status) { - logger.error('[HITL] Status check response status: ' + error.response.status); } throw new NodeOperationError( this.getNode(), From c735406ec4ca98d2347952541c1e105e329d038d Mon Sep 17 00:00:00 2001 From: Ghost Jake <89829542+Deepak-Kesavan@users.noreply.github.com> Date: Fri, 21 Nov 2025 03:29:54 +0530 Subject: [PATCH 9/9] fix: remove unused logger variable declarations --- nodes/LlmWhisperer/LlmWhisperer.node.ts | 2 +- nodes/Unstract/Unstract.node.ts | 2 +- nodes/UnstractHitlPush/UnstractHitlPush.node.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nodes/LlmWhisperer/LlmWhisperer.node.ts b/nodes/LlmWhisperer/LlmWhisperer.node.ts index 0155ec8..5a417f3 100644 --- a/nodes/LlmWhisperer/LlmWhisperer.node.ts +++ b/nodes/LlmWhisperer/LlmWhisperer.node.ts @@ -195,7 +195,7 @@ export class LlmWhisperer implements INodeType { const returnData: INodeExecutionData[] = []; try { - const { helpers, logger } = this; + const { helpers } = this; for (let i = 0; i < items.length; i++) { const fileContents = this.getNodeParameter('file_contents', i) as string; diff --git a/nodes/Unstract/Unstract.node.ts b/nodes/Unstract/Unstract.node.ts index b985d85..4b7a422 100644 --- a/nodes/Unstract/Unstract.node.ts +++ b/nodes/Unstract/Unstract.node.ts @@ -114,7 +114,7 @@ export class Unstract implements INodeType { const credentials = await this.getCredentials('unstractApi'); const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; - const { helpers, logger } = this; + const { helpers } = this; for (let i = 0; i < items.length; i++) { const binaryPropertyName = this.getNodeParameter('file_contents', i) as string; diff --git a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts index f4f5045..3778baf 100644 --- a/nodes/UnstractHitlPush/UnstractHitlPush.node.ts +++ b/nodes/UnstractHitlPush/UnstractHitlPush.node.ts @@ -116,7 +116,7 @@ export class UnstractHitlPush implements INodeType { const credentials = await this.getCredentials('unstractApi'); const apiKey = credentials.apiKey as string; const orgId = credentials.orgId as string; - const { helpers, logger } = this; + const { helpers } = this; for (let i = 0; i < items.length; i++) { const binaryPropertyName = this.getNodeParameter('file_contents', i) as string;