n8n-nodes-lenz 0.4.2 → 0.5.0
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/README.md +13 -5
- package/dist/nodes/Lenz/Lenz.node.js +150 -35
- package/dist/nodes/Lenz/Lenz.node.js.map +1 -1
- package/package.json +4 -2
- package/dist/package.json +0 -60
- package/dist/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Operations are grouped under a **Resource** picker. (Nodes added before v0.1.10
|
|
|
29
29
|
|---|---|
|
|
30
30
|
| **Verify (Deep)** *(default)* | Full multi-model pipeline (research → debate → adjudication), ~90 seconds. Returns a verdict, confidence, `lenz_score` (1-10), `key_finding`, sourced citations, and an executive summary. Reserve for high-stakes claims that need a thorough, cited answer. **Depth** trades work for price: *Low* searches fewer sources, skips the recovery fetch tiers and stops the debate after the opening arguments, for **5 credits instead of 10**. |
|
|
31
31
|
| **Assess (Fast)** | A quick 3-model panel verdict, ~10 seconds, one entry per claim identified in the input text. Good default for lower-stakes checks. |
|
|
32
|
-
| **Extract Claims** | Free — pulls the verifiable factual claims out of a block of text without checking them. Useful as a first step before running Assess or Verify on each claim individually. **Focus** narrows the result to the claims you describe (300 characters, no extra cost); when none of them match, `status` comes back as `no_match` with an empty list rather than the unfocused claims. |
|
|
32
|
+
| **Extract Claims** | Free — pulls the verifiable factual claims out of a block of text without checking them. Useful as a first step before running Assess or Verify on each claim individually. **Focus** narrows the result to the claims you describe (300 characters, no extra cost); when none of them match, `status` comes back as `no_match` with an empty list rather than the unfocused claims. **Text** can also be a single public web page URL: Lenz reads the page, or a YouTube video's transcript, and extracts the claims from its first 50,000 characters. Pages behind a login (Facebook, Instagram, Threads, LinkedIn) can't be read, and a URL call typically takes 5-40 seconds. |
|
|
33
33
|
|
|
34
34
|
### Verification — manage submitted and stored work
|
|
35
35
|
|
|
@@ -65,9 +65,11 @@ Verify and Get also expose an **Include Audit Trail** toggle, which adds the adj
|
|
|
65
65
|
|
|
66
66
|
### Retry safety
|
|
67
67
|
|
|
68
|
-
Billable calls (Verify, Assess, Extract, Submit Batch, Select Claims) send an `Idempotency-Key` derived from the execution ID, a hash of the node's identity, the item index, and a fingerprint of the request body. If n8n retries the node — via **Retry On Fail**, or after a dropped response — the input is identical, so Lenz replays the original response instead of charging you a second time. A fresh run of the workflow is a new execution, so it bills normally.
|
|
68
|
+
Billable calls (Verify, Assess, Extract, Submit Batch, Select Claims, Ask Follow-Up) send an `Idempotency-Key` derived from the execution ID, a hash of the node's identity, the item index, and a fingerprint of the request — its path as well as its body. If n8n retries the node — via **Retry On Fail**, or after a dropped response — the input is identical, so Lenz replays the original response instead of charging you a second time. A fresh run of the workflow is a new execution, so it bills normally.
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
On **Ask Follow-Up** the key also keeps the conversation clean: an unkeyed retry asks the question again, so the question and a second answer are appended to the history that **Get Ask History** returns and that the next follow-up reads as context. A retry that arrives while the first question is still being answered gets a `409` — there is no answer yet to replay.
|
|
71
|
+
|
|
72
|
+
Including the request in the key is what makes repeated runs safe: **Loop Over Items** and **AI Agent** tool calls both execute the node several times within a single execution, each time restarting the item index at 0, so a position-only key would send one key with different inputs and the API would reject it. The path is part of it because **Ask Follow-Up** and **Select Claims** name what they act on in the URL rather than in the body — asking one fixed question of several verifications, the wiring suggested below, is otherwise indistinguishable from a retry of the first.
|
|
71
73
|
|
|
72
74
|
Note that **Assess bills per claim found in the text**, not per request: a paragraph containing five claims spends five assess units.
|
|
73
75
|
|
|
@@ -85,6 +87,7 @@ Built against `n8n-workflow` (n8n API version 1) and tested against n8n v2.30.4.
|
|
|
85
87
|
## Usage
|
|
86
88
|
|
|
87
89
|
- **Verify (Deep) takes ~90 seconds** — it's the full multi-model pipeline, not an instant call. The node blocks/polls until the result is ready, so no separate polling setup is needed on your end.
|
|
90
|
+
- **If it outlasts Max Wait (Seconds)**, the node returns `status: "timeout"` with the `task_id` and `passed: null` instead of a verdict. Nothing is lost — giving up does not cancel anything, the verification keeps running server-side, and the credits were spent at submit either way. Collect it later with **Get Verify Status**, or raise **Max Wait (Seconds)** (default 120). A poll that fails with a 5xx, a 429 or a 408/425 is retried for as long as the window lasts rather than failing the item; a 429 waits the reopening time the API states instead of returning on the node's own cadence. A failure carrying no HTTP status at all — a DNS or TLS problem, say — is different: it could equally be a bug or a bad credential, so it gets two retries and is then surfaced as the error it is. If the window ends on a failed poll, the `timeout` result names it in `last_error` and `last_error_status`.
|
|
88
91
|
- **Low depth drops a debate round, not just sources.** As well as searching fewer sources and skipping the recovery fetch tiers, *Low* stops the debate after the opening arguments — so with **Include Audit Trail** on, `audit.debate_pro.rebuttal` and `audit.debate_con.rebuttal` come back as empty strings. Every step that does run uses the same models, so Low is a volume lever rather than a model downgrade — but it is one round of argument fewer, not merely a narrower search.
|
|
89
92
|
- **You are charged for the Depth you asked for, not the one you got.** A *Low* request that Lenz can answer from an existing *standard* verdict still costs 5 — and the `depth` field on the result reads `standard`, because it describes the evidence behind the verdict rather than the request. Seeing `standard` come back from a *Low* request is correct, not a bug.
|
|
90
93
|
- To feed data from a previous node instead of a fixed value, toggle a field to **Expression** and reference it, e.g. `{{ $json.output }}`.
|
|
@@ -106,8 +109,11 @@ A simple "fact-check gate" pattern — verify an LLM's output before acting on i
|
|
|
106
109
|
|
|
107
110
|
1. Add an **LLM node** (or any node producing text) upstream.
|
|
108
111
|
2. Add the **Lenz node**, set Operation to **Verify (Deep)**, and set the Claim field to an expression referencing the upstream output, e.g. `{{ $json.text }}`.
|
|
109
|
-
3. Add an **IF node** after Lenz with the condition `{{ $json.
|
|
110
|
-
4.
|
|
112
|
+
3. Add an **IF node** after Lenz with the condition `{{ $json.status }}` **equals** `completed`. Send the false branch wherever unfinished work should go — a timeout, a `failed` pipeline or a `needs_input` interrupt is not a verdict, and its `passed` is `null`.
|
|
113
|
+
4. After that, add a second **IF node** with the condition `{{ $json.passed }}` **is true**.
|
|
114
|
+
5. Wire its true branch to continue the workflow normally, and its false branch to whatever your "needs review" path is (Slack alert, email, a manual-approval step, etc.).
|
|
115
|
+
|
|
116
|
+
> **Check `status` before `passed`.** `passed` only means something once a verdict exists. A timeout, a `failed` pipeline or a `needs_input` interrupt has no verdict, and `passed` is `null` or absent — which an IF reads as false. Branching straight on `passed` therefore reports "this claim did not pass" for a claim nobody ever checked, and a provider outage becomes a debunking.
|
|
111
117
|
|
|
112
118
|
For a lighter check on lower-stakes content, swap the Lenz operation to **Assess (Fast)** instead — same wiring, ~10s instead of ~90s.
|
|
113
119
|
|
|
@@ -218,6 +224,8 @@ So the Wait node has a number to read, the error output carries the refusal as f
|
|
|
218
224
|
|
|
219
225
|
* **0.4.2** — Two corrections to what 0.4.x said about **Depth**, both of them wrong rather than merely unclear. The low-depth price was documented as appearing in `costs` under `verify_low`, with the advice to divide `credits.remaining` by it: that key does not exist — the API carries the four capability keys in `costs` and nothing else — so the suggested expression evaluated to `NaN`. The price is readable only at `cost_options.verify.depth.low`, which is where the docs now point. Separately, *Low* was described as searching fewer sources and skipping the recovery fetch tiers; it also stops the debate after the opening arguments, so a Low verdict has no rebuttal round and comes back with an empty `rebuttal` on its debate entries under **Include Audit Trail**. Every description of Depth in the node and the README now says so.
|
|
220
226
|
|
|
227
|
+
* **0.5.0** — **Verify (Deep) no longer loses a verification you have already paid for.** Credits are debited the moment a submit is accepted, and the poll loop that waits for the verdict had no tolerance for a failed poll: one 502 or network blip on any of roughly fifteen polls threw straight out of the operation and took the `task_id` with it, leaving a verification that was charged for, still running server-side, and unreachable — not even **Get Verify Status** could fetch it back. Transient poll failures (5xx, 429, and the 408/425 an intervening proxy produces) are now retried for as long as the wait lasts, and a 429 waits the reopening time the API states rather than returning on the node's own cadence, which is what tripped the limiter to begin with. A failure carrying no HTTP status is treated differently on purpose — a DNS or TLS problem is indistinguishable from a bug or a bad credential, so it gets a small bounded number of attempts and is then surfaced as the error it is, rather than retried for the window and reported as a timeout, which would invent a fact. The `task_id` now rides every post-submit failure, on the error output and on the thrown error alike. The hardcoded 120-second deadline becomes **Max Wait (Seconds)**, validated and clamped before the claim is submitted — validated afterwards, a non-numeric expression got the claim charged and then threw an error with nowhere to carry the task id, which is the same bug by another route. Non-verdict terminals now carry `passed: null` so the key is visible in n8n's output schema; be aware that null is still falsy, so an **IF** node reading `{{ $json.passed }}` alone routes a timeout down the false arm exactly as before. Checking `status` first is the actual fix, and the README's gate pattern now leads with it — a provider outage reading as a debunking was the real bug. A timeout that ends on a failed poll names it in `last_error` and `last_error_status`, instead of reporting success-shaped JSON that implies the task was seen running. Alongside that, four correctness fixes a first external user would hit: **Get Many** duplicated and skipped rows above Limit 100 (#21), **Select Claims** collided two paused tasks that offered the same claim text onto one Idempotency-Key (#22), validation failures surfaced as API errors with no HTTP code (#23), and **Ask Follow-Up** sent no Idempotency-Key, so a retry appended a second question and answer to the stored conversation. No output field was removed or renamed.
|
|
228
|
+
|
|
221
229
|
It also carries two fixes that are behaviour, not wording. The `Idempotency-Key` was built from the node's **display name**, which is user-editable free text. Node refuses to send a header value containing anything above U+00FF, so a node named in Cyrillic, Greek, Hebrew, Arabic or any CJK script — or carrying an emoji — failed **every billable call** before the request left the machine, with an error naming the header rather than the node. Latin-1 names such as `Prüfung` and `Vérification` were never affected, despite what an earlier draft of this entry said. The identity is now *hashed* into the key rather than written into it, which makes the guarantee structural rather than an assumption about what the id contains; the node id is preferred, so a rename mid-execution no longer changes the key, and the name never reaches the wire.
|
|
222
230
|
|
|
223
231
|
Separately, a `sources` value that was not a list — or a list containing `null` — threw while mapping the result, failing a verification that had already completed and been charged for. A `null` entry now costs that one citation; a `sources` that is not a list returns none at all, which is worth knowing if you branch on `citations.length`. No output field was removed or renamed. Two things changed on the wire: the `User-Agent` version, and the `Idempotency-Key` format.
|
|
@@ -3,10 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Lenz = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
5
|
const BASE_URL = 'https://lenz.io/api/v1';
|
|
6
|
-
const USER_AGENT = 'n8n-nodes-lenz/0.
|
|
6
|
+
const USER_AGENT = 'n8n-nodes-lenz/0.5.0';
|
|
7
7
|
const API_VERSION = '2026-08-05';
|
|
8
8
|
const POLL_TIMEOUT_MS = 120000;
|
|
9
9
|
const POLL_BACKOFF_MS = [2000, 4000, 8000];
|
|
10
|
+
const MAX_WAIT_FLOOR_SECONDS = 10;
|
|
11
|
+
const MAX_WAIT_CEILING_SECONDS = 900;
|
|
12
|
+
const MAX_UNCLASSIFIED_POLL_RETRIES = 2;
|
|
13
|
+
async function waitForNextPoll(pollIdx, deadline, overrideMs) {
|
|
14
|
+
const remaining = deadline - Date.now();
|
|
15
|
+
if (remaining <= 0) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const backoff = overrideMs !== undefined
|
|
19
|
+
? overrideMs
|
|
20
|
+
: POLL_BACKOFF_MS[Math.min(pollIdx, POLL_BACKOFF_MS.length - 1)];
|
|
21
|
+
await (0, n8n_workflow_1.sleep)(Math.min(backoff, remaining));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
function submittedReceipt(taskId) {
|
|
25
|
+
return (`A verification for this item was submitted and charged before this failed — ` +
|
|
26
|
+
`its task ID is ${taskId}. It may still be running; fetch the result with ` +
|
|
27
|
+
`Get Verify Status rather than resubmitting.`);
|
|
28
|
+
}
|
|
29
|
+
function statedRetryAfterMs(error) {
|
|
30
|
+
const body = responseBodyOf(error);
|
|
31
|
+
for (const raw of [body.retry_after, body.reset_in_seconds]) {
|
|
32
|
+
const seconds = Number(raw);
|
|
33
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
34
|
+
return Math.ceil(seconds) * 1000;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
10
39
|
const BATCH_MAX_CLAIMS = 20;
|
|
11
40
|
const MAX_FOCUS_CHARS = 300;
|
|
12
41
|
const MAX_PAGE_SIZE = 100;
|
|
@@ -199,6 +228,7 @@ function mapVerifyStatus(status, taskId, includeAudit) {
|
|
|
199
228
|
const reason = status.reason;
|
|
200
229
|
return {
|
|
201
230
|
status: 'needs_input',
|
|
231
|
+
passed: null,
|
|
202
232
|
reason: reason !== null && reason !== void 0 ? reason : null,
|
|
203
233
|
task_id: taskId,
|
|
204
234
|
claims: (_b = status.claims) !== null && _b !== void 0 ? _b : [],
|
|
@@ -208,10 +238,11 @@ function mapVerifyStatus(status, taskId, includeAudit) {
|
|
|
208
238
|
};
|
|
209
239
|
}
|
|
210
240
|
if (state === 'failed') {
|
|
211
|
-
return { status: 'failed', task_id: taskId, ...failureFields(status) };
|
|
241
|
+
return { status: 'failed', passed: null, task_id: taskId, ...failureFields(status) };
|
|
212
242
|
}
|
|
213
243
|
return {
|
|
214
244
|
status: 'processing',
|
|
245
|
+
passed: null,
|
|
215
246
|
task_id: taskId,
|
|
216
247
|
progress: (_e = status.progress) !== null && _e !== void 0 ? _e : {},
|
|
217
248
|
};
|
|
@@ -285,7 +316,7 @@ class Lenz {
|
|
|
285
316
|
{
|
|
286
317
|
name: 'Extract Claims',
|
|
287
318
|
value: 'extract',
|
|
288
|
-
description: 'Pull verifiable claims out of text. Free, capped at 1000 calls per account per day, shared across your API keys (resets 00:00 UTC).',
|
|
319
|
+
description: 'Pull verifiable claims out of text, or out of a public web page given its URL. Free, capped at 1000 calls per account per day, shared across your API keys (resets 00:00 UTC).',
|
|
289
320
|
action: 'Extract claims from text',
|
|
290
321
|
},
|
|
291
322
|
{
|
|
@@ -429,7 +460,7 @@ class Lenz {
|
|
|
429
460
|
{
|
|
430
461
|
name: 'Extract Claims',
|
|
431
462
|
value: 'extract',
|
|
432
|
-
description: 'Pull verifiable claims out of text. Free, capped at 1000 calls per account per day, shared across your API keys (resets 00:00 UTC).',
|
|
463
|
+
description: 'Pull verifiable claims out of text, or out of a public web page given its URL. Free, capped at 1000 calls per account per day, shared across your API keys (resets 00:00 UTC).',
|
|
433
464
|
action: 'Extract claims from text',
|
|
434
465
|
},
|
|
435
466
|
{
|
|
@@ -475,7 +506,7 @@ class Lenz {
|
|
|
475
506
|
displayOptions: {
|
|
476
507
|
show: { operation: ['extract'] },
|
|
477
508
|
},
|
|
478
|
-
description:
|
|
509
|
+
description: "The text to pull the verifiable claims out of, or a single public web page URL. Lenz reads the page, or a YouTube video's transcript, and extracts the claims from its first 50,000 characters; pages behind a login can't be read. A URL call typically takes 5-40 seconds.",
|
|
479
510
|
},
|
|
480
511
|
{
|
|
481
512
|
displayName: 'Claims',
|
|
@@ -633,6 +664,17 @@ class Lenz {
|
|
|
633
664
|
},
|
|
634
665
|
description: 'Whether to poll until the verification finishes (~90s). Turn off to return the task ID immediately and collect the result later via Get Verify Status or a webhook.',
|
|
635
666
|
},
|
|
667
|
+
{
|
|
668
|
+
displayName: 'Max Wait (Seconds)',
|
|
669
|
+
name: 'maxWaitSeconds',
|
|
670
|
+
type: 'number',
|
|
671
|
+
default: 120,
|
|
672
|
+
typeOptions: { minValue: 10, maxValue: 900 },
|
|
673
|
+
displayOptions: {
|
|
674
|
+
show: { operation: ['verify'], waitForCompletion: [true] },
|
|
675
|
+
},
|
|
676
|
+
description: 'How long to keep polling before giving up and returning Status "timeout" with the task ID. A verification usually takes ~90s but can legitimately run longer. Giving up never cancels anything: the task keeps running server-side and stays fetchable with Get Verify Status, so the credits are not lost. Raise this only if the workflow can afford to block that long.',
|
|
677
|
+
},
|
|
636
678
|
{
|
|
637
679
|
displayName: 'Include Audit Trail',
|
|
638
680
|
name: 'includeAudit',
|
|
@@ -765,14 +807,14 @@ class Lenz {
|
|
|
765
807
|
};
|
|
766
808
|
}
|
|
767
809
|
async execute() {
|
|
768
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
810
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
769
811
|
const items = this.getInputData();
|
|
770
812
|
const returnData = [];
|
|
771
813
|
const executionId = this.getExecutionId();
|
|
772
814
|
const node = this.getNode();
|
|
773
815
|
const nodeKey = bodyFingerprint({ node: node.id || node.name });
|
|
774
|
-
const buildIdempotencyKey = (operation, itemIndex, body) => executionId
|
|
775
|
-
? `n8n:${executionId}:${nodeKey}:${operation}:${itemIndex}:${bodyFingerprint(body)}`
|
|
816
|
+
const buildIdempotencyKey = (operation, itemIndex, path, body) => executionId
|
|
817
|
+
? `n8n:${executionId}:${nodeKey}:${operation}:${itemIndex}:${bodyFingerprint({ path, body })}`
|
|
776
818
|
: '';
|
|
777
819
|
const lenzRequest = async (method, path, body, extra) => {
|
|
778
820
|
const headers = {
|
|
@@ -780,7 +822,7 @@ class Lenz {
|
|
|
780
822
|
'X-Lenz-API-Version': API_VERSION,
|
|
781
823
|
};
|
|
782
824
|
if (extra === null || extra === void 0 ? void 0 : extra.idempotent) {
|
|
783
|
-
const key = buildIdempotencyKey(extra.idempotent.operation, extra.idempotent.itemIndex, body);
|
|
825
|
+
const key = buildIdempotencyKey(extra.idempotent.operation, extra.idempotent.itemIndex, path, body);
|
|
784
826
|
if (key) {
|
|
785
827
|
headers['Idempotency-Key'] = key;
|
|
786
828
|
}
|
|
@@ -801,6 +843,7 @@ class Lenz {
|
|
|
801
843
|
return (await this.helpers.httpRequestWithAuthentication.call(this, 'lenzApi', options));
|
|
802
844
|
};
|
|
803
845
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
846
|
+
let pendingTaskId = '';
|
|
804
847
|
try {
|
|
805
848
|
const operation = this.getNodeParameter('operation', itemIndex);
|
|
806
849
|
const language = this.getNodeParameter('language', itemIndex, '') || undefined;
|
|
@@ -820,6 +863,14 @@ class Lenz {
|
|
|
820
863
|
const webhookUrl = this.getNodeParameter('webhookUrl', itemIndex, '').trim();
|
|
821
864
|
const visibility = this.getNodeParameter('visibility', itemIndex, '');
|
|
822
865
|
const depth = this.getNodeParameter('depth', itemIndex, 'standard');
|
|
866
|
+
let waitSeconds = 0;
|
|
867
|
+
if (waitForCompletion) {
|
|
868
|
+
const requestedWait = Number(this.getNodeParameter('maxWaitSeconds', itemIndex, POLL_TIMEOUT_MS / 1000));
|
|
869
|
+
if (!Number.isFinite(requestedWait)) {
|
|
870
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Max Wait (Seconds) must be a number of seconds', { itemIndex });
|
|
871
|
+
}
|
|
872
|
+
waitSeconds = Math.min(MAX_WAIT_CEILING_SECONDS, Math.max(MAX_WAIT_FLOOR_SECONDS, requestedWait));
|
|
873
|
+
}
|
|
823
874
|
const submitBody = { text: claim };
|
|
824
875
|
if (language) {
|
|
825
876
|
submitBody.language = language;
|
|
@@ -843,6 +894,7 @@ class Lenz {
|
|
|
843
894
|
if (!taskId) {
|
|
844
895
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Lenz accepted the claim but returned no task_id, so the verification cannot be polled', { itemIndex });
|
|
845
896
|
}
|
|
897
|
+
pendingTaskId = taskId;
|
|
846
898
|
if (!waitForCompletion) {
|
|
847
899
|
responseData = {
|
|
848
900
|
status: (_b = accepted.status) !== null && _b !== void 0 ? _b : 'queued',
|
|
@@ -852,25 +904,71 @@ class Lenz {
|
|
|
852
904
|
};
|
|
853
905
|
}
|
|
854
906
|
else {
|
|
855
|
-
const deadline = Date.now() +
|
|
907
|
+
const deadline = Date.now() + waitSeconds * 1000;
|
|
856
908
|
let terminal;
|
|
909
|
+
let lastObservedStatus = '';
|
|
857
910
|
let pollIdx = 0;
|
|
911
|
+
let unclassifiedRetries = 0;
|
|
912
|
+
let lastPollError;
|
|
858
913
|
while (Date.now() < deadline) {
|
|
859
|
-
|
|
914
|
+
let status;
|
|
915
|
+
try {
|
|
916
|
+
status = await lenzRequest('GET', `/verify/status/${taskId}`);
|
|
917
|
+
}
|
|
918
|
+
catch (pollError) {
|
|
919
|
+
const code = statusCodeOf(pollError);
|
|
920
|
+
const retryable = code === undefined
|
|
921
|
+
? unclassifiedRetries < MAX_UNCLASSIFIED_POLL_RETRIES
|
|
922
|
+
: code >= 500 || code === 429 || code === 408 || code === 425;
|
|
923
|
+
if (!retryable) {
|
|
924
|
+
throw describeApiError(this.getNode(), pollError, itemIndex, {
|
|
925
|
+
message: pollError.message,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
lastPollError = {
|
|
929
|
+
message: pollError.message,
|
|
930
|
+
status: code !== null && code !== void 0 ? code : null,
|
|
931
|
+
};
|
|
932
|
+
if (code === undefined) {
|
|
933
|
+
unclassifiedRetries += 1;
|
|
934
|
+
}
|
|
935
|
+
const statedWait = code === 429 ? statedRetryAfterMs(pollError) : undefined;
|
|
936
|
+
if (!(await waitForNextPoll(pollIdx, deadline, statedWait))) {
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
pollIdx += 1;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
unclassifiedRetries = 0;
|
|
943
|
+
lastPollError = undefined;
|
|
860
944
|
const state = status.status;
|
|
945
|
+
lastObservedStatus = state || lastObservedStatus;
|
|
861
946
|
if (state === 'completed' || state === 'needs_input' || state === 'failed') {
|
|
862
947
|
terminal = status;
|
|
863
948
|
break;
|
|
864
949
|
}
|
|
865
|
-
|
|
866
|
-
|
|
950
|
+
if (!(await waitForNextPoll(pollIdx, deadline))) {
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
867
953
|
pollIdx += 1;
|
|
868
954
|
}
|
|
869
955
|
if (!terminal) {
|
|
870
956
|
responseData = {
|
|
871
957
|
status: 'timeout',
|
|
958
|
+
passed: null,
|
|
872
959
|
task_id: taskId,
|
|
873
|
-
|
|
960
|
+
last_status: lastObservedStatus || null,
|
|
961
|
+
last_error: (_d = lastPollError === null || lastPollError === void 0 ? void 0 : lastPollError.message) !== null && _d !== void 0 ? _d : null,
|
|
962
|
+
last_error_status: (_e = lastPollError === null || lastPollError === void 0 ? void 0 : lastPollError.status) !== null && _e !== void 0 ? _e : null,
|
|
963
|
+
message: 'The verification did not complete within Max Wait (task_id: ' +
|
|
964
|
+
taskId +
|
|
965
|
+
'). ' +
|
|
966
|
+
(lastPollError
|
|
967
|
+
? 'The last attempt to read its status failed with: ' +
|
|
968
|
+
lastPollError.message +
|
|
969
|
+
'. The task itself may be unaffected — '
|
|
970
|
+
: 'It may still be running server-side — ') +
|
|
971
|
+
'the credits were spent at submit either way, so fetch the result later with Get Verify Status rather than resubmitting.',
|
|
874
972
|
};
|
|
875
973
|
}
|
|
876
974
|
else {
|
|
@@ -893,7 +991,7 @@ class Lenz {
|
|
|
893
991
|
}
|
|
894
992
|
else if (operation === 'verifyBatch') {
|
|
895
993
|
const batchUi = this.getNodeParameter('batchClaims', itemIndex, {});
|
|
896
|
-
const entries = ((
|
|
994
|
+
const entries = ((_f = batchUi.claim) !== null && _f !== void 0 ? _f : []).filter((entry) => { var _a; return ((_a = entry.text) !== null && _a !== void 0 ? _a : '').trim() !== ''; });
|
|
897
995
|
if (!entries.length) {
|
|
898
996
|
returnData.push({
|
|
899
997
|
json: { skipped: true, reason: 'empty_input' },
|
|
@@ -945,14 +1043,14 @@ class Lenz {
|
|
|
945
1043
|
const accepted = await lenzRequest('POST', '/verify/batch', body, {
|
|
946
1044
|
idempotent: { operation, itemIndex },
|
|
947
1045
|
});
|
|
948
|
-
const batchId = (
|
|
1046
|
+
const batchId = (_g = accepted.batch_id) !== null && _g !== void 0 ? _g : '';
|
|
949
1047
|
const partial = accepted.partial === true;
|
|
950
|
-
for (const spawned of ((
|
|
1048
|
+
for (const spawned of ((_h = accepted.items) !== null && _h !== void 0 ? _h : [])) {
|
|
951
1049
|
returnData.push({
|
|
952
1050
|
json: {
|
|
953
1051
|
batch_id: batchId,
|
|
954
|
-
task_id: (
|
|
955
|
-
claim_text: (
|
|
1052
|
+
task_id: (_j = spawned.task_id) !== null && _j !== void 0 ? _j : '',
|
|
1053
|
+
claim_text: (_k = spawned.claim_text) !== null && _k !== void 0 ? _k : '',
|
|
956
1054
|
status: 'queued',
|
|
957
1055
|
partial,
|
|
958
1056
|
},
|
|
@@ -963,7 +1061,7 @@ class Lenz {
|
|
|
963
1061
|
}
|
|
964
1062
|
else if (operation === 'select') {
|
|
965
1063
|
const taskId = this.getNodeParameter('taskId', itemIndex).trim();
|
|
966
|
-
const selected = ((
|
|
1064
|
+
const selected = ((_l = this.getNodeParameter('selectedClaims', itemIndex, [])) !== null && _l !== void 0 ? _l : [])
|
|
967
1065
|
.map((text) => (text !== null && text !== void 0 ? text : '').trim())
|
|
968
1066
|
.filter((text) => text !== '');
|
|
969
1067
|
if (!taskId || !selected.length) {
|
|
@@ -977,14 +1075,14 @@ class Lenz {
|
|
|
977
1075
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `At most ${BATCH_MAX_CLAIMS} claims can be selected, got ${selected.length}`, { itemIndex });
|
|
978
1076
|
}
|
|
979
1077
|
const accepted = await lenzRequest('POST', `/verify/${taskId}/select`, { texts: selected }, { idempotent: { operation, itemIndex } });
|
|
980
|
-
const batchId = (
|
|
1078
|
+
const batchId = (_m = accepted.batch_id) !== null && _m !== void 0 ? _m : '';
|
|
981
1079
|
const partial = accepted.partial === true;
|
|
982
|
-
for (const spawned of ((
|
|
1080
|
+
for (const spawned of ((_o = accepted.items) !== null && _o !== void 0 ? _o : [])) {
|
|
983
1081
|
returnData.push({
|
|
984
1082
|
json: {
|
|
985
1083
|
batch_id: batchId,
|
|
986
|
-
task_id: (
|
|
987
|
-
claim_text: (
|
|
1084
|
+
task_id: (_p = spawned.task_id) !== null && _p !== void 0 ? _p : '',
|
|
1085
|
+
claim_text: (_q = spawned.claim_text) !== null && _q !== void 0 ? _q : '',
|
|
988
1086
|
status: 'queued',
|
|
989
1087
|
partial,
|
|
990
1088
|
},
|
|
@@ -1009,12 +1107,12 @@ class Lenz {
|
|
|
1009
1107
|
const result = await lenzRequest('POST', '/assess', body, {
|
|
1010
1108
|
idempotent: { operation, itemIndex },
|
|
1011
1109
|
});
|
|
1012
|
-
const claims = ((
|
|
1110
|
+
const claims = ((_r = result.claims) !== null && _r !== void 0 ? _r : []);
|
|
1013
1111
|
if (!claims.length) {
|
|
1014
1112
|
responseData = {
|
|
1015
1113
|
status: result.error_code === 'ambiguous' ? 'ambiguous' : 'no_claim',
|
|
1016
|
-
message: (
|
|
1017
|
-
candidate_claims: (
|
|
1114
|
+
message: (_s = result.error) !== null && _s !== void 0 ? _s : 'No verifiable factual claim was detected.',
|
|
1115
|
+
candidate_claims: (_t = result.candidate_claims) !== null && _t !== void 0 ? _t : [],
|
|
1018
1116
|
};
|
|
1019
1117
|
}
|
|
1020
1118
|
else {
|
|
@@ -1071,9 +1169,11 @@ class Lenz {
|
|
|
1071
1169
|
if (language) {
|
|
1072
1170
|
body.language = language;
|
|
1073
1171
|
}
|
|
1074
|
-
const reply = await lenzRequest('POST', `/ask/${verificationId}`, body
|
|
1172
|
+
const reply = await lenzRequest('POST', `/ask/${verificationId}`, body, {
|
|
1173
|
+
idempotent: { operation, itemIndex },
|
|
1174
|
+
});
|
|
1075
1175
|
responseData = {
|
|
1076
|
-
answer: (
|
|
1176
|
+
answer: (_u = reply.content) !== null && _u !== void 0 ? _u : '',
|
|
1077
1177
|
};
|
|
1078
1178
|
}
|
|
1079
1179
|
else if (operation === 'askHistory') {
|
|
@@ -1113,7 +1213,7 @@ class Lenz {
|
|
|
1113
1213
|
detail.status === 'failed'
|
|
1114
1214
|
? {
|
|
1115
1215
|
status: 'failed',
|
|
1116
|
-
verification_id: (
|
|
1216
|
+
verification_id: (_v = detail.verification_id) !== null && _v !== void 0 ? _v : verificationId,
|
|
1117
1217
|
...failureFields(detail),
|
|
1118
1218
|
}
|
|
1119
1219
|
: mapCompletedVerification(detail, includeAudit);
|
|
@@ -1136,12 +1236,12 @@ class Lenz {
|
|
|
1136
1236
|
: this.getNodeParameter('limit', itemIndex, 50);
|
|
1137
1237
|
let page = 1;
|
|
1138
1238
|
let collected = 0;
|
|
1239
|
+
const pageSize = returnAll ? MAX_PAGE_SIZE : Math.min(MAX_PAGE_SIZE, limit);
|
|
1139
1240
|
while (collected < limit) {
|
|
1140
|
-
const pageSize = returnAll ? MAX_PAGE_SIZE : Math.min(MAX_PAGE_SIZE, limit - collected);
|
|
1141
1241
|
const response = await lenzRequest('GET', '/verifications', undefined, {
|
|
1142
1242
|
qs: { page, page_size: pageSize },
|
|
1143
1243
|
});
|
|
1144
|
-
const pageItems = ((
|
|
1244
|
+
const pageItems = ((_w = response.items) !== null && _w !== void 0 ? _w : []);
|
|
1145
1245
|
for (const verification of pageItems) {
|
|
1146
1246
|
if (collected >= limit) {
|
|
1147
1247
|
break;
|
|
@@ -1152,7 +1252,7 @@ class Lenz {
|
|
|
1152
1252
|
});
|
|
1153
1253
|
collected += 1;
|
|
1154
1254
|
}
|
|
1155
|
-
const total = (
|
|
1255
|
+
const total = (_x = response.total) !== null && _x !== void 0 ? _x : 0;
|
|
1156
1256
|
if (pageItems.length < pageSize || collected >= total) {
|
|
1157
1257
|
break;
|
|
1158
1258
|
}
|
|
@@ -1171,7 +1271,7 @@ class Lenz {
|
|
|
1171
1271
|
}
|
|
1172
1272
|
const relatedLimit = this.getNodeParameter('relatedLimit', itemIndex, 5);
|
|
1173
1273
|
const response = await lenzRequest('GET', `/verifications/${verificationId}/related`, undefined, { qs: { limit: relatedLimit } });
|
|
1174
|
-
for (const related of ((
|
|
1274
|
+
for (const related of ((_y = response.items) !== null && _y !== void 0 ? _y : [])) {
|
|
1175
1275
|
returnData.push({
|
|
1176
1276
|
json: related,
|
|
1177
1277
|
pairedItem: { item: itemIndex },
|
|
@@ -1195,6 +1295,9 @@ class Lenz {
|
|
|
1195
1295
|
catch (error) {
|
|
1196
1296
|
if (this.continueOnFail()) {
|
|
1197
1297
|
const json = { error: error.message };
|
|
1298
|
+
if (pendingTaskId) {
|
|
1299
|
+
json.task_id = pendingTaskId;
|
|
1300
|
+
}
|
|
1198
1301
|
const status = statusCodeOf(error);
|
|
1199
1302
|
if (status !== undefined) {
|
|
1200
1303
|
json.status_code = status;
|
|
@@ -1213,7 +1316,7 @@ class Lenz {
|
|
|
1213
1316
|
if (typeof body.credits_remaining === 'number') {
|
|
1214
1317
|
json.credits_remaining = body.credits_remaining;
|
|
1215
1318
|
}
|
|
1216
|
-
const typed = (
|
|
1319
|
+
const typed = (_z = quotaMessageFor(error)) !== null && _z !== void 0 ? _z : capacityMessageFor(error);
|
|
1217
1320
|
if (typed) {
|
|
1218
1321
|
json.error_message = typed.message;
|
|
1219
1322
|
json.error_description = typed.description;
|
|
@@ -1224,6 +1327,13 @@ class Lenz {
|
|
|
1224
1327
|
});
|
|
1225
1328
|
continue;
|
|
1226
1329
|
}
|
|
1330
|
+
if (error instanceof n8n_workflow_1.NodeOperationError) {
|
|
1331
|
+
const opReceipt = pendingTaskId ? submittedReceipt(pendingTaskId) : '';
|
|
1332
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, {
|
|
1333
|
+
itemIndex,
|
|
1334
|
+
description: [(_0 = error.description) !== null && _0 !== void 0 ? _0 : '', opReceipt].filter(Boolean).join(' ') || undefined,
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1227
1337
|
const quota = quotaMessageFor(error);
|
|
1228
1338
|
if (quota) {
|
|
1229
1339
|
throw describeApiError(this.getNode(), error, itemIndex, quota, '402');
|
|
@@ -1232,8 +1342,13 @@ class Lenz {
|
|
|
1232
1342
|
if (capacity) {
|
|
1233
1343
|
throw describeApiError(this.getNode(), error, itemIndex, capacity, '503');
|
|
1234
1344
|
}
|
|
1345
|
+
const existingDescription = error instanceof n8n_workflow_1.NodeApiError ? ((_1 = error.description) !== null && _1 !== void 0 ? _1 : '') : '';
|
|
1346
|
+
const receipt = pendingTaskId ? submittedReceipt(pendingTaskId) : '';
|
|
1235
1347
|
throw describeApiError(this.getNode(), error, itemIndex, {
|
|
1236
1348
|
message: error.message,
|
|
1349
|
+
...(receipt
|
|
1350
|
+
? { description: [existingDescription, receipt].filter(Boolean).join(' ') }
|
|
1351
|
+
: {}),
|
|
1237
1352
|
});
|
|
1238
1353
|
}
|
|
1239
1354
|
}
|