@signaliz/sdk 1.0.30 → 1.0.32

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 CHANGED
@@ -77,9 +77,12 @@ resumes long-running research through the same `/company-signals` endpoint;
77
77
  callers never need an internal Trigger.dev status URL. The SDK follows the
78
78
  server's adaptive polling hints by default and still honors `pollIntervalMs`
79
79
  when a caller explicitly overrides the cadence.
80
- All four products are transported in 25-row REST chunks. Exact duplicate tasks
81
- are single-flighted without changing input order or result cardinality, and
82
- Signal to Copy also shares company research across copy recipients.
80
+ Exact duplicate tasks are single-flighted across each full batch without
81
+ changing input order or result cardinality. Find Email, Verify Email, and
82
+ Company Signal Enrichment use 25-row REST chunks. Signal to Copy batches larger
83
+ than 25 use one recoverable REST job when they can reuse available signal data;
84
+ fresh or deep-search requests keep the 25-row path. Signal to Copy also shares
85
+ company research across copy recipients.
83
86
 
84
87
  Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
85
88
  unless the caller explicitly disables them. Signal to Copy AI returns three
@@ -511,9 +511,34 @@ var Signaliz = class {
511
511
  async createSignalCopyBatch(requests, options) {
512
512
  validateBatchSize(requests);
513
513
  const startedAt = Date.now();
514
+ const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
515
+ request: signalToCopyRequestBody(params),
516
+ wait_for_result: params.waitForResult,
517
+ max_wait_ms: params.maxWaitMs,
518
+ poll_interval_ms: params.pollIntervalMs
519
+ }));
520
+ const uniqueRequests = deduplicated.uniqueItems;
521
+ const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
522
+ (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
523
+ );
524
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
525
+ const results = requests.map((_, index) => ({
526
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
527
+ index
528
+ }));
529
+ const succeeded = results.filter((item) => item.success).length;
530
+ return {
531
+ total: requests.length,
532
+ succeeded,
533
+ failed: requests.length - succeeded,
534
+ durationMs: Date.now() - startedAt,
535
+ results
536
+ };
537
+ }
538
+ async runSignalCopyBatchChunks(uniqueRequests, options) {
514
539
  const chunks = [];
515
- for (let offset = 0; offset < requests.length; offset += 25) {
516
- chunks.push({ offset, requests: requests.slice(offset, offset + 25) });
540
+ for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
541
+ chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
517
542
  }
518
543
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
519
544
  const chunkBatch = await runBatch(
@@ -521,12 +546,12 @@ var Signaliz = class {
521
546
  (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
522
547
  { concurrency: chunkConcurrency }
523
548
  );
524
- const results = new Array(requests.length);
549
+ const uniqueResults = new Array(uniqueRequests.length);
525
550
  for (const chunkResult of chunkBatch.results) {
526
551
  const chunk = chunks[chunkResult.index];
527
552
  if (!chunkResult.success || !chunkResult.data) {
528
553
  for (let index = 0; index < chunk.requests.length; index += 1) {
529
- results[chunk.offset + index] = {
554
+ uniqueResults[chunk.offset + index] = {
530
555
  index: chunk.offset + index,
531
556
  success: false,
532
557
  error: chunkResult.error || "Signal to Copy batch request failed"
@@ -535,17 +560,58 @@ var Signaliz = class {
535
560
  continue;
536
561
  }
537
562
  for (const item of chunkResult.data) {
538
- results[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
563
+ uniqueResults[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
539
564
  }
540
565
  }
541
- const succeeded = results.filter((item) => item.success).length;
542
- return {
543
- total: requests.length,
544
- succeeded,
545
- failed: requests.length - succeeded,
546
- durationMs: Date.now() - startedAt,
547
- results
548
- };
566
+ return uniqueResults;
567
+ }
568
+ async runAvailableSignalCopyBatchJob(requests) {
569
+ const startedAt = Date.now();
570
+ const maxWaitMs = 20 * 6e4;
571
+ let status = await this.client.post("api/v1/signal-to-copy", {
572
+ requests: requests.map(signalToCopyRequestBody)
573
+ });
574
+ const jobId = String(status.job_id || "").trim();
575
+ if (!jobId) throw new Error("Signal to Copy batch did not return a recoverable job_id");
576
+ while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
577
+ if (Date.now() - startedAt >= maxWaitMs) {
578
+ throw new Error(`Signal to Copy batch ${jobId} did not complete within ${maxWaitMs}ms`);
579
+ }
580
+ const retryAfterMs = Number(status.next_poll_after_seconds || 2) * 1e3;
581
+ await sleep2(Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt)));
582
+ status = await this.client.post("api/v1/signal-to-copy", {
583
+ job_id: jobId,
584
+ page: 1,
585
+ page_size: 500
586
+ });
587
+ }
588
+ const totalPages = Math.max(1, Number(status.total_pages || 1));
589
+ const pages = [Array.isArray(status.results) ? status.results : []];
590
+ if (totalPages > 1) {
591
+ const remainingPages = await Promise.all(Array.from(
592
+ { length: totalPages - 1 },
593
+ (_, index) => this.client.post("api/v1/signal-to-copy", {
594
+ job_id: jobId,
595
+ page: index + 2,
596
+ page_size: 500
597
+ })
598
+ ));
599
+ for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
600
+ }
601
+ const rows = pages.flat();
602
+ if (rows.length !== requests.length) {
603
+ throw new Error(`Signal to Copy batch returned ${rows.length} results for ${requests.length} requests`);
604
+ }
605
+ const results = new Array(requests.length);
606
+ for (const row of rows) {
607
+ const index = Number(row.index);
608
+ if (!Number.isInteger(index) || index < 0 || index >= requests.length) {
609
+ throw new Error("Signal to Copy batch returned an invalid result index");
610
+ }
611
+ results[index] = row.success === false ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
612
+ }
613
+ if (results.some((item) => !item)) throw new Error("Signal to Copy batch returned incomplete indexed results");
614
+ return results;
549
615
  }
550
616
  async runSignalCopyBatchChunk(requests, concurrency) {
551
617
  const startedAt = Date.now();
@@ -611,9 +677,11 @@ var Signaliz = class {
611
677
  }
612
678
  async runCoreBatchRound(path, tasks, options) {
613
679
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
680
+ const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
681
+ const uniqueTasks = deduplicated.uniqueItems;
614
682
  const chunks = [];
615
- for (let offset = 0; offset < tasks.length; offset += 25) {
616
- chunks.push({ offset, tasks: tasks.slice(offset, offset + 25) });
683
+ for (let offset = 0; offset < uniqueTasks.length; offset += 25) {
684
+ chunks.push({ offset, tasks: uniqueTasks.slice(offset, offset + 25) });
617
685
  }
618
686
  const chunkBatch = await runBatch(chunks, async (chunk) => {
619
687
  const data = await this.client.post(path, {
@@ -628,12 +696,12 @@ var Signaliz = class {
628
696
  }
629
697
  return data.results;
630
698
  }, { concurrency: chunkConcurrency });
631
- const results = new Array(tasks.length);
699
+ const uniqueResults = new Array(uniqueTasks.length);
632
700
  for (const chunkResult of chunkBatch.results) {
633
701
  const chunk = chunks[chunkResult.index];
634
702
  if (!chunkResult.success || !chunkResult.data) {
635
703
  for (let index = 0; index < chunk.tasks.length; index += 1) {
636
- results[chunk.offset + index] = {
704
+ uniqueResults[chunk.offset + index] = {
637
705
  index: chunk.tasks[index].index,
638
706
  success: false,
639
707
  error: chunkResult.error || `${path} batch request failed`
@@ -643,7 +711,7 @@ var Signaliz = class {
643
711
  }
644
712
  for (let index = 0; index < chunkResult.data.length; index += 1) {
645
713
  const raw = chunkResult.data[index];
646
- results[chunk.offset + index] = {
714
+ uniqueResults[chunk.offset + index] = {
647
715
  index: chunk.tasks[index].index,
648
716
  success: raw.success !== false,
649
717
  raw,
@@ -651,7 +719,10 @@ var Signaliz = class {
651
719
  };
652
720
  }
653
721
  }
654
- return results;
722
+ return tasks.map((task, index) => ({
723
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
724
+ index: task.index
725
+ }));
655
726
  }
656
727
  async listTools() {
657
728
  const result = await this.client.mcp("tools/list");
@@ -808,6 +879,22 @@ function batchConcurrencyPlan(options) {
808
879
  chunkConcurrency: Math.max(1, Math.floor(concurrency / serverConcurrency))
809
880
  };
810
881
  }
882
+ function dedupeExactBatchItems(items, keyForItem) {
883
+ const uniqueItems = [];
884
+ const sourceIndexByInput = [];
885
+ const uniqueIndexByKey = /* @__PURE__ */ new Map();
886
+ for (const item of items) {
887
+ const key = keyForItem(item);
888
+ let uniqueIndex = uniqueIndexByKey.get(key);
889
+ if (uniqueIndex === void 0) {
890
+ uniqueIndex = uniqueItems.length;
891
+ uniqueIndexByKey.set(key, uniqueIndex);
892
+ uniqueItems.push(item);
893
+ }
894
+ sourceIndexByInput.push(uniqueIndex);
895
+ }
896
+ return { uniqueItems, sourceIndexByInput };
897
+ }
811
898
  function finalizeCoreBatch(total, startedAt, round, normalize) {
812
899
  const results = new Array(total);
813
900
  for (const item of round) {
package/dist/index.d.mts CHANGED
@@ -203,6 +203,8 @@ declare class Signaliz {
203
203
  enrichCompanies(companies: CompanySignalEnrichmentParams[], options?: BatchOptions): Promise<BatchResult<CompanySignalEnrichmentResult>>;
204
204
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
205
205
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
206
+ private runSignalCopyBatchChunks;
207
+ private runAvailableSignalCopyBatchJob;
206
208
  private runSignalCopyBatchChunk;
207
209
  private runCoreBatchRound;
208
210
  listTools(): Promise<MCPToolInfo[]>;
package/dist/index.d.ts CHANGED
@@ -203,6 +203,8 @@ declare class Signaliz {
203
203
  enrichCompanies(companies: CompanySignalEnrichmentParams[], options?: BatchOptions): Promise<BatchResult<CompanySignalEnrichmentResult>>;
204
204
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
205
205
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
206
+ private runSignalCopyBatchChunks;
207
+ private runAvailableSignalCopyBatchJob;
206
208
  private runSignalCopyBatchChunk;
207
209
  private runCoreBatchRound;
208
210
  listTools(): Promise<MCPToolInfo[]>;
package/dist/index.js CHANGED
@@ -538,9 +538,34 @@ var Signaliz = class {
538
538
  async createSignalCopyBatch(requests, options) {
539
539
  validateBatchSize(requests);
540
540
  const startedAt = Date.now();
541
+ const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
542
+ request: signalToCopyRequestBody(params),
543
+ wait_for_result: params.waitForResult,
544
+ max_wait_ms: params.maxWaitMs,
545
+ poll_interval_ms: params.pollIntervalMs
546
+ }));
547
+ const uniqueRequests = deduplicated.uniqueItems;
548
+ const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
549
+ (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
550
+ );
551
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
552
+ const results = requests.map((_, index) => ({
553
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
554
+ index
555
+ }));
556
+ const succeeded = results.filter((item) => item.success).length;
557
+ return {
558
+ total: requests.length,
559
+ succeeded,
560
+ failed: requests.length - succeeded,
561
+ durationMs: Date.now() - startedAt,
562
+ results
563
+ };
564
+ }
565
+ async runSignalCopyBatchChunks(uniqueRequests, options) {
541
566
  const chunks = [];
542
- for (let offset = 0; offset < requests.length; offset += 25) {
543
- chunks.push({ offset, requests: requests.slice(offset, offset + 25) });
567
+ for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
568
+ chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
544
569
  }
545
570
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
546
571
  const chunkBatch = await runBatch(
@@ -548,12 +573,12 @@ var Signaliz = class {
548
573
  (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
549
574
  { concurrency: chunkConcurrency }
550
575
  );
551
- const results = new Array(requests.length);
576
+ const uniqueResults = new Array(uniqueRequests.length);
552
577
  for (const chunkResult of chunkBatch.results) {
553
578
  const chunk = chunks[chunkResult.index];
554
579
  if (!chunkResult.success || !chunkResult.data) {
555
580
  for (let index = 0; index < chunk.requests.length; index += 1) {
556
- results[chunk.offset + index] = {
581
+ uniqueResults[chunk.offset + index] = {
557
582
  index: chunk.offset + index,
558
583
  success: false,
559
584
  error: chunkResult.error || "Signal to Copy batch request failed"
@@ -562,17 +587,58 @@ var Signaliz = class {
562
587
  continue;
563
588
  }
564
589
  for (const item of chunkResult.data) {
565
- results[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
590
+ uniqueResults[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
566
591
  }
567
592
  }
568
- const succeeded = results.filter((item) => item.success).length;
569
- return {
570
- total: requests.length,
571
- succeeded,
572
- failed: requests.length - succeeded,
573
- durationMs: Date.now() - startedAt,
574
- results
575
- };
593
+ return uniqueResults;
594
+ }
595
+ async runAvailableSignalCopyBatchJob(requests) {
596
+ const startedAt = Date.now();
597
+ const maxWaitMs = 20 * 6e4;
598
+ let status = await this.client.post("api/v1/signal-to-copy", {
599
+ requests: requests.map(signalToCopyRequestBody)
600
+ });
601
+ const jobId = String(status.job_id || "").trim();
602
+ if (!jobId) throw new Error("Signal to Copy batch did not return a recoverable job_id");
603
+ while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
604
+ if (Date.now() - startedAt >= maxWaitMs) {
605
+ throw new Error(`Signal to Copy batch ${jobId} did not complete within ${maxWaitMs}ms`);
606
+ }
607
+ const retryAfterMs = Number(status.next_poll_after_seconds || 2) * 1e3;
608
+ await sleep2(Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt)));
609
+ status = await this.client.post("api/v1/signal-to-copy", {
610
+ job_id: jobId,
611
+ page: 1,
612
+ page_size: 500
613
+ });
614
+ }
615
+ const totalPages = Math.max(1, Number(status.total_pages || 1));
616
+ const pages = [Array.isArray(status.results) ? status.results : []];
617
+ if (totalPages > 1) {
618
+ const remainingPages = await Promise.all(Array.from(
619
+ { length: totalPages - 1 },
620
+ (_, index) => this.client.post("api/v1/signal-to-copy", {
621
+ job_id: jobId,
622
+ page: index + 2,
623
+ page_size: 500
624
+ })
625
+ ));
626
+ for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
627
+ }
628
+ const rows = pages.flat();
629
+ if (rows.length !== requests.length) {
630
+ throw new Error(`Signal to Copy batch returned ${rows.length} results for ${requests.length} requests`);
631
+ }
632
+ const results = new Array(requests.length);
633
+ for (const row of rows) {
634
+ const index = Number(row.index);
635
+ if (!Number.isInteger(index) || index < 0 || index >= requests.length) {
636
+ throw new Error("Signal to Copy batch returned an invalid result index");
637
+ }
638
+ results[index] = row.success === false ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
639
+ }
640
+ if (results.some((item) => !item)) throw new Error("Signal to Copy batch returned incomplete indexed results");
641
+ return results;
576
642
  }
577
643
  async runSignalCopyBatchChunk(requests, concurrency) {
578
644
  const startedAt = Date.now();
@@ -638,9 +704,11 @@ var Signaliz = class {
638
704
  }
639
705
  async runCoreBatchRound(path, tasks, options) {
640
706
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
707
+ const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
708
+ const uniqueTasks = deduplicated.uniqueItems;
641
709
  const chunks = [];
642
- for (let offset = 0; offset < tasks.length; offset += 25) {
643
- chunks.push({ offset, tasks: tasks.slice(offset, offset + 25) });
710
+ for (let offset = 0; offset < uniqueTasks.length; offset += 25) {
711
+ chunks.push({ offset, tasks: uniqueTasks.slice(offset, offset + 25) });
644
712
  }
645
713
  const chunkBatch = await runBatch(chunks, async (chunk) => {
646
714
  const data = await this.client.post(path, {
@@ -655,12 +723,12 @@ var Signaliz = class {
655
723
  }
656
724
  return data.results;
657
725
  }, { concurrency: chunkConcurrency });
658
- const results = new Array(tasks.length);
726
+ const uniqueResults = new Array(uniqueTasks.length);
659
727
  for (const chunkResult of chunkBatch.results) {
660
728
  const chunk = chunks[chunkResult.index];
661
729
  if (!chunkResult.success || !chunkResult.data) {
662
730
  for (let index = 0; index < chunk.tasks.length; index += 1) {
663
- results[chunk.offset + index] = {
731
+ uniqueResults[chunk.offset + index] = {
664
732
  index: chunk.tasks[index].index,
665
733
  success: false,
666
734
  error: chunkResult.error || `${path} batch request failed`
@@ -670,7 +738,7 @@ var Signaliz = class {
670
738
  }
671
739
  for (let index = 0; index < chunkResult.data.length; index += 1) {
672
740
  const raw = chunkResult.data[index];
673
- results[chunk.offset + index] = {
741
+ uniqueResults[chunk.offset + index] = {
674
742
  index: chunk.tasks[index].index,
675
743
  success: raw.success !== false,
676
744
  raw,
@@ -678,7 +746,10 @@ var Signaliz = class {
678
746
  };
679
747
  }
680
748
  }
681
- return results;
749
+ return tasks.map((task, index) => ({
750
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
751
+ index: task.index
752
+ }));
682
753
  }
683
754
  async listTools() {
684
755
  const result = await this.client.mcp("tools/list");
@@ -835,6 +906,22 @@ function batchConcurrencyPlan(options) {
835
906
  chunkConcurrency: Math.max(1, Math.floor(concurrency / serverConcurrency))
836
907
  };
837
908
  }
909
+ function dedupeExactBatchItems(items, keyForItem) {
910
+ const uniqueItems = [];
911
+ const sourceIndexByInput = [];
912
+ const uniqueIndexByKey = /* @__PURE__ */ new Map();
913
+ for (const item of items) {
914
+ const key = keyForItem(item);
915
+ let uniqueIndex = uniqueIndexByKey.get(key);
916
+ if (uniqueIndex === void 0) {
917
+ uniqueIndex = uniqueItems.length;
918
+ uniqueIndexByKey.set(key, uniqueIndex);
919
+ uniqueItems.push(item);
920
+ }
921
+ sourceIndexByInput.push(uniqueIndex);
922
+ }
923
+ return { uniqueItems, sourceIndexByInput };
924
+ }
838
925
  function finalizeCoreBatch(total, startedAt, round, normalize) {
839
926
  const results = new Array(total);
840
927
  for (const item of round) {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-INM326KO.mjs";
4
+ } from "./chunk-JO4MYKSK.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -542,9 +542,34 @@ var Signaliz = class {
542
542
  async createSignalCopyBatch(requests, options) {
543
543
  validateBatchSize(requests);
544
544
  const startedAt = Date.now();
545
+ const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
546
+ request: signalToCopyRequestBody(params),
547
+ wait_for_result: params.waitForResult,
548
+ max_wait_ms: params.maxWaitMs,
549
+ poll_interval_ms: params.pollIntervalMs
550
+ }));
551
+ const uniqueRequests = deduplicated.uniqueItems;
552
+ const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
553
+ (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
554
+ );
555
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
556
+ const results = requests.map((_, index) => ({
557
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
558
+ index
559
+ }));
560
+ const succeeded = results.filter((item) => item.success).length;
561
+ return {
562
+ total: requests.length,
563
+ succeeded,
564
+ failed: requests.length - succeeded,
565
+ durationMs: Date.now() - startedAt,
566
+ results
567
+ };
568
+ }
569
+ async runSignalCopyBatchChunks(uniqueRequests, options) {
545
570
  const chunks = [];
546
- for (let offset = 0; offset < requests.length; offset += 25) {
547
- chunks.push({ offset, requests: requests.slice(offset, offset + 25) });
571
+ for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
572
+ chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
548
573
  }
549
574
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
550
575
  const chunkBatch = await runBatch(
@@ -552,12 +577,12 @@ var Signaliz = class {
552
577
  (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
553
578
  { concurrency: chunkConcurrency }
554
579
  );
555
- const results = new Array(requests.length);
580
+ const uniqueResults = new Array(uniqueRequests.length);
556
581
  for (const chunkResult of chunkBatch.results) {
557
582
  const chunk = chunks[chunkResult.index];
558
583
  if (!chunkResult.success || !chunkResult.data) {
559
584
  for (let index = 0; index < chunk.requests.length; index += 1) {
560
- results[chunk.offset + index] = {
585
+ uniqueResults[chunk.offset + index] = {
561
586
  index: chunk.offset + index,
562
587
  success: false,
563
588
  error: chunkResult.error || "Signal to Copy batch request failed"
@@ -566,17 +591,58 @@ var Signaliz = class {
566
591
  continue;
567
592
  }
568
593
  for (const item of chunkResult.data) {
569
- results[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
594
+ uniqueResults[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
570
595
  }
571
596
  }
572
- const succeeded = results.filter((item) => item.success).length;
573
- return {
574
- total: requests.length,
575
- succeeded,
576
- failed: requests.length - succeeded,
577
- durationMs: Date.now() - startedAt,
578
- results
579
- };
597
+ return uniqueResults;
598
+ }
599
+ async runAvailableSignalCopyBatchJob(requests) {
600
+ const startedAt = Date.now();
601
+ const maxWaitMs = 20 * 6e4;
602
+ let status = await this.client.post("api/v1/signal-to-copy", {
603
+ requests: requests.map(signalToCopyRequestBody)
604
+ });
605
+ const jobId = String(status.job_id || "").trim();
606
+ if (!jobId) throw new Error("Signal to Copy batch did not return a recoverable job_id");
607
+ while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
608
+ if (Date.now() - startedAt >= maxWaitMs) {
609
+ throw new Error(`Signal to Copy batch ${jobId} did not complete within ${maxWaitMs}ms`);
610
+ }
611
+ const retryAfterMs = Number(status.next_poll_after_seconds || 2) * 1e3;
612
+ await sleep2(Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt)));
613
+ status = await this.client.post("api/v1/signal-to-copy", {
614
+ job_id: jobId,
615
+ page: 1,
616
+ page_size: 500
617
+ });
618
+ }
619
+ const totalPages = Math.max(1, Number(status.total_pages || 1));
620
+ const pages = [Array.isArray(status.results) ? status.results : []];
621
+ if (totalPages > 1) {
622
+ const remainingPages = await Promise.all(Array.from(
623
+ { length: totalPages - 1 },
624
+ (_, index) => this.client.post("api/v1/signal-to-copy", {
625
+ job_id: jobId,
626
+ page: index + 2,
627
+ page_size: 500
628
+ })
629
+ ));
630
+ for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
631
+ }
632
+ const rows = pages.flat();
633
+ if (rows.length !== requests.length) {
634
+ throw new Error(`Signal to Copy batch returned ${rows.length} results for ${requests.length} requests`);
635
+ }
636
+ const results = new Array(requests.length);
637
+ for (const row of rows) {
638
+ const index = Number(row.index);
639
+ if (!Number.isInteger(index) || index < 0 || index >= requests.length) {
640
+ throw new Error("Signal to Copy batch returned an invalid result index");
641
+ }
642
+ results[index] = row.success === false ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
643
+ }
644
+ if (results.some((item) => !item)) throw new Error("Signal to Copy batch returned incomplete indexed results");
645
+ return results;
580
646
  }
581
647
  async runSignalCopyBatchChunk(requests, concurrency) {
582
648
  const startedAt = Date.now();
@@ -642,9 +708,11 @@ var Signaliz = class {
642
708
  }
643
709
  async runCoreBatchRound(path2, tasks, options) {
644
710
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
711
+ const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
712
+ const uniqueTasks = deduplicated.uniqueItems;
645
713
  const chunks = [];
646
- for (let offset = 0; offset < tasks.length; offset += 25) {
647
- chunks.push({ offset, tasks: tasks.slice(offset, offset + 25) });
714
+ for (let offset = 0; offset < uniqueTasks.length; offset += 25) {
715
+ chunks.push({ offset, tasks: uniqueTasks.slice(offset, offset + 25) });
648
716
  }
649
717
  const chunkBatch = await runBatch(chunks, async (chunk) => {
650
718
  const data = await this.client.post(path2, {
@@ -659,12 +727,12 @@ var Signaliz = class {
659
727
  }
660
728
  return data.results;
661
729
  }, { concurrency: chunkConcurrency });
662
- const results = new Array(tasks.length);
730
+ const uniqueResults = new Array(uniqueTasks.length);
663
731
  for (const chunkResult of chunkBatch.results) {
664
732
  const chunk = chunks[chunkResult.index];
665
733
  if (!chunkResult.success || !chunkResult.data) {
666
734
  for (let index = 0; index < chunk.tasks.length; index += 1) {
667
- results[chunk.offset + index] = {
735
+ uniqueResults[chunk.offset + index] = {
668
736
  index: chunk.tasks[index].index,
669
737
  success: false,
670
738
  error: chunkResult.error || `${path2} batch request failed`
@@ -674,7 +742,7 @@ var Signaliz = class {
674
742
  }
675
743
  for (let index = 0; index < chunkResult.data.length; index += 1) {
676
744
  const raw = chunkResult.data[index];
677
- results[chunk.offset + index] = {
745
+ uniqueResults[chunk.offset + index] = {
678
746
  index: chunk.tasks[index].index,
679
747
  success: raw.success !== false,
680
748
  raw,
@@ -682,7 +750,10 @@ var Signaliz = class {
682
750
  };
683
751
  }
684
752
  }
685
- return results;
753
+ return tasks.map((task, index) => ({
754
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
755
+ index: task.index
756
+ }));
686
757
  }
687
758
  async listTools() {
688
759
  const result = await this.client.mcp("tools/list");
@@ -839,6 +910,22 @@ function batchConcurrencyPlan(options) {
839
910
  chunkConcurrency: Math.max(1, Math.floor(concurrency / serverConcurrency))
840
911
  };
841
912
  }
913
+ function dedupeExactBatchItems(items, keyForItem) {
914
+ const uniqueItems = [];
915
+ const sourceIndexByInput = [];
916
+ const uniqueIndexByKey = /* @__PURE__ */ new Map();
917
+ for (const item of items) {
918
+ const key = keyForItem(item);
919
+ let uniqueIndex = uniqueIndexByKey.get(key);
920
+ if (uniqueIndex === void 0) {
921
+ uniqueIndex = uniqueItems.length;
922
+ uniqueIndexByKey.set(key, uniqueIndex);
923
+ uniqueItems.push(item);
924
+ }
925
+ sourceIndexByInput.push(uniqueIndex);
926
+ }
927
+ return { uniqueItems, sourceIndexByInput };
928
+ }
842
929
  function finalizeCoreBatch(total, startedAt, round, normalize) {
843
930
  const results = new Array(total);
844
931
  for (const item of round) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-INM326KO.mjs";
4
+ } from "./chunk-JO4MYKSK.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Signaliz SDK for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",