@signaliz/sdk 1.0.46 → 1.0.48
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 +24 -33
- package/dist/{chunk-4LXMT3S5.mjs → chunk-NOOQDYOJ.mjs} +129 -228
- package/dist/index.d.mts +67 -4
- package/dist/index.d.ts +67 -4
- package/dist/index.js +129 -228
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +129 -228
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,15 @@ const signaliz = new Signaliz({
|
|
|
20
20
|
apiKey: process.env.SIGNALIZ_API_KEY,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
// Every product supports a strict no-spend preflight. Batch methods accept the
|
|
24
|
+
// same option and return one plan for the full input.
|
|
25
|
+
const plan = await signaliz.findEmail({
|
|
26
|
+
companyDomain: 'example.com',
|
|
27
|
+
fullName: 'Jane Doe',
|
|
28
|
+
dryRun: true,
|
|
29
|
+
});
|
|
30
|
+
console.log(plan.estimatedCredits.max, plan.creditsCharged); // 2, 0
|
|
31
|
+
|
|
23
32
|
const found = await signaliz.findEmail({
|
|
24
33
|
companyDomain: 'example.com',
|
|
25
34
|
fullName: 'Jane Doe',
|
|
@@ -50,15 +59,6 @@ const signals = await signaliz.enrichCompanySignals({
|
|
|
50
59
|
console.log(signals.signals[0].evidencePublishedDate);
|
|
51
60
|
console.log(signals.signals[0].signalFeedSource, signals.signals[0].derived);
|
|
52
61
|
|
|
53
|
-
// Persist a no-wait run ID and resume it later without duplicate spend.
|
|
54
|
-
const queuedSignals = await signaliz.enrichCompanySignals({
|
|
55
|
-
companyDomain: 'example.com',
|
|
56
|
-
waitForResult: false,
|
|
57
|
-
});
|
|
58
|
-
const resumedSignals = await signaliz.enrichCompanySignals({
|
|
59
|
-
signalRunId: queuedSignals.signalRunId,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
62
|
const copy = await signaliz.signalToCopy({
|
|
63
63
|
companyDomain: 'example.com',
|
|
64
64
|
personName: 'Jane Doe',
|
|
@@ -70,16 +70,8 @@ const copy = await signaliz.signalToCopy({
|
|
|
70
70
|
enableDeepSearch: false,
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
const queuedCopy = await signaliz.signalToCopy({
|
|
76
|
-
companyDomain: 'example.com',
|
|
77
|
-
personName: 'Jane Doe',
|
|
78
|
-
title: 'VP Sales',
|
|
79
|
-
campaignOffer: 'pipeline intelligence',
|
|
80
|
-
waitForResult: false,
|
|
81
|
-
});
|
|
82
|
-
const resumedCopy = await signaliz.signalToCopy({ signalRunId: queuedCopy.runId });
|
|
73
|
+
// Company Signals and Signal to Copy are hard synchronous contracts: these
|
|
74
|
+
// methods return complete data or throw a terminal error in this same call.
|
|
83
75
|
```
|
|
84
76
|
|
|
85
77
|
All four products support bounded batch execution over the same REST endpoints.
|
|
@@ -104,29 +96,28 @@ Failed rows preserve `errorCode`, `retryEligible`, and `retryAfterSeconds`
|
|
|
104
96
|
when the REST API supplies them, including after automatic row retries are
|
|
105
97
|
exhausted.
|
|
106
98
|
|
|
107
|
-
For an ambiguous submission failure, retry with the
|
|
108
|
-
`idempotencyKey` to recover the original job without duplicate
|
|
99
|
+
For an ambiguous single request or batch submission failure, retry with the
|
|
100
|
+
same `idempotencyKey` to recover the original request or supported email job without duplicate
|
|
101
|
+
work.
|
|
109
102
|
|
|
110
|
-
Each batch accepts up to 5,000 items. Company Signal
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
server's adaptive polling hints by default and still honors `pollIntervalMs`
|
|
114
|
-
when a caller explicitly overrides the cadence.
|
|
103
|
+
Each batch accepts up to 5,000 items. For Company Signals and Signal to Copy,
|
|
104
|
+
the SDK chunks inputs into synchronous requests of at most 25 and waits for
|
|
105
|
+
every chunk. It never returns queued work, job IDs, or polling instructions.
|
|
115
106
|
Exact duplicate tasks are single-flighted across each full batch without
|
|
116
107
|
changing input order or result cardinality. Set `compactDuplicates: true` to
|
|
117
108
|
return repeated successful rows as `duplicateOf` references to the zero-based
|
|
118
109
|
canonical input index; the default remains expanded for compatibility. Find Email and Verify Email
|
|
119
110
|
batches
|
|
120
111
|
larger than 25 use one cache-aware recoverable REST job with 500-row result
|
|
121
|
-
pages.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Copy batches larger than 25 use one recoverable REST job when they can reuse
|
|
125
|
-
available signal data; fresh or deep-search requests keep the 25-row path.
|
|
126
|
-
Signal to Copy also shares company research across copy recipients.
|
|
112
|
+
pages. Company Signal Enrichment and Signal to Copy always use client-side
|
|
113
|
+
25-row chunks with terminal per-row results. Signal to Copy also shares company
|
|
114
|
+
research across copy recipients.
|
|
127
115
|
|
|
128
116
|
Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
|
|
129
|
-
unless the caller explicitly disables them.
|
|
117
|
+
unless the caller explicitly disables them. Its `searchMode` defaults to
|
|
118
|
+
`balanced`; use `fast` for the primary low-latency search lane only or
|
|
119
|
+
`coverage` when breadth matters more than the sub-cent average budget target.
|
|
120
|
+
Signal to Copy AI returns three
|
|
130
121
|
complete, evidence-backed email variations when supported signals are available.
|
|
131
122
|
Signal to Copy is cache-first and defaults `enableDeepSearch` to `false` so it
|
|
132
123
|
normally finishes within an agent turn; set `skipCache` or `enableDeepSearch`
|
|
@@ -378,10 +378,17 @@ var Signaliz = class {
|
|
|
378
378
|
}
|
|
379
379
|
async findEmail(params) {
|
|
380
380
|
const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
|
|
381
|
-
return normalizeFindEmailResult(data);
|
|
381
|
+
return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
|
|
382
382
|
}
|
|
383
383
|
async findEmails(params, options) {
|
|
384
384
|
validateBatchSize(params);
|
|
385
|
+
if (options?.dryRun === true) {
|
|
386
|
+
return this.runCoreProductDryRun(
|
|
387
|
+
"api/v1/find-email",
|
|
388
|
+
params.map(findEmailRequestBody),
|
|
389
|
+
options.idempotencyKey
|
|
390
|
+
);
|
|
391
|
+
}
|
|
385
392
|
const startedAt = Date.now();
|
|
386
393
|
const round = await this.runCoreBatchRound(
|
|
387
394
|
"api/v1/find-email",
|
|
@@ -400,9 +407,12 @@ var Signaliz = class {
|
|
|
400
407
|
const request = compact({
|
|
401
408
|
email: email || void 0,
|
|
402
409
|
verification_run_id: options?.verificationRunId,
|
|
403
|
-
skip_cache: options?.skipCache
|
|
410
|
+
skip_cache: options?.skipCache,
|
|
411
|
+
dry_run: options?.dryRun,
|
|
412
|
+
idempotency_key: options?.idempotencyKey
|
|
404
413
|
});
|
|
405
414
|
let data = await this.client.post("api/v1/verify-email", request);
|
|
415
|
+
if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
|
|
406
416
|
if (options?.waitForResult !== false && isPendingSignalResponse(data) && data.verification_run_id) {
|
|
407
417
|
const maxWaitMs = Math.max(1e3, options?.maxWaitMs ?? 10 * 6e4);
|
|
408
418
|
const startedAt = Date.now();
|
|
@@ -422,6 +432,13 @@ var Signaliz = class {
|
|
|
422
432
|
}
|
|
423
433
|
async verifyEmails(emails, options) {
|
|
424
434
|
validateBatchSize(emails);
|
|
435
|
+
if (options?.dryRun === true) {
|
|
436
|
+
return this.runCoreProductDryRun(
|
|
437
|
+
"api/v1/verify-email",
|
|
438
|
+
emails.map((email) => ({ email, skip_cache: options.skipCache })),
|
|
439
|
+
options.idempotencyKey
|
|
440
|
+
);
|
|
441
|
+
}
|
|
425
442
|
const startedAt = Date.now();
|
|
426
443
|
const round = await this.runCoreBatchRound(
|
|
427
444
|
"api/v1/verify-email",
|
|
@@ -441,100 +458,37 @@ var Signaliz = class {
|
|
|
441
458
|
}
|
|
442
459
|
async enrichCompanySignals(params) {
|
|
443
460
|
const request = companySignalRequestBody(params);
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
const startedAt = Date.now();
|
|
448
|
-
while (Date.now() - startedAt < maxWaitMs) {
|
|
449
|
-
if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
|
|
450
|
-
const status = await this.client.post("api/v1/company-signals", {
|
|
451
|
-
...request,
|
|
452
|
-
signal_run_id: data.run_id
|
|
453
|
-
});
|
|
454
|
-
if (isCompletedSignalRun(status)) {
|
|
455
|
-
data = status.output ?? status.result ?? status.data ?? status;
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
if (isFailedSignalRun(status)) {
|
|
459
|
-
throw new Error(`Company signal enrichment ${data.run_id} failed with status ${status.status || "unknown"}`);
|
|
460
|
-
}
|
|
461
|
-
if (!isPendingSignalResponse(status)) {
|
|
462
|
-
data = status.output ?? status.result ?? status.data ?? status;
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
data = status;
|
|
466
|
-
}
|
|
467
|
-
if (isPendingSignalResponse(data)) {
|
|
468
|
-
throw new Error(`Company signal enrichment ${data.run_id} did not complete within ${maxWaitMs}ms`);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
461
|
+
const data = await this.client.post("api/v1/company-signals", request);
|
|
462
|
+
if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
|
|
463
|
+
assertTerminalSignalResponse(data, "Company Signals");
|
|
471
464
|
return normalizeCompanySignalResult(params, data);
|
|
472
465
|
}
|
|
473
466
|
async enrichCompanies(companies, options) {
|
|
474
467
|
validateBatchSize(companies);
|
|
468
|
+
if (options?.dryRun === true) {
|
|
469
|
+
return this.runCoreProductDryRun(
|
|
470
|
+
"api/v1/company-signals",
|
|
471
|
+
companies.map(companySignalRequestBody),
|
|
472
|
+
options.idempotencyKey
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
475
|
const startedAt = Date.now();
|
|
476
|
-
const results = new Array(companies.length);
|
|
477
476
|
const initialTasks = companies.map((params, index) => ({
|
|
478
477
|
index,
|
|
479
478
|
request: companySignalRequestBody(params)
|
|
480
479
|
}));
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const round = await this.runCoreBatchRound(
|
|
486
|
-
"api/v1/company-signals",
|
|
487
|
-
pending,
|
|
488
|
-
options,
|
|
489
|
-
firstRound ? recoverableBatch : void 0
|
|
490
|
-
);
|
|
491
|
-
firstRound = false;
|
|
492
|
-
const taskByIndex = new Map(pending.map((task) => [task.index, task]));
|
|
493
|
-
const nextPending = [];
|
|
494
|
-
let nextDelayMs = 6e4;
|
|
495
|
-
for (const item of round) {
|
|
496
|
-
const params = companies[item.index];
|
|
497
|
-
const task = taskByIndex.get(item.index);
|
|
498
|
-
if (!item.success || !item.raw) {
|
|
499
|
-
results[item.index] = batchItemFailure(
|
|
500
|
-
item.index,
|
|
501
|
-
item.error || "Company Signal batch request failed",
|
|
502
|
-
item.raw ?? item
|
|
503
|
-
);
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
|
|
507
|
-
results[item.index] = { index: item.index, success: true, data: normalizeCompanySignalResult(params, item.raw) };
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
|
|
511
|
-
if (Date.now() - startedAt >= maxWaitMs) {
|
|
512
|
-
results[item.index] = {
|
|
513
|
-
index: item.index,
|
|
514
|
-
success: false,
|
|
515
|
-
error: `Company signal enrichment ${item.raw.signal_run_id || item.raw.run_id || ""} did not complete within ${maxWaitMs}ms`
|
|
516
|
-
};
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
const signalRunId = item.raw.signal_run_id || item.raw.run_id;
|
|
520
|
-
if (!signalRunId) {
|
|
521
|
-
results[item.index] = { index: item.index, success: false, error: "Company signal enrichment is processing without a signal_run_id" };
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
nextDelayMs = Math.min(
|
|
525
|
-
nextDelayMs,
|
|
526
|
-
signalPollDelayMs(item.raw, params.pollIntervalMs),
|
|
527
|
-
maxWaitMs - (Date.now() - startedAt)
|
|
528
|
-
);
|
|
529
|
-
nextPending.push({
|
|
530
|
-
...task,
|
|
531
|
-
request: { ...task.request, signal_run_id: signalRunId }
|
|
532
|
-
});
|
|
480
|
+
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
481
|
+
const results = round.map((item) => {
|
|
482
|
+
if (!item.success || !item.raw) {
|
|
483
|
+
return batchItemFailure(item.index, item.error || "Company Signal batch request failed", item.raw ?? item);
|
|
533
484
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
485
|
+
try {
|
|
486
|
+
assertTerminalSignalResponse(item.raw, "Company Signals");
|
|
487
|
+
return { index: item.index, success: true, data: normalizeCompanySignalResult(companies[item.index], item.raw) };
|
|
488
|
+
} catch (error) {
|
|
489
|
+
return batchItemFailure(item.index, error instanceof Error ? error.message : String(error), item.raw);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
538
492
|
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
539
493
|
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
540
494
|
return {
|
|
@@ -548,37 +502,27 @@ var Signaliz = class {
|
|
|
548
502
|
async signalToCopy(params) {
|
|
549
503
|
validateSignalToCopyParams(params);
|
|
550
504
|
const request = signalToCopyRequestBody(params);
|
|
551
|
-
|
|
552
|
-
if (
|
|
553
|
-
|
|
554
|
-
const startedAt = Date.now();
|
|
555
|
-
while (Date.now() - startedAt < maxWaitMs) {
|
|
556
|
-
if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
|
|
557
|
-
const signalRunId = data.signal_run_id || data.run_id;
|
|
558
|
-
data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
|
|
559
|
-
if (!isPendingSignalResponse(data)) break;
|
|
560
|
-
}
|
|
561
|
-
if (isPendingSignalResponse(data)) {
|
|
562
|
-
throw new Error(`Signal Copy ${data.run_id} did not complete within ${maxWaitMs}ms`);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
505
|
+
const data = await this.client.post("api/v1/signal-to-copy", request);
|
|
506
|
+
if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
|
|
507
|
+
assertTerminalSignalResponse(data, "Signal to Copy");
|
|
565
508
|
return normalizeSignalToCopyResult(data);
|
|
566
509
|
}
|
|
567
510
|
async createSignalCopyBatch(requests, options) {
|
|
568
511
|
validateBatchSize(requests);
|
|
569
512
|
requests.forEach(validateSignalToCopyParams);
|
|
513
|
+
if (options?.dryRun === true) {
|
|
514
|
+
return this.runCoreProductDryRun(
|
|
515
|
+
"api/v1/signal-to-copy",
|
|
516
|
+
requests.map(signalToCopyRequestBody),
|
|
517
|
+
options.idempotencyKey
|
|
518
|
+
);
|
|
519
|
+
}
|
|
570
520
|
const startedAt = Date.now();
|
|
571
521
|
const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
|
|
572
|
-
request: signalToCopyRequestBody(params)
|
|
573
|
-
wait_for_result: params.waitForResult,
|
|
574
|
-
max_wait_ms: params.maxWaitMs,
|
|
575
|
-
poll_interval_ms: params.pollIntervalMs
|
|
522
|
+
request: signalToCopyRequestBody(params)
|
|
576
523
|
}));
|
|
577
524
|
const uniqueRequests = deduplicated.uniqueItems;
|
|
578
|
-
const
|
|
579
|
-
(params) => (!params.signalRunId || !params.signalRunId.startsWith("copy_") && [params.companyDomain, params.personName, params.title, params.campaignOffer].every((value) => typeof value === "string" && Boolean(value.trim()))) && params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
580
|
-
);
|
|
581
|
-
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
525
|
+
const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
582
526
|
const results = requests.map((_, index) => ({
|
|
583
527
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
584
528
|
index
|
|
@@ -593,6 +537,17 @@ var Signaliz = class {
|
|
|
593
537
|
results: compactedResults
|
|
594
538
|
};
|
|
595
539
|
}
|
|
540
|
+
async runCoreProductDryRun(path, requests, idempotencyKey) {
|
|
541
|
+
const data = await this.client.post(path, compact({
|
|
542
|
+
requests,
|
|
543
|
+
dry_run: true,
|
|
544
|
+
idempotency_key: idempotencyKey
|
|
545
|
+
}));
|
|
546
|
+
if (!isCoreProductDryRun(data)) {
|
|
547
|
+
throw new Error(`${path} dry run returned a live-result contract`);
|
|
548
|
+
}
|
|
549
|
+
return normalizeCoreProductDryRunResult(data);
|
|
550
|
+
}
|
|
596
551
|
async runSignalCopyBatchChunks(uniqueRequests, options) {
|
|
597
552
|
const chunks = [];
|
|
598
553
|
for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
|
|
@@ -623,22 +578,6 @@ var Signaliz = class {
|
|
|
623
578
|
}
|
|
624
579
|
return uniqueResults;
|
|
625
580
|
}
|
|
626
|
-
async runAvailableSignalCopyBatchJob(requests, options) {
|
|
627
|
-
const rows = await this.runRecoverableCoreBatchJob(
|
|
628
|
-
"api/v1/signal-to-copy",
|
|
629
|
-
requests.map(signalToCopyRequestBody),
|
|
630
|
-
"Signal to Copy",
|
|
631
|
-
500,
|
|
632
|
-
20 * 6e4,
|
|
633
|
-
options?.idempotencyKey
|
|
634
|
-
);
|
|
635
|
-
const results = new Array(requests.length);
|
|
636
|
-
for (const row of rows) {
|
|
637
|
-
const index = Number(row.index);
|
|
638
|
-
results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
|
|
639
|
-
}
|
|
640
|
-
return results;
|
|
641
|
-
}
|
|
642
581
|
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
|
|
643
582
|
const startedAt = Date.now();
|
|
644
583
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
@@ -710,77 +649,35 @@ var Signaliz = class {
|
|
|
710
649
|
return rows;
|
|
711
650
|
}
|
|
712
651
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
|
|
713
|
-
const startedAt = Date.now();
|
|
714
652
|
const results = new Array(requests.length);
|
|
715
653
|
const rateLimited = [];
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const task = pending[index];
|
|
733
|
-
const item = data.results[index];
|
|
734
|
-
if (item.success === false) {
|
|
735
|
-
if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
|
|
736
|
-
index: task.index,
|
|
737
|
-
success: false,
|
|
738
|
-
raw: item
|
|
739
|
-
})) {
|
|
740
|
-
rateLimited.push({ index: task.index, params: task.params, raw: item });
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
results[task.index] = batchItemFailure(
|
|
744
|
-
task.index,
|
|
745
|
-
item.error || item.message || "Signal to Copy failed",
|
|
746
|
-
item
|
|
747
|
-
);
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (!isPendingSignalResponse(item)) {
|
|
751
|
-
results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
if (task.params.waitForResult === false) {
|
|
755
|
-
results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const maxWaitMs = Math.max(1e3, task.params.maxWaitMs ?? 20 * 6e4);
|
|
759
|
-
if (Date.now() - startedAt >= maxWaitMs) {
|
|
760
|
-
results[task.index] = {
|
|
761
|
-
index: task.index,
|
|
762
|
-
success: false,
|
|
763
|
-
error: `Signal Copy ${item.signal_run_id || item.run_id || ""} did not complete within ${maxWaitMs}ms`
|
|
764
|
-
};
|
|
765
|
-
continue;
|
|
766
|
-
}
|
|
767
|
-
const signalRunId = item.signal_run_id || item.run_id;
|
|
768
|
-
if (!signalRunId) {
|
|
769
|
-
results[task.index] = { index: task.index, success: false, error: "Signal Copy is processing without a signal_run_id" };
|
|
654
|
+
const data = await this.client.post("api/v1/signal-to-copy", {
|
|
655
|
+
requests: requests.map(signalToCopyRequestBody),
|
|
656
|
+
concurrency
|
|
657
|
+
});
|
|
658
|
+
if (!Array.isArray(data.results) || data.results.length !== requests.length) {
|
|
659
|
+
throw new Error("Signal to Copy batch returned an invalid result count");
|
|
660
|
+
}
|
|
661
|
+
for (let index = 0; index < requests.length; index += 1) {
|
|
662
|
+
const item = data.results[index];
|
|
663
|
+
if (item.success === false) {
|
|
664
|
+
if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
|
|
665
|
+
index,
|
|
666
|
+
success: false,
|
|
667
|
+
raw: item
|
|
668
|
+
})) {
|
|
669
|
+
rateLimited.push({ index, params: requests[index], raw: item });
|
|
770
670
|
continue;
|
|
771
671
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
);
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
});
|
|
672
|
+
results[index] = batchItemFailure(index, item.error || item.message || "Signal to Copy failed", item);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
assertTerminalSignalResponse(item, "Signal to Copy");
|
|
677
|
+
results[index] = { index, success: true, data: normalizeSignalToCopyResult(item) };
|
|
678
|
+
} catch (error) {
|
|
679
|
+
results[index] = batchItemFailure(index, error instanceof Error ? error.message : String(error), item);
|
|
781
680
|
}
|
|
782
|
-
pending = nextPending;
|
|
783
|
-
if (pending.length > 0) await sleep2(nextDelayMs);
|
|
784
681
|
}
|
|
785
682
|
if (rateLimited.length > 0) {
|
|
786
683
|
await sleep2(rowRateLimitDelayMs(rateLimited.map((item) => ({
|
|
@@ -799,12 +696,12 @@ var Signaliz = class {
|
|
|
799
696
|
}
|
|
800
697
|
return results;
|
|
801
698
|
}
|
|
802
|
-
async runCoreBatchRound(path, tasks, options,
|
|
699
|
+
async runCoreBatchRound(path, tasks, options, rowRateLimitAttempt = 0) {
|
|
803
700
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
804
701
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
805
702
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
806
|
-
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } :
|
|
807
|
-
if (recoverableBatch && uniqueTasks.length > 25) {
|
|
703
|
+
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
|
|
704
|
+
if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
|
|
808
705
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
809
706
|
path,
|
|
810
707
|
uniqueTasks.map((task) => task.request),
|
|
@@ -876,7 +773,6 @@ var Signaliz = class {
|
|
|
876
773
|
path,
|
|
877
774
|
retryTasks,
|
|
878
775
|
options,
|
|
879
|
-
void 0,
|
|
880
776
|
rowRateLimitAttempt + 1
|
|
881
777
|
);
|
|
882
778
|
const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
|
|
@@ -951,9 +847,34 @@ function findEmailRequestBody(params) {
|
|
|
951
847
|
last_name: params.lastName,
|
|
952
848
|
linkedin_url: params.linkedinUrl,
|
|
953
849
|
company_name: params.companyName,
|
|
954
|
-
skip_cache: params.skipCache
|
|
850
|
+
skip_cache: params.skipCache,
|
|
851
|
+
dry_run: params.dryRun,
|
|
852
|
+
idempotency_key: params.idempotencyKey
|
|
955
853
|
});
|
|
956
854
|
}
|
|
855
|
+
function isCoreProductDryRun(data) {
|
|
856
|
+
return data.dry_run === true && data.status === "planned";
|
|
857
|
+
}
|
|
858
|
+
function normalizeCoreProductDryRunResult(data) {
|
|
859
|
+
const estimatedCredits = data.estimated_credits && typeof data.estimated_credits === "object" ? data.estimated_credits : { min: 0, max: 0 };
|
|
860
|
+
return {
|
|
861
|
+
success: true,
|
|
862
|
+
dryRun: true,
|
|
863
|
+
status: "planned",
|
|
864
|
+
capability: String(data.capability || ""),
|
|
865
|
+
inputCount: Number(data.input_count || 0),
|
|
866
|
+
recoveryReadCount: Number(data.recovery_read_count || 0),
|
|
867
|
+
plan: data.plan && typeof data.plan === "object" ? data.plan : {},
|
|
868
|
+
estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
|
|
869
|
+
estimatedCredits: {
|
|
870
|
+
min: Number(estimatedCredits.min || 0),
|
|
871
|
+
max: Number(estimatedCredits.max || 0)
|
|
872
|
+
},
|
|
873
|
+
creditsCharged: 0,
|
|
874
|
+
sideEffects: Array.isArray(data.side_effects) ? data.side_effects : [],
|
|
875
|
+
raw: data
|
|
876
|
+
};
|
|
877
|
+
}
|
|
957
878
|
function normalizeFindEmailResult(data) {
|
|
958
879
|
const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
|
|
959
880
|
const found = Boolean(email) && data.found !== false;
|
|
@@ -1015,39 +936,17 @@ function companySignalRequestBody(params) {
|
|
|
1015
936
|
lookback_days: params.lookbackDays,
|
|
1016
937
|
online,
|
|
1017
938
|
enable_deep_search: params.enableDeepSearch ?? online,
|
|
939
|
+
search_mode: params.searchMode ?? "balanced",
|
|
1018
940
|
include_summary: params.includeSummary,
|
|
1019
941
|
enable_outreach_intelligence: params.enableOutreachIntelligence,
|
|
1020
942
|
enable_predictive_intelligence: params.enablePredictiveIntelligence,
|
|
1021
943
|
skip_cache: params.skipCache,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
944
|
+
wait_for_result: true,
|
|
945
|
+
max_wait_ms: params.maxWaitMs,
|
|
946
|
+
dry_run: params.dryRun,
|
|
947
|
+
idempotency_key: params.idempotencyKey
|
|
1026
948
|
});
|
|
1027
949
|
}
|
|
1028
|
-
function companySignalRecoverableBatchOptions(companies, tasks) {
|
|
1029
|
-
if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
|
|
1030
|
-
return void 0;
|
|
1031
|
-
}
|
|
1032
|
-
const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
|
|
1033
|
-
if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
|
|
1034
|
-
return void 0;
|
|
1035
|
-
}
|
|
1036
|
-
const configKey = (request) => {
|
|
1037
|
-
const {
|
|
1038
|
-
company_domain: _companyDomain,
|
|
1039
|
-
company_name: _companyName,
|
|
1040
|
-
signal_run_id: _signalRunId,
|
|
1041
|
-
wait_for_result: _waitForResult,
|
|
1042
|
-
max_wait_ms: _maxWaitMs,
|
|
1043
|
-
...config
|
|
1044
|
-
} = request;
|
|
1045
|
-
return JSON.stringify(config);
|
|
1046
|
-
};
|
|
1047
|
-
const sharedConfig = configKey(tasks[0].request);
|
|
1048
|
-
if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
|
|
1049
|
-
return { label: "Company Signals", pageSize: 25, maxWaitMs };
|
|
1050
|
-
}
|
|
1051
950
|
function normalizeCompanySignalResult(params, data) {
|
|
1052
951
|
const rawSignals = data.signals ?? data.signal_feed ?? [];
|
|
1053
952
|
return {
|
|
@@ -1194,7 +1093,10 @@ function signalToCopyRequestBody(params) {
|
|
|
1194
1093
|
research_prompt: params.researchPrompt,
|
|
1195
1094
|
signal_run_id: params.signalRunId,
|
|
1196
1095
|
skip_cache: params.skipCache,
|
|
1197
|
-
enable_deep_search: params.enableDeepSearch
|
|
1096
|
+
enable_deep_search: params.enableDeepSearch,
|
|
1097
|
+
max_wait_ms: params.maxWaitMs,
|
|
1098
|
+
dry_run: params.dryRun,
|
|
1099
|
+
idempotency_key: params.idempotencyKey
|
|
1198
1100
|
});
|
|
1199
1101
|
}
|
|
1200
1102
|
function validateSignalToCopyParams(params) {
|
|
@@ -1239,11 +1141,10 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1239
1141
|
function isPendingSignalResponse(data) {
|
|
1240
1142
|
return ["triggered", "queued", "processing", "pending", "pending_version", "waiting_for_deploy"].includes(String(data.status || "").toLowerCase());
|
|
1241
1143
|
}
|
|
1242
|
-
function
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
return ["failed", "error", "cancelled", "canceled", "crashed", "timed_out"].includes(String(data.status || "").toLowerCase());
|
|
1144
|
+
function assertTerminalSignalResponse(data, capability) {
|
|
1145
|
+
if (isPendingSignalResponse(data) || data.job_id) {
|
|
1146
|
+
throw new Error(`${capability} violated the synchronous response contract.`);
|
|
1147
|
+
}
|
|
1247
1148
|
}
|
|
1248
1149
|
function signalPollDelayMs(data, override) {
|
|
1249
1150
|
if (override !== void 0) return Math.max(250, override);
|