@signaliz/sdk 1.0.60 → 1.0.62

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.
@@ -40,17 +40,21 @@ var SignalizError = class extends Error {
40
40
  this.details = data.details;
41
41
  }
42
42
  get isRetryable() {
43
+ if (typeof this.details?.retry_eligible === "boolean") {
44
+ return this.details.retry_eligible;
45
+ }
43
46
  return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
44
47
  }
45
48
  };
46
49
  function parseError(status, body) {
47
50
  if (body?.error && typeof body.error === "object") {
51
+ const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
48
52
  return new SignalizError({
49
53
  code: body.error.code || `HTTP_${status}`,
50
54
  message: body.error.message || `Request failed with status ${status}`,
51
55
  errorType: mapErrorType(body.error.code, status),
52
56
  retryAfter: retryAfterSeconds(body.error),
53
- details: body.error.details
57
+ details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
54
58
  });
55
59
  }
56
60
  const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
@@ -59,7 +63,7 @@ function parseError(status, body) {
59
63
  message: body?.message || body?.error || `Request failed with status ${status}`,
60
64
  errorType: mapErrorType(code, status),
61
65
  retryAfter: retryAfterSeconds(body),
62
- details: body?.details || publicRetryDetails(body)
66
+ details: mergePublicErrorDetails(body, body?.details)
63
67
  });
64
68
  }
65
69
  function retryAfterSeconds(body) {
@@ -71,17 +75,35 @@ function publicRetryDetails(body) {
71
75
  ...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
72
76
  ...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
73
77
  ...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
78
+ ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
79
+ ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
80
+ ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
81
+ ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
82
+ ...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
83
+ ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
74
84
  ...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
85
+ ...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
86
+ ...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
87
+ ...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
88
+ ...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
75
89
  ...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
76
90
  ...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
77
91
  ...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
78
92
  };
79
93
  return Object.keys(details).length > 0 ? details : void 0;
80
94
  }
95
+ function mergePublicErrorDetails(body, explicitDetails) {
96
+ const merged = { ...publicRetryDetails(body) || {} };
97
+ if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
98
+ Object.assign(merged, explicitDetails);
99
+ }
100
+ return Object.keys(merged).length > 0 ? merged : void 0;
101
+ }
81
102
  function mapErrorType(code, status) {
82
103
  if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
83
- if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
104
+ if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
84
105
  if (code === "NOT_FOUND" || status === 404) return "not_found";
106
+ if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
85
107
  if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
86
108
  if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
87
109
  if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
@@ -92,7 +114,7 @@ function mapErrorType(code, status) {
92
114
 
93
115
  // src/client.ts
94
116
  var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
95
- var DEFAULT_TIMEOUT = 12e4;
117
+ var DEFAULT_TIMEOUT = 13e4;
96
118
  var DEFAULT_MAX_RETRIES = 3;
97
119
  var HttpClient = class {
98
120
  constructor(config) {
@@ -106,9 +128,9 @@ var HttpClient = class {
106
128
  throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
107
129
  }
108
130
  }
109
- async request(functionName, body, method = "POST") {
131
+ async request(functionName, body, method = "POST", options = {}) {
110
132
  let lastError;
111
- const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
133
+ const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
112
134
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
113
135
  let timer;
114
136
  try {
@@ -139,7 +161,9 @@ var HttpClient = class {
139
161
  const errBody = await res.json().catch(() => ({}));
140
162
  const err = parseError(res.status, errBody);
141
163
  if (err.isRetryable && attempt < this.maxRetries) {
142
- const wait = err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
164
+ const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
165
+ if (retryWithNewKey) throw err;
166
+ const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
143
167
  await sleep(wait);
144
168
  lastError = err;
145
169
  continue;
@@ -174,8 +198,8 @@ var HttpClient = class {
174
198
  throw lastError || new Error("Request failed after retries");
175
199
  }
176
200
  /** Convenience wrapper for POST-based edge function calls */
177
- async post(functionName, body) {
178
- return this.request(functionName, body, "POST");
201
+ async post(functionName, body, options = {}) {
202
+ return this.request(functionName, body, "POST", options);
179
203
  }
180
204
  /** Convenience wrapper for GET-based edge function calls with query params */
181
205
  async get(functionName, query = {}) {
@@ -292,10 +316,12 @@ var HttpClient = class {
292
316
  return this.accessTokenPromise;
293
317
  }
294
318
  };
295
- function requestIdempotencyKey(body) {
319
+ function requestIdempotencyKey(body, automaticIdempotency) {
296
320
  const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
321
+ if (requested) return requested;
322
+ if (!automaticIdempotency) return void 0;
297
323
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
298
- return requested || `sdk_${nonce}`;
324
+ return `sdk_${nonce}`;
299
325
  }
300
326
  function sleep(ms) {
301
327
  return new Promise((r) => setTimeout(r, ms));
@@ -397,7 +423,7 @@ function mapMcpErrorType(error) {
397
423
  function mapErrorTypeFromCode(code) {
398
424
  if (!code) return "unknown";
399
425
  if (code === "RATE_LIMITED") return "rate_limited";
400
- if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
426
+ if (code === "INVALID_ARGUMENT" || code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
401
427
  if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
402
428
  if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
403
429
  if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
@@ -405,6 +431,166 @@ function mapErrorTypeFromCode(code) {
405
431
  return "unknown";
406
432
  }
407
433
 
434
+ // src/email-validation.ts
435
+ function isValidEmailString(value) {
436
+ if (typeof value !== "string") return false;
437
+ const email = value.trim();
438
+ if (!email || email.length > 254) return false;
439
+ const separator = email.indexOf("@");
440
+ if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
441
+ const local = email.slice(0, separator);
442
+ const domain = email.slice(separator + 1).toLowerCase();
443
+ if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
444
+ if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
445
+ const labels = domain.split(".");
446
+ if (labels.length < 2) return false;
447
+ return labels.every(
448
+ (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
449
+ );
450
+ }
451
+
452
+ // src/core-product-input-bounds.ts
453
+ var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
454
+ var DOMAIN_MAX_UTF8_BYTES = 253;
455
+ var NAME_MAX_UTF8_BYTES = 500;
456
+ var TITLE_MAX_UTF8_BYTES = 500;
457
+ var ID_MAX_UTF8_BYTES = 200;
458
+ var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
459
+ var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
460
+ var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
461
+ var UTF8_ENCODER = new TextEncoder();
462
+ function byteLength(value) {
463
+ return UTF8_ENCODER.encode(value).byteLength;
464
+ }
465
+ function payloadTooLarge(message, details) {
466
+ throw new SignalizError({
467
+ code: "PAYLOAD_TOO_LARGE",
468
+ message,
469
+ errorType: "validation",
470
+ details: {
471
+ ...details,
472
+ retry_eligible: false,
473
+ credits_used: 0,
474
+ credits_charged: 0
475
+ }
476
+ });
477
+ }
478
+ function assertStringBytes(value, field, maxBytes, index) {
479
+ if (typeof value !== "string") return;
480
+ const encodedBytes = byteLength(value);
481
+ if (encodedBytes <= maxBytes) return;
482
+ payloadTooLarge(
483
+ `${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
484
+ {
485
+ field,
486
+ ...index !== void 0 ? { index } : {},
487
+ encoded_bytes: encodedBytes,
488
+ max_bytes: maxBytes
489
+ }
490
+ );
491
+ }
492
+ function assertCompanySignalRows(body) {
493
+ const requests = Array.isArray(body.requests) ? body.requests : [];
494
+ assertStringBytes(
495
+ body.research_prompt,
496
+ "research_prompt",
497
+ RESEARCH_PROMPT_MAX_UTF8_BYTES
498
+ );
499
+ if (Array.isArray(body.signal_types)) {
500
+ body.signal_types.forEach(
501
+ (signalType, index) => assertStringBytes(
502
+ signalType,
503
+ `signal_types[${index}]`,
504
+ SIGNAL_TYPE_MAX_UTF8_BYTES,
505
+ index
506
+ )
507
+ );
508
+ }
509
+ requests.forEach((value, index) => {
510
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
511
+ const request = value;
512
+ assertStringBytes(
513
+ request.company_domain,
514
+ `requests[${index}].company_domain`,
515
+ DOMAIN_MAX_UTF8_BYTES,
516
+ index
517
+ );
518
+ assertStringBytes(
519
+ request.company_name,
520
+ `requests[${index}].company_name`,
521
+ NAME_MAX_UTF8_BYTES,
522
+ index
523
+ );
524
+ });
525
+ }
526
+ function assertSignalCopyRows(body) {
527
+ const requests = Array.isArray(body.requests) ? body.requests : [];
528
+ requests.forEach((value, index) => {
529
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
530
+ const request = value;
531
+ assertStringBytes(
532
+ request.company_domain,
533
+ `requests[${index}].company_domain`,
534
+ DOMAIN_MAX_UTF8_BYTES,
535
+ index
536
+ );
537
+ assertStringBytes(
538
+ request.person_name,
539
+ `requests[${index}].person_name`,
540
+ NAME_MAX_UTF8_BYTES,
541
+ index
542
+ );
543
+ assertStringBytes(
544
+ request.title,
545
+ `requests[${index}].title`,
546
+ TITLE_MAX_UTF8_BYTES,
547
+ index
548
+ );
549
+ assertStringBytes(
550
+ request.campaign_offer,
551
+ `requests[${index}].campaign_offer`,
552
+ CAMPAIGN_OFFER_MAX_UTF8_BYTES,
553
+ index
554
+ );
555
+ assertStringBytes(
556
+ request.research_prompt,
557
+ `requests[${index}].research_prompt`,
558
+ RESEARCH_PROMPT_MAX_UTF8_BYTES,
559
+ index
560
+ );
561
+ assertStringBytes(
562
+ request.signal_run_id,
563
+ `requests[${index}].signal_run_id`,
564
+ ID_MAX_UTF8_BYTES,
565
+ index
566
+ );
567
+ });
568
+ }
569
+ function assertLargeCoreProductInputBounds(path2, body) {
570
+ const requests = Array.isArray(body.requests) ? body.requests : [];
571
+ if (requests.length <= 25) return;
572
+ assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
573
+ if (path2 === "api/v1/company-signals") {
574
+ assertCompanySignalRows(body);
575
+ } else {
576
+ assertSignalCopyRows(body);
577
+ }
578
+ const encoded = JSON.stringify(body);
579
+ if (typeof encoded !== "string") {
580
+ throw new TypeError("Core product batch input must be JSON serializable.");
581
+ }
582
+ const encodedBytes = byteLength(encoded);
583
+ if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
584
+ payloadTooLarge(
585
+ `${path2 === "api/v1/company-signals" ? "Company Signal" : "Signal Copy"} batch request is ${encodedBytes} UTF-8 bytes after JSON encoding; maximum is ${LARGE_BATCH_MAX_ENCODED_BYTES} bytes`,
586
+ {
587
+ field: "requests",
588
+ encoded_bytes: encodedBytes,
589
+ max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
590
+ }
591
+ );
592
+ }
593
+
408
594
  // src/index.ts
409
595
  var MAX_ROW_RATE_LIMIT_RETRIES = 2;
410
596
  var Signaliz = class {
@@ -438,9 +624,25 @@ var Signaliz = class {
438
624
  options
439
625
  );
440
626
  }
627
+ async resumeFindEmailBatch(jobId, options) {
628
+ const startedAt = Date.now();
629
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
630
+ "api/v1/find-email",
631
+ jobId,
632
+ "Find Email",
633
+ options
634
+ );
635
+ return finalizeCoreBatch(
636
+ total,
637
+ startedAt,
638
+ recoverableRowsToRound(rows, "Find Email"),
639
+ (item) => normalizeFindEmailResult(item.raw),
640
+ options
641
+ );
642
+ }
441
643
  async verifyEmail(email, options) {
442
644
  const normalizedEmail = typeof email === "string" ? email.trim() : "";
443
- if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
645
+ if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
444
646
  const error = `Invalid email format: "${normalizedEmail}"`;
445
647
  return normalizeVerifyEmailResult(normalizedEmail, {
446
648
  success: false,
@@ -490,19 +692,20 @@ var Signaliz = class {
490
692
  }
491
693
  async verifyEmails(emails, options) {
492
694
  validateBatchSize(emails);
695
+ const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
493
696
  if (options?.dryRun === true) {
494
697
  return this.runCoreProductDryRun(
495
698
  "api/v1/verify-email",
496
- emails.map((email) => ({ email, skip_cache: options.skipCache })),
699
+ requests,
497
700
  options.idempotencyKey
498
701
  );
499
702
  }
500
703
  const startedAt = Date.now();
501
704
  const round = await this.runCoreBatchRound(
502
705
  "api/v1/verify-email",
503
- emails.map((email, index) => ({
706
+ requests.map((request, index) => ({
504
707
  index,
505
- request: { email, skip_cache: options?.skipCache }
708
+ request
506
709
  })),
507
710
  options
508
711
  );
@@ -510,7 +713,23 @@ var Signaliz = class {
510
713
  emails.length,
511
714
  startedAt,
512
715
  round,
513
- (item) => normalizeVerifyEmailResult(emails[item.index], item.raw),
716
+ (item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
717
+ options
718
+ );
719
+ }
720
+ async resumeVerifyEmailBatch(jobId, options) {
721
+ const startedAt = Date.now();
722
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
723
+ "api/v1/verify-email",
724
+ jobId,
725
+ "Verify Email",
726
+ options
727
+ );
728
+ return finalizeCoreBatch(
729
+ total,
730
+ startedAt,
731
+ recoverableRowsToRound(rows, "Verify Email"),
732
+ (item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
514
733
  options
515
734
  );
516
735
  }
@@ -526,25 +745,92 @@ var Signaliz = class {
526
745
  validateSignalDiscoveryParams(params);
527
746
  const data = await this.client.post(
528
747
  "api/v1/signals",
529
- signalDiscoveryRequestBody(params)
748
+ signalDiscoveryRequestBody(params),
749
+ // Signals Everything owns a normalized, workspace-scoped automatic key
750
+ // at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
751
+ { automaticIdempotency: false }
530
752
  );
531
753
  return normalizeSignalDiscoveryResult(params, data);
532
754
  }
533
755
  async enrichCompanies(companies, options) {
534
756
  validateBatchSize(companies);
535
757
  companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
758
+ const requests = companies.map(companySignalRequestBody);
759
+ if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
760
+ throw new RangeError(
761
+ "includeCandidateEvidence is not supported in Company Signals batches larger than 25 because rejected evidence diagnostics are intentionally excluded from durable scale storage; split diagnostic requests into batches of at most 25."
762
+ );
763
+ }
764
+ const largeBatch = companySignalLargeBatchSubmission(requests);
765
+ if (companies.length > 25 && !largeBatch) {
766
+ throw new RangeError(
767
+ "Company Signals batches larger than 25 require homogeneous new-enrichment rows with company identity only and uniform controls; split recovery or mixed-control rows into batches of at most 25."
768
+ );
769
+ }
536
770
  if (options?.dryRun === true) {
537
771
  return this.runCoreProductDryRun(
538
772
  "api/v1/company-signals",
539
- companies.map(companySignalRequestBody),
540
- options.idempotencyKey
773
+ largeBatch?.requests || requests,
774
+ options.idempotencyKey,
775
+ largeBatch?.submissionFields
541
776
  );
542
777
  }
543
778
  const startedAt = Date.now();
544
- const initialTasks = companies.map((params, index) => ({
545
- index,
546
- request: companySignalRequestBody(params)
547
- }));
779
+ if (options?.waitForResult === false) {
780
+ if (!largeBatch) {
781
+ throw new RangeError(
782
+ "Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
783
+ );
784
+ }
785
+ return this.submitRecoverableCoreBatchJob(
786
+ "api/v1/company-signals",
787
+ largeBatch.requests,
788
+ "Company Signals",
789
+ options.idempotencyKey,
790
+ companies.map((_, index) => index),
791
+ largeBatch.submissionFields
792
+ );
793
+ }
794
+ if (largeBatch) {
795
+ const job = await this.submitRecoverableCoreBatchJob(
796
+ "api/v1/company-signals",
797
+ largeBatch.requests,
798
+ "Company Signals",
799
+ options?.idempotencyKey,
800
+ companies.map((_, index) => index),
801
+ largeBatch.submissionFields
802
+ );
803
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
804
+ "api/v1/company-signals",
805
+ job.jobId,
806
+ "Company Signals",
807
+ {
808
+ pageSize: 25,
809
+ maxWaitMs: options?.maxWaitMs,
810
+ pollIntervalMs: options?.pollIntervalMs,
811
+ idempotencyKey: job.idempotencyKey,
812
+ compactDuplicates: options?.compactDuplicates === true
813
+ }
814
+ );
815
+ if (total !== companies.length) {
816
+ throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
817
+ }
818
+ const batch = finalizeCoreBatch(
819
+ companies.length,
820
+ startedAt,
821
+ recoverableRowsToRound(rows, "Company Signals"),
822
+ (item) => normalizeCompanySignalResult(companies[item.index], item.raw)
823
+ );
824
+ return compactKnownDuplicateBatch(
825
+ batch,
826
+ dedupeExactBatchItems(
827
+ requests,
828
+ (request) => coreProductBatchRequestKey("api/v1/company-signals", request)
829
+ ).sourceIndexByInput,
830
+ options?.compactDuplicates === true
831
+ );
832
+ }
833
+ const initialTasks = requests.map((request, index) => ({ index, request }));
548
834
  const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
549
835
  const results = round.map((item) => {
550
836
  if (!item.success || !item.raw) {
@@ -567,6 +853,22 @@ var Signaliz = class {
567
853
  results: compactedResults
568
854
  };
569
855
  }
856
+ async resumeCompanySignalBatch(jobId, options) {
857
+ const startedAt = Date.now();
858
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
859
+ "api/v1/company-signals",
860
+ jobId,
861
+ "Company Signals",
862
+ options
863
+ );
864
+ return finalizeRecoveredCoreBatch(
865
+ total,
866
+ startedAt,
867
+ recoverableRowsToRound(rows, "Company Signals"),
868
+ (item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
869
+ options
870
+ );
871
+ }
570
872
  async signalToCopy(params) {
571
873
  validateSignalToCopyParams(params);
572
874
  const request = signalToCopyRequestBody(params);
@@ -578,6 +880,7 @@ var Signaliz = class {
578
880
  async createSignalCopyBatch(requests, options) {
579
881
  validateBatchSize(requests);
580
882
  requests.forEach(validateSignalToCopyParams);
883
+ validateLargeAvailableDataSignalCopyBatch(requests);
581
884
  if (options?.dryRun === true) {
582
885
  return this.runCoreProductDryRun(
583
886
  "api/v1/signal-to-copy",
@@ -586,6 +889,59 @@ var Signaliz = class {
586
889
  );
587
890
  }
588
891
  const startedAt = Date.now();
892
+ if (requests.length > 25) {
893
+ if (options?.waitForResult === false) {
894
+ return this.submitRecoverableCoreBatchJob(
895
+ "api/v1/signal-to-copy",
896
+ requests.map(availableDataSignalCopyRequestBody),
897
+ "Signal to Copy",
898
+ options.idempotencyKey,
899
+ requests.map((_, index) => index)
900
+ );
901
+ }
902
+ const job = await this.submitRecoverableCoreBatchJob(
903
+ "api/v1/signal-to-copy",
904
+ requests.map(availableDataSignalCopyRequestBody),
905
+ "Signal to Copy",
906
+ options?.idempotencyKey,
907
+ requests.map((_, index) => index)
908
+ );
909
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
910
+ "api/v1/signal-to-copy",
911
+ job.jobId,
912
+ "Signal to Copy",
913
+ {
914
+ pageSize: 100,
915
+ maxWaitMs: options?.maxWaitMs,
916
+ pollIntervalMs: options?.pollIntervalMs,
917
+ idempotencyKey: job.idempotencyKey,
918
+ compactDuplicates: options?.compactDuplicates === true
919
+ }
920
+ );
921
+ if (total !== requests.length) {
922
+ throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
923
+ }
924
+ const results2 = recoverableSignalCopyRows(rows);
925
+ const succeeded2 = results2.filter((item) => item.success).length;
926
+ const batch = {
927
+ total: requests.length,
928
+ succeeded: succeeded2,
929
+ failed: requests.length - succeeded2,
930
+ durationMs: Date.now() - startedAt,
931
+ results: results2
932
+ };
933
+ return compactKnownDuplicateBatch(
934
+ batch,
935
+ dedupeExactBatchItems(
936
+ requests,
937
+ (params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
938
+ ).sourceIndexByInput,
939
+ options?.compactDuplicates === true
940
+ );
941
+ }
942
+ if (options?.waitForResult === false) {
943
+ throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
944
+ }
589
945
  const deduplicated = dedupeExactBatchItems(
590
946
  requests,
591
947
  (params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
@@ -610,12 +966,36 @@ var Signaliz = class {
610
966
  results: compactedResults
611
967
  };
612
968
  }
613
- async runCoreProductDryRun(path2, requests, idempotencyKey) {
614
- const data = await this.client.post(path2, compact({
969
+ async resumeSignalCopyBatch(jobId, options) {
970
+ const startedAt = Date.now();
971
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
972
+ "api/v1/signal-to-copy",
973
+ jobId,
974
+ "Signal to Copy",
975
+ options
976
+ );
977
+ const results = recoverableSignalCopyRows(rows);
978
+ const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
979
+ const succeeded = compactedResults.filter((item) => item.success).length;
980
+ return {
981
+ total,
982
+ succeeded,
983
+ failed: total - succeeded,
984
+ durationMs: Date.now() - startedAt,
985
+ results: compactedResults
986
+ };
987
+ }
988
+ async runCoreProductDryRun(path2, requests, idempotencyKey, defaults = {}) {
989
+ const requestBody = compact({
615
990
  requests,
991
+ ...defaults,
616
992
  dry_run: true,
617
993
  idempotency_key: idempotencyKey
618
- }));
994
+ });
995
+ if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
996
+ assertLargeCoreProductInputBounds(path2, requestBody);
997
+ }
998
+ const data = await this.client.post(path2, requestBody);
619
999
  if (!isCoreProductDryRun(data)) {
620
1000
  throw new Error(`${path2} dry run returned a live-result contract`);
621
1001
  }
@@ -661,76 +1041,209 @@ var Signaliz = class {
661
1041
  }
662
1042
  return uniqueResults;
663
1043
  }
664
- async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
665
- const startedAt = Date.now();
1044
+ async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
1045
+ const job = await this.submitRecoverableCoreBatchJob(
1046
+ path2,
1047
+ requests,
1048
+ label,
1049
+ idempotencyKey,
1050
+ idempotencyItemIndices,
1051
+ submissionFields
1052
+ );
1053
+ const recovered = await this.readRecoverableCoreBatchJob(path2, job.jobId, label, {
1054
+ pageSize,
1055
+ maxWaitMs,
1056
+ idempotencyKey: job.idempotencyKey
1057
+ });
1058
+ if (recovered.total !== requests.length) {
1059
+ throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
1060
+ }
1061
+ return recovered.rows;
1062
+ }
1063
+ async submitRecoverableCoreBatchJob(path2, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
666
1064
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
1065
+ const requestBody = {
1066
+ ...submissionFields,
1067
+ requests,
1068
+ idempotency_key: submissionIdempotencyKey,
1069
+ batch_item_indices: idempotencyItemIndices
1070
+ };
1071
+ if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
1072
+ assertLargeCoreProductInputBounds(path2, requestBody);
1073
+ }
667
1074
  let submission;
668
1075
  try {
669
- submission = await this.client.post(path2, {
670
- requests,
671
- idempotency_key: submissionIdempotencyKey,
672
- batch_item_indices: idempotencyItemIndices
673
- });
1076
+ submission = await this.client.post(path2, requestBody);
674
1077
  } catch (error) {
675
1078
  if (error instanceof SignalizError && !error.isRetryable) throw error;
676
1079
  const message = error instanceof Error ? error.message : String(error);
677
- throw new Error(
678
- `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
679
- );
1080
+ throw new SignalizError({
1081
+ code: "BATCH_SUBMISSION_UNCONFIRMED",
1082
+ message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
1083
+ errorType: "provider_error",
1084
+ details: {
1085
+ idempotency_key: submissionIdempotencyKey,
1086
+ suggested_action: "retry_with_idempotency_key",
1087
+ retry_eligible: true
1088
+ }
1089
+ });
680
1090
  }
681
1091
  const jobId = String(submission.job_id || "").trim();
682
- if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
683
- let status = await this.client.post(path2, {
684
- job_id: jobId,
685
- page: 1,
686
- page_size: pageSize
687
- });
688
- while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
689
- if (Date.now() - startedAt >= maxWaitMs) {
690
- throw new Error(`${label} batch ${jobId} did not complete within ${maxWaitMs}ms`);
691
- }
692
- const retryAfterMs = Number(status.next_poll_after_seconds || 2) * 1e3;
693
- await sleep2(Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt)));
694
- status = await this.client.post(path2, {
1092
+ if (!jobId) {
1093
+ throw new SignalizError({
1094
+ code: "INVALID_JOB_RESPONSE",
1095
+ message: `${label} batch did not return a recoverable job_id`,
1096
+ errorType: "provider_error",
1097
+ details: {
1098
+ idempotency_key: submissionIdempotencyKey,
1099
+ suggested_action: "retry_with_idempotency_key",
1100
+ retry_eligible: true
1101
+ }
1102
+ });
1103
+ }
1104
+ const submissionStatus = String(submission.status || "").toLowerCase();
1105
+ const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
1106
+ return {
1107
+ success: true,
1108
+ status,
1109
+ capability: path2 === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
1110
+ jobId,
1111
+ idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
1112
+ total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
1113
+ ...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
1114
+ nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
1115
+ } : {}
1116
+ };
1117
+ }
1118
+ async readRecoverableCoreBatchJob(path2, jobIdInput, label, options = {}) {
1119
+ const jobId = String(jobIdInput || "").trim();
1120
+ if (!jobId) throw new RangeError(`${label} jobId is required`);
1121
+ if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
1122
+ throw new RangeError("pageSize must be a positive finite number");
1123
+ }
1124
+ if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
1125
+ throw new RangeError("maxWaitMs must be a positive finite number");
1126
+ }
1127
+ if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
1128
+ throw new RangeError("pollIntervalMs must be a positive finite number");
1129
+ }
1130
+ const maximumPageSize = path2 === "api/v1/signal-to-copy" ? 100 : 25;
1131
+ const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
1132
+ const emailJob = path2 === "api/v1/find-email" || path2 === "api/v1/verify-email";
1133
+ const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
1134
+ const startedAt = Date.now();
1135
+ let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
1136
+ try {
1137
+ let status = await this.client.post(path2, {
695
1138
  job_id: jobId,
696
1139
  page: 1,
697
- page_size: pageSize
1140
+ page_size: pageSize,
1141
+ compact_duplicates: options.compactDuplicates === true
698
1142
  });
699
- }
700
- const totalPages = Math.max(1, Number(status.total_pages || 1));
701
- const pages = [Array.isArray(status.results) ? status.results : []];
702
- if (totalPages > 1) {
703
- const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
704
- const remainingPages = await runBatch(
705
- pageNumbers,
706
- async (page) => await this.client.post(path2, {
707
- job_id: jobId,
708
- page,
709
- page_size: pageSize
710
- }),
711
- { concurrency: Math.min(10, pageNumbers.length) }
1143
+ observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
1144
+ assertRecoverableBatchResultsRetained(
1145
+ status,
1146
+ label,
1147
+ jobId,
1148
+ observedIdempotencyKey
712
1149
  );
713
- for (const page of remainingPages.results) {
714
- if (!page.success || !page.data) {
715
- throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
1150
+ while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
1151
+ if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
1152
+ throw new SignalizError({
1153
+ code: "TIMEOUT",
1154
+ message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
1155
+ errorType: "timeout",
1156
+ retryAfter: Number(status.next_poll_after_seconds || 2),
1157
+ details: {
1158
+ job_id: jobId,
1159
+ ...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
1160
+ suggested_action: "check_job_status",
1161
+ retry_eligible: true
1162
+ }
1163
+ });
716
1164
  }
717
- pages.push(Array.isArray(page.data.results) ? page.data.results : []);
1165
+ const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
1166
+ const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
1167
+ await sleep2(boundedDelay);
1168
+ status = await this.client.post(path2, {
1169
+ job_id: jobId,
1170
+ page: 1,
1171
+ page_size: pageSize,
1172
+ compact_duplicates: options.compactDuplicates === true
1173
+ });
1174
+ observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
1175
+ assertRecoverableBatchResultsRetained(
1176
+ status,
1177
+ label,
1178
+ jobId,
1179
+ observedIdempotencyKey
1180
+ );
718
1181
  }
719
- }
720
- const rows = pages.flat();
721
- if (rows.length !== requests.length) {
722
- throw new Error(`${label} batch returned ${rows.length} results for ${requests.length} requests`);
723
- }
724
- const seen = /* @__PURE__ */ new Set();
725
- for (const row of rows) {
726
- const index = Number(row.index);
727
- if (!Number.isInteger(index) || index < 0 || index >= requests.length || seen.has(index)) {
728
- throw new Error(`${label} batch returned an invalid result index`);
1182
+ const totalPages = Math.max(1, Number(status.total_pages || 1));
1183
+ const pages = [Array.isArray(status.results) ? status.results : []];
1184
+ if (totalPages > 1) {
1185
+ const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
1186
+ const remainingPages = await runBatch(
1187
+ pageNumbers,
1188
+ async (page) => await this.client.post(path2, {
1189
+ job_id: jobId,
1190
+ page,
1191
+ page_size: pageSize,
1192
+ compact_duplicates: options.compactDuplicates === true
1193
+ }),
1194
+ { concurrency: Math.min(10, pageNumbers.length) }
1195
+ );
1196
+ for (const page of remainingPages.results) {
1197
+ if (!page.success || !page.data) {
1198
+ throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
1199
+ }
1200
+ observedIdempotencyKey = String(
1201
+ page.data.idempotency_key || observedIdempotencyKey
1202
+ ).trim();
1203
+ assertRecoverableBatchResultsRetained(
1204
+ page.data,
1205
+ label,
1206
+ jobId,
1207
+ observedIdempotencyKey
1208
+ );
1209
+ pages.push(Array.isArray(page.data.results) ? page.data.results : []);
1210
+ }
1211
+ }
1212
+ const rows = pages.flat();
1213
+ const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
1214
+ if (rows.length !== total) {
1215
+ throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
1216
+ }
1217
+ const seen = /* @__PURE__ */ new Set();
1218
+ for (const row of rows) {
1219
+ const index = Number(row.index);
1220
+ if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
1221
+ throw new Error(`${label} batch returned an invalid result index`);
1222
+ }
1223
+ seen.add(index);
729
1224
  }
730
- seen.add(index);
1225
+ if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
1226
+ return { rows, total };
1227
+ } catch (error) {
1228
+ if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
1229
+ throw error;
1230
+ }
1231
+ const signalizError = error instanceof SignalizError ? error : null;
1232
+ const message = error instanceof Error ? error.message : String(error);
1233
+ throw new SignalizError({
1234
+ code: signalizError?.code || "BATCH_RECOVERY_FAILED",
1235
+ message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
1236
+ errorType: signalizError?.errorType || "provider_error",
1237
+ retryAfter: signalizError?.retryAfter,
1238
+ details: {
1239
+ ...signalizError?.details || {},
1240
+ job_id: jobId,
1241
+ ...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
1242
+ suggested_action: "check_job_status",
1243
+ retry_eligible: true
1244
+ }
1245
+ });
731
1246
  }
732
- if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
733
- return rows;
734
1247
  }
735
1248
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
736
1249
  const results = new Array(requests.length);
@@ -795,8 +1308,11 @@ var Signaliz = class {
795
1308
  (task) => coreProductBatchRequestKey(path2, task.request)
796
1309
  );
797
1310
  const uniqueTasks = deduplicated.uniqueItems;
798
- const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
799
- if (path2 !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
1311
+ const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : void 0;
1312
+ const containsFindRecoveryReads = path2 === "api/v1/find-email" && uniqueTasks.some(
1313
+ (task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
1314
+ );
1315
+ if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
800
1316
  const rows = await this.runRecoverableCoreBatchJob(
801
1317
  path2,
802
1318
  uniqueTasks.map((task) => task.request),
@@ -922,18 +1438,68 @@ function batchItemFailure(index, error, raw) {
922
1438
  raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
923
1439
  );
924
1440
  const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
1441
+ const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
1442
+ const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
1443
+ const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
1444
+ const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
1445
+ const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
1446
+ const jobId = raw?.job_id ?? raw?.jobId;
1447
+ const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
925
1448
  return {
926
1449
  index,
927
1450
  success: false,
928
1451
  error,
929
1452
  ...errorCode ? { errorCode } : {},
930
1453
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
931
- ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
1454
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
1455
+ ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
1456
+ ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
1457
+ ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
1458
+ ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
1459
+ ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
1460
+ ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
1461
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
932
1462
  };
933
1463
  }
934
1464
  function recoverableJobRowFailed(row) {
935
1465
  return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
936
1466
  }
1467
+ function recoverableRowsToRound(rows, label) {
1468
+ return rows.map((raw) => {
1469
+ const index = Number(raw.index);
1470
+ const failed = recoverableJobRowFailed(raw);
1471
+ return {
1472
+ index,
1473
+ success: !failed,
1474
+ raw,
1475
+ error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
1476
+ };
1477
+ });
1478
+ }
1479
+ function recoverableSignalCopyRows(rows) {
1480
+ return rows.map((raw) => {
1481
+ const index = Number(raw.index);
1482
+ const duplicateOf = recoveredDuplicateOf(raw);
1483
+ if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
1484
+ if (recoverableJobRowFailed(raw)) {
1485
+ return batchItemFailure(
1486
+ index,
1487
+ raw.error || raw._error || raw.message || "Signal to Copy item failed",
1488
+ raw
1489
+ );
1490
+ }
1491
+ try {
1492
+ assertTerminalSignalResponse(raw, "Signal to Copy");
1493
+ return { index, success: true, data: normalizeSignalToCopyResult(raw) };
1494
+ } catch (error) {
1495
+ return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
1496
+ }
1497
+ });
1498
+ }
1499
+ function recoveredDuplicateOf(raw) {
1500
+ const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
1501
+ return Number.isInteger(value) && value >= 0 ? value : void 0;
1502
+ }
937
1503
  function newBatchIdempotencyKey(label) {
938
1504
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
939
1505
  return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
@@ -943,6 +1509,7 @@ function compact(input) {
943
1509
  }
944
1510
  function findEmailRequestBody(params) {
945
1511
  return compact({
1512
+ run_id: params.runId,
946
1513
  company_domain: params.companyDomain,
947
1514
  full_name: params.fullName,
948
1515
  first_name: params.firstName,
@@ -954,6 +1521,16 @@ function findEmailRequestBody(params) {
954
1521
  idempotency_key: params.idempotencyKey
955
1522
  });
956
1523
  }
1524
+ function verifyEmailBatchRequestBody(input, options) {
1525
+ if (typeof input === "string") {
1526
+ return compact({ email: input, skip_cache: options?.skipCache });
1527
+ }
1528
+ return compact({
1529
+ email: input.email,
1530
+ skip_cache: input.skipCache ?? options?.skipCache,
1531
+ idempotency_key: input.idempotencyKey
1532
+ });
1533
+ }
957
1534
  function isCoreProductDryRun(data) {
958
1535
  return data.dry_run === true && data.status === "planned";
959
1536
  }
@@ -982,76 +1559,111 @@ function normalizeCoreProductDryRunResult(data) {
982
1559
  };
983
1560
  }
984
1561
  function normalizeFindEmailResult(data) {
1562
+ const processing = String(data.status || "").toLowerCase() === "processing";
985
1563
  const failed = coreProductResponseFailed(data);
986
1564
  const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
987
1565
  const email = failed ? null : rawEmail;
988
1566
  const found = Boolean(email) && data.found !== false;
989
1567
  const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
1568
+ const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
1569
+ const verificationVerdict = data.verification_verdict ?? verificationStatus;
990
1570
  const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
991
1571
  const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
992
1572
  const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
993
1573
  const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
994
1574
  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()));
995
1575
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
1576
+ const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
1577
+ const verificationSource = data.verification_source ?? providerUsed;
1578
+ const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
996
1579
  return {
997
- success: !failed && found,
1580
+ success: processing || !failed && found,
998
1581
  found,
999
1582
  email,
1000
1583
  firstName: data.first_name,
1001
1584
  lastName: data.last_name,
1002
1585
  confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
1003
1586
  verificationStatus: failed ? "error" : verificationStatus,
1587
+ deliverabilityStatus: failed ? "error" : deliverabilityStatus,
1588
+ verificationVerdict: failed ? "error" : verificationVerdict,
1004
1589
  isVerified: !failed && verified,
1005
1590
  isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
1591
+ isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
1006
1592
  isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
1007
1593
  verificationFreshness,
1008
1594
  verificationAgeDays,
1009
1595
  verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
1010
1596
  needsReverification: failed || needsReverification,
1011
- providerUsed: data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown",
1012
- creditsUsed: data.credits_used,
1013
- estimatedExternalCostUsd: data.estimated_external_cost_usd,
1014
- billingMetadata: data.billing_metadata,
1015
- resultReturnedFrom: data.result_returned_from,
1016
- cacheHit: data.cache_hit,
1597
+ providerUsed,
1598
+ verificationSource,
1599
+ verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1600
+ failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
1601
+ creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
1602
+ estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
1603
+ billingMetadata,
1604
+ historicalBillingMetadata: data.historical_billing_metadata,
1605
+ resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
1606
+ cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
1017
1607
  negativeCacheHit: data.negative_cache_hit,
1018
1608
  negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
1019
1609
  cacheTier: data.cache_tier,
1020
- freshEnrichmentUsed: data.fresh_enrichment_used,
1021
- liveProviderCalled: data.live_provider_called,
1022
- openrouterCalled: data.openrouter_called,
1023
- byoOpenrouterUsed: data.byo_openrouter_used,
1024
- error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1025
- errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1610
+ freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
1611
+ liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
1612
+ openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
1613
+ byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
1614
+ error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1615
+ errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1616
+ status: processing ? "processing" : "completed",
1617
+ runId: data.run_id,
1618
+ nextPollAfterSeconds: data.next_poll_after_seconds,
1619
+ suggestedAction: data.suggested_action,
1620
+ cachePublicationStatus: data.cache_publication_status,
1621
+ cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1026
1622
  raw: data
1027
1623
  };
1028
1624
  }
1029
1625
  function normalizeVerifyEmailResult(email, data) {
1030
1626
  const failed = coreProductResponseFailed(data);
1031
- const verificationStatus = String(data.verification_status ?? "").toLowerCase();
1032
- const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
1033
1627
  const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
1628
+ const verificationStatus = String(
1629
+ failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
1630
+ ).toLowerCase();
1631
+ const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
1632
+ const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
1633
+ const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
1634
+ const verifiedForSending = !failed && data.verified_for_sending === true;
1635
+ const verificationRunId = data.verification_run_id ?? data.run_id;
1034
1636
  return {
1035
1637
  success: !failed,
1036
1638
  error: failed ? coreProductErrorMessage(data) : void 0,
1037
1639
  errorCode: failed ? coreProductErrorCode(data) : void 0,
1038
1640
  email: data.email ?? email,
1039
1641
  status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
1040
- verificationRunId: data.verification_run_id ?? data.run_id,
1642
+ verificationRunId,
1041
1643
  retryAfterMs: data.retry_after_ms,
1042
1644
  nextPollAfterSeconds: data.next_poll_after_seconds,
1043
1645
  isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
1044
1646
  isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
1045
1647
  isMalformed: malformed,
1046
- isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
1047
- verifiedForSending: !failed && data.verified_for_sending === true,
1648
+ isCatchAll,
1649
+ verifiedForSending,
1650
+ verificationStatus,
1651
+ deliverabilityStatus,
1048
1652
  verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
1653
+ isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
1654
+ quality: typeof data.quality === "string" ? data.quality : null,
1655
+ recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
1656
+ smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
1657
+ providerStatus: data.provider_status ?? verificationStatus,
1658
+ failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
1049
1659
  confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
1050
1660
  provider: data.provider,
1051
1661
  verificationSource: data.verification_source,
1662
+ verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1052
1663
  billingReplayed: data.billing_replayed,
1053
1664
  creditsUsed: data.credits_used,
1054
1665
  billingMetadata: data.billing_metadata,
1666
+ historicalBillingMetadata: data.historical_billing_metadata,
1055
1667
  resultReturnedFrom: data.result_returned_from,
1056
1668
  cacheHit: data.cache_hit,
1057
1669
  negativeCacheHit: data.negative_cache_hit,
@@ -1061,6 +1673,11 @@ function normalizeVerifyEmailResult(email, data) {
1061
1673
  liveProviderCalled: data.live_provider_called,
1062
1674
  openrouterCalled: data.openrouter_called,
1063
1675
  byoOpenrouterUsed: data.byo_openrouter_used,
1676
+ estimatedExternalCostUsd: data.estimated_external_cost_usd,
1677
+ runId: data.run_id ?? verificationRunId,
1678
+ cachePublicationStatus: data.cache_publication_status,
1679
+ cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1680
+ suggestedAction: data.suggested_action,
1064
1681
  raw: data
1065
1682
  };
1066
1683
  }
@@ -1088,6 +1705,54 @@ function companySignalRequestBody(params) {
1088
1705
  idempotency_key: params.idempotencyKey
1089
1706
  });
1090
1707
  }
1708
+ function companySignalParamsFromRecoveredRow(row) {
1709
+ return {
1710
+ companyDomain: row.company?.domain ?? row.company_domain,
1711
+ companyName: row.company?.name ?? row.company_name,
1712
+ signalRunId: row.signal_run_id ?? row.run_id
1713
+ };
1714
+ }
1715
+ var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
1716
+ "research_prompt",
1717
+ "signal_types",
1718
+ "target_signal_count",
1719
+ "lookback_days",
1720
+ "online",
1721
+ "enable_deep_search",
1722
+ "search_mode",
1723
+ "include_summary",
1724
+ "enable_outreach_intelligence",
1725
+ "enable_predictive_intelligence",
1726
+ "skip_cache",
1727
+ "include_candidate_evidence"
1728
+ ];
1729
+ function companySignalLargeBatchSubmission(requests) {
1730
+ if (requests.length <= 25) return null;
1731
+ if (requests.some(
1732
+ (request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
1733
+ )) {
1734
+ return null;
1735
+ }
1736
+ const submissionFields = Object.fromEntries(
1737
+ COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
1738
+ );
1739
+ const controlKey = JSON.stringify(submissionFields);
1740
+ for (const request of requests) {
1741
+ const requestControls = Object.fromEntries(
1742
+ COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
1743
+ );
1744
+ if (JSON.stringify(requestControls) !== controlKey) return null;
1745
+ const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
1746
+ if (!hasIdentity) return null;
1747
+ }
1748
+ return {
1749
+ requests: requests.map((request) => compact({
1750
+ company_domain: request.company_domain,
1751
+ company_name: request.company_name
1752
+ })),
1753
+ submissionFields
1754
+ };
1755
+ }
1091
1756
  function normalizeCompanySignalResult(params, data) {
1092
1757
  const failed = coreProductResponseFailed(data);
1093
1758
  const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
@@ -1158,6 +1823,12 @@ function validateSignalDiscoveryParams(params) {
1158
1823
  if (query && (query.length < 2 || query.length > 2e3)) {
1159
1824
  throw new RangeError("query must contain between 2 and 2000 characters");
1160
1825
  }
1826
+ if (params.icpContext !== void 0) {
1827
+ const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
1828
+ if (icpContext.length < 2 || icpContext.length > 4e3) {
1829
+ throw new RangeError("icpContext must contain between 2 and 4000 characters");
1830
+ }
1831
+ }
1161
1832
  if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
1162
1833
  throw new RangeError("limit must be an integer between 1 and 1000");
1163
1834
  }
@@ -1182,6 +1853,7 @@ function signalDiscoveryRequestBody(params) {
1182
1853
  }
1183
1854
  return compact({
1184
1855
  query: params.query?.trim(),
1856
+ icp_context: params.icpContext?.trim(),
1185
1857
  limit: params.limit ?? 100,
1186
1858
  sources: params.sources,
1187
1859
  lookback_days: params.lookbackDays,
@@ -1207,6 +1879,8 @@ function normalizeSignalDiscoveryResult(params, data) {
1207
1879
  availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
1208
1880
  sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
1209
1881
  evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
1882
+ discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
1883
+ identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
1210
1884
  costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
1211
1885
  warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
1212
1886
  raw: data
@@ -1235,7 +1909,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
1235
1909
  guardrail.projected_cost_per_requested_company_signal_usd
1236
1910
  ),
1237
1911
  sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
1238
- costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1912
+ costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1913
+ identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
1914
+ identityCostSource: [
1915
+ "observed_zero_credit_receipts",
1916
+ "contractual_zero_cost",
1917
+ "not_used",
1918
+ "unavailable"
1919
+ ].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
1920
+ identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
1239
1921
  qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
1240
1922
  returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
1241
1923
  availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
@@ -1321,6 +2003,9 @@ function dedupeExactBatchItems(items, keyForItem) {
1321
2003
  function normalizedBatchText(value) {
1322
2004
  return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
1323
2005
  }
2006
+ function normalizedBatchIdempotencyKey(value) {
2007
+ return typeof value === "string" ? value.trim() : "";
2008
+ }
1324
2009
  function normalizedBatchDomain(value) {
1325
2010
  return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
1326
2011
  }
@@ -1339,11 +2024,13 @@ function normalizedFindEmailBatchName(request) {
1339
2024
  function coreProductBatchRequestKey(path2, request) {
1340
2025
  if (path2 === "api/v1/find-email") {
1341
2026
  return JSON.stringify([
2027
+ normalizedBatchText(request.run_id),
1342
2028
  normalizedFindEmailBatchName(request),
1343
2029
  normalizedBatchDomain(request.company_domain),
1344
2030
  normalizedBatchLinkedIn(request.linkedin_url),
1345
2031
  normalizedBatchText(request.company_name),
1346
- request.skip_cache === true || request.bypass_cache === true
2032
+ request.skip_cache === true || request.bypass_cache === true,
2033
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1347
2034
  ]);
1348
2035
  }
1349
2036
  if (path2 === "api/v1/verify-email") {
@@ -1351,7 +2038,8 @@ function coreProductBatchRequestKey(path2, request) {
1351
2038
  normalizedBatchText(request.email),
1352
2039
  request.skip_cache === true || request.bypass_cache === true,
1353
2040
  request.skip_catch_all_verification === true,
1354
- request.skip_esp_check === true
2041
+ request.skip_esp_check === true,
2042
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1355
2043
  ]);
1356
2044
  }
1357
2045
  if (path2 === "api/v1/company-signals") {
@@ -1373,7 +2061,8 @@ function coreProductBatchRequestKey(path2, request) {
1373
2061
  normalizedBatchText(request.search_mode || "balanced"),
1374
2062
  request.enable_outreach_intelligence === true,
1375
2063
  request.enable_predictive_intelligence === true,
1376
- request.skip_cache === true || request.bypass_cache === true
2064
+ request.skip_cache === true || request.bypass_cache === true,
2065
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1377
2066
  ]);
1378
2067
  }
1379
2068
  return JSON.stringify([
@@ -1382,9 +2071,11 @@ function coreProductBatchRequestKey(path2, request) {
1382
2071
  typeof request.title === "string" ? request.title.trim() : "",
1383
2072
  typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
1384
2073
  typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
2074
+ Number(request.lookback_days) || null,
1385
2075
  typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
1386
2076
  request.skip_cache === true,
1387
- request.enable_deep_search === true
2077
+ request.enable_deep_search === true,
2078
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1388
2079
  ]);
1389
2080
  }
1390
2081
  function finalizeCoreBatch(total, startedAt, round, normalize, options) {
@@ -1422,6 +2113,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
1422
2113
  results: compactedResults
1423
2114
  };
1424
2115
  }
2116
+ function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
2117
+ const results = round.map((item) => {
2118
+ const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
2119
+ if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
2120
+ if (!item.success || !item.raw) {
2121
+ return batchItemFailure(
2122
+ item.index,
2123
+ item.error || "Signaliz batch item failed",
2124
+ item.raw ?? item
2125
+ );
2126
+ }
2127
+ try {
2128
+ return { index: item.index, success: true, data: normalize(item) };
2129
+ } catch (error) {
2130
+ return {
2131
+ index: item.index,
2132
+ success: false,
2133
+ error: error instanceof Error ? error.message : String(error)
2134
+ };
2135
+ }
2136
+ });
2137
+ const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
2138
+ const succeeded = compactedResults.filter((item) => item.success).length;
2139
+ return {
2140
+ total,
2141
+ succeeded,
2142
+ failed: total - succeeded,
2143
+ durationMs: Date.now() - startedAt,
2144
+ results: compactedResults
2145
+ };
2146
+ }
1425
2147
  function compactExactDuplicateResults(results, enabled) {
1426
2148
  if (!enabled) return results;
1427
2149
  const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
@@ -1437,6 +2159,19 @@ function compactExactDuplicateResults(results, enabled) {
1437
2159
  return item;
1438
2160
  });
1439
2161
  }
2162
+ function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
2163
+ if (!enabled) return batch;
2164
+ const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
2165
+ sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
2166
+ if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
2167
+ });
2168
+ const results = batch.results.map((item, index) => {
2169
+ const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
2170
+ if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
2171
+ return { index, success: true, duplicateOf: canonicalIndex };
2172
+ });
2173
+ return { ...batch, results };
2174
+ }
1440
2175
  function signalToCopyRequestBody(params) {
1441
2176
  return compact({
1442
2177
  company_domain: params.companyDomain,
@@ -1444,6 +2179,7 @@ function signalToCopyRequestBody(params) {
1444
2179
  title: params.title,
1445
2180
  campaign_offer: params.campaignOffer,
1446
2181
  research_prompt: params.researchPrompt,
2182
+ lookback_days: params.lookbackDays,
1447
2183
  signal_run_id: params.signalRunId,
1448
2184
  skip_cache: params.skipCache,
1449
2185
  enable_deep_search: params.enableDeepSearch,
@@ -1452,8 +2188,33 @@ function signalToCopyRequestBody(params) {
1452
2188
  idempotency_key: params.idempotencyKey
1453
2189
  });
1454
2190
  }
2191
+ function validateLargeAvailableDataSignalCopyBatch(requests) {
2192
+ if (requests.length <= 25) return;
2193
+ const invalidIndex = requests.findIndex(
2194
+ (request) => request.skipCache === true || request.enableDeepSearch === true || Boolean(request.idempotencyKey?.trim()) || request.maxWaitMs !== void 0 || request.pollIntervalMs !== void 0 || request.waitForResult === false || !request.companyDomain?.trim() || !request.personName?.trim() || !request.title?.trim() || !request.campaignOffer?.trim()
2195
+ );
2196
+ if (invalidIndex >= 0) {
2197
+ throw new RangeError(
2198
+ `Signal to Copy batches larger than 25 are available-data-only; row ${invalidIndex} requests fresh/deep/synchronous controls or lacks required recipient fields. Split fresh requests into batches of at most 25.`
2199
+ );
2200
+ }
2201
+ }
2202
+ function availableDataSignalCopyRequestBody(params) {
2203
+ return compact({
2204
+ company_domain: params.companyDomain,
2205
+ person_name: params.personName,
2206
+ title: params.title,
2207
+ campaign_offer: params.campaignOffer,
2208
+ research_prompt: params.researchPrompt,
2209
+ lookback_days: params.lookbackDays,
2210
+ signal_run_id: params.signalRunId
2211
+ });
2212
+ }
1455
2213
  function validateSignalToCopyParams(params) {
1456
2214
  validateCoreProductMaxWaitMs(params.maxWaitMs);
2215
+ if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
2216
+ throw new RangeError("lookbackDays must be an integer between 1 and 365");
2217
+ }
1457
2218
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1458
2219
  const missing = [
1459
2220
  ["companyDomain", params.companyDomain],
@@ -1582,6 +2343,21 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
1582
2343
  function sleep2(ms) {
1583
2344
  return new Promise((resolve) => setTimeout(resolve, ms));
1584
2345
  }
2346
+ function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
2347
+ if (status.results_expired !== true && status.results_cleaned !== true) return;
2348
+ throw new SignalizError({
2349
+ code: "BATCH_RESULTS_EXPIRED",
2350
+ message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
2351
+ errorType: "not_found",
2352
+ details: {
2353
+ job_id: jobId,
2354
+ ...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
2355
+ suggested_action: "review_before_resubmitting",
2356
+ retry_eligible: false,
2357
+ results_expired: true
2358
+ }
2359
+ });
2360
+ }
1585
2361
  async function runBatch(items, execute, options) {
1586
2362
  validateBatchSize(items);
1587
2363
  const concurrency = validatedBatchConcurrency(options);