@signaliz/sdk 1.0.55 → 1.0.56

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
@@ -29,6 +29,8 @@ const plan = await signaliz.findEmail({
29
29
  dryRun: true,
30
30
  });
31
31
  console.log(plan.estimatedCredits.max, plan.creditsCharged); // 2, 0
32
+ // Batch plans price only semantic unique work and expose duplicate suppression.
33
+ console.log(plan.inputCount, plan.uniqueFreshInputCount, plan.inputDuplicatesSuppressed);
32
34
 
33
35
  const found = await signaliz.findEmail({
34
36
  companyDomain: 'example.com',
@@ -111,8 +113,11 @@ when the REST API supplies them, including after automatic row retries are
111
113
  exhausted.
112
114
 
113
115
  For an ambiguous single request or batch submission failure, retry with the
114
- same `idempotencyKey` to recover the original request or durable job without duplicate
115
- work.
116
+ same `idempotencyKey` to recover the original request, durable job, or provider
117
+ run without duplicate work or spend. The key is portable across REST API, MCP,
118
+ SDK, and CLI calls for the same workspace and logical input. Chunked batches
119
+ derive a stable key from each row's original input index, including after exact
120
+ duplicate compaction and rate-limit retries.
116
121
 
117
122
  Each batch accepts up to 5,000 items. For the four row-oriented core products, batches larger
118
123
  than 25 use one recoverable REST job; the SDK waits for completion and retrieves
@@ -536,7 +536,11 @@ var Signaliz = class {
536
536
  request: signalToCopyRequestBody(params)
537
537
  }));
538
538
  const uniqueRequests = deduplicated.uniqueItems;
539
- const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
539
+ const uniqueInputIndices = new Array(uniqueRequests.length);
540
+ deduplicated.sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
541
+ if (uniqueInputIndices[uniqueIndex] === void 0) uniqueInputIndices[uniqueIndex] = inputIndex;
542
+ });
543
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options, uniqueInputIndices);
540
544
  const results = requests.map((_, index) => ({
541
545
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
542
546
  index
@@ -562,15 +566,25 @@ var Signaliz = class {
562
566
  }
563
567
  return normalizeCoreProductDryRunResult(data);
564
568
  }
565
- async runSignalCopyBatchChunks(uniqueRequests, options) {
569
+ async runSignalCopyBatchChunks(uniqueRequests, options, idempotencyItemIndices = uniqueRequests.map((_, index) => index)) {
566
570
  const chunks = [];
567
571
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
568
- chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
572
+ chunks.push({
573
+ offset,
574
+ requests: uniqueRequests.slice(offset, offset + 25),
575
+ idempotencyItemIndices: idempotencyItemIndices.slice(offset, offset + 25)
576
+ });
569
577
  }
570
578
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options, 25, 25);
571
579
  const chunkBatch = await runBatch(
572
580
  chunks,
573
- (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
581
+ (chunk) => this.runSignalCopyBatchChunk(
582
+ chunk.requests,
583
+ serverConcurrency,
584
+ 0,
585
+ options?.idempotencyKey,
586
+ chunk.idempotencyItemIndices
587
+ ),
574
588
  { concurrency: chunkConcurrency }
575
589
  );
576
590
  const uniqueResults = new Array(uniqueRequests.length);
@@ -592,14 +606,15 @@ var Signaliz = class {
592
606
  }
593
607
  return uniqueResults;
594
608
  }
595
- async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
609
+ async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
596
610
  const startedAt = Date.now();
597
611
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
598
612
  let submission;
599
613
  try {
600
614
  submission = await this.client.post(path, {
601
615
  requests,
602
- idempotency_key: submissionIdempotencyKey
616
+ idempotency_key: submissionIdempotencyKey,
617
+ batch_item_indices: idempotencyItemIndices
603
618
  });
604
619
  } catch (error) {
605
620
  if (error instanceof SignalizError && !error.isRetryable) throw error;
@@ -662,12 +677,14 @@ var Signaliz = class {
662
677
  if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
663
678
  return rows;
664
679
  }
665
- async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
680
+ async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
666
681
  const results = new Array(requests.length);
667
682
  const rateLimited = [];
668
683
  const data = await this.client.post("api/v1/signal-to-copy", {
669
684
  requests: requests.map(signalToCopyRequestBody),
670
- concurrency
685
+ concurrency,
686
+ idempotency_key: idempotencyKey,
687
+ batch_item_indices: idempotencyItemIndices
671
688
  });
672
689
  if (!Array.isArray(data.results) || data.results.length !== requests.length) {
673
690
  throw new Error("Signal to Copy batch returned an invalid result count");
@@ -702,7 +719,9 @@ var Signaliz = class {
702
719
  const retried = await this.runSignalCopyBatchChunk(
703
720
  rateLimited.map((item) => item.params),
704
721
  concurrency,
705
- rowRateLimitAttempt + 1
722
+ rowRateLimitAttempt + 1,
723
+ idempotencyKey,
724
+ rateLimited.map((item) => idempotencyItemIndices[item.index])
706
725
  );
707
726
  for (let index = 0; index < rateLimited.length; index += 1) {
708
727
  results[rateLimited[index].index] = { ...retried[index], index: rateLimited[index].index };
@@ -722,7 +741,8 @@ var Signaliz = class {
722
741
  recoverableBatch.label,
723
742
  recoverableBatch.pageSize,
724
743
  recoverableBatch.maxWaitMs,
725
- options?.idempotencyKey
744
+ options?.idempotencyKey,
745
+ uniqueTasks.map((task) => task.index)
726
746
  );
727
747
  const uniqueResults2 = new Array(uniqueTasks.length);
728
748
  for (const raw of rows) {
@@ -746,7 +766,9 @@ var Signaliz = class {
746
766
  const chunkBatch = await runBatch(chunks, async (chunk) => {
747
767
  const data = await this.client.post(path, {
748
768
  requests: chunk.tasks.map((task) => task.request),
749
- concurrency: serverConcurrency
769
+ concurrency: serverConcurrency,
770
+ idempotency_key: options?.idempotencyKey,
771
+ batch_item_indices: chunk.tasks.map((task) => task.index)
750
772
  });
751
773
  if (!Array.isArray(data.results) || data.results.length !== chunk.tasks.length) {
752
774
  throw new Error(`${path} batch returned an invalid result count`);
@@ -881,7 +903,11 @@ function normalizeCoreProductDryRunResult(data) {
881
903
  status: "planned",
882
904
  capability: String(data.capability || ""),
883
905
  inputCount: Number(data.input_count || 0),
906
+ freshInputCount: Number(data.fresh_input_count ?? data.input_count ?? 0),
907
+ uniqueFreshInputCount: Number(data.unique_fresh_input_count ?? data.fresh_input_count ?? data.input_count ?? 0),
908
+ inputDuplicatesSuppressed: Number(data.input_duplicates_suppressed || 0),
884
909
  recoveryReadCount: Number(data.recovery_read_count || 0),
910
+ uniqueRecoveryReadCount: Number(data.unique_recovery_read_count ?? data.recovery_read_count ?? 0),
885
911
  plan: data.plan && typeof data.plan === "object" ? data.plan : {},
886
912
  estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
887
913
  estimatedCredits: {
@@ -896,12 +922,12 @@ function normalizeCoreProductDryRunResult(data) {
896
922
  function normalizeFindEmailResult(data) {
897
923
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
898
924
  const found = Boolean(email) && data.found !== false;
899
- const verificationStatus = data.verification_status ?? data.status ?? "unknown";
925
+ const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
900
926
  const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
901
927
  const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
902
928
  const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
903
929
  const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
904
- const verified = !explicitlyNotSendSafe && (data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
930
+ const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
905
931
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
906
932
  return {
907
933
  success: data.success !== false && found,
@@ -924,6 +950,9 @@ function normalizeFindEmailResult(data) {
924
950
  billingMetadata: data.billing_metadata,
925
951
  resultReturnedFrom: data.result_returned_from,
926
952
  cacheHit: data.cache_hit,
953
+ negativeCacheHit: data.negative_cache_hit,
954
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
955
+ cacheTier: data.cache_tier,
927
956
  freshEnrichmentUsed: data.fresh_enrichment_used,
928
957
  liveProviderCalled: data.live_provider_called,
929
958
  openrouterCalled: data.openrouter_called,
@@ -945,6 +974,7 @@ function normalizeVerifyEmailResult(email, data) {
945
974
  nextPollAfterSeconds: data.next_poll_after_seconds,
946
975
  isValid: data.is_valid ?? data.valid ?? verified,
947
976
  isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
977
+ isMalformed: data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed",
948
978
  isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
949
979
  verifiedForSending: data.verified_for_sending === true,
950
980
  verificationVerdict: data.verification_verdict ?? data.verification_status ?? "unknown",
@@ -956,6 +986,9 @@ function normalizeVerifyEmailResult(email, data) {
956
986
  billingMetadata: data.billing_metadata,
957
987
  resultReturnedFrom: data.result_returned_from,
958
988
  cacheHit: data.cache_hit,
989
+ negativeCacheHit: data.negative_cache_hit,
990
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
991
+ cacheTier: data.cache_tier,
959
992
  freshEnrichmentUsed: data.fresh_enrichment_used,
960
993
  liveProviderCalled: data.live_provider_called,
961
994
  openrouterCalled: data.openrouter_called,
@@ -1025,6 +1058,14 @@ function normalizeCompanySignalResult(params, data) {
1025
1058
  evidenceCountTotal: data.evidence_count_total,
1026
1059
  evidenceSourcesOmitted: data.evidence_sources_omitted,
1027
1060
  evidenceScope: data.evidence_scope,
1061
+ billingMetadata: data.billing_metadata,
1062
+ resultReturnedFrom: data.result_returned_from,
1063
+ cacheHit: data.cache_hit,
1064
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1065
+ creditsUsed: data.credits_used,
1066
+ liveProviderCalled: data.live_provider_called,
1067
+ aiProviderCalled: data.ai_provider_called,
1068
+ byoAiUsed: data.byo_ai_used,
1028
1069
  raw: data
1029
1070
  };
1030
1071
  }
@@ -1302,6 +1343,14 @@ function normalizeSignalToCopyResult(data) {
1302
1343
  empty: data.empty,
1303
1344
  emptyReason: data.empty_reason,
1304
1345
  researchMetadata: data.research_metadata,
1346
+ billingMetadata: data.billing_metadata,
1347
+ resultReturnedFrom: data.result_returned_from,
1348
+ cacheHit: data.cache_hit,
1349
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1350
+ creditsUsed: data.credits_used,
1351
+ liveProviderCalled: data.live_provider_called,
1352
+ aiProviderCalled: data.ai_provider_called,
1353
+ byoAiUsed: data.byo_ai_used,
1305
1354
  variations: (data.variations ?? []).map((variation) => ({
1306
1355
  style: variation.style,
1307
1356
  subjectLine: variation.subject_line ?? "",
package/dist/index.d.mts CHANGED
@@ -9,7 +9,7 @@ interface SignalizConfig {
9
9
  interface BatchOptions {
10
10
  /** Maximum simultaneous API requests. Defaults to 10; allowed range is 1-50. */
11
11
  concurrency?: number;
12
- /** Stable key for safely retrying an ambiguous recoverable batch submission. */
12
+ /** Stable cross-surface key. Retries reuse the same provider work; chunked batches derive one collision-free key per original row. */
13
13
  idempotencyKey?: string;
14
14
  /** Return exact repeated successes as duplicateOf references. Defaults to false. */
15
15
  compactDuplicates?: boolean;
@@ -51,7 +51,11 @@ interface CoreProductDryRunResult {
51
51
  status: 'planned';
52
52
  capability: string;
53
53
  inputCount: number;
54
+ freshInputCount: number;
55
+ uniqueFreshInputCount: number;
56
+ inputDuplicatesSuppressed: number;
54
57
  recoveryReadCount: number;
58
+ uniqueRecoveryReadCount: number;
55
59
  plan: Record<string, unknown>;
56
60
  estimate: Record<string, unknown>;
57
61
  estimatedCredits: {
@@ -97,6 +101,11 @@ interface FindEmailResult {
97
101
  billingMetadata?: Record<string, unknown>;
98
102
  resultReturnedFrom?: string;
99
103
  cacheHit?: boolean;
104
+ /** True when a definitive no-email result came from the short-lived negative cache. */
105
+ negativeCacheHit?: boolean;
106
+ /** Remaining lifetime of the negative cache entry. */
107
+ negativeCacheTtlSeconds?: number;
108
+ cacheTier?: string;
100
109
  freshEnrichmentUsed?: boolean;
101
110
  liveProviderCalled?: boolean;
102
111
  openrouterCalled?: boolean;
@@ -116,6 +125,8 @@ interface VerifyEmailResult {
116
125
  nextPollAfterSeconds?: number;
117
126
  isValid: boolean;
118
127
  isDeliverable: boolean;
128
+ /** True when the address failed syntax validation before any provider call. */
129
+ isMalformed: boolean;
119
130
  isCatchAll: boolean;
120
131
  /** Explicit send-safety verdict. Missing or false never becomes true by inference. */
121
132
  verifiedForSending: boolean;
@@ -128,6 +139,11 @@ interface VerifyEmailResult {
128
139
  billingMetadata?: Record<string, unknown>;
129
140
  resultReturnedFrom?: string;
130
141
  cacheHit?: boolean;
142
+ /** True when a conclusive send-blocking verdict came from the short-lived rejection cache. */
143
+ negativeCacheHit?: boolean;
144
+ /** Remaining lifetime of the rejection cache entry. */
145
+ negativeCacheTtlSeconds?: number;
146
+ cacheTier?: string;
131
147
  freshEnrichmentUsed?: boolean;
132
148
  liveProviderCalled?: boolean;
133
149
  openrouterCalled?: boolean;
@@ -217,6 +233,14 @@ interface CompanySignalEnrichmentResult {
217
233
  evidenceCountTotal?: number;
218
234
  evidenceSourcesOmitted?: number;
219
235
  evidenceScope?: 'selected_signals' | 'all_candidates';
236
+ billingMetadata?: Record<string, unknown>;
237
+ resultReturnedFrom?: string;
238
+ cacheHit?: boolean;
239
+ freshEnrichmentUsed?: boolean;
240
+ creditsUsed?: number;
241
+ liveProviderCalled?: boolean;
242
+ aiProviderCalled?: boolean;
243
+ byoAiUsed?: boolean;
220
244
  raw: Record<string, unknown>;
221
245
  }
222
246
  type SignalDiscoverySource = 'web' | 'news';
@@ -311,7 +335,7 @@ interface SignalToCopyParams {
311
335
  signalRunId?: string;
312
336
  /** Bypass cached company signals and start fresh research. */
313
337
  skipCache?: boolean;
314
- /** Opt into slower deep research. Defaults to false. */
338
+ /** Use the full deep-research provider portfolio. The default uses bounded credit-free first-party and Bing grounding. */
315
339
  enableDeepSearch?: boolean;
316
340
  /** @deprecated Signal to Copy is always synchronous; this option is ignored. */
317
341
  waitForResult?: boolean;
@@ -346,6 +370,14 @@ interface SignalToCopyResult {
346
370
  empty?: boolean;
347
371
  emptyReason?: string;
348
372
  researchMetadata?: Record<string, unknown>;
373
+ billingMetadata?: Record<string, unknown>;
374
+ resultReturnedFrom?: string;
375
+ cacheHit?: boolean;
376
+ freshEnrichmentUsed?: boolean;
377
+ creditsUsed?: number;
378
+ liveProviderCalled?: boolean;
379
+ aiProviderCalled?: boolean;
380
+ byoAiUsed?: boolean;
349
381
  variations: SignalToCopyVariation[];
350
382
  raw: Record<string, unknown>;
351
383
  }
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ interface SignalizConfig {
9
9
  interface BatchOptions {
10
10
  /** Maximum simultaneous API requests. Defaults to 10; allowed range is 1-50. */
11
11
  concurrency?: number;
12
- /** Stable key for safely retrying an ambiguous recoverable batch submission. */
12
+ /** Stable cross-surface key. Retries reuse the same provider work; chunked batches derive one collision-free key per original row. */
13
13
  idempotencyKey?: string;
14
14
  /** Return exact repeated successes as duplicateOf references. Defaults to false. */
15
15
  compactDuplicates?: boolean;
@@ -51,7 +51,11 @@ interface CoreProductDryRunResult {
51
51
  status: 'planned';
52
52
  capability: string;
53
53
  inputCount: number;
54
+ freshInputCount: number;
55
+ uniqueFreshInputCount: number;
56
+ inputDuplicatesSuppressed: number;
54
57
  recoveryReadCount: number;
58
+ uniqueRecoveryReadCount: number;
55
59
  plan: Record<string, unknown>;
56
60
  estimate: Record<string, unknown>;
57
61
  estimatedCredits: {
@@ -97,6 +101,11 @@ interface FindEmailResult {
97
101
  billingMetadata?: Record<string, unknown>;
98
102
  resultReturnedFrom?: string;
99
103
  cacheHit?: boolean;
104
+ /** True when a definitive no-email result came from the short-lived negative cache. */
105
+ negativeCacheHit?: boolean;
106
+ /** Remaining lifetime of the negative cache entry. */
107
+ negativeCacheTtlSeconds?: number;
108
+ cacheTier?: string;
100
109
  freshEnrichmentUsed?: boolean;
101
110
  liveProviderCalled?: boolean;
102
111
  openrouterCalled?: boolean;
@@ -116,6 +125,8 @@ interface VerifyEmailResult {
116
125
  nextPollAfterSeconds?: number;
117
126
  isValid: boolean;
118
127
  isDeliverable: boolean;
128
+ /** True when the address failed syntax validation before any provider call. */
129
+ isMalformed: boolean;
119
130
  isCatchAll: boolean;
120
131
  /** Explicit send-safety verdict. Missing or false never becomes true by inference. */
121
132
  verifiedForSending: boolean;
@@ -128,6 +139,11 @@ interface VerifyEmailResult {
128
139
  billingMetadata?: Record<string, unknown>;
129
140
  resultReturnedFrom?: string;
130
141
  cacheHit?: boolean;
142
+ /** True when a conclusive send-blocking verdict came from the short-lived rejection cache. */
143
+ negativeCacheHit?: boolean;
144
+ /** Remaining lifetime of the rejection cache entry. */
145
+ negativeCacheTtlSeconds?: number;
146
+ cacheTier?: string;
131
147
  freshEnrichmentUsed?: boolean;
132
148
  liveProviderCalled?: boolean;
133
149
  openrouterCalled?: boolean;
@@ -217,6 +233,14 @@ interface CompanySignalEnrichmentResult {
217
233
  evidenceCountTotal?: number;
218
234
  evidenceSourcesOmitted?: number;
219
235
  evidenceScope?: 'selected_signals' | 'all_candidates';
236
+ billingMetadata?: Record<string, unknown>;
237
+ resultReturnedFrom?: string;
238
+ cacheHit?: boolean;
239
+ freshEnrichmentUsed?: boolean;
240
+ creditsUsed?: number;
241
+ liveProviderCalled?: boolean;
242
+ aiProviderCalled?: boolean;
243
+ byoAiUsed?: boolean;
220
244
  raw: Record<string, unknown>;
221
245
  }
222
246
  type SignalDiscoverySource = 'web' | 'news';
@@ -311,7 +335,7 @@ interface SignalToCopyParams {
311
335
  signalRunId?: string;
312
336
  /** Bypass cached company signals and start fresh research. */
313
337
  skipCache?: boolean;
314
- /** Opt into slower deep research. Defaults to false. */
338
+ /** Use the full deep-research provider portfolio. The default uses bounded credit-free first-party and Bing grounding. */
315
339
  enableDeepSearch?: boolean;
316
340
  /** @deprecated Signal to Copy is always synchronous; this option is ignored. */
317
341
  waitForResult?: boolean;
@@ -346,6 +370,14 @@ interface SignalToCopyResult {
346
370
  empty?: boolean;
347
371
  emptyReason?: string;
348
372
  researchMetadata?: Record<string, unknown>;
373
+ billingMetadata?: Record<string, unknown>;
374
+ resultReturnedFrom?: string;
375
+ cacheHit?: boolean;
376
+ freshEnrichmentUsed?: boolean;
377
+ creditsUsed?: number;
378
+ liveProviderCalled?: boolean;
379
+ aiProviderCalled?: boolean;
380
+ byoAiUsed?: boolean;
349
381
  variations: SignalToCopyVariation[];
350
382
  raw: Record<string, unknown>;
351
383
  }
package/dist/index.js CHANGED
@@ -563,7 +563,11 @@ var Signaliz = class {
563
563
  request: signalToCopyRequestBody(params)
564
564
  }));
565
565
  const uniqueRequests = deduplicated.uniqueItems;
566
- const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
566
+ const uniqueInputIndices = new Array(uniqueRequests.length);
567
+ deduplicated.sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
568
+ if (uniqueInputIndices[uniqueIndex] === void 0) uniqueInputIndices[uniqueIndex] = inputIndex;
569
+ });
570
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options, uniqueInputIndices);
567
571
  const results = requests.map((_, index) => ({
568
572
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
569
573
  index
@@ -589,15 +593,25 @@ var Signaliz = class {
589
593
  }
590
594
  return normalizeCoreProductDryRunResult(data);
591
595
  }
592
- async runSignalCopyBatchChunks(uniqueRequests, options) {
596
+ async runSignalCopyBatchChunks(uniqueRequests, options, idempotencyItemIndices = uniqueRequests.map((_, index) => index)) {
593
597
  const chunks = [];
594
598
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
595
- chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
599
+ chunks.push({
600
+ offset,
601
+ requests: uniqueRequests.slice(offset, offset + 25),
602
+ idempotencyItemIndices: idempotencyItemIndices.slice(offset, offset + 25)
603
+ });
596
604
  }
597
605
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options, 25, 25);
598
606
  const chunkBatch = await runBatch(
599
607
  chunks,
600
- (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
608
+ (chunk) => this.runSignalCopyBatchChunk(
609
+ chunk.requests,
610
+ serverConcurrency,
611
+ 0,
612
+ options?.idempotencyKey,
613
+ chunk.idempotencyItemIndices
614
+ ),
601
615
  { concurrency: chunkConcurrency }
602
616
  );
603
617
  const uniqueResults = new Array(uniqueRequests.length);
@@ -619,14 +633,15 @@ var Signaliz = class {
619
633
  }
620
634
  return uniqueResults;
621
635
  }
622
- async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
636
+ async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
623
637
  const startedAt = Date.now();
624
638
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
625
639
  let submission;
626
640
  try {
627
641
  submission = await this.client.post(path, {
628
642
  requests,
629
- idempotency_key: submissionIdempotencyKey
643
+ idempotency_key: submissionIdempotencyKey,
644
+ batch_item_indices: idempotencyItemIndices
630
645
  });
631
646
  } catch (error) {
632
647
  if (error instanceof SignalizError && !error.isRetryable) throw error;
@@ -689,12 +704,14 @@ var Signaliz = class {
689
704
  if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
690
705
  return rows;
691
706
  }
692
- async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
707
+ async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
693
708
  const results = new Array(requests.length);
694
709
  const rateLimited = [];
695
710
  const data = await this.client.post("api/v1/signal-to-copy", {
696
711
  requests: requests.map(signalToCopyRequestBody),
697
- concurrency
712
+ concurrency,
713
+ idempotency_key: idempotencyKey,
714
+ batch_item_indices: idempotencyItemIndices
698
715
  });
699
716
  if (!Array.isArray(data.results) || data.results.length !== requests.length) {
700
717
  throw new Error("Signal to Copy batch returned an invalid result count");
@@ -729,7 +746,9 @@ var Signaliz = class {
729
746
  const retried = await this.runSignalCopyBatchChunk(
730
747
  rateLimited.map((item) => item.params),
731
748
  concurrency,
732
- rowRateLimitAttempt + 1
749
+ rowRateLimitAttempt + 1,
750
+ idempotencyKey,
751
+ rateLimited.map((item) => idempotencyItemIndices[item.index])
733
752
  );
734
753
  for (let index = 0; index < rateLimited.length; index += 1) {
735
754
  results[rateLimited[index].index] = { ...retried[index], index: rateLimited[index].index };
@@ -749,7 +768,8 @@ var Signaliz = class {
749
768
  recoverableBatch.label,
750
769
  recoverableBatch.pageSize,
751
770
  recoverableBatch.maxWaitMs,
752
- options?.idempotencyKey
771
+ options?.idempotencyKey,
772
+ uniqueTasks.map((task) => task.index)
753
773
  );
754
774
  const uniqueResults2 = new Array(uniqueTasks.length);
755
775
  for (const raw of rows) {
@@ -773,7 +793,9 @@ var Signaliz = class {
773
793
  const chunkBatch = await runBatch(chunks, async (chunk) => {
774
794
  const data = await this.client.post(path, {
775
795
  requests: chunk.tasks.map((task) => task.request),
776
- concurrency: serverConcurrency
796
+ concurrency: serverConcurrency,
797
+ idempotency_key: options?.idempotencyKey,
798
+ batch_item_indices: chunk.tasks.map((task) => task.index)
777
799
  });
778
800
  if (!Array.isArray(data.results) || data.results.length !== chunk.tasks.length) {
779
801
  throw new Error(`${path} batch returned an invalid result count`);
@@ -908,7 +930,11 @@ function normalizeCoreProductDryRunResult(data) {
908
930
  status: "planned",
909
931
  capability: String(data.capability || ""),
910
932
  inputCount: Number(data.input_count || 0),
933
+ freshInputCount: Number(data.fresh_input_count ?? data.input_count ?? 0),
934
+ uniqueFreshInputCount: Number(data.unique_fresh_input_count ?? data.fresh_input_count ?? data.input_count ?? 0),
935
+ inputDuplicatesSuppressed: Number(data.input_duplicates_suppressed || 0),
911
936
  recoveryReadCount: Number(data.recovery_read_count || 0),
937
+ uniqueRecoveryReadCount: Number(data.unique_recovery_read_count ?? data.recovery_read_count ?? 0),
912
938
  plan: data.plan && typeof data.plan === "object" ? data.plan : {},
913
939
  estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
914
940
  estimatedCredits: {
@@ -923,12 +949,12 @@ function normalizeCoreProductDryRunResult(data) {
923
949
  function normalizeFindEmailResult(data) {
924
950
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
925
951
  const found = Boolean(email) && data.found !== false;
926
- const verificationStatus = data.verification_status ?? data.status ?? "unknown";
952
+ const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
927
953
  const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
928
954
  const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
929
955
  const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
930
956
  const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
931
- const verified = !explicitlyNotSendSafe && (data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
957
+ const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
932
958
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
933
959
  return {
934
960
  success: data.success !== false && found,
@@ -951,6 +977,9 @@ function normalizeFindEmailResult(data) {
951
977
  billingMetadata: data.billing_metadata,
952
978
  resultReturnedFrom: data.result_returned_from,
953
979
  cacheHit: data.cache_hit,
980
+ negativeCacheHit: data.negative_cache_hit,
981
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
982
+ cacheTier: data.cache_tier,
954
983
  freshEnrichmentUsed: data.fresh_enrichment_used,
955
984
  liveProviderCalled: data.live_provider_called,
956
985
  openrouterCalled: data.openrouter_called,
@@ -972,6 +1001,7 @@ function normalizeVerifyEmailResult(email, data) {
972
1001
  nextPollAfterSeconds: data.next_poll_after_seconds,
973
1002
  isValid: data.is_valid ?? data.valid ?? verified,
974
1003
  isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
1004
+ isMalformed: data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed",
975
1005
  isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
976
1006
  verifiedForSending: data.verified_for_sending === true,
977
1007
  verificationVerdict: data.verification_verdict ?? data.verification_status ?? "unknown",
@@ -983,6 +1013,9 @@ function normalizeVerifyEmailResult(email, data) {
983
1013
  billingMetadata: data.billing_metadata,
984
1014
  resultReturnedFrom: data.result_returned_from,
985
1015
  cacheHit: data.cache_hit,
1016
+ negativeCacheHit: data.negative_cache_hit,
1017
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
1018
+ cacheTier: data.cache_tier,
986
1019
  freshEnrichmentUsed: data.fresh_enrichment_used,
987
1020
  liveProviderCalled: data.live_provider_called,
988
1021
  openrouterCalled: data.openrouter_called,
@@ -1052,6 +1085,14 @@ function normalizeCompanySignalResult(params, data) {
1052
1085
  evidenceCountTotal: data.evidence_count_total,
1053
1086
  evidenceSourcesOmitted: data.evidence_sources_omitted,
1054
1087
  evidenceScope: data.evidence_scope,
1088
+ billingMetadata: data.billing_metadata,
1089
+ resultReturnedFrom: data.result_returned_from,
1090
+ cacheHit: data.cache_hit,
1091
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1092
+ creditsUsed: data.credits_used,
1093
+ liveProviderCalled: data.live_provider_called,
1094
+ aiProviderCalled: data.ai_provider_called,
1095
+ byoAiUsed: data.byo_ai_used,
1055
1096
  raw: data
1056
1097
  };
1057
1098
  }
@@ -1329,6 +1370,14 @@ function normalizeSignalToCopyResult(data) {
1329
1370
  empty: data.empty,
1330
1371
  emptyReason: data.empty_reason,
1331
1372
  researchMetadata: data.research_metadata,
1373
+ billingMetadata: data.billing_metadata,
1374
+ resultReturnedFrom: data.result_returned_from,
1375
+ cacheHit: data.cache_hit,
1376
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1377
+ creditsUsed: data.credits_used,
1378
+ liveProviderCalled: data.live_provider_called,
1379
+ aiProviderCalled: data.ai_provider_called,
1380
+ byoAiUsed: data.byo_ai_used,
1332
1381
  variations: (data.variations ?? []).map((variation) => ({
1333
1382
  style: variation.style,
1334
1383
  subjectLine: variation.subject_line ?? "",
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-QXMVHZIQ.mjs";
4
+ } from "./chunk-K7RDDRMJ.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -567,7 +567,11 @@ var Signaliz = class {
567
567
  request: signalToCopyRequestBody(params)
568
568
  }));
569
569
  const uniqueRequests = deduplicated.uniqueItems;
570
- const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
570
+ const uniqueInputIndices = new Array(uniqueRequests.length);
571
+ deduplicated.sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
572
+ if (uniqueInputIndices[uniqueIndex] === void 0) uniqueInputIndices[uniqueIndex] = inputIndex;
573
+ });
574
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options, uniqueInputIndices);
571
575
  const results = requests.map((_, index) => ({
572
576
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
573
577
  index
@@ -593,15 +597,25 @@ var Signaliz = class {
593
597
  }
594
598
  return normalizeCoreProductDryRunResult(data);
595
599
  }
596
- async runSignalCopyBatchChunks(uniqueRequests, options) {
600
+ async runSignalCopyBatchChunks(uniqueRequests, options, idempotencyItemIndices = uniqueRequests.map((_, index) => index)) {
597
601
  const chunks = [];
598
602
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
599
- chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
603
+ chunks.push({
604
+ offset,
605
+ requests: uniqueRequests.slice(offset, offset + 25),
606
+ idempotencyItemIndices: idempotencyItemIndices.slice(offset, offset + 25)
607
+ });
600
608
  }
601
609
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options, 25, 25);
602
610
  const chunkBatch = await runBatch(
603
611
  chunks,
604
- (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
612
+ (chunk) => this.runSignalCopyBatchChunk(
613
+ chunk.requests,
614
+ serverConcurrency,
615
+ 0,
616
+ options?.idempotencyKey,
617
+ chunk.idempotencyItemIndices
618
+ ),
605
619
  { concurrency: chunkConcurrency }
606
620
  );
607
621
  const uniqueResults = new Array(uniqueRequests.length);
@@ -623,14 +637,15 @@ var Signaliz = class {
623
637
  }
624
638
  return uniqueResults;
625
639
  }
626
- async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
640
+ async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
627
641
  const startedAt = Date.now();
628
642
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
629
643
  let submission;
630
644
  try {
631
645
  submission = await this.client.post(path2, {
632
646
  requests,
633
- idempotency_key: submissionIdempotencyKey
647
+ idempotency_key: submissionIdempotencyKey,
648
+ batch_item_indices: idempotencyItemIndices
634
649
  });
635
650
  } catch (error) {
636
651
  if (error instanceof SignalizError && !error.isRetryable) throw error;
@@ -693,12 +708,14 @@ var Signaliz = class {
693
708
  if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
694
709
  return rows;
695
710
  }
696
- async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
711
+ async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
697
712
  const results = new Array(requests.length);
698
713
  const rateLimited = [];
699
714
  const data = await this.client.post("api/v1/signal-to-copy", {
700
715
  requests: requests.map(signalToCopyRequestBody),
701
- concurrency
716
+ concurrency,
717
+ idempotency_key: idempotencyKey,
718
+ batch_item_indices: idempotencyItemIndices
702
719
  });
703
720
  if (!Array.isArray(data.results) || data.results.length !== requests.length) {
704
721
  throw new Error("Signal to Copy batch returned an invalid result count");
@@ -733,7 +750,9 @@ var Signaliz = class {
733
750
  const retried = await this.runSignalCopyBatchChunk(
734
751
  rateLimited.map((item) => item.params),
735
752
  concurrency,
736
- rowRateLimitAttempt + 1
753
+ rowRateLimitAttempt + 1,
754
+ idempotencyKey,
755
+ rateLimited.map((item) => idempotencyItemIndices[item.index])
737
756
  );
738
757
  for (let index = 0; index < rateLimited.length; index += 1) {
739
758
  results[rateLimited[index].index] = { ...retried[index], index: rateLimited[index].index };
@@ -753,7 +772,8 @@ var Signaliz = class {
753
772
  recoverableBatch.label,
754
773
  recoverableBatch.pageSize,
755
774
  recoverableBatch.maxWaitMs,
756
- options?.idempotencyKey
775
+ options?.idempotencyKey,
776
+ uniqueTasks.map((task) => task.index)
757
777
  );
758
778
  const uniqueResults2 = new Array(uniqueTasks.length);
759
779
  for (const raw of rows) {
@@ -777,7 +797,9 @@ var Signaliz = class {
777
797
  const chunkBatch = await runBatch(chunks, async (chunk) => {
778
798
  const data = await this.client.post(path2, {
779
799
  requests: chunk.tasks.map((task) => task.request),
780
- concurrency: serverConcurrency
800
+ concurrency: serverConcurrency,
801
+ idempotency_key: options?.idempotencyKey,
802
+ batch_item_indices: chunk.tasks.map((task) => task.index)
781
803
  });
782
804
  if (!Array.isArray(data.results) || data.results.length !== chunk.tasks.length) {
783
805
  throw new Error(`${path2} batch returned an invalid result count`);
@@ -912,7 +934,11 @@ function normalizeCoreProductDryRunResult(data) {
912
934
  status: "planned",
913
935
  capability: String(data.capability || ""),
914
936
  inputCount: Number(data.input_count || 0),
937
+ freshInputCount: Number(data.fresh_input_count ?? data.input_count ?? 0),
938
+ uniqueFreshInputCount: Number(data.unique_fresh_input_count ?? data.fresh_input_count ?? data.input_count ?? 0),
939
+ inputDuplicatesSuppressed: Number(data.input_duplicates_suppressed || 0),
915
940
  recoveryReadCount: Number(data.recovery_read_count || 0),
941
+ uniqueRecoveryReadCount: Number(data.unique_recovery_read_count ?? data.recovery_read_count ?? 0),
916
942
  plan: data.plan && typeof data.plan === "object" ? data.plan : {},
917
943
  estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
918
944
  estimatedCredits: {
@@ -927,12 +953,12 @@ function normalizeCoreProductDryRunResult(data) {
927
953
  function normalizeFindEmailResult(data) {
928
954
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
929
955
  const found = Boolean(email) && data.found !== false;
930
- const verificationStatus = data.verification_status ?? data.status ?? "unknown";
956
+ const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
931
957
  const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
932
958
  const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
933
959
  const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
934
960
  const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
935
- const verified = !explicitlyNotSendSafe && (data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
961
+ const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
936
962
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
937
963
  return {
938
964
  success: data.success !== false && found,
@@ -955,6 +981,9 @@ function normalizeFindEmailResult(data) {
955
981
  billingMetadata: data.billing_metadata,
956
982
  resultReturnedFrom: data.result_returned_from,
957
983
  cacheHit: data.cache_hit,
984
+ negativeCacheHit: data.negative_cache_hit,
985
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
986
+ cacheTier: data.cache_tier,
958
987
  freshEnrichmentUsed: data.fresh_enrichment_used,
959
988
  liveProviderCalled: data.live_provider_called,
960
989
  openrouterCalled: data.openrouter_called,
@@ -976,6 +1005,7 @@ function normalizeVerifyEmailResult(email, data) {
976
1005
  nextPollAfterSeconds: data.next_poll_after_seconds,
977
1006
  isValid: data.is_valid ?? data.valid ?? verified,
978
1007
  isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
1008
+ isMalformed: data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed",
979
1009
  isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
980
1010
  verifiedForSending: data.verified_for_sending === true,
981
1011
  verificationVerdict: data.verification_verdict ?? data.verification_status ?? "unknown",
@@ -987,6 +1017,9 @@ function normalizeVerifyEmailResult(email, data) {
987
1017
  billingMetadata: data.billing_metadata,
988
1018
  resultReturnedFrom: data.result_returned_from,
989
1019
  cacheHit: data.cache_hit,
1020
+ negativeCacheHit: data.negative_cache_hit,
1021
+ negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
1022
+ cacheTier: data.cache_tier,
990
1023
  freshEnrichmentUsed: data.fresh_enrichment_used,
991
1024
  liveProviderCalled: data.live_provider_called,
992
1025
  openrouterCalled: data.openrouter_called,
@@ -1056,6 +1089,14 @@ function normalizeCompanySignalResult(params, data) {
1056
1089
  evidenceCountTotal: data.evidence_count_total,
1057
1090
  evidenceSourcesOmitted: data.evidence_sources_omitted,
1058
1091
  evidenceScope: data.evidence_scope,
1092
+ billingMetadata: data.billing_metadata,
1093
+ resultReturnedFrom: data.result_returned_from,
1094
+ cacheHit: data.cache_hit,
1095
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1096
+ creditsUsed: data.credits_used,
1097
+ liveProviderCalled: data.live_provider_called,
1098
+ aiProviderCalled: data.ai_provider_called,
1099
+ byoAiUsed: data.byo_ai_used,
1059
1100
  raw: data
1060
1101
  };
1061
1102
  }
@@ -1333,6 +1374,14 @@ function normalizeSignalToCopyResult(data) {
1333
1374
  empty: data.empty,
1334
1375
  emptyReason: data.empty_reason,
1335
1376
  researchMetadata: data.research_metadata,
1377
+ billingMetadata: data.billing_metadata,
1378
+ resultReturnedFrom: data.result_returned_from,
1379
+ cacheHit: data.cache_hit,
1380
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1381
+ creditsUsed: data.credits_used,
1382
+ liveProviderCalled: data.live_provider_called,
1383
+ aiProviderCalled: data.ai_provider_called,
1384
+ byoAiUsed: data.byo_ai_used,
1336
1385
  variations: (data.variations ?? []).map((variation) => ({
1337
1386
  style: variation.style,
1338
1387
  subjectLine: variation.subject_line ?? "",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-QXMVHZIQ.mjs";
4
+ } from "./chunk-K7RDDRMJ.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.55",
3
+ "version": "1.0.56",
4
4
  "description": "Signaliz SDK for Signals Everything, Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",