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