n8n-nodes-lenz 0.0.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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/credentials/LenzApi.credentials.d.ts +12 -0
- package/dist/credentials/LenzApi.credentials.js +36 -0
- package/dist/credentials/LenzApi.credentials.js.map +1 -0
- package/dist/nodes/Lenz/Lenz.node.d.ts +5 -0
- package/dist/nodes/Lenz/Lenz.node.js +1099 -0
- package/dist/nodes/Lenz/Lenz.node.js.map +1 -0
- package/dist/nodes/Lenz/lenz.dark.svg +4 -0
- package/dist/nodes/Lenz/lenz.svg +4 -0
- package/dist/package.json +56 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,1099 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
4
|
+
try {
|
|
5
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
6
|
+
} catch (e) {
|
|
7
|
+
throw mod = 0, e;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// node_modules/lenz-io/dist/index.cjs
|
|
12
|
+
var require_dist = __commonJS({
|
|
13
|
+
"node_modules/lenz-io/dist/index.cjs"(exports2, module2) {
|
|
14
|
+
"use strict";
|
|
15
|
+
var __defProp = Object.defineProperty;
|
|
16
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
17
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
18
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
var __copyProps = (to, from, except, desc) => {
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
25
|
+
for (let key of __getOwnPropNames2(from))
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
27
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
28
|
+
}
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
API_VERSION: () => API_VERSION,
|
|
35
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
36
|
+
DEFAULT_REPLAY_WINDOW_SECONDS: () => DEFAULT_REPLAY_WINDOW_SECONDS,
|
|
37
|
+
Lenz: () => Lenz2,
|
|
38
|
+
LenzAPIError: () => LenzAPIError,
|
|
39
|
+
LenzAuthError: () => LenzAuthError,
|
|
40
|
+
LenzError: () => LenzError,
|
|
41
|
+
LenzNeedsInputError: () => LenzNeedsInputError,
|
|
42
|
+
LenzPipelineError: () => LenzPipelineError,
|
|
43
|
+
LenzQuotaExceededError: () => LenzQuotaExceededError,
|
|
44
|
+
LenzRateLimitError: () => LenzRateLimitError,
|
|
45
|
+
LenzTimeoutError: () => LenzTimeoutError,
|
|
46
|
+
LenzValidationError: () => LenzValidationError,
|
|
47
|
+
LenzWebhookSignatureError: () => LenzWebhookSignatureError,
|
|
48
|
+
LenzWebhooks: () => LenzWebhooks,
|
|
49
|
+
SIGNATURE_HEADER: () => SIGNATURE_HEADER,
|
|
50
|
+
VERSION: () => VERSION,
|
|
51
|
+
mapResponseToError: () => mapResponseToError,
|
|
52
|
+
verifySignature: () => verifySignature
|
|
53
|
+
});
|
|
54
|
+
module2.exports = __toCommonJS(index_exports);
|
|
55
|
+
var import_node_crypto = require("crypto");
|
|
56
|
+
var LenzError = class extends Error {
|
|
57
|
+
cause_;
|
|
58
|
+
fix;
|
|
59
|
+
docUrl;
|
|
60
|
+
requestId;
|
|
61
|
+
statusCode;
|
|
62
|
+
body;
|
|
63
|
+
constructor(ctx = {}) {
|
|
64
|
+
super(ctx.message || new.target.name);
|
|
65
|
+
this.name = new.target.name;
|
|
66
|
+
this.cause_ = ctx.cause ?? "";
|
|
67
|
+
this.fix = ctx.fix ?? "";
|
|
68
|
+
this.docUrl = ctx.docUrl ?? "";
|
|
69
|
+
this.requestId = ctx.requestId ?? "";
|
|
70
|
+
this.statusCode = ctx.statusCode ?? 0;
|
|
71
|
+
this.body = ctx.body ?? null;
|
|
72
|
+
}
|
|
73
|
+
toString() {
|
|
74
|
+
const lines = [this.message || this.name];
|
|
75
|
+
if (this.cause_) lines.push(` Cause: ${this.cause_}`);
|
|
76
|
+
if (this.fix) lines.push(` Fix: ${this.fix}`);
|
|
77
|
+
if (this.docUrl) lines.push(` Docs: ${this.docUrl}`);
|
|
78
|
+
if (this.requestId) lines.push(` Request ID: ${this.requestId}`);
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var LenzAuthError = class extends LenzError {
|
|
83
|
+
};
|
|
84
|
+
var LenzQuotaExceededError = class extends LenzError {
|
|
85
|
+
creditsRemaining = 0;
|
|
86
|
+
};
|
|
87
|
+
var LenzValidationError = class extends LenzError {
|
|
88
|
+
errors = [];
|
|
89
|
+
};
|
|
90
|
+
var LenzRateLimitError = class extends LenzError {
|
|
91
|
+
retryAfter = 0;
|
|
92
|
+
};
|
|
93
|
+
var LenzAPIError = class extends LenzError {
|
|
94
|
+
};
|
|
95
|
+
var LenzTimeoutError = class extends LenzError {
|
|
96
|
+
taskId = "";
|
|
97
|
+
};
|
|
98
|
+
var LenzNeedsInputError = class extends LenzError {
|
|
99
|
+
taskId = "";
|
|
100
|
+
kind = "";
|
|
101
|
+
payload = {};
|
|
102
|
+
};
|
|
103
|
+
var LenzPipelineError = class extends LenzError {
|
|
104
|
+
taskId = "";
|
|
105
|
+
failureReason = "";
|
|
106
|
+
};
|
|
107
|
+
var LenzWebhookSignatureError = class extends LenzError {
|
|
108
|
+
};
|
|
109
|
+
var DOCS_BASE = "https://lenz.io/docs";
|
|
110
|
+
var STATUS_MAP = {
|
|
111
|
+
401: { cls: LenzAuthError, message: "Unauthorized", docUrl: `${DOCS_BASE}/auth` },
|
|
112
|
+
403: { cls: LenzAuthError, message: "Forbidden", docUrl: `${DOCS_BASE}/auth` },
|
|
113
|
+
402: {
|
|
114
|
+
cls: LenzQuotaExceededError,
|
|
115
|
+
message: "Payment required",
|
|
116
|
+
docUrl: `${DOCS_BASE}/billing`
|
|
117
|
+
},
|
|
118
|
+
422: {
|
|
119
|
+
cls: LenzValidationError,
|
|
120
|
+
message: "Validation failed",
|
|
121
|
+
docUrl: `${DOCS_BASE}/errors/validation`
|
|
122
|
+
},
|
|
123
|
+
429: {
|
|
124
|
+
cls: LenzRateLimitError,
|
|
125
|
+
message: "Rate limit exceeded",
|
|
126
|
+
docUrl: `${DOCS_BASE}/rate-limits`
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var FIX_HINTS = {
|
|
130
|
+
401: "Generate a new key at https://lenz.io/api-integration.",
|
|
131
|
+
403: "This key doesn't have access to that resource.",
|
|
132
|
+
402: "Upgrade your plan or wait for the period reset.",
|
|
133
|
+
422: "Check the request body against the OpenAPI spec.",
|
|
134
|
+
429: "Wait Retry-After seconds and retry."
|
|
135
|
+
};
|
|
136
|
+
function parseBody(raw) {
|
|
137
|
+
if (!raw) return {};
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(raw);
|
|
140
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
141
|
+
} catch {
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function getHeader(headers, name) {
|
|
146
|
+
return headers[name] ?? headers[name.toLowerCase()] ?? headers[name.toUpperCase()] ?? "";
|
|
147
|
+
}
|
|
148
|
+
function mapResponseToError(statusCode, body, headers = {}) {
|
|
149
|
+
const parsed = parseBody(body);
|
|
150
|
+
const requestId = getHeader(headers, "X-Request-ID");
|
|
151
|
+
let entry;
|
|
152
|
+
if (statusCode in STATUS_MAP) {
|
|
153
|
+
entry = STATUS_MAP[statusCode];
|
|
154
|
+
} else if (statusCode >= 500 && statusCode < 600) {
|
|
155
|
+
entry = { cls: LenzAPIError, message: "Server error", docUrl: `${DOCS_BASE}/errors` };
|
|
156
|
+
} else {
|
|
157
|
+
entry = { cls: LenzError, message: `HTTP ${statusCode}`, docUrl: `${DOCS_BASE}/errors` };
|
|
158
|
+
}
|
|
159
|
+
const detailRaw = parsed["detail"];
|
|
160
|
+
const detail = typeof detailRaw === "string" ? detailRaw : Array.isArray(detailRaw) ? "Validation failed" : entry.message;
|
|
161
|
+
const err = new entry.cls({
|
|
162
|
+
message: detail,
|
|
163
|
+
cause: detail,
|
|
164
|
+
fix: FIX_HINTS[statusCode] ?? "Retry; if the error persists, file an issue with the Request ID.",
|
|
165
|
+
docUrl: entry.docUrl,
|
|
166
|
+
requestId,
|
|
167
|
+
statusCode,
|
|
168
|
+
body: parsed
|
|
169
|
+
});
|
|
170
|
+
if (err instanceof LenzQuotaExceededError) {
|
|
171
|
+
err.creditsRemaining = Number(parsed["credits_remaining"] ?? 0);
|
|
172
|
+
} else if (err instanceof LenzValidationError) {
|
|
173
|
+
if (Array.isArray(parsed["detail"])) {
|
|
174
|
+
err.errors = parsed["detail"];
|
|
175
|
+
} else if (Array.isArray(parsed["errors"])) {
|
|
176
|
+
err.errors = parsed["errors"];
|
|
177
|
+
}
|
|
178
|
+
} else if (err instanceof LenzRateLimitError) {
|
|
179
|
+
const ra = getHeader(headers, "Retry-After") || String(parsed["retry_after"] ?? "");
|
|
180
|
+
const parsedRa = Number(ra);
|
|
181
|
+
err.retryAfter = Number.isFinite(parsedRa) ? parsedRa : 0;
|
|
182
|
+
}
|
|
183
|
+
return err;
|
|
184
|
+
}
|
|
185
|
+
var VERSION = "2.3.0";
|
|
186
|
+
var API_VERSION = "2026-05-13";
|
|
187
|
+
var DEFAULT_BASE_URL = "https://lenz.io/api/v1";
|
|
188
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
189
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
190
|
+
var RETRY_BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
191
|
+
var POLL_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
192
|
+
var POLL_BACKOFF_CAP_MS = 1e4;
|
|
193
|
+
function sleep(ms) {
|
|
194
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
195
|
+
}
|
|
196
|
+
function retrySleepMs(attempt) {
|
|
197
|
+
return RETRY_BACKOFF_MS[Math.min(attempt, RETRY_BACKOFF_MS.length - 1)] ?? 4e3;
|
|
198
|
+
}
|
|
199
|
+
function pollSleepMs(idx, remainingMs) {
|
|
200
|
+
const base = POLL_BACKOFF_MS[Math.min(idx, POLL_BACKOFF_MS.length - 1)] ?? POLL_BACKOFF_CAP_MS;
|
|
201
|
+
return Math.min(base, POLL_BACKOFF_CAP_MS, Math.max(0, remainingMs));
|
|
202
|
+
}
|
|
203
|
+
var VerificationsNamespace = class {
|
|
204
|
+
constructor(client) {
|
|
205
|
+
this.client = client;
|
|
206
|
+
}
|
|
207
|
+
client;
|
|
208
|
+
list({ page = 1 } = {}) {
|
|
209
|
+
return this.client.request({
|
|
210
|
+
method: "GET",
|
|
211
|
+
path: "/verifications",
|
|
212
|
+
query: { page }
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Fetch a single verification. Accepts anon callers — any non-hidden
|
|
217
|
+
* public claim resolves without an API key (the old `library.get`
|
|
218
|
+
* endpoint merged into this one).
|
|
219
|
+
*/
|
|
220
|
+
get(verificationId) {
|
|
221
|
+
return this.client.request({
|
|
222
|
+
method: "GET",
|
|
223
|
+
path: `/verifications/${verificationId}`,
|
|
224
|
+
authRequired: false,
|
|
225
|
+
authOptional: true
|
|
226
|
+
// send the key if we have one → owner sees private rows
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async delete(verificationId) {
|
|
230
|
+
try {
|
|
231
|
+
await this.client.request({
|
|
232
|
+
method: "DELETE",
|
|
233
|
+
path: `/verifications/${verificationId}`
|
|
234
|
+
});
|
|
235
|
+
return true;
|
|
236
|
+
} catch (exc) {
|
|
237
|
+
if (exc instanceof LenzError && exc.statusCode === 404) return true;
|
|
238
|
+
throw exc;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Public verifications semantically related to this one (pgvector ANN).
|
|
243
|
+
* Server clamps `limit` to 10. Excludes the verification itself and
|
|
244
|
+
* editorially-hidden claims. Accessible for any verification the caller
|
|
245
|
+
* owns or any public library item.
|
|
246
|
+
*/
|
|
247
|
+
related(verificationId, { limit = 5 } = {}) {
|
|
248
|
+
return this.client.request({
|
|
249
|
+
method: "GET",
|
|
250
|
+
path: `/verifications/${verificationId}/related`,
|
|
251
|
+
query: { limit }
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var AskNamespace = class {
|
|
256
|
+
constructor(client) {
|
|
257
|
+
this.client = client;
|
|
258
|
+
}
|
|
259
|
+
client;
|
|
260
|
+
history(verificationId) {
|
|
261
|
+
return this.client.request({
|
|
262
|
+
method: "GET",
|
|
263
|
+
path: `/ask/${verificationId}`
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
send(verificationId, input) {
|
|
267
|
+
const body = { message: input.message };
|
|
268
|
+
if (input.language) body.language = input.language;
|
|
269
|
+
return this.client.request({
|
|
270
|
+
method: "POST",
|
|
271
|
+
path: `/ask/${verificationId}`,
|
|
272
|
+
json: body
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async reset(verificationId) {
|
|
276
|
+
await this.client.request({
|
|
277
|
+
method: "DELETE",
|
|
278
|
+
path: `/ask/${verificationId}`
|
|
279
|
+
});
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
var LibraryNamespace = class {
|
|
284
|
+
constructor(client) {
|
|
285
|
+
this.client = client;
|
|
286
|
+
}
|
|
287
|
+
client;
|
|
288
|
+
list(input = {}) {
|
|
289
|
+
return this.client.request({
|
|
290
|
+
method: "GET",
|
|
291
|
+
path: "/library",
|
|
292
|
+
query: {
|
|
293
|
+
page: input.page ?? 1,
|
|
294
|
+
sort: input.sort ?? "recent",
|
|
295
|
+
search: input.search,
|
|
296
|
+
domain: input.domain,
|
|
297
|
+
entity: input.entity
|
|
298
|
+
},
|
|
299
|
+
authRequired: false
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
var Lenz2 = class {
|
|
304
|
+
apiKey;
|
|
305
|
+
baseUrl;
|
|
306
|
+
timeoutMs;
|
|
307
|
+
maxRetries;
|
|
308
|
+
fetchImpl;
|
|
309
|
+
verifications;
|
|
310
|
+
ask;
|
|
311
|
+
library;
|
|
312
|
+
constructor(opts = {}) {
|
|
313
|
+
this.apiKey = opts.apiKey ?? process.env["LENZ_API_KEY"] ?? "";
|
|
314
|
+
this.baseUrl = (opts.baseUrl ?? process.env["LENZ_BASE_URL"] ?? DEFAULT_BASE_URL).replace(
|
|
315
|
+
/\/$/,
|
|
316
|
+
""
|
|
317
|
+
);
|
|
318
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
319
|
+
this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
320
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
321
|
+
this.verifications = new VerificationsNamespace(this);
|
|
322
|
+
this.ask = new AskNamespace(this);
|
|
323
|
+
this.library = new LibraryNamespace(this);
|
|
324
|
+
}
|
|
325
|
+
// ── Marquee verbs ──
|
|
326
|
+
async verify(input) {
|
|
327
|
+
return this.submit(input);
|
|
328
|
+
}
|
|
329
|
+
async verifyBatch(input) {
|
|
330
|
+
const body = {
|
|
331
|
+
// Per-item shape passes through verbatim — `VerifyBatchItem` allows
|
|
332
|
+
// any subset including a per-item `language` override.
|
|
333
|
+
claims: input.claims.map((c) => {
|
|
334
|
+
const item = {
|
|
335
|
+
text: c.text,
|
|
336
|
+
source_url: c.source_url ?? "",
|
|
337
|
+
webhook_url: c.webhook_url ?? ""
|
|
338
|
+
};
|
|
339
|
+
if (c.language) item.language = c.language;
|
|
340
|
+
return item;
|
|
341
|
+
})
|
|
342
|
+
};
|
|
343
|
+
if (input.webhookUrl) body["webhook_url"] = input.webhookUrl;
|
|
344
|
+
if (input.language) body["language"] = input.language;
|
|
345
|
+
const headers = {};
|
|
346
|
+
if (input.idempotencyKey) headers["Idempotency-Key"] = input.idempotencyKey;
|
|
347
|
+
return this.request({
|
|
348
|
+
method: "POST",
|
|
349
|
+
path: "/verify/batch",
|
|
350
|
+
json: body,
|
|
351
|
+
headers
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
async extract(input) {
|
|
355
|
+
const body = { text: input.text };
|
|
356
|
+
if (input.language) body.language = input.language;
|
|
357
|
+
return this.request({
|
|
358
|
+
method: "POST",
|
|
359
|
+
path: "/extract",
|
|
360
|
+
json: body
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Fast 3-model panel verdict on each identified claim in the input.
|
|
365
|
+
* Sync, ~5-10s. Returns one entry per atomic_claim. For deeper analysis
|
|
366
|
+
* (citations, full audit trail), escalate low-confidence claims to
|
|
367
|
+
* `verifyAndWait`; the two endpoints share a result cache server-side.
|
|
368
|
+
*
|
|
369
|
+
* Pass `language: "es"` (or any of the 12 supported codes) to receive
|
|
370
|
+
* the claim text in that language. Verdict labels stay English.
|
|
371
|
+
*/
|
|
372
|
+
async assess(input) {
|
|
373
|
+
const body = { text: input.text };
|
|
374
|
+
if (input.language) body.language = input.language;
|
|
375
|
+
return this.request({
|
|
376
|
+
method: "POST",
|
|
377
|
+
path: "/assess",
|
|
378
|
+
json: body
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Resolve a needs-input interrupt by selecting one or more claims.
|
|
383
|
+
*
|
|
384
|
+
* Each selected claim fans out into its own pipeline; the returned
|
|
385
|
+
* `BatchAccepted` carries one `items` entry (each with its own `task_id`)
|
|
386
|
+
* per claim. Poll each via `getStatus` / `wait`. Every text must match a
|
|
387
|
+
* claim offered in the prior interrupt — the server rejects anything else.
|
|
388
|
+
*/
|
|
389
|
+
async select(taskId, input) {
|
|
390
|
+
if (!input.texts || input.texts.length === 0) {
|
|
391
|
+
throw new Error("select requires a non-empty texts array");
|
|
392
|
+
}
|
|
393
|
+
return this.request({
|
|
394
|
+
method: "POST",
|
|
395
|
+
path: `/verify/${taskId}/select`,
|
|
396
|
+
json: { texts: input.texts }
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async getStatus(taskId) {
|
|
400
|
+
return this.request({
|
|
401
|
+
method: "GET",
|
|
402
|
+
path: `/verify/status/${taskId}`
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async usage() {
|
|
406
|
+
return this.request({ method: "GET", path: "/me/usage" });
|
|
407
|
+
}
|
|
408
|
+
// ── Headline ergonomic ──
|
|
409
|
+
/**
|
|
410
|
+
* Submit + poll until the pipeline terminates. Returns the completed
|
|
411
|
+
* Verification, or throws LenzNeedsInputError / LenzPipelineError /
|
|
412
|
+
* LenzTimeoutError. By default sends an auto-generated Idempotency-Key
|
|
413
|
+
* so a network retry on submit doesn't spawn a duplicate task.
|
|
414
|
+
*/
|
|
415
|
+
async verifyAndWait(input) {
|
|
416
|
+
const timeoutMs = input.timeoutMs ?? 12e4;
|
|
417
|
+
const idempotencyKey = input.idempotencyKey ?? (input.idempotency !== false ? (0, import_node_crypto.randomUUID)().replace(/-/g, "") : void 0);
|
|
418
|
+
const accepted = await this.submit({ ...input, idempotencyKey });
|
|
419
|
+
console.info(`[lenz-io] Submitted task: ${accepted.task_id}`);
|
|
420
|
+
return this.wait(accepted, { timeoutMs });
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Block on an already-submitted task until it terminates, then return its
|
|
424
|
+
* `Verification`. `task` is a `task_id` string OR the `TaskAccepted` returned
|
|
425
|
+
* by `verify` / `select` — so `client.wait(await client.verify({claim}))`
|
|
426
|
+
* reads naturally. Throws for an empty id, `LenzNeedsInputError` /
|
|
427
|
+
* `LenzPipelineError` on terminal non-success, and `LenzTimeoutError` on
|
|
428
|
+
* deadline.
|
|
429
|
+
*/
|
|
430
|
+
async wait(task, opts = {}) {
|
|
431
|
+
const taskId = typeof task === "string" ? task : task.task_id;
|
|
432
|
+
if (!taskId) {
|
|
433
|
+
throw new Error("wait() requires a non-empty task_id (got an empty TaskAccepted.task_id).");
|
|
434
|
+
}
|
|
435
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
436
|
+
const { terminal, timedOut } = await this._pollToTerminal([taskId], timeoutMs);
|
|
437
|
+
if (timedOut.has(taskId)) {
|
|
438
|
+
const err = new LenzTimeoutError({
|
|
439
|
+
message: `wait timed out after ${timeoutMs}ms`,
|
|
440
|
+
cause: "Pipeline still running server-side.",
|
|
441
|
+
fix: `Resume via client.getStatus('${taskId}') later.`,
|
|
442
|
+
docUrl: "https://lenz.io/docs/verify#timeout"
|
|
443
|
+
});
|
|
444
|
+
err.taskId = taskId;
|
|
445
|
+
throw err;
|
|
446
|
+
}
|
|
447
|
+
return this._verificationFromTerminal(terminal.get(taskId), taskId);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Submit a batch and poll every item to a terminal state. Returns one
|
|
451
|
+
* `BatchItemResult` per task the batch accepted, in input order. Never throws
|
|
452
|
+
* on a per-item outcome — a claim that fails, pauses, or times out becomes a
|
|
453
|
+
* `BatchItemResult` with the matching `status`. (Transport/auth errors on the
|
|
454
|
+
* initial submit still throw.)
|
|
455
|
+
*/
|
|
456
|
+
async verifyBatchAndWait(input) {
|
|
457
|
+
const timeoutMs = input.timeoutMs ?? 18e4;
|
|
458
|
+
const accepted = await this.verifyBatch(input);
|
|
459
|
+
const ids = accepted.items.map((it) => it.task_id).filter((id) => Boolean(id));
|
|
460
|
+
const { terminal, timedOut } = await this._pollToTerminal(ids, timeoutMs);
|
|
461
|
+
return accepted.items.map((it) => {
|
|
462
|
+
const status = terminal.get(it.task_id);
|
|
463
|
+
if (!it.task_id || timedOut.has(it.task_id) || !status) {
|
|
464
|
+
return { task_id: it.task_id, claim_text: it.claim_text, status: "timeout" };
|
|
465
|
+
}
|
|
466
|
+
if (status.status === "completed" && status.result) {
|
|
467
|
+
return {
|
|
468
|
+
task_id: it.task_id,
|
|
469
|
+
claim_text: it.claim_text,
|
|
470
|
+
status: "completed",
|
|
471
|
+
verification: status.result,
|
|
472
|
+
status_detail: status
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (status.status === "needs_input") {
|
|
476
|
+
return {
|
|
477
|
+
task_id: it.task_id,
|
|
478
|
+
claim_text: it.claim_text,
|
|
479
|
+
status: "needs_input",
|
|
480
|
+
status_detail: status
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
task_id: it.task_id,
|
|
485
|
+
claim_text: it.claim_text,
|
|
486
|
+
status: "failed",
|
|
487
|
+
status_detail: status
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
// ── poll engine (shared by wait + verifyBatchAndWait) ──
|
|
492
|
+
/**
|
|
493
|
+
* Round-robin poll `taskIds` until each reaches a terminal state or the
|
|
494
|
+
* deadline elapses. Returns `{terminal, timedOut}`; a timed-out task has no
|
|
495
|
+
* `TaskStatus` (`"timeout"` is client-side, never a wire status).
|
|
496
|
+
*
|
|
497
|
+
* Each round polls every still-pending id once (via `Promise.allSettled`, so
|
|
498
|
+
* one poll's transport failure doesn't abort the batch — that id stays
|
|
499
|
+
* pending and retries next round) BEFORE the deadline check, preserving the
|
|
500
|
+
* legacy `verifyAndWait` behavior of polling once more after sleeping the
|
|
501
|
+
* remaining time. Timeout is therefore approximate. Backoff reuses the
|
|
502
|
+
* existing 2/4/8/8…ms sequence; the 10s cap is currently unreachable and kept
|
|
503
|
+
* only to preserve identical timing.
|
|
504
|
+
*/
|
|
505
|
+
async _pollToTerminal(taskIds, timeoutMs) {
|
|
506
|
+
let pending = [...taskIds];
|
|
507
|
+
const terminal = /* @__PURE__ */ new Map();
|
|
508
|
+
const timedOut = /* @__PURE__ */ new Set();
|
|
509
|
+
const deadline = Date.now() + timeoutMs;
|
|
510
|
+
let backoffIdx = 0;
|
|
511
|
+
while (pending.length > 0) {
|
|
512
|
+
const settled = await Promise.allSettled(pending.map((id) => this.getStatus(id)));
|
|
513
|
+
const stillPending = [];
|
|
514
|
+
settled.forEach((res, i) => {
|
|
515
|
+
const id = pending[i];
|
|
516
|
+
if (res.status === "fulfilled") {
|
|
517
|
+
const s = res.value;
|
|
518
|
+
if (s.status === "completed" || s.status === "needs_input" || s.status === "failed") {
|
|
519
|
+
terminal.set(id, s);
|
|
520
|
+
} else {
|
|
521
|
+
stillPending.push(id);
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
stillPending.push(id);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
pending = stillPending;
|
|
528
|
+
if (pending.length === 0) break;
|
|
529
|
+
const remaining = deadline - Date.now();
|
|
530
|
+
if (remaining <= 0) {
|
|
531
|
+
pending.forEach((id) => timedOut.add(id));
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
await sleep(pollSleepMs(backoffIdx, remaining));
|
|
535
|
+
backoffIdx += 1;
|
|
536
|
+
}
|
|
537
|
+
return { terminal, timedOut };
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Map a terminal `TaskStatus` to a `Verification` or throw the matching typed
|
|
541
|
+
* error. Shared by `wait` (and thus `verifyAndWait`).
|
|
542
|
+
*/
|
|
543
|
+
_verificationFromTerminal(status, taskId) {
|
|
544
|
+
if (status.status === "completed") {
|
|
545
|
+
if (!status.result) {
|
|
546
|
+
throw new LenzPipelineError({
|
|
547
|
+
message: "Pipeline completed but the result is empty.",
|
|
548
|
+
cause: "Server reported status=completed without a result block.",
|
|
549
|
+
fix: "File an issue at https://github.com/lenzhq/lenz-io-node/issues with the Request ID.",
|
|
550
|
+
docUrl: "https://lenz.io/docs/errors"
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
return status.result;
|
|
554
|
+
}
|
|
555
|
+
if (status.status === "needs_input") {
|
|
556
|
+
const err2 = new LenzNeedsInputError({
|
|
557
|
+
message: `Pipeline paused: ${status.reason ?? "needs input"}`,
|
|
558
|
+
cause: "The verification needs caller input to proceed.",
|
|
559
|
+
fix: "Inspect the payload, then call client.select(taskId, { texts: [...] }) with the chosen claim(s).",
|
|
560
|
+
docUrl: "https://lenz.io/docs/verify#needs-input"
|
|
561
|
+
});
|
|
562
|
+
err2.taskId = taskId;
|
|
563
|
+
err2.kind = status.reason ?? "";
|
|
564
|
+
err2.payload = status;
|
|
565
|
+
throw err2;
|
|
566
|
+
}
|
|
567
|
+
const detail = status.error || status.failure_detail || status.failure_reason || "unknown";
|
|
568
|
+
const err = new LenzPipelineError({
|
|
569
|
+
message: `Pipeline failed: ${detail}`,
|
|
570
|
+
cause: detail,
|
|
571
|
+
fix: "Retry with a different claim, or check status.error for the diagnostic.",
|
|
572
|
+
docUrl: "https://lenz.io/docs/errors"
|
|
573
|
+
});
|
|
574
|
+
err.taskId = taskId;
|
|
575
|
+
err.failureReason = status.failure_reason ?? "";
|
|
576
|
+
throw err;
|
|
577
|
+
}
|
|
578
|
+
// ── internal helpers ──
|
|
579
|
+
async submit(input) {
|
|
580
|
+
const body = {
|
|
581
|
+
text: input.claim,
|
|
582
|
+
source_url: input.sourceUrl ?? "",
|
|
583
|
+
webhook_url: input.webhookUrl ?? ""
|
|
584
|
+
};
|
|
585
|
+
if (input.language) body.language = input.language;
|
|
586
|
+
const headers = {};
|
|
587
|
+
if (input.idempotencyKey) headers["Idempotency-Key"] = input.idempotencyKey;
|
|
588
|
+
return this.request({
|
|
589
|
+
method: "POST",
|
|
590
|
+
path: "/verify",
|
|
591
|
+
json: body,
|
|
592
|
+
headers
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
/** Internal: dispatch an HTTP call with auth + retry. Public so the
|
|
596
|
+
* namespace classes can use it; not part of the documented surface. */
|
|
597
|
+
async request(opts) {
|
|
598
|
+
const authRequired = opts.authRequired !== false;
|
|
599
|
+
if (authRequired && !this.apiKey) {
|
|
600
|
+
throw new LenzAuthError({
|
|
601
|
+
message: "API key required",
|
|
602
|
+
cause: "This method requires authentication; no API key was provided.",
|
|
603
|
+
fix: "Pass apiKey to new Lenz(), set LENZ_API_KEY env var, or get one at https://lenz.io/api-integration. Library endpoints work without a key.",
|
|
604
|
+
docUrl: "https://lenz.io/docs/auth"
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const url = new URL(`${this.baseUrl}${opts.path}`);
|
|
608
|
+
if (opts.query) {
|
|
609
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
610
|
+
if (v !== void 0 && v !== "" && v !== null) {
|
|
611
|
+
url.searchParams.set(k, String(v));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const headers = {
|
|
616
|
+
"User-Agent": `lenz-io-node/${VERSION}`,
|
|
617
|
+
"X-Lenz-API-Version": API_VERSION,
|
|
618
|
+
Accept: "application/json",
|
|
619
|
+
...opts.headers ?? {}
|
|
620
|
+
};
|
|
621
|
+
if (this.apiKey && (authRequired || opts.authOptional)) {
|
|
622
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
623
|
+
}
|
|
624
|
+
if (opts.json !== void 0) {
|
|
625
|
+
headers["Content-Type"] = "application/json";
|
|
626
|
+
}
|
|
627
|
+
let lastErr = void 0;
|
|
628
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
629
|
+
const controller = new AbortController();
|
|
630
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
631
|
+
let response;
|
|
632
|
+
try {
|
|
633
|
+
response = await this.fetchImpl(url.toString(), {
|
|
634
|
+
method: opts.method,
|
|
635
|
+
headers,
|
|
636
|
+
body: opts.json !== void 0 ? JSON.stringify(opts.json) : void 0,
|
|
637
|
+
signal: controller.signal
|
|
638
|
+
});
|
|
639
|
+
} catch (exc) {
|
|
640
|
+
lastErr = exc;
|
|
641
|
+
clearTimeout(timer);
|
|
642
|
+
if (attempt >= this.maxRetries) {
|
|
643
|
+
throw new LenzAPIError({
|
|
644
|
+
message: `${opts.method} ${opts.path} failed after ${attempt + 1} attempts: ${String(exc)}`,
|
|
645
|
+
cause: String(exc),
|
|
646
|
+
fix: "Check your network connection; verify baseUrl is reachable.",
|
|
647
|
+
docUrl: "https://lenz.io/docs/errors"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
await sleep(retrySleepMs(attempt));
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
clearTimeout(timer);
|
|
654
|
+
if (response.status < 400) {
|
|
655
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
656
|
+
return {};
|
|
657
|
+
}
|
|
658
|
+
return await response.json();
|
|
659
|
+
}
|
|
660
|
+
if (attempt < this.maxRetries && (response.status >= 500 || response.status === 429)) {
|
|
661
|
+
const ra = response.headers.get("Retry-After");
|
|
662
|
+
let waitMs = retrySleepMs(attempt);
|
|
663
|
+
if (ra) {
|
|
664
|
+
const parsed = Number(ra);
|
|
665
|
+
if (Number.isFinite(parsed)) waitMs = parsed * 1e3;
|
|
666
|
+
}
|
|
667
|
+
await sleep(waitMs);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const rawBody = await response.text();
|
|
671
|
+
const respHeaders = {};
|
|
672
|
+
response.headers.forEach((v, k) => {
|
|
673
|
+
respHeaders[k] = v;
|
|
674
|
+
});
|
|
675
|
+
throw mapResponseToError(response.status, rawBody, respHeaders);
|
|
676
|
+
}
|
|
677
|
+
if (lastErr) {
|
|
678
|
+
throw new LenzAPIError({
|
|
679
|
+
message: String(lastErr),
|
|
680
|
+
cause: String(lastErr)
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
throw new LenzAPIError({ message: `${opts.method} ${opts.path} failed without diagnostic` });
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
var import_node_crypto2 = require("crypto");
|
|
687
|
+
var import_node_buffer = require("buffer");
|
|
688
|
+
var SIGNATURE_HEADER = "X-Lenz-Signature";
|
|
689
|
+
var SIGNATURE_PREFIX = "sha256=";
|
|
690
|
+
var DEFAULT_REPLAY_WINDOW_SECONDS = 300;
|
|
691
|
+
function toBuffer(body) {
|
|
692
|
+
if (import_node_buffer.Buffer.isBuffer(body)) return body;
|
|
693
|
+
if (body instanceof Uint8Array) return import_node_buffer.Buffer.from(body);
|
|
694
|
+
return import_node_buffer.Buffer.from(body, "utf-8");
|
|
695
|
+
}
|
|
696
|
+
function sign(body, secret) {
|
|
697
|
+
const mac = (0, import_node_crypto2.createHmac)("sha256", secret).update(body).digest("hex");
|
|
698
|
+
return `${SIGNATURE_PREFIX}${mac}`;
|
|
699
|
+
}
|
|
700
|
+
function verifySignature(rawBody, signature, secret) {
|
|
701
|
+
if (!signature) {
|
|
702
|
+
throw new LenzWebhookSignatureError({
|
|
703
|
+
message: "Missing webhook signature",
|
|
704
|
+
cause: `No ${SIGNATURE_HEADER} header on the request.`,
|
|
705
|
+
fix: "Inspect the webhook delivery in /api-integration to confirm the secret is set.",
|
|
706
|
+
docUrl: "https://lenz.io/docs/webhooks"
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const buf = toBuffer(rawBody);
|
|
710
|
+
const expected = sign(buf, secret);
|
|
711
|
+
const a = import_node_buffer.Buffer.from(expected);
|
|
712
|
+
const b = import_node_buffer.Buffer.from(signature);
|
|
713
|
+
if (a.length !== b.length || !(0, import_node_crypto2.timingSafeEqual)(a, b)) {
|
|
714
|
+
throw new LenzWebhookSignatureError({
|
|
715
|
+
message: "Webhook signature mismatch",
|
|
716
|
+
cause: "HMAC of the raw body using your secret does not match X-Lenz-Signature.",
|
|
717
|
+
fix: "Verify the secret in /api-integration matches the one you configured here.",
|
|
718
|
+
docUrl: "https://lenz.io/docs/webhooks"
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
function buildEvent(payload) {
|
|
724
|
+
const event = String(payload["event"] ?? "");
|
|
725
|
+
const base = {
|
|
726
|
+
event,
|
|
727
|
+
taskId: String(payload["task_id"] ?? ""),
|
|
728
|
+
attempt: Number(payload["attempt"] ?? 1) || 1,
|
|
729
|
+
deliveredAt: String(payload["delivered_at"] ?? ""),
|
|
730
|
+
verificationId: payload["verification_id"] ?? null,
|
|
731
|
+
batchId: payload["batch_id"] ?? null,
|
|
732
|
+
status: String(payload["status"] ?? ""),
|
|
733
|
+
raw: payload
|
|
734
|
+
};
|
|
735
|
+
if (event === "verification.completed") {
|
|
736
|
+
return {
|
|
737
|
+
...base,
|
|
738
|
+
event: "verification.completed",
|
|
739
|
+
result: payload["result"] ?? {}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
if (event === "verification.failed") {
|
|
743
|
+
return {
|
|
744
|
+
...base,
|
|
745
|
+
event: "verification.failed",
|
|
746
|
+
error: String(payload["error"] ?? "")
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (event === "verification.needs_input") {
|
|
750
|
+
return {
|
|
751
|
+
...base,
|
|
752
|
+
event: "verification.needs_input",
|
|
753
|
+
needsInput: payload["needs_input"] ?? {}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return base;
|
|
757
|
+
}
|
|
758
|
+
var LenzWebhooks = class {
|
|
759
|
+
secret;
|
|
760
|
+
replayWindow;
|
|
761
|
+
constructor(opts) {
|
|
762
|
+
if (!opts.secret) {
|
|
763
|
+
throw new Error("LenzWebhooks requires a non-empty secret. Get it from /api-integration.");
|
|
764
|
+
}
|
|
765
|
+
this.secret = opts.secret;
|
|
766
|
+
this.replayWindow = opts.replayWindowSeconds ?? DEFAULT_REPLAY_WINDOW_SECONDS;
|
|
767
|
+
}
|
|
768
|
+
parse(rawBody, headers) {
|
|
769
|
+
const sig = this.lookupHeader(headers, SIGNATURE_HEADER);
|
|
770
|
+
verifySignature(rawBody, sig, this.secret);
|
|
771
|
+
const text = toBuffer(rawBody).toString("utf-8");
|
|
772
|
+
let payload;
|
|
773
|
+
try {
|
|
774
|
+
payload = JSON.parse(text);
|
|
775
|
+
} catch (exc) {
|
|
776
|
+
throw new LenzWebhookSignatureError({
|
|
777
|
+
message: "Webhook body is not valid JSON",
|
|
778
|
+
cause: String(exc),
|
|
779
|
+
fix: "The signature verified but the body is malformed. Check your reverse proxy isn't rewriting payloads.",
|
|
780
|
+
docUrl: "https://lenz.io/docs/webhooks"
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
784
|
+
throw new LenzWebhookSignatureError({
|
|
785
|
+
message: "Webhook body must be a JSON object",
|
|
786
|
+
cause: `Got ${typeof payload}.`,
|
|
787
|
+
fix: "Confirm the request comes from Lenz; an upstream proxy may be wrapping the body.",
|
|
788
|
+
docUrl: "https://lenz.io/docs/webhooks"
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const obj = payload;
|
|
792
|
+
this.checkReplay(obj);
|
|
793
|
+
return buildEvent(obj);
|
|
794
|
+
}
|
|
795
|
+
// ── helpers ──
|
|
796
|
+
lookupHeader(headers, name) {
|
|
797
|
+
if (typeof headers.get === "function") {
|
|
798
|
+
const v = headers.get(name);
|
|
799
|
+
return v ? String(v) : "";
|
|
800
|
+
}
|
|
801
|
+
const h = headers;
|
|
802
|
+
return h[name] ?? h[name.toLowerCase()] ?? h[name.toUpperCase()] ?? "";
|
|
803
|
+
}
|
|
804
|
+
checkReplay(payload) {
|
|
805
|
+
const raw = payload["delivered_at"];
|
|
806
|
+
if (!raw) return;
|
|
807
|
+
const ts = new Date(String(raw));
|
|
808
|
+
if (Number.isNaN(ts.getTime())) return;
|
|
809
|
+
const ageSec = (Date.now() - ts.getTime()) / 1e3;
|
|
810
|
+
if (ageSec > this.replayWindow) {
|
|
811
|
+
throw new LenzWebhookSignatureError({
|
|
812
|
+
message: "Webhook delivered_at is outside the replay window",
|
|
813
|
+
cause: `Payload is ${Math.round(ageSec)}s old; window is ${this.replayWindow}s.`,
|
|
814
|
+
fix: "Confirm your server clock is in sync; raise replayWindowSeconds if you intentionally batch deliveries.",
|
|
815
|
+
docUrl: "https://lenz.io/docs/webhooks"
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// dist/nodes/Lenz/Lenz.node.js
|
|
824
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
825
|
+
exports.Lenz = void 0;
|
|
826
|
+
var n8n_workflow_1 = require("n8n-workflow");
|
|
827
|
+
var lenz_io_1 = require_dist();
|
|
828
|
+
function isPassingVerdict(verdict) {
|
|
829
|
+
return verdict === "True" || verdict === "Mostly True";
|
|
830
|
+
}
|
|
831
|
+
var Lenz = class {
|
|
832
|
+
constructor() {
|
|
833
|
+
this.description = {
|
|
834
|
+
displayName: "Lenz",
|
|
835
|
+
name: "lenz",
|
|
836
|
+
icon: { light: "file:lenz.svg", dark: "file:lenz.dark.svg" },
|
|
837
|
+
group: ["transform"],
|
|
838
|
+
version: [1],
|
|
839
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
840
|
+
description: "Fact-check claims and catch AI hallucinations with sourced, audit-grade verdicts",
|
|
841
|
+
defaults: {
|
|
842
|
+
name: "Lenz"
|
|
843
|
+
},
|
|
844
|
+
inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
845
|
+
outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
846
|
+
usableAsTool: true,
|
|
847
|
+
credentials: [
|
|
848
|
+
{
|
|
849
|
+
name: "lenzApi",
|
|
850
|
+
required: true
|
|
851
|
+
}
|
|
852
|
+
],
|
|
853
|
+
properties: [
|
|
854
|
+
{
|
|
855
|
+
displayName: "Operation",
|
|
856
|
+
name: "operation",
|
|
857
|
+
type: "options",
|
|
858
|
+
noDataExpression: true,
|
|
859
|
+
options: [
|
|
860
|
+
{
|
|
861
|
+
name: "Ask Follow-Up",
|
|
862
|
+
value: "ask",
|
|
863
|
+
description: "Ask a grounded follow-up question about a completed Verify (Deep) result",
|
|
864
|
+
action: "Ask a follow up question"
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: "Assess (Fast)",
|
|
868
|
+
value: "assess",
|
|
869
|
+
description: "Fast 3-model panel verdict (~5-10s), one entry per claim found in the text",
|
|
870
|
+
action: "Quickly assess text for factual claims"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
name: "Check Usage",
|
|
874
|
+
value: "usage",
|
|
875
|
+
description: "Check remaining quota for the current API key",
|
|
876
|
+
action: "Check usage and quota"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: "Extract Claims",
|
|
880
|
+
value: "extract",
|
|
881
|
+
description: "Pull verifiable claims out of text (free)",
|
|
882
|
+
action: "Extract claims from text"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: "Verify (Deep)",
|
|
886
|
+
value: "verify",
|
|
887
|
+
description: "Full 8-model pipeline with sourced citations (~90s). Reserve for high-stakes claims.",
|
|
888
|
+
action: "Deeply verify a claim"
|
|
889
|
+
}
|
|
890
|
+
],
|
|
891
|
+
default: "verify"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
displayName: "Claim",
|
|
895
|
+
name: "claim",
|
|
896
|
+
type: "string",
|
|
897
|
+
typeOptions: { rows: 3 },
|
|
898
|
+
default: "",
|
|
899
|
+
required: true,
|
|
900
|
+
displayOptions: {
|
|
901
|
+
show: { operation: ["verify"] }
|
|
902
|
+
},
|
|
903
|
+
description: "The claim to investigate in depth. Reserve for high-stakes statements that warrant a thorough, sourced check."
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
displayName: "Text",
|
|
907
|
+
name: "text",
|
|
908
|
+
type: "string",
|
|
909
|
+
typeOptions: { rows: 3 },
|
|
910
|
+
default: "",
|
|
911
|
+
required: true,
|
|
912
|
+
displayOptions: {
|
|
913
|
+
show: { operation: ["assess", "extract"] }
|
|
914
|
+
},
|
|
915
|
+
description: "The text to check. If it contains several claims, each is handled separately."
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
displayName: "Verification ID",
|
|
919
|
+
name: "verificationId",
|
|
920
|
+
type: "string",
|
|
921
|
+
default: "",
|
|
922
|
+
required: true,
|
|
923
|
+
displayOptions: {
|
|
924
|
+
show: { operation: ["ask"] }
|
|
925
|
+
},
|
|
926
|
+
description: "The verification_id from a completed Verify (Deep) result (not a task_id)"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
displayName: "Question",
|
|
930
|
+
name: "question",
|
|
931
|
+
type: "string",
|
|
932
|
+
default: "",
|
|
933
|
+
required: true,
|
|
934
|
+
displayOptions: {
|
|
935
|
+
show: { operation: ["ask"] }
|
|
936
|
+
},
|
|
937
|
+
description: "The follow-up question, answered from the verification full research and evidence"
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
displayName: "Language",
|
|
941
|
+
name: "language",
|
|
942
|
+
type: "string",
|
|
943
|
+
default: "",
|
|
944
|
+
placeholder: "Es",
|
|
945
|
+
description: "Optional ISO 639-1 response language code. Defaults to English.",
|
|
946
|
+
displayOptions: {
|
|
947
|
+
show: { operation: ["verify", "assess", "extract", "ask"] }
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
]
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
async execute() {
|
|
954
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
955
|
+
const items = this.getInputData();
|
|
956
|
+
const returnData = [];
|
|
957
|
+
const credentials = await this.getCredentials("lenzApi");
|
|
958
|
+
const client = new lenz_io_1.Lenz({ apiKey: credentials.apiKey });
|
|
959
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
960
|
+
try {
|
|
961
|
+
const operation = this.getNodeParameter("operation", itemIndex);
|
|
962
|
+
const language = this.getNodeParameter("language", itemIndex, "") || void 0;
|
|
963
|
+
let responseData;
|
|
964
|
+
if (operation === "verify") {
|
|
965
|
+
const claim = this.getNodeParameter("claim", itemIndex).trim();
|
|
966
|
+
if (!claim) {
|
|
967
|
+
returnData.push({
|
|
968
|
+
json: { skipped: true, reason: "empty_input" },
|
|
969
|
+
pairedItem: { item: itemIndex }
|
|
970
|
+
});
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const result = await client.verifyAndWait({ claim, language });
|
|
975
|
+
responseData = {
|
|
976
|
+
status: "completed",
|
|
977
|
+
passed: isPassingVerdict(result.verdict),
|
|
978
|
+
verdict: (_a = result.verdict) !== null && _a !== void 0 ? _a : null,
|
|
979
|
+
confidence: (_b = result.confidence) !== null && _b !== void 0 ? _b : null,
|
|
980
|
+
lenz_score: (_c = result.lenz_score) !== null && _c !== void 0 ? _c : null,
|
|
981
|
+
executive_summary: (_d = result.executive_summary) !== null && _d !== void 0 ? _d : "",
|
|
982
|
+
citations: ((_e = result.sources) !== null && _e !== void 0 ? _e : []).filter((s) => s.url).map((s) => {
|
|
983
|
+
var _a2, _b2;
|
|
984
|
+
return { title: (_a2 = s.title) !== null && _a2 !== void 0 ? _a2 : "", url: (_b2 = s.url) !== null && _b2 !== void 0 ? _b2 : "" };
|
|
985
|
+
}),
|
|
986
|
+
verification_id: (_f = result.verification_id) !== null && _f !== void 0 ? _f : null
|
|
987
|
+
};
|
|
988
|
+
} catch (err) {
|
|
989
|
+
if (err instanceof lenz_io_1.LenzNeedsInputError) {
|
|
990
|
+
responseData = {
|
|
991
|
+
status: "needs_input",
|
|
992
|
+
reason: err.kind,
|
|
993
|
+
task_id: err.taskId,
|
|
994
|
+
message: "This claim is ambiguous or contains multiple sub-claims. Rephrase it to be more specific and re-run."
|
|
995
|
+
};
|
|
996
|
+
} else if (err instanceof lenz_io_1.LenzTimeoutError) {
|
|
997
|
+
responseData = {
|
|
998
|
+
status: "timeout",
|
|
999
|
+
task_id: err.taskId,
|
|
1000
|
+
message: "The verification did not complete in time (task_id: " + err.taskId + "). It may still be running server-side."
|
|
1001
|
+
};
|
|
1002
|
+
} else if (err instanceof lenz_io_1.LenzPipelineError) {
|
|
1003
|
+
responseData = {
|
|
1004
|
+
status: "failed",
|
|
1005
|
+
task_id: err.taskId,
|
|
1006
|
+
message: err.message
|
|
1007
|
+
};
|
|
1008
|
+
} else if (err instanceof lenz_io_1.LenzError) {
|
|
1009
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: err.message, description: err.toString() }, {
|
|
1010
|
+
itemIndex,
|
|
1011
|
+
httpCode: err.statusCode ? String(err.statusCode) : void 0
|
|
1012
|
+
});
|
|
1013
|
+
} else {
|
|
1014
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: err.message }, { itemIndex });
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} else if (operation === "assess") {
|
|
1018
|
+
const text = this.getNodeParameter("text", itemIndex).trim();
|
|
1019
|
+
if (!text) {
|
|
1020
|
+
returnData.push({
|
|
1021
|
+
json: { skipped: true, reason: "empty_input" },
|
|
1022
|
+
pairedItem: { item: itemIndex }
|
|
1023
|
+
});
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const result = await client.assess({ text, language });
|
|
1027
|
+
if (!result.claims.length) {
|
|
1028
|
+
responseData = {
|
|
1029
|
+
status: result.error_code === "ambiguous" ? "ambiguous" : "no_claim",
|
|
1030
|
+
message: (_g = result.error) !== null && _g !== void 0 ? _g : "No verifiable factual claim was detected.",
|
|
1031
|
+
candidate_claims: (_h = result.candidate_claims) !== null && _h !== void 0 ? _h : []
|
|
1032
|
+
};
|
|
1033
|
+
} else {
|
|
1034
|
+
responseData = {
|
|
1035
|
+
status: "ok",
|
|
1036
|
+
claims: result.claims.map((c) => {
|
|
1037
|
+
var _a2, _b2, _c2, _d2;
|
|
1038
|
+
return {
|
|
1039
|
+
claim: (_a2 = c.claim) !== null && _a2 !== void 0 ? _a2 : "",
|
|
1040
|
+
verdict: (_b2 = c.verdict) !== null && _b2 !== void 0 ? _b2 : null,
|
|
1041
|
+
confidence: (_c2 = c.confidence) !== null && _c2 !== void 0 ? _c2 : null,
|
|
1042
|
+
passed: isPassingVerdict(c.verdict),
|
|
1043
|
+
verification_url: (_d2 = c.verification_url) !== null && _d2 !== void 0 ? _d2 : null
|
|
1044
|
+
};
|
|
1045
|
+
})
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
} else if (operation === "extract") {
|
|
1049
|
+
const text = this.getNodeParameter("text", itemIndex).trim();
|
|
1050
|
+
if (!text) {
|
|
1051
|
+
returnData.push({
|
|
1052
|
+
json: { skipped: true, reason: "empty_input" },
|
|
1053
|
+
pairedItem: { item: itemIndex }
|
|
1054
|
+
});
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
responseData = await client.extract({ text, language });
|
|
1058
|
+
} else if (operation === "ask") {
|
|
1059
|
+
const verificationId = this.getNodeParameter("verificationId", itemIndex);
|
|
1060
|
+
const question = this.getNodeParameter("question", itemIndex);
|
|
1061
|
+
const reply = await client.ask.send(verificationId, { message: question, language });
|
|
1062
|
+
responseData = {
|
|
1063
|
+
answer: (_j = reply.content) !== null && _j !== void 0 ? _j : ""
|
|
1064
|
+
};
|
|
1065
|
+
} else if (operation === "usage") {
|
|
1066
|
+
responseData = await client.usage();
|
|
1067
|
+
} else {
|
|
1068
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Unknown operation: " + operation, {
|
|
1069
|
+
itemIndex
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
returnData.push({
|
|
1073
|
+
json: responseData,
|
|
1074
|
+
pairedItem: { item: itemIndex }
|
|
1075
|
+
});
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
if (this.continueOnFail()) {
|
|
1078
|
+
returnData.push({
|
|
1079
|
+
json: { error: error.message },
|
|
1080
|
+
pairedItem: { item: itemIndex }
|
|
1081
|
+
});
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (error instanceof n8n_workflow_1.NodeOperationError) {
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
if (error instanceof lenz_io_1.LenzError) {
|
|
1088
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: error.message, description: error.toString() }, {
|
|
1089
|
+
itemIndex,
|
|
1090
|
+
httpCode: error.statusCode ? String(error.statusCode) : void 0
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: error.message }, { itemIndex });
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return [returnData];
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
exports.Lenz = Lenz;
|