@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.
@@ -9,17 +9,21 @@ var SignalizError = class extends Error {
9
9
  this.details = data.details;
10
10
  }
11
11
  get isRetryable() {
12
+ if (typeof this.details?.retry_eligible === "boolean") {
13
+ return this.details.retry_eligible;
14
+ }
12
15
  return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
13
16
  }
14
17
  };
15
18
  function parseError(status, body) {
16
19
  if (body?.error && typeof body.error === "object") {
20
+ const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
17
21
  return new SignalizError({
18
22
  code: body.error.code || `HTTP_${status}`,
19
23
  message: body.error.message || `Request failed with status ${status}`,
20
24
  errorType: mapErrorType(body.error.code, status),
21
25
  retryAfter: retryAfterSeconds(body.error),
22
- details: body.error.details
26
+ details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
23
27
  });
24
28
  }
25
29
  const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
@@ -28,7 +32,7 @@ function parseError(status, body) {
28
32
  message: body?.message || body?.error || `Request failed with status ${status}`,
29
33
  errorType: mapErrorType(code, status),
30
34
  retryAfter: retryAfterSeconds(body),
31
- details: body?.details || publicRetryDetails(body)
35
+ details: mergePublicErrorDetails(body, body?.details)
32
36
  });
33
37
  }
34
38
  function retryAfterSeconds(body) {
@@ -40,17 +44,35 @@ function publicRetryDetails(body) {
40
44
  ...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
41
45
  ...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
42
46
  ...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
47
+ ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
48
+ ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
49
+ ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
50
+ ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
51
+ ...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
52
+ ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
43
53
  ...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
54
+ ...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
55
+ ...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
56
+ ...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
57
+ ...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
44
58
  ...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
45
59
  ...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
46
60
  ...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
47
61
  };
48
62
  return Object.keys(details).length > 0 ? details : void 0;
49
63
  }
64
+ function mergePublicErrorDetails(body, explicitDetails) {
65
+ const merged = { ...publicRetryDetails(body) || {} };
66
+ if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
67
+ Object.assign(merged, explicitDetails);
68
+ }
69
+ return Object.keys(merged).length > 0 ? merged : void 0;
70
+ }
50
71
  function mapErrorType(code, status) {
51
72
  if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
52
- if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
73
+ if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
53
74
  if (code === "NOT_FOUND" || status === 404) return "not_found";
75
+ if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
54
76
  if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
55
77
  if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
56
78
  if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
@@ -61,7 +83,7 @@ function mapErrorType(code, status) {
61
83
 
62
84
  // src/client.ts
63
85
  var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
64
- var DEFAULT_TIMEOUT = 12e4;
86
+ var DEFAULT_TIMEOUT = 13e4;
65
87
  var DEFAULT_MAX_RETRIES = 3;
66
88
  var HttpClient = class {
67
89
  constructor(config) {
@@ -75,9 +97,9 @@ var HttpClient = class {
75
97
  throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
76
98
  }
77
99
  }
78
- async request(functionName, body, method = "POST") {
100
+ async request(functionName, body, method = "POST", options = {}) {
79
101
  let lastError;
80
- const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
102
+ const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
81
103
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
82
104
  let timer;
83
105
  try {
@@ -108,7 +130,9 @@ var HttpClient = class {
108
130
  const errBody = await res.json().catch(() => ({}));
109
131
  const err = parseError(res.status, errBody);
110
132
  if (err.isRetryable && attempt < this.maxRetries) {
111
- const wait = err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
133
+ const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
134
+ if (retryWithNewKey) throw err;
135
+ const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
112
136
  await sleep(wait);
113
137
  lastError = err;
114
138
  continue;
@@ -143,8 +167,8 @@ var HttpClient = class {
143
167
  throw lastError || new Error("Request failed after retries");
144
168
  }
145
169
  /** Convenience wrapper for POST-based edge function calls */
146
- async post(functionName, body) {
147
- return this.request(functionName, body, "POST");
170
+ async post(functionName, body, options = {}) {
171
+ return this.request(functionName, body, "POST", options);
148
172
  }
149
173
  /** Convenience wrapper for GET-based edge function calls with query params */
150
174
  async get(functionName, query = {}) {
@@ -261,10 +285,12 @@ var HttpClient = class {
261
285
  return this.accessTokenPromise;
262
286
  }
263
287
  };
264
- function requestIdempotencyKey(body) {
288
+ function requestIdempotencyKey(body, automaticIdempotency) {
265
289
  const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
290
+ if (requested) return requested;
291
+ if (!automaticIdempotency) return void 0;
266
292
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
267
- return requested || `sdk_${nonce}`;
293
+ return `sdk_${nonce}`;
268
294
  }
269
295
  function sleep(ms) {
270
296
  return new Promise((r) => setTimeout(r, ms));
@@ -366,7 +392,7 @@ function mapMcpErrorType(error) {
366
392
  function mapErrorTypeFromCode(code) {
367
393
  if (!code) return "unknown";
368
394
  if (code === "RATE_LIMITED") return "rate_limited";
369
- 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";
395
+ 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";
370
396
  if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
371
397
  if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
372
398
  if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
@@ -374,6 +400,166 @@ function mapErrorTypeFromCode(code) {
374
400
  return "unknown";
375
401
  }
376
402
 
403
+ // src/email-validation.ts
404
+ function isValidEmailString(value) {
405
+ if (typeof value !== "string") return false;
406
+ const email = value.trim();
407
+ if (!email || email.length > 254) return false;
408
+ const separator = email.indexOf("@");
409
+ if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
410
+ const local = email.slice(0, separator);
411
+ const domain = email.slice(separator + 1).toLowerCase();
412
+ if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
413
+ if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
414
+ const labels = domain.split(".");
415
+ if (labels.length < 2) return false;
416
+ return labels.every(
417
+ (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
418
+ );
419
+ }
420
+
421
+ // src/core-product-input-bounds.ts
422
+ var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
423
+ var DOMAIN_MAX_UTF8_BYTES = 253;
424
+ var NAME_MAX_UTF8_BYTES = 500;
425
+ var TITLE_MAX_UTF8_BYTES = 500;
426
+ var ID_MAX_UTF8_BYTES = 200;
427
+ var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
428
+ var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
429
+ var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
430
+ var UTF8_ENCODER = new TextEncoder();
431
+ function byteLength(value) {
432
+ return UTF8_ENCODER.encode(value).byteLength;
433
+ }
434
+ function payloadTooLarge(message, details) {
435
+ throw new SignalizError({
436
+ code: "PAYLOAD_TOO_LARGE",
437
+ message,
438
+ errorType: "validation",
439
+ details: {
440
+ ...details,
441
+ retry_eligible: false,
442
+ credits_used: 0,
443
+ credits_charged: 0
444
+ }
445
+ });
446
+ }
447
+ function assertStringBytes(value, field, maxBytes, index) {
448
+ if (typeof value !== "string") return;
449
+ const encodedBytes = byteLength(value);
450
+ if (encodedBytes <= maxBytes) return;
451
+ payloadTooLarge(
452
+ `${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
453
+ {
454
+ field,
455
+ ...index !== void 0 ? { index } : {},
456
+ encoded_bytes: encodedBytes,
457
+ max_bytes: maxBytes
458
+ }
459
+ );
460
+ }
461
+ function assertCompanySignalRows(body) {
462
+ const requests = Array.isArray(body.requests) ? body.requests : [];
463
+ assertStringBytes(
464
+ body.research_prompt,
465
+ "research_prompt",
466
+ RESEARCH_PROMPT_MAX_UTF8_BYTES
467
+ );
468
+ if (Array.isArray(body.signal_types)) {
469
+ body.signal_types.forEach(
470
+ (signalType, index) => assertStringBytes(
471
+ signalType,
472
+ `signal_types[${index}]`,
473
+ SIGNAL_TYPE_MAX_UTF8_BYTES,
474
+ index
475
+ )
476
+ );
477
+ }
478
+ requests.forEach((value, index) => {
479
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
480
+ const request = value;
481
+ assertStringBytes(
482
+ request.company_domain,
483
+ `requests[${index}].company_domain`,
484
+ DOMAIN_MAX_UTF8_BYTES,
485
+ index
486
+ );
487
+ assertStringBytes(
488
+ request.company_name,
489
+ `requests[${index}].company_name`,
490
+ NAME_MAX_UTF8_BYTES,
491
+ index
492
+ );
493
+ });
494
+ }
495
+ function assertSignalCopyRows(body) {
496
+ const requests = Array.isArray(body.requests) ? body.requests : [];
497
+ requests.forEach((value, index) => {
498
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
499
+ const request = value;
500
+ assertStringBytes(
501
+ request.company_domain,
502
+ `requests[${index}].company_domain`,
503
+ DOMAIN_MAX_UTF8_BYTES,
504
+ index
505
+ );
506
+ assertStringBytes(
507
+ request.person_name,
508
+ `requests[${index}].person_name`,
509
+ NAME_MAX_UTF8_BYTES,
510
+ index
511
+ );
512
+ assertStringBytes(
513
+ request.title,
514
+ `requests[${index}].title`,
515
+ TITLE_MAX_UTF8_BYTES,
516
+ index
517
+ );
518
+ assertStringBytes(
519
+ request.campaign_offer,
520
+ `requests[${index}].campaign_offer`,
521
+ CAMPAIGN_OFFER_MAX_UTF8_BYTES,
522
+ index
523
+ );
524
+ assertStringBytes(
525
+ request.research_prompt,
526
+ `requests[${index}].research_prompt`,
527
+ RESEARCH_PROMPT_MAX_UTF8_BYTES,
528
+ index
529
+ );
530
+ assertStringBytes(
531
+ request.signal_run_id,
532
+ `requests[${index}].signal_run_id`,
533
+ ID_MAX_UTF8_BYTES,
534
+ index
535
+ );
536
+ });
537
+ }
538
+ function assertLargeCoreProductInputBounds(path, body) {
539
+ const requests = Array.isArray(body.requests) ? body.requests : [];
540
+ if (requests.length <= 25) return;
541
+ assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
542
+ if (path === "api/v1/company-signals") {
543
+ assertCompanySignalRows(body);
544
+ } else {
545
+ assertSignalCopyRows(body);
546
+ }
547
+ const encoded = JSON.stringify(body);
548
+ if (typeof encoded !== "string") {
549
+ throw new TypeError("Core product batch input must be JSON serializable.");
550
+ }
551
+ const encodedBytes = byteLength(encoded);
552
+ if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
553
+ payloadTooLarge(
554
+ `${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`,
555
+ {
556
+ field: "requests",
557
+ encoded_bytes: encodedBytes,
558
+ max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
559
+ }
560
+ );
561
+ }
562
+
377
563
  // src/index.ts
378
564
  var MAX_ROW_RATE_LIMIT_RETRIES = 2;
379
565
  var Signaliz = class {
@@ -407,9 +593,25 @@ var Signaliz = class {
407
593
  options
408
594
  );
409
595
  }
596
+ async resumeFindEmailBatch(jobId, options) {
597
+ const startedAt = Date.now();
598
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
599
+ "api/v1/find-email",
600
+ jobId,
601
+ "Find Email",
602
+ options
603
+ );
604
+ return finalizeCoreBatch(
605
+ total,
606
+ startedAt,
607
+ recoverableRowsToRound(rows, "Find Email"),
608
+ (item) => normalizeFindEmailResult(item.raw),
609
+ options
610
+ );
611
+ }
410
612
  async verifyEmail(email, options) {
411
613
  const normalizedEmail = typeof email === "string" ? email.trim() : "";
412
- if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
614
+ if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
413
615
  const error = `Invalid email format: "${normalizedEmail}"`;
414
616
  return normalizeVerifyEmailResult(normalizedEmail, {
415
617
  success: false,
@@ -459,19 +661,20 @@ var Signaliz = class {
459
661
  }
460
662
  async verifyEmails(emails, options) {
461
663
  validateBatchSize(emails);
664
+ const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
462
665
  if (options?.dryRun === true) {
463
666
  return this.runCoreProductDryRun(
464
667
  "api/v1/verify-email",
465
- emails.map((email) => ({ email, skip_cache: options.skipCache })),
668
+ requests,
466
669
  options.idempotencyKey
467
670
  );
468
671
  }
469
672
  const startedAt = Date.now();
470
673
  const round = await this.runCoreBatchRound(
471
674
  "api/v1/verify-email",
472
- emails.map((email, index) => ({
675
+ requests.map((request, index) => ({
473
676
  index,
474
- request: { email, skip_cache: options?.skipCache }
677
+ request
475
678
  })),
476
679
  options
477
680
  );
@@ -479,7 +682,23 @@ var Signaliz = class {
479
682
  emails.length,
480
683
  startedAt,
481
684
  round,
482
- (item) => normalizeVerifyEmailResult(emails[item.index], item.raw),
685
+ (item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
686
+ options
687
+ );
688
+ }
689
+ async resumeVerifyEmailBatch(jobId, options) {
690
+ const startedAt = Date.now();
691
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
692
+ "api/v1/verify-email",
693
+ jobId,
694
+ "Verify Email",
695
+ options
696
+ );
697
+ return finalizeCoreBatch(
698
+ total,
699
+ startedAt,
700
+ recoverableRowsToRound(rows, "Verify Email"),
701
+ (item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
483
702
  options
484
703
  );
485
704
  }
@@ -495,25 +714,92 @@ var Signaliz = class {
495
714
  validateSignalDiscoveryParams(params);
496
715
  const data = await this.client.post(
497
716
  "api/v1/signals",
498
- signalDiscoveryRequestBody(params)
717
+ signalDiscoveryRequestBody(params),
718
+ // Signals Everything owns a normalized, workspace-scoped automatic key
719
+ // at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
720
+ { automaticIdempotency: false }
499
721
  );
500
722
  return normalizeSignalDiscoveryResult(params, data);
501
723
  }
502
724
  async enrichCompanies(companies, options) {
503
725
  validateBatchSize(companies);
504
726
  companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
727
+ const requests = companies.map(companySignalRequestBody);
728
+ if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
729
+ throw new RangeError(
730
+ "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."
731
+ );
732
+ }
733
+ const largeBatch = companySignalLargeBatchSubmission(requests);
734
+ if (companies.length > 25 && !largeBatch) {
735
+ throw new RangeError(
736
+ "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."
737
+ );
738
+ }
505
739
  if (options?.dryRun === true) {
506
740
  return this.runCoreProductDryRun(
507
741
  "api/v1/company-signals",
508
- companies.map(companySignalRequestBody),
509
- options.idempotencyKey
742
+ largeBatch?.requests || requests,
743
+ options.idempotencyKey,
744
+ largeBatch?.submissionFields
510
745
  );
511
746
  }
512
747
  const startedAt = Date.now();
513
- const initialTasks = companies.map((params, index) => ({
514
- index,
515
- request: companySignalRequestBody(params)
516
- }));
748
+ if (options?.waitForResult === false) {
749
+ if (!largeBatch) {
750
+ throw new RangeError(
751
+ "Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
752
+ );
753
+ }
754
+ return this.submitRecoverableCoreBatchJob(
755
+ "api/v1/company-signals",
756
+ largeBatch.requests,
757
+ "Company Signals",
758
+ options.idempotencyKey,
759
+ companies.map((_, index) => index),
760
+ largeBatch.submissionFields
761
+ );
762
+ }
763
+ if (largeBatch) {
764
+ const job = await this.submitRecoverableCoreBatchJob(
765
+ "api/v1/company-signals",
766
+ largeBatch.requests,
767
+ "Company Signals",
768
+ options?.idempotencyKey,
769
+ companies.map((_, index) => index),
770
+ largeBatch.submissionFields
771
+ );
772
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
773
+ "api/v1/company-signals",
774
+ job.jobId,
775
+ "Company Signals",
776
+ {
777
+ pageSize: 25,
778
+ maxWaitMs: options?.maxWaitMs,
779
+ pollIntervalMs: options?.pollIntervalMs,
780
+ idempotencyKey: job.idempotencyKey,
781
+ compactDuplicates: options?.compactDuplicates === true
782
+ }
783
+ );
784
+ if (total !== companies.length) {
785
+ throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
786
+ }
787
+ const batch = finalizeCoreBatch(
788
+ companies.length,
789
+ startedAt,
790
+ recoverableRowsToRound(rows, "Company Signals"),
791
+ (item) => normalizeCompanySignalResult(companies[item.index], item.raw)
792
+ );
793
+ return compactKnownDuplicateBatch(
794
+ batch,
795
+ dedupeExactBatchItems(
796
+ requests,
797
+ (request) => coreProductBatchRequestKey("api/v1/company-signals", request)
798
+ ).sourceIndexByInput,
799
+ options?.compactDuplicates === true
800
+ );
801
+ }
802
+ const initialTasks = requests.map((request, index) => ({ index, request }));
517
803
  const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
518
804
  const results = round.map((item) => {
519
805
  if (!item.success || !item.raw) {
@@ -536,6 +822,22 @@ var Signaliz = class {
536
822
  results: compactedResults
537
823
  };
538
824
  }
825
+ async resumeCompanySignalBatch(jobId, options) {
826
+ const startedAt = Date.now();
827
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
828
+ "api/v1/company-signals",
829
+ jobId,
830
+ "Company Signals",
831
+ options
832
+ );
833
+ return finalizeRecoveredCoreBatch(
834
+ total,
835
+ startedAt,
836
+ recoverableRowsToRound(rows, "Company Signals"),
837
+ (item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
838
+ options
839
+ );
840
+ }
539
841
  async signalToCopy(params) {
540
842
  validateSignalToCopyParams(params);
541
843
  const request = signalToCopyRequestBody(params);
@@ -547,6 +849,7 @@ var Signaliz = class {
547
849
  async createSignalCopyBatch(requests, options) {
548
850
  validateBatchSize(requests);
549
851
  requests.forEach(validateSignalToCopyParams);
852
+ validateLargeAvailableDataSignalCopyBatch(requests);
550
853
  if (options?.dryRun === true) {
551
854
  return this.runCoreProductDryRun(
552
855
  "api/v1/signal-to-copy",
@@ -555,6 +858,59 @@ var Signaliz = class {
555
858
  );
556
859
  }
557
860
  const startedAt = Date.now();
861
+ if (requests.length > 25) {
862
+ if (options?.waitForResult === false) {
863
+ return this.submitRecoverableCoreBatchJob(
864
+ "api/v1/signal-to-copy",
865
+ requests.map(availableDataSignalCopyRequestBody),
866
+ "Signal to Copy",
867
+ options.idempotencyKey,
868
+ requests.map((_, index) => index)
869
+ );
870
+ }
871
+ const job = await this.submitRecoverableCoreBatchJob(
872
+ "api/v1/signal-to-copy",
873
+ requests.map(availableDataSignalCopyRequestBody),
874
+ "Signal to Copy",
875
+ options?.idempotencyKey,
876
+ requests.map((_, index) => index)
877
+ );
878
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
879
+ "api/v1/signal-to-copy",
880
+ job.jobId,
881
+ "Signal to Copy",
882
+ {
883
+ pageSize: 100,
884
+ maxWaitMs: options?.maxWaitMs,
885
+ pollIntervalMs: options?.pollIntervalMs,
886
+ idempotencyKey: job.idempotencyKey,
887
+ compactDuplicates: options?.compactDuplicates === true
888
+ }
889
+ );
890
+ if (total !== requests.length) {
891
+ throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
892
+ }
893
+ const results2 = recoverableSignalCopyRows(rows);
894
+ const succeeded2 = results2.filter((item) => item.success).length;
895
+ const batch = {
896
+ total: requests.length,
897
+ succeeded: succeeded2,
898
+ failed: requests.length - succeeded2,
899
+ durationMs: Date.now() - startedAt,
900
+ results: results2
901
+ };
902
+ return compactKnownDuplicateBatch(
903
+ batch,
904
+ dedupeExactBatchItems(
905
+ requests,
906
+ (params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
907
+ ).sourceIndexByInput,
908
+ options?.compactDuplicates === true
909
+ );
910
+ }
911
+ if (options?.waitForResult === false) {
912
+ throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
913
+ }
558
914
  const deduplicated = dedupeExactBatchItems(
559
915
  requests,
560
916
  (params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
@@ -579,12 +935,36 @@ var Signaliz = class {
579
935
  results: compactedResults
580
936
  };
581
937
  }
582
- async runCoreProductDryRun(path, requests, idempotencyKey) {
583
- const data = await this.client.post(path, compact({
938
+ async resumeSignalCopyBatch(jobId, options) {
939
+ const startedAt = Date.now();
940
+ const { rows, total } = await this.readRecoverableCoreBatchJob(
941
+ "api/v1/signal-to-copy",
942
+ jobId,
943
+ "Signal to Copy",
944
+ options
945
+ );
946
+ const results = recoverableSignalCopyRows(rows);
947
+ const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
948
+ const succeeded = compactedResults.filter((item) => item.success).length;
949
+ return {
950
+ total,
951
+ succeeded,
952
+ failed: total - succeeded,
953
+ durationMs: Date.now() - startedAt,
954
+ results: compactedResults
955
+ };
956
+ }
957
+ async runCoreProductDryRun(path, requests, idempotencyKey, defaults = {}) {
958
+ const requestBody = compact({
584
959
  requests,
960
+ ...defaults,
585
961
  dry_run: true,
586
962
  idempotency_key: idempotencyKey
587
- }));
963
+ });
964
+ if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
965
+ assertLargeCoreProductInputBounds(path, requestBody);
966
+ }
967
+ const data = await this.client.post(path, requestBody);
588
968
  if (!isCoreProductDryRun(data)) {
589
969
  throw new Error(`${path} dry run returned a live-result contract`);
590
970
  }
@@ -630,76 +1010,209 @@ var Signaliz = class {
630
1010
  }
631
1011
  return uniqueResults;
632
1012
  }
633
- async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
634
- const startedAt = Date.now();
1013
+ async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
1014
+ const job = await this.submitRecoverableCoreBatchJob(
1015
+ path,
1016
+ requests,
1017
+ label,
1018
+ idempotencyKey,
1019
+ idempotencyItemIndices,
1020
+ submissionFields
1021
+ );
1022
+ const recovered = await this.readRecoverableCoreBatchJob(path, job.jobId, label, {
1023
+ pageSize,
1024
+ maxWaitMs,
1025
+ idempotencyKey: job.idempotencyKey
1026
+ });
1027
+ if (recovered.total !== requests.length) {
1028
+ throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
1029
+ }
1030
+ return recovered.rows;
1031
+ }
1032
+ async submitRecoverableCoreBatchJob(path, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
635
1033
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
1034
+ const requestBody = {
1035
+ ...submissionFields,
1036
+ requests,
1037
+ idempotency_key: submissionIdempotencyKey,
1038
+ batch_item_indices: idempotencyItemIndices
1039
+ };
1040
+ if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
1041
+ assertLargeCoreProductInputBounds(path, requestBody);
1042
+ }
636
1043
  let submission;
637
1044
  try {
638
- submission = await this.client.post(path, {
639
- requests,
640
- idempotency_key: submissionIdempotencyKey,
641
- batch_item_indices: idempotencyItemIndices
642
- });
1045
+ submission = await this.client.post(path, requestBody);
643
1046
  } catch (error) {
644
1047
  if (error instanceof SignalizError && !error.isRetryable) throw error;
645
1048
  const message = error instanceof Error ? error.message : String(error);
646
- throw new Error(
647
- `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
648
- );
1049
+ throw new SignalizError({
1050
+ code: "BATCH_SUBMISSION_UNCONFIRMED",
1051
+ message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
1052
+ errorType: "provider_error",
1053
+ details: {
1054
+ idempotency_key: submissionIdempotencyKey,
1055
+ suggested_action: "retry_with_idempotency_key",
1056
+ retry_eligible: true
1057
+ }
1058
+ });
649
1059
  }
650
1060
  const jobId = String(submission.job_id || "").trim();
651
- if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
652
- let status = await this.client.post(path, {
653
- job_id: jobId,
654
- page: 1,
655
- page_size: pageSize
656
- });
657
- while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
658
- if (Date.now() - startedAt >= maxWaitMs) {
659
- throw new Error(`${label} batch ${jobId} did not complete within ${maxWaitMs}ms`);
660
- }
661
- const retryAfterMs = Number(status.next_poll_after_seconds || 2) * 1e3;
662
- await sleep2(Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt)));
663
- status = await this.client.post(path, {
1061
+ if (!jobId) {
1062
+ throw new SignalizError({
1063
+ code: "INVALID_JOB_RESPONSE",
1064
+ message: `${label} batch did not return a recoverable job_id`,
1065
+ errorType: "provider_error",
1066
+ details: {
1067
+ idempotency_key: submissionIdempotencyKey,
1068
+ suggested_action: "retry_with_idempotency_key",
1069
+ retry_eligible: true
1070
+ }
1071
+ });
1072
+ }
1073
+ const submissionStatus = String(submission.status || "").toLowerCase();
1074
+ const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
1075
+ return {
1076
+ success: true,
1077
+ status,
1078
+ capability: path === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
1079
+ jobId,
1080
+ idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
1081
+ total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
1082
+ ...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
1083
+ nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
1084
+ } : {}
1085
+ };
1086
+ }
1087
+ async readRecoverableCoreBatchJob(path, jobIdInput, label, options = {}) {
1088
+ const jobId = String(jobIdInput || "").trim();
1089
+ if (!jobId) throw new RangeError(`${label} jobId is required`);
1090
+ if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
1091
+ throw new RangeError("pageSize must be a positive finite number");
1092
+ }
1093
+ if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
1094
+ throw new RangeError("maxWaitMs must be a positive finite number");
1095
+ }
1096
+ if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
1097
+ throw new RangeError("pollIntervalMs must be a positive finite number");
1098
+ }
1099
+ const maximumPageSize = path === "api/v1/signal-to-copy" ? 100 : 25;
1100
+ const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
1101
+ const emailJob = path === "api/v1/find-email" || path === "api/v1/verify-email";
1102
+ const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
1103
+ const startedAt = Date.now();
1104
+ let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
1105
+ try {
1106
+ let status = await this.client.post(path, {
664
1107
  job_id: jobId,
665
1108
  page: 1,
666
- page_size: pageSize
1109
+ page_size: pageSize,
1110
+ compact_duplicates: options.compactDuplicates === true
667
1111
  });
668
- }
669
- const totalPages = Math.max(1, Number(status.total_pages || 1));
670
- const pages = [Array.isArray(status.results) ? status.results : []];
671
- if (totalPages > 1) {
672
- const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
673
- const remainingPages = await runBatch(
674
- pageNumbers,
675
- async (page) => await this.client.post(path, {
676
- job_id: jobId,
677
- page,
678
- page_size: pageSize
679
- }),
680
- { concurrency: Math.min(10, pageNumbers.length) }
1112
+ observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
1113
+ assertRecoverableBatchResultsRetained(
1114
+ status,
1115
+ label,
1116
+ jobId,
1117
+ observedIdempotencyKey
681
1118
  );
682
- for (const page of remainingPages.results) {
683
- if (!page.success || !page.data) {
684
- throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
1119
+ while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
1120
+ if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
1121
+ throw new SignalizError({
1122
+ code: "TIMEOUT",
1123
+ message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
1124
+ errorType: "timeout",
1125
+ retryAfter: Number(status.next_poll_after_seconds || 2),
1126
+ details: {
1127
+ job_id: jobId,
1128
+ ...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
1129
+ suggested_action: "check_job_status",
1130
+ retry_eligible: true
1131
+ }
1132
+ });
685
1133
  }
686
- pages.push(Array.isArray(page.data.results) ? page.data.results : []);
1134
+ const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
1135
+ const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
1136
+ await sleep2(boundedDelay);
1137
+ status = await this.client.post(path, {
1138
+ job_id: jobId,
1139
+ page: 1,
1140
+ page_size: pageSize,
1141
+ compact_duplicates: options.compactDuplicates === true
1142
+ });
1143
+ observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
1144
+ assertRecoverableBatchResultsRetained(
1145
+ status,
1146
+ label,
1147
+ jobId,
1148
+ observedIdempotencyKey
1149
+ );
687
1150
  }
688
- }
689
- const rows = pages.flat();
690
- if (rows.length !== requests.length) {
691
- throw new Error(`${label} batch returned ${rows.length} results for ${requests.length} requests`);
692
- }
693
- const seen = /* @__PURE__ */ new Set();
694
- for (const row of rows) {
695
- const index = Number(row.index);
696
- if (!Number.isInteger(index) || index < 0 || index >= requests.length || seen.has(index)) {
697
- throw new Error(`${label} batch returned an invalid result index`);
1151
+ const totalPages = Math.max(1, Number(status.total_pages || 1));
1152
+ const pages = [Array.isArray(status.results) ? status.results : []];
1153
+ if (totalPages > 1) {
1154
+ const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
1155
+ const remainingPages = await runBatch(
1156
+ pageNumbers,
1157
+ async (page) => await this.client.post(path, {
1158
+ job_id: jobId,
1159
+ page,
1160
+ page_size: pageSize,
1161
+ compact_duplicates: options.compactDuplicates === true
1162
+ }),
1163
+ { concurrency: Math.min(10, pageNumbers.length) }
1164
+ );
1165
+ for (const page of remainingPages.results) {
1166
+ if (!page.success || !page.data) {
1167
+ throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
1168
+ }
1169
+ observedIdempotencyKey = String(
1170
+ page.data.idempotency_key || observedIdempotencyKey
1171
+ ).trim();
1172
+ assertRecoverableBatchResultsRetained(
1173
+ page.data,
1174
+ label,
1175
+ jobId,
1176
+ observedIdempotencyKey
1177
+ );
1178
+ pages.push(Array.isArray(page.data.results) ? page.data.results : []);
1179
+ }
1180
+ }
1181
+ const rows = pages.flat();
1182
+ const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
1183
+ if (rows.length !== total) {
1184
+ throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
1185
+ }
1186
+ const seen = /* @__PURE__ */ new Set();
1187
+ for (const row of rows) {
1188
+ const index = Number(row.index);
1189
+ if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
1190
+ throw new Error(`${label} batch returned an invalid result index`);
1191
+ }
1192
+ seen.add(index);
1193
+ }
1194
+ if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
1195
+ return { rows, total };
1196
+ } catch (error) {
1197
+ if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
1198
+ throw error;
698
1199
  }
699
- seen.add(index);
1200
+ const signalizError = error instanceof SignalizError ? error : null;
1201
+ const message = error instanceof Error ? error.message : String(error);
1202
+ throw new SignalizError({
1203
+ code: signalizError?.code || "BATCH_RECOVERY_FAILED",
1204
+ message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
1205
+ errorType: signalizError?.errorType || "provider_error",
1206
+ retryAfter: signalizError?.retryAfter,
1207
+ details: {
1208
+ ...signalizError?.details || {},
1209
+ job_id: jobId,
1210
+ ...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
1211
+ suggested_action: "check_job_status",
1212
+ retry_eligible: true
1213
+ }
1214
+ });
700
1215
  }
701
- if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
702
- return rows;
703
1216
  }
704
1217
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
705
1218
  const results = new Array(requests.length);
@@ -764,8 +1277,11 @@ var Signaliz = class {
764
1277
  (task) => coreProductBatchRequestKey(path, task.request)
765
1278
  );
766
1279
  const uniqueTasks = deduplicated.uniqueItems;
767
- 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;
768
- if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
1280
+ 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;
1281
+ const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
1282
+ (task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
1283
+ );
1284
+ if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
769
1285
  const rows = await this.runRecoverableCoreBatchJob(
770
1286
  path,
771
1287
  uniqueTasks.map((task) => task.request),
@@ -891,18 +1407,68 @@ function batchItemFailure(index, error, raw) {
891
1407
  raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
892
1408
  );
893
1409
  const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
1410
+ const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
1411
+ const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
1412
+ const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
1413
+ const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
1414
+ const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
1415
+ const jobId = raw?.job_id ?? raw?.jobId;
1416
+ const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
894
1417
  return {
895
1418
  index,
896
1419
  success: false,
897
1420
  error,
898
1421
  ...errorCode ? { errorCode } : {},
899
1422
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
900
- ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
1423
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
1424
+ ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
1425
+ ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
1426
+ ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
1427
+ ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
1428
+ ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
1429
+ ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
1430
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
901
1431
  };
902
1432
  }
903
1433
  function recoverableJobRowFailed(row) {
904
1434
  return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
905
1435
  }
1436
+ function recoverableRowsToRound(rows, label) {
1437
+ return rows.map((raw) => {
1438
+ const index = Number(raw.index);
1439
+ const failed = recoverableJobRowFailed(raw);
1440
+ return {
1441
+ index,
1442
+ success: !failed,
1443
+ raw,
1444
+ error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
1445
+ };
1446
+ });
1447
+ }
1448
+ function recoverableSignalCopyRows(rows) {
1449
+ return rows.map((raw) => {
1450
+ const index = Number(raw.index);
1451
+ const duplicateOf = recoveredDuplicateOf(raw);
1452
+ if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
1453
+ if (recoverableJobRowFailed(raw)) {
1454
+ return batchItemFailure(
1455
+ index,
1456
+ raw.error || raw._error || raw.message || "Signal to Copy item failed",
1457
+ raw
1458
+ );
1459
+ }
1460
+ try {
1461
+ assertTerminalSignalResponse(raw, "Signal to Copy");
1462
+ return { index, success: true, data: normalizeSignalToCopyResult(raw) };
1463
+ } catch (error) {
1464
+ return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
1465
+ }
1466
+ });
1467
+ }
1468
+ function recoveredDuplicateOf(raw) {
1469
+ const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
1470
+ return Number.isInteger(value) && value >= 0 ? value : void 0;
1471
+ }
906
1472
  function newBatchIdempotencyKey(label) {
907
1473
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
908
1474
  return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
@@ -912,6 +1478,7 @@ function compact(input) {
912
1478
  }
913
1479
  function findEmailRequestBody(params) {
914
1480
  return compact({
1481
+ run_id: params.runId,
915
1482
  company_domain: params.companyDomain,
916
1483
  full_name: params.fullName,
917
1484
  first_name: params.firstName,
@@ -923,6 +1490,16 @@ function findEmailRequestBody(params) {
923
1490
  idempotency_key: params.idempotencyKey
924
1491
  });
925
1492
  }
1493
+ function verifyEmailBatchRequestBody(input, options) {
1494
+ if (typeof input === "string") {
1495
+ return compact({ email: input, skip_cache: options?.skipCache });
1496
+ }
1497
+ return compact({
1498
+ email: input.email,
1499
+ skip_cache: input.skipCache ?? options?.skipCache,
1500
+ idempotency_key: input.idempotencyKey
1501
+ });
1502
+ }
926
1503
  function isCoreProductDryRun(data) {
927
1504
  return data.dry_run === true && data.status === "planned";
928
1505
  }
@@ -951,76 +1528,111 @@ function normalizeCoreProductDryRunResult(data) {
951
1528
  };
952
1529
  }
953
1530
  function normalizeFindEmailResult(data) {
1531
+ const processing = String(data.status || "").toLowerCase() === "processing";
954
1532
  const failed = coreProductResponseFailed(data);
955
1533
  const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
956
1534
  const email = failed ? null : rawEmail;
957
1535
  const found = Boolean(email) && data.found !== false;
958
1536
  const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
1537
+ const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
1538
+ const verificationVerdict = data.verification_verdict ?? verificationStatus;
959
1539
  const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
960
1540
  const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
961
1541
  const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
962
1542
  const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
963
1543
  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()));
964
1544
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
1545
+ const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
1546
+ const verificationSource = data.verification_source ?? providerUsed;
1547
+ const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
965
1548
  return {
966
- success: !failed && found,
1549
+ success: processing || !failed && found,
967
1550
  found,
968
1551
  email,
969
1552
  firstName: data.first_name,
970
1553
  lastName: data.last_name,
971
1554
  confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
972
1555
  verificationStatus: failed ? "error" : verificationStatus,
1556
+ deliverabilityStatus: failed ? "error" : deliverabilityStatus,
1557
+ verificationVerdict: failed ? "error" : verificationVerdict,
973
1558
  isVerified: !failed && verified,
974
1559
  isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
1560
+ isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
975
1561
  isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
976
1562
  verificationFreshness,
977
1563
  verificationAgeDays,
978
1564
  verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
979
1565
  needsReverification: failed || needsReverification,
980
- providerUsed: data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown",
981
- creditsUsed: data.credits_used,
982
- estimatedExternalCostUsd: data.estimated_external_cost_usd,
983
- billingMetadata: data.billing_metadata,
984
- resultReturnedFrom: data.result_returned_from,
985
- cacheHit: data.cache_hit,
1566
+ providerUsed,
1567
+ verificationSource,
1568
+ verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1569
+ failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
1570
+ creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
1571
+ estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
1572
+ billingMetadata,
1573
+ historicalBillingMetadata: data.historical_billing_metadata,
1574
+ resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
1575
+ cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
986
1576
  negativeCacheHit: data.negative_cache_hit,
987
1577
  negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
988
1578
  cacheTier: data.cache_tier,
989
- freshEnrichmentUsed: data.fresh_enrichment_used,
990
- liveProviderCalled: data.live_provider_called,
991
- openrouterCalled: data.openrouter_called,
992
- byoOpenrouterUsed: data.byo_openrouter_used,
993
- error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
994
- errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1579
+ freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
1580
+ liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
1581
+ openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
1582
+ byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
1583
+ error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1584
+ errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1585
+ status: processing ? "processing" : "completed",
1586
+ runId: data.run_id,
1587
+ nextPollAfterSeconds: data.next_poll_after_seconds,
1588
+ suggestedAction: data.suggested_action,
1589
+ cachePublicationStatus: data.cache_publication_status,
1590
+ cachePublicationRetryEligible: data.cache_publication_retry_eligible,
995
1591
  raw: data
996
1592
  };
997
1593
  }
998
1594
  function normalizeVerifyEmailResult(email, data) {
999
1595
  const failed = coreProductResponseFailed(data);
1000
- const verificationStatus = String(data.verification_status ?? "").toLowerCase();
1001
- const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
1002
1596
  const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
1597
+ const verificationStatus = String(
1598
+ failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
1599
+ ).toLowerCase();
1600
+ const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
1601
+ const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
1602
+ const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
1603
+ const verifiedForSending = !failed && data.verified_for_sending === true;
1604
+ const verificationRunId = data.verification_run_id ?? data.run_id;
1003
1605
  return {
1004
1606
  success: !failed,
1005
1607
  error: failed ? coreProductErrorMessage(data) : void 0,
1006
1608
  errorCode: failed ? coreProductErrorCode(data) : void 0,
1007
1609
  email: data.email ?? email,
1008
1610
  status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
1009
- verificationRunId: data.verification_run_id ?? data.run_id,
1611
+ verificationRunId,
1010
1612
  retryAfterMs: data.retry_after_ms,
1011
1613
  nextPollAfterSeconds: data.next_poll_after_seconds,
1012
1614
  isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
1013
1615
  isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
1014
1616
  isMalformed: malformed,
1015
- isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
1016
- verifiedForSending: !failed && data.verified_for_sending === true,
1617
+ isCatchAll,
1618
+ verifiedForSending,
1619
+ verificationStatus,
1620
+ deliverabilityStatus,
1017
1621
  verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
1622
+ isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
1623
+ quality: typeof data.quality === "string" ? data.quality : null,
1624
+ recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
1625
+ smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
1626
+ providerStatus: data.provider_status ?? verificationStatus,
1627
+ failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
1018
1628
  confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
1019
1629
  provider: data.provider,
1020
1630
  verificationSource: data.verification_source,
1631
+ verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1021
1632
  billingReplayed: data.billing_replayed,
1022
1633
  creditsUsed: data.credits_used,
1023
1634
  billingMetadata: data.billing_metadata,
1635
+ historicalBillingMetadata: data.historical_billing_metadata,
1024
1636
  resultReturnedFrom: data.result_returned_from,
1025
1637
  cacheHit: data.cache_hit,
1026
1638
  negativeCacheHit: data.negative_cache_hit,
@@ -1030,6 +1642,11 @@ function normalizeVerifyEmailResult(email, data) {
1030
1642
  liveProviderCalled: data.live_provider_called,
1031
1643
  openrouterCalled: data.openrouter_called,
1032
1644
  byoOpenrouterUsed: data.byo_openrouter_used,
1645
+ estimatedExternalCostUsd: data.estimated_external_cost_usd,
1646
+ runId: data.run_id ?? verificationRunId,
1647
+ cachePublicationStatus: data.cache_publication_status,
1648
+ cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1649
+ suggestedAction: data.suggested_action,
1033
1650
  raw: data
1034
1651
  };
1035
1652
  }
@@ -1057,6 +1674,54 @@ function companySignalRequestBody(params) {
1057
1674
  idempotency_key: params.idempotencyKey
1058
1675
  });
1059
1676
  }
1677
+ function companySignalParamsFromRecoveredRow(row) {
1678
+ return {
1679
+ companyDomain: row.company?.domain ?? row.company_domain,
1680
+ companyName: row.company?.name ?? row.company_name,
1681
+ signalRunId: row.signal_run_id ?? row.run_id
1682
+ };
1683
+ }
1684
+ var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
1685
+ "research_prompt",
1686
+ "signal_types",
1687
+ "target_signal_count",
1688
+ "lookback_days",
1689
+ "online",
1690
+ "enable_deep_search",
1691
+ "search_mode",
1692
+ "include_summary",
1693
+ "enable_outreach_intelligence",
1694
+ "enable_predictive_intelligence",
1695
+ "skip_cache",
1696
+ "include_candidate_evidence"
1697
+ ];
1698
+ function companySignalLargeBatchSubmission(requests) {
1699
+ if (requests.length <= 25) return null;
1700
+ if (requests.some(
1701
+ (request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
1702
+ )) {
1703
+ return null;
1704
+ }
1705
+ const submissionFields = Object.fromEntries(
1706
+ COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
1707
+ );
1708
+ const controlKey = JSON.stringify(submissionFields);
1709
+ for (const request of requests) {
1710
+ const requestControls = Object.fromEntries(
1711
+ COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
1712
+ );
1713
+ if (JSON.stringify(requestControls) !== controlKey) return null;
1714
+ const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
1715
+ if (!hasIdentity) return null;
1716
+ }
1717
+ return {
1718
+ requests: requests.map((request) => compact({
1719
+ company_domain: request.company_domain,
1720
+ company_name: request.company_name
1721
+ })),
1722
+ submissionFields
1723
+ };
1724
+ }
1060
1725
  function normalizeCompanySignalResult(params, data) {
1061
1726
  const failed = coreProductResponseFailed(data);
1062
1727
  const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
@@ -1127,6 +1792,12 @@ function validateSignalDiscoveryParams(params) {
1127
1792
  if (query && (query.length < 2 || query.length > 2e3)) {
1128
1793
  throw new RangeError("query must contain between 2 and 2000 characters");
1129
1794
  }
1795
+ if (params.icpContext !== void 0) {
1796
+ const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
1797
+ if (icpContext.length < 2 || icpContext.length > 4e3) {
1798
+ throw new RangeError("icpContext must contain between 2 and 4000 characters");
1799
+ }
1800
+ }
1130
1801
  if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
1131
1802
  throw new RangeError("limit must be an integer between 1 and 1000");
1132
1803
  }
@@ -1151,6 +1822,7 @@ function signalDiscoveryRequestBody(params) {
1151
1822
  }
1152
1823
  return compact({
1153
1824
  query: params.query?.trim(),
1825
+ icp_context: params.icpContext?.trim(),
1154
1826
  limit: params.limit ?? 100,
1155
1827
  sources: params.sources,
1156
1828
  lookback_days: params.lookbackDays,
@@ -1176,6 +1848,8 @@ function normalizeSignalDiscoveryResult(params, data) {
1176
1848
  availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
1177
1849
  sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
1178
1850
  evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
1851
+ discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
1852
+ identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
1179
1853
  costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
1180
1854
  warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
1181
1855
  raw: data
@@ -1204,7 +1878,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
1204
1878
  guardrail.projected_cost_per_requested_company_signal_usd
1205
1879
  ),
1206
1880
  sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
1207
- costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1881
+ costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1882
+ identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
1883
+ identityCostSource: [
1884
+ "observed_zero_credit_receipts",
1885
+ "contractual_zero_cost",
1886
+ "not_used",
1887
+ "unavailable"
1888
+ ].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
1889
+ identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
1208
1890
  qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
1209
1891
  returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
1210
1892
  availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
@@ -1290,6 +1972,9 @@ function dedupeExactBatchItems(items, keyForItem) {
1290
1972
  function normalizedBatchText(value) {
1291
1973
  return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
1292
1974
  }
1975
+ function normalizedBatchIdempotencyKey(value) {
1976
+ return typeof value === "string" ? value.trim() : "";
1977
+ }
1293
1978
  function normalizedBatchDomain(value) {
1294
1979
  return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
1295
1980
  }
@@ -1308,11 +1993,13 @@ function normalizedFindEmailBatchName(request) {
1308
1993
  function coreProductBatchRequestKey(path, request) {
1309
1994
  if (path === "api/v1/find-email") {
1310
1995
  return JSON.stringify([
1996
+ normalizedBatchText(request.run_id),
1311
1997
  normalizedFindEmailBatchName(request),
1312
1998
  normalizedBatchDomain(request.company_domain),
1313
1999
  normalizedBatchLinkedIn(request.linkedin_url),
1314
2000
  normalizedBatchText(request.company_name),
1315
- request.skip_cache === true || request.bypass_cache === true
2001
+ request.skip_cache === true || request.bypass_cache === true,
2002
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1316
2003
  ]);
1317
2004
  }
1318
2005
  if (path === "api/v1/verify-email") {
@@ -1320,7 +2007,8 @@ function coreProductBatchRequestKey(path, request) {
1320
2007
  normalizedBatchText(request.email),
1321
2008
  request.skip_cache === true || request.bypass_cache === true,
1322
2009
  request.skip_catch_all_verification === true,
1323
- request.skip_esp_check === true
2010
+ request.skip_esp_check === true,
2011
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1324
2012
  ]);
1325
2013
  }
1326
2014
  if (path === "api/v1/company-signals") {
@@ -1342,7 +2030,8 @@ function coreProductBatchRequestKey(path, request) {
1342
2030
  normalizedBatchText(request.search_mode || "balanced"),
1343
2031
  request.enable_outreach_intelligence === true,
1344
2032
  request.enable_predictive_intelligence === true,
1345
- request.skip_cache === true || request.bypass_cache === true
2033
+ request.skip_cache === true || request.bypass_cache === true,
2034
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1346
2035
  ]);
1347
2036
  }
1348
2037
  return JSON.stringify([
@@ -1351,9 +2040,11 @@ function coreProductBatchRequestKey(path, request) {
1351
2040
  typeof request.title === "string" ? request.title.trim() : "",
1352
2041
  typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
1353
2042
  typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
2043
+ Number(request.lookback_days) || null,
1354
2044
  typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
1355
2045
  request.skip_cache === true,
1356
- request.enable_deep_search === true
2046
+ request.enable_deep_search === true,
2047
+ normalizedBatchIdempotencyKey(request.idempotency_key)
1357
2048
  ]);
1358
2049
  }
1359
2050
  function finalizeCoreBatch(total, startedAt, round, normalize, options) {
@@ -1391,6 +2082,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
1391
2082
  results: compactedResults
1392
2083
  };
1393
2084
  }
2085
+ function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
2086
+ const results = round.map((item) => {
2087
+ const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
2088
+ if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
2089
+ if (!item.success || !item.raw) {
2090
+ return batchItemFailure(
2091
+ item.index,
2092
+ item.error || "Signaliz batch item failed",
2093
+ item.raw ?? item
2094
+ );
2095
+ }
2096
+ try {
2097
+ return { index: item.index, success: true, data: normalize(item) };
2098
+ } catch (error) {
2099
+ return {
2100
+ index: item.index,
2101
+ success: false,
2102
+ error: error instanceof Error ? error.message : String(error)
2103
+ };
2104
+ }
2105
+ });
2106
+ const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
2107
+ const succeeded = compactedResults.filter((item) => item.success).length;
2108
+ return {
2109
+ total,
2110
+ succeeded,
2111
+ failed: total - succeeded,
2112
+ durationMs: Date.now() - startedAt,
2113
+ results: compactedResults
2114
+ };
2115
+ }
1394
2116
  function compactExactDuplicateResults(results, enabled) {
1395
2117
  if (!enabled) return results;
1396
2118
  const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
@@ -1406,6 +2128,19 @@ function compactExactDuplicateResults(results, enabled) {
1406
2128
  return item;
1407
2129
  });
1408
2130
  }
2131
+ function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
2132
+ if (!enabled) return batch;
2133
+ const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
2134
+ sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
2135
+ if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
2136
+ });
2137
+ const results = batch.results.map((item, index) => {
2138
+ const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
2139
+ if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
2140
+ return { index, success: true, duplicateOf: canonicalIndex };
2141
+ });
2142
+ return { ...batch, results };
2143
+ }
1409
2144
  function signalToCopyRequestBody(params) {
1410
2145
  return compact({
1411
2146
  company_domain: params.companyDomain,
@@ -1413,6 +2148,7 @@ function signalToCopyRequestBody(params) {
1413
2148
  title: params.title,
1414
2149
  campaign_offer: params.campaignOffer,
1415
2150
  research_prompt: params.researchPrompt,
2151
+ lookback_days: params.lookbackDays,
1416
2152
  signal_run_id: params.signalRunId,
1417
2153
  skip_cache: params.skipCache,
1418
2154
  enable_deep_search: params.enableDeepSearch,
@@ -1421,8 +2157,33 @@ function signalToCopyRequestBody(params) {
1421
2157
  idempotency_key: params.idempotencyKey
1422
2158
  });
1423
2159
  }
2160
+ function validateLargeAvailableDataSignalCopyBatch(requests) {
2161
+ if (requests.length <= 25) return;
2162
+ const invalidIndex = requests.findIndex(
2163
+ (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()
2164
+ );
2165
+ if (invalidIndex >= 0) {
2166
+ throw new RangeError(
2167
+ `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.`
2168
+ );
2169
+ }
2170
+ }
2171
+ function availableDataSignalCopyRequestBody(params) {
2172
+ return compact({
2173
+ company_domain: params.companyDomain,
2174
+ person_name: params.personName,
2175
+ title: params.title,
2176
+ campaign_offer: params.campaignOffer,
2177
+ research_prompt: params.researchPrompt,
2178
+ lookback_days: params.lookbackDays,
2179
+ signal_run_id: params.signalRunId
2180
+ });
2181
+ }
1424
2182
  function validateSignalToCopyParams(params) {
1425
2183
  validateCoreProductMaxWaitMs(params.maxWaitMs);
2184
+ if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
2185
+ throw new RangeError("lookbackDays must be an integer between 1 and 365");
2186
+ }
1426
2187
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1427
2188
  const missing = [
1428
2189
  ["companyDomain", params.companyDomain],
@@ -1551,6 +2312,21 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
1551
2312
  function sleep2(ms) {
1552
2313
  return new Promise((resolve) => setTimeout(resolve, ms));
1553
2314
  }
2315
+ function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
2316
+ if (status.results_expired !== true && status.results_cleaned !== true) return;
2317
+ throw new SignalizError({
2318
+ code: "BATCH_RESULTS_EXPIRED",
2319
+ message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
2320
+ errorType: "not_found",
2321
+ details: {
2322
+ job_id: jobId,
2323
+ ...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
2324
+ suggested_action: "review_before_resubmitting",
2325
+ retry_eligible: false,
2326
+ results_expired: true
2327
+ }
2328
+ });
2329
+ }
1554
2330
  async function runBatch(items, execute, options) {
1555
2331
  validateBatchSize(items);
1556
2332
  const concurrency = validatedBatchConcurrency(options);