@signaliz/sdk 1.0.47 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,15 +59,6 @@ const signals = await signaliz.enrichCompanySignals({
59
59
  console.log(signals.signals[0].evidencePublishedDate);
60
60
  console.log(signals.signals[0].signalFeedSource, signals.signals[0].derived);
61
61
 
62
- // Persist a no-wait run ID and resume it later without duplicate spend.
63
- const queuedSignals = await signaliz.enrichCompanySignals({
64
- companyDomain: 'example.com',
65
- waitForResult: false,
66
- });
67
- const resumedSignals = await signaliz.enrichCompanySignals({
68
- signalRunId: queuedSignals.signalRunId,
69
- });
70
-
71
62
  const copy = await signaliz.signalToCopy({
72
63
  companyDomain: 'example.com',
73
64
  personName: 'Jane Doe',
@@ -79,16 +70,8 @@ const copy = await signaliz.signalToCopy({
79
70
  enableDeepSearch: false,
80
71
  });
81
72
 
82
- // Persist a queued copy ID and resume with that ID alone. Signaliz restores
83
- // the recipient, title, offer, prompt, and underlying research run.
84
- const queuedCopy = await signaliz.signalToCopy({
85
- companyDomain: 'example.com',
86
- personName: 'Jane Doe',
87
- title: 'VP Sales',
88
- campaignOffer: 'pipeline intelligence',
89
- waitForResult: false,
90
- });
91
- const resumedCopy = await signaliz.signalToCopy({ signalRunId: queuedCopy.runId });
73
+ // Company Signals and Signal to Copy are hard synchronous contracts: these
74
+ // methods return complete data or throw a terminal error in this same call.
92
75
  ```
93
76
 
94
77
  All four products support bounded batch execution over the same REST endpoints.
@@ -114,36 +97,35 @@ when the REST API supplies them, including after automatic row retries are
114
97
  exhausted.
115
98
 
116
99
  For an ambiguous single request or batch submission failure, retry with the
117
- same `idempotencyKey` to recover the original request or job without duplicate
100
+ same `idempotencyKey` to recover the original request or supported email job without duplicate
118
101
  work.
119
102
 
120
- Each batch accepts up to 5,000 items. Company Signal Enrichment transparently
121
- resumes long-running research through the same `/company-signals` endpoint;
122
- callers never need an internal Trigger.dev status URL. The SDK follows the
123
- server's adaptive polling hints by default and still honors `pollIntervalMs`
124
- when a caller explicitly overrides the cadence.
103
+ Each batch accepts up to 5,000 items. For Company Signals and Signal to Copy,
104
+ the SDK chunks inputs into synchronous requests of at most 25 and waits for
105
+ every chunk. It never returns queued work, job IDs, or polling instructions.
125
106
  Exact duplicate tasks are single-flighted across each full batch without
126
107
  changing input order or result cardinality. Set `compactDuplicates: true` to
127
108
  return repeated successful rows as `duplicateOf` references to the zero-based
128
109
  canonical input index; the default remains expanded for compatibility. Find Email and Verify Email
129
110
  batches
130
111
  larger than 25 use one cache-aware recoverable REST job with 500-row result
131
- pages. Uniform Company Signal Enrichment batches larger than 25 use one
132
- cache-aware recoverable REST job with 25-row result pages; heterogeneous,
133
- no-wait, and per-run recovery inputs keep the 25-row request path. Signal to
134
- Copy batches larger than 25 use one recoverable REST job when they can reuse
135
- available signal data; fresh or deep-search requests keep the 25-row path.
136
- Signal to Copy also shares company research across copy recipients.
112
+ pages. Company Signal Enrichment and Signal to Copy always use client-side
113
+ 25-row chunks with terminal per-row results. Signal to Copy also shares company
114
+ research across copy recipients.
137
115
 
138
116
  Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
139
- unless the caller explicitly disables them. Signal to Copy AI returns three
117
+ unless the caller explicitly disables them. Its `searchMode` defaults to
118
+ `balanced`; use `fast` for the primary low-latency search lane only or
119
+ `coverage` when breadth matters more than the sub-cent average budget target.
120
+ Signal to Copy AI returns three
140
121
  complete, evidence-backed email variations when supported signals are available.
141
122
  Signal to Copy is cache-first and defaults `enableDeepSearch` to `false` so it
142
123
  normally finishes within an agent turn; set `skipCache` or `enableDeepSearch`
143
124
  only when fresh or deeper research is required.
144
125
  Unclassified evidence is returned by signal enrichment but is not used to
145
126
  generate outreach copy.
146
- Each normalized signal also exposes `datePrecision`, `sourceProvenance`,
127
+ Each normalized signal keeps `date` separate from crawl-time `detectedAt` and
128
+ also exposes `datePrecision`, `hasSpecificDate`, `sourceProvenance`,
147
129
  `entityConfidence`, `classification`, `derived`, `evidencePublishedDate`,
148
130
  `signalFeedSource`, and the provider `metadata` needed for integrity audits.
149
131
 
@@ -457,39 +457,16 @@ var Signaliz = class {
457
457
  );
458
458
  }
459
459
  async enrichCompanySignals(params) {
460
+ validateCoreProductMaxWaitMs(params.maxWaitMs);
460
461
  const request = companySignalRequestBody(params);
461
- let data = await this.client.post("api/v1/company-signals", request);
462
+ const data = await this.client.post("api/v1/company-signals", request);
462
463
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
463
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
464
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
465
- const startedAt = Date.now();
466
- while (Date.now() - startedAt < maxWaitMs) {
467
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
468
- const status = await this.client.post("api/v1/company-signals", {
469
- ...request,
470
- signal_run_id: data.run_id
471
- });
472
- if (isCompletedSignalRun(status)) {
473
- data = status.output ?? status.result ?? status.data ?? status;
474
- break;
475
- }
476
- if (isFailedSignalRun(status)) {
477
- throw new Error(`Company signal enrichment ${data.run_id} failed with status ${status.status || "unknown"}`);
478
- }
479
- if (!isPendingSignalResponse(status)) {
480
- data = status.output ?? status.result ?? status.data ?? status;
481
- break;
482
- }
483
- data = status;
484
- }
485
- if (isPendingSignalResponse(data)) {
486
- throw new Error(`Company signal enrichment ${data.run_id} did not complete within ${maxWaitMs}ms`);
487
- }
488
- }
464
+ assertTerminalSignalResponse(data, "Company Signals");
489
465
  return normalizeCompanySignalResult(params, data);
490
466
  }
491
467
  async enrichCompanies(companies, options) {
492
468
  validateBatchSize(companies);
469
+ companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
493
470
  if (options?.dryRun === true) {
494
471
  return this.runCoreProductDryRun(
495
472
  "api/v1/company-signals",
@@ -498,68 +475,22 @@ var Signaliz = class {
498
475
  );
499
476
  }
500
477
  const startedAt = Date.now();
501
- const results = new Array(companies.length);
502
478
  const initialTasks = companies.map((params, index) => ({
503
479
  index,
504
480
  request: companySignalRequestBody(params)
505
481
  }));
506
- const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
507
- let pending = initialTasks;
508
- let firstRound = true;
509
- while (pending.length > 0) {
510
- const round = await this.runCoreBatchRound(
511
- "api/v1/company-signals",
512
- pending,
513
- options,
514
- firstRound ? recoverableBatch : void 0
515
- );
516
- firstRound = false;
517
- const taskByIndex = new Map(pending.map((task) => [task.index, task]));
518
- const nextPending = [];
519
- let nextDelayMs = 6e4;
520
- for (const item of round) {
521
- const params = companies[item.index];
522
- const task = taskByIndex.get(item.index);
523
- if (!item.success || !item.raw) {
524
- results[item.index] = batchItemFailure(
525
- item.index,
526
- item.error || "Company Signal batch request failed",
527
- item.raw ?? item
528
- );
529
- continue;
530
- }
531
- if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
532
- results[item.index] = { index: item.index, success: true, data: normalizeCompanySignalResult(params, item.raw) };
533
- continue;
534
- }
535
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
536
- if (Date.now() - startedAt >= maxWaitMs) {
537
- results[item.index] = {
538
- index: item.index,
539
- success: false,
540
- error: `Company signal enrichment ${item.raw.signal_run_id || item.raw.run_id || ""} did not complete within ${maxWaitMs}ms`
541
- };
542
- continue;
543
- }
544
- const signalRunId = item.raw.signal_run_id || item.raw.run_id;
545
- if (!signalRunId) {
546
- results[item.index] = { index: item.index, success: false, error: "Company signal enrichment is processing without a signal_run_id" };
547
- continue;
548
- }
549
- nextDelayMs = Math.min(
550
- nextDelayMs,
551
- signalPollDelayMs(item.raw, params.pollIntervalMs),
552
- maxWaitMs - (Date.now() - startedAt)
553
- );
554
- nextPending.push({
555
- ...task,
556
- request: { ...task.request, signal_run_id: signalRunId }
557
- });
482
+ const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
483
+ const results = round.map((item) => {
484
+ if (!item.success || !item.raw) {
485
+ return batchItemFailure(item.index, item.error || "Company Signal batch request failed", item.raw ?? item);
558
486
  }
559
- if (nextPending.length === 0) break;
560
- await sleep2(Math.max(1, nextDelayMs));
561
- pending = nextPending;
562
- }
487
+ try {
488
+ assertTerminalSignalResponse(item.raw, "Company Signals");
489
+ return { index: item.index, success: true, data: normalizeCompanySignalResult(companies[item.index], item.raw) };
490
+ } catch (error) {
491
+ return batchItemFailure(item.index, error instanceof Error ? error.message : String(error), item.raw);
492
+ }
493
+ });
563
494
  const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
564
495
  const succeeded = compactedResults.filter((item) => item.success).length;
565
496
  return {
@@ -573,21 +504,9 @@ var Signaliz = class {
573
504
  async signalToCopy(params) {
574
505
  validateSignalToCopyParams(params);
575
506
  const request = signalToCopyRequestBody(params);
576
- let data = await this.client.post("api/v1/signal-to-copy", request);
507
+ const data = await this.client.post("api/v1/signal-to-copy", request);
577
508
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
578
- if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
579
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
580
- const startedAt = Date.now();
581
- while (Date.now() - startedAt < maxWaitMs) {
582
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
583
- const signalRunId = data.signal_run_id || data.run_id;
584
- data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
585
- if (!isPendingSignalResponse(data)) break;
586
- }
587
- if (isPendingSignalResponse(data)) {
588
- throw new Error(`Signal Copy ${data.run_id} did not complete within ${maxWaitMs}ms`);
589
- }
590
- }
509
+ assertTerminalSignalResponse(data, "Signal to Copy");
591
510
  return normalizeSignalToCopyResult(data);
592
511
  }
593
512
  async createSignalCopyBatch(requests, options) {
@@ -602,16 +521,10 @@ var Signaliz = class {
602
521
  }
603
522
  const startedAt = Date.now();
604
523
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
605
- request: signalToCopyRequestBody(params),
606
- wait_for_result: params.waitForResult,
607
- max_wait_ms: params.maxWaitMs,
608
- poll_interval_ms: params.pollIntervalMs
524
+ request: signalToCopyRequestBody(params)
609
525
  }));
610
526
  const uniqueRequests = deduplicated.uniqueItems;
611
- const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
612
- (params) => (!params.signalRunId || !params.signalRunId.startsWith("copy_") && [params.companyDomain, params.personName, params.title, params.campaignOffer].every((value) => typeof value === "string" && Boolean(value.trim()))) && params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
613
- );
614
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
527
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
615
528
  const results = requests.map((_, index) => ({
616
529
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
617
530
  index
@@ -667,22 +580,6 @@ var Signaliz = class {
667
580
  }
668
581
  return uniqueResults;
669
582
  }
670
- async runAvailableSignalCopyBatchJob(requests, options) {
671
- const rows = await this.runRecoverableCoreBatchJob(
672
- "api/v1/signal-to-copy",
673
- requests.map(signalToCopyRequestBody),
674
- "Signal to Copy",
675
- 500,
676
- 20 * 6e4,
677
- options?.idempotencyKey
678
- );
679
- const results = new Array(requests.length);
680
- for (const row of rows) {
681
- const index = Number(row.index);
682
- results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
683
- }
684
- return results;
685
- }
686
583
  async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
687
584
  const startedAt = Date.now();
688
585
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
@@ -754,77 +651,35 @@ var Signaliz = class {
754
651
  return rows;
755
652
  }
756
653
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
757
- const startedAt = Date.now();
758
654
  const results = new Array(requests.length);
759
655
  const rateLimited = [];
760
- let pending = requests.map((params, index) => ({
761
- index,
762
- params,
763
- request: signalToCopyRequestBody(params)
764
- }));
765
- while (pending.length > 0) {
766
- const data = await this.client.post("api/v1/signal-to-copy", {
767
- requests: pending.map((item) => item.request),
768
- concurrency
769
- });
770
- if (!Array.isArray(data.results) || data.results.length !== pending.length) {
771
- throw new Error("Signal to Copy batch returned an invalid result count");
772
- }
773
- const nextPending = [];
774
- let nextDelayMs = 6e4;
775
- for (let index = 0; index < pending.length; index += 1) {
776
- const task = pending[index];
777
- const item = data.results[index];
778
- if (item.success === false) {
779
- if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
780
- index: task.index,
781
- success: false,
782
- raw: item
783
- })) {
784
- rateLimited.push({ index: task.index, params: task.params, raw: item });
785
- continue;
786
- }
787
- results[task.index] = batchItemFailure(
788
- task.index,
789
- item.error || item.message || "Signal to Copy failed",
790
- item
791
- );
792
- continue;
793
- }
794
- if (!isPendingSignalResponse(item)) {
795
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
796
- continue;
797
- }
798
- if (task.params.waitForResult === false) {
799
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
800
- continue;
801
- }
802
- const maxWaitMs = Math.max(1e3, task.params.maxWaitMs ?? 20 * 6e4);
803
- if (Date.now() - startedAt >= maxWaitMs) {
804
- results[task.index] = {
805
- index: task.index,
806
- success: false,
807
- error: `Signal Copy ${item.signal_run_id || item.run_id || ""} did not complete within ${maxWaitMs}ms`
808
- };
809
- continue;
810
- }
811
- const signalRunId = item.signal_run_id || item.run_id;
812
- if (!signalRunId) {
813
- results[task.index] = { index: task.index, success: false, error: "Signal Copy is processing without a signal_run_id" };
656
+ const data = await this.client.post("api/v1/signal-to-copy", {
657
+ requests: requests.map(signalToCopyRequestBody),
658
+ concurrency
659
+ });
660
+ if (!Array.isArray(data.results) || data.results.length !== requests.length) {
661
+ throw new Error("Signal to Copy batch returned an invalid result count");
662
+ }
663
+ for (let index = 0; index < requests.length; index += 1) {
664
+ const item = data.results[index];
665
+ if (item.success === false) {
666
+ if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
667
+ index,
668
+ success: false,
669
+ raw: item
670
+ })) {
671
+ rateLimited.push({ index, params: requests[index], raw: item });
814
672
  continue;
815
673
  }
816
- nextDelayMs = Math.min(
817
- nextDelayMs,
818
- signalPollDelayMs(item, task.params.pollIntervalMs),
819
- maxWaitMs - (Date.now() - startedAt)
820
- );
821
- nextPending.push({
822
- ...task,
823
- request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
824
- });
674
+ results[index] = batchItemFailure(index, item.error || item.message || "Signal to Copy failed", item);
675
+ continue;
676
+ }
677
+ try {
678
+ assertTerminalSignalResponse(item, "Signal to Copy");
679
+ results[index] = { index, success: true, data: normalizeSignalToCopyResult(item) };
680
+ } catch (error) {
681
+ results[index] = batchItemFailure(index, error instanceof Error ? error.message : String(error), item);
825
682
  }
826
- pending = nextPending;
827
- if (pending.length > 0) await sleep2(nextDelayMs);
828
683
  }
829
684
  if (rateLimited.length > 0) {
830
685
  await sleep2(rowRateLimitDelayMs(rateLimited.map((item) => ({
@@ -843,12 +698,12 @@ var Signaliz = class {
843
698
  }
844
699
  return results;
845
700
  }
846
- async runCoreBatchRound(path, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
701
+ async runCoreBatchRound(path, tasks, options, rowRateLimitAttempt = 0) {
847
702
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
848
703
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
849
704
  const uniqueTasks = deduplicated.uniqueItems;
850
- 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 } : companyRecoverableBatch;
851
- if (recoverableBatch && uniqueTasks.length > 25) {
705
+ 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;
706
+ if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
852
707
  const rows = await this.runRecoverableCoreBatchJob(
853
708
  path,
854
709
  uniqueTasks.map((task) => task.request),
@@ -920,7 +775,6 @@ var Signaliz = class {
920
775
  path,
921
776
  retryTasks,
922
777
  options,
923
- void 0,
924
778
  rowRateLimitAttempt + 1
925
779
  );
926
780
  const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
@@ -1049,6 +903,15 @@ function normalizeFindEmailResult(data) {
1049
903
  verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
1050
904
  needsReverification,
1051
905
  providerUsed: data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown",
906
+ creditsUsed: data.credits_used,
907
+ estimatedExternalCostUsd: data.estimated_external_cost_usd,
908
+ billingMetadata: data.billing_metadata,
909
+ resultReturnedFrom: data.result_returned_from,
910
+ cacheHit: data.cache_hit,
911
+ freshEnrichmentUsed: data.fresh_enrichment_used,
912
+ liveProviderCalled: data.live_provider_called,
913
+ openrouterCalled: data.openrouter_called,
914
+ byoOpenrouterUsed: data.byo_openrouter_used,
1052
915
  error: found ? void 0 : data.error ?? "No verified email found",
1053
916
  errorCode: found ? void 0 : data.error_code ?? "EMAIL_NOT_FOUND",
1054
917
  raw: data
@@ -1058,6 +921,7 @@ function normalizeVerifyEmailResult(email, data) {
1058
921
  const verificationStatus = String(data.verification_status ?? "").toLowerCase();
1059
922
  const verified = data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus);
1060
923
  return {
924
+ success: data.success !== false,
1061
925
  email: data.email ?? email,
1062
926
  status: String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
1063
927
  verificationRunId: data.verification_run_id ?? data.run_id,
@@ -1066,9 +930,20 @@ function normalizeVerifyEmailResult(email, data) {
1066
930
  isValid: data.is_valid ?? data.valid ?? verified,
1067
931
  isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
1068
932
  isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
933
+ verifiedForSending: data.verified_for_sending === true,
934
+ verificationVerdict: data.verification_verdict ?? data.verification_status ?? "unknown",
1069
935
  confidenceScore: data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
1070
936
  provider: data.provider,
1071
937
  verificationSource: data.verification_source,
938
+ billingReplayed: data.billing_replayed,
939
+ creditsUsed: data.credits_used,
940
+ billingMetadata: data.billing_metadata,
941
+ resultReturnedFrom: data.result_returned_from,
942
+ cacheHit: data.cache_hit,
943
+ freshEnrichmentUsed: data.fresh_enrichment_used,
944
+ liveProviderCalled: data.live_provider_called,
945
+ openrouterCalled: data.openrouter_called,
946
+ byoOpenrouterUsed: data.byo_openrouter_used,
1072
947
  raw: data
1073
948
  };
1074
949
  }
@@ -1084,41 +959,17 @@ function companySignalRequestBody(params) {
1084
959
  lookback_days: params.lookbackDays,
1085
960
  online,
1086
961
  enable_deep_search: params.enableDeepSearch ?? online,
962
+ search_mode: params.searchMode ?? "balanced",
1087
963
  include_summary: params.includeSummary,
1088
964
  enable_outreach_intelligence: params.enableOutreachIntelligence,
1089
965
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
1090
966
  skip_cache: params.skipCache,
1091
- // REST always returns a resumable run instead of holding the connection;
1092
- // waitForResult controls the SDK polling loop.
1093
- wait_for_result: false,
967
+ wait_for_result: true,
1094
968
  max_wait_ms: params.maxWaitMs,
1095
969
  dry_run: params.dryRun,
1096
970
  idempotency_key: params.idempotencyKey
1097
971
  });
1098
972
  }
1099
- function companySignalRecoverableBatchOptions(companies, tasks) {
1100
- if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
1101
- return void 0;
1102
- }
1103
- const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
1104
- if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
1105
- return void 0;
1106
- }
1107
- const configKey = (request) => {
1108
- const {
1109
- company_domain: _companyDomain,
1110
- company_name: _companyName,
1111
- signal_run_id: _signalRunId,
1112
- wait_for_result: _waitForResult,
1113
- max_wait_ms: _maxWaitMs,
1114
- ...config
1115
- } = request;
1116
- return JSON.stringify(config);
1117
- };
1118
- const sharedConfig = configKey(tasks[0].request);
1119
- if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
1120
- return { label: "Company Signals", pageSize: 25, maxWaitMs };
1121
- }
1122
973
  function normalizeCompanySignalResult(params, data) {
1123
974
  const rawSignals = data.signals ?? data.signal_feed ?? [];
1124
975
  return {
@@ -1136,8 +987,10 @@ function normalizeCompanySignalResult(params, data) {
1136
987
  title: signal.title ?? "",
1137
988
  type: signal.type ?? signal.signal_type ?? "unknown",
1138
989
  content: signal.content ?? signal.description ?? "",
1139
- date: signal.date ?? signal.detected_at ?? null,
990
+ date: signal.date ?? null,
991
+ detectedAt: signal.detected_at ?? null,
1140
992
  datePrecision: signal.date_precision,
993
+ hasSpecificDate: signal.has_specific_date ?? metadata?.has_specific_date,
1141
994
  sourceUrl: signal.source_url ?? signal.url ?? null,
1142
995
  sourceType: signal.source_type,
1143
996
  sourceProvenance: signal.source_provenance,
@@ -1166,7 +1019,10 @@ function normalizeCompanySignalResult(params, data) {
1166
1019
  billingMetadata: data.billing_metadata,
1167
1020
  resultReturnedFrom: data.result_returned_from,
1168
1021
  cacheHit: data.cache_hit,
1022
+ freshEnrichmentUsed: data.fresh_enrichment_used,
1169
1023
  liveProviderCalled: data.live_provider_called,
1024
+ aiProviderCalled: data.ai_provider_called,
1025
+ byoAiUsed: data.byo_ai_used,
1170
1026
  metadata: data.metadata,
1171
1027
  raw: data
1172
1028
  };
@@ -1266,11 +1122,13 @@ function signalToCopyRequestBody(params) {
1266
1122
  signal_run_id: params.signalRunId,
1267
1123
  skip_cache: params.skipCache,
1268
1124
  enable_deep_search: params.enableDeepSearch,
1125
+ max_wait_ms: params.maxWaitMs,
1269
1126
  dry_run: params.dryRun,
1270
1127
  idempotency_key: params.idempotencyKey
1271
1128
  });
1272
1129
  }
1273
1130
  function validateSignalToCopyParams(params) {
1131
+ validateCoreProductMaxWaitMs(params.maxWaitMs);
1274
1132
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1275
1133
  const missing = [
1276
1134
  ["companyDomain", params.companyDomain],
@@ -1282,6 +1140,12 @@ function validateSignalToCopyParams(params) {
1282
1140
  throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
1283
1141
  }
1284
1142
  }
1143
+ function validateCoreProductMaxWaitMs(value) {
1144
+ if (value === void 0) return;
1145
+ if (!Number.isInteger(value) || value < 1e4 || value > 12e4) {
1146
+ throw new RangeError("maxWaitMs must be an integer between 10000 and 120000");
1147
+ }
1148
+ }
1285
1149
  function normalizeSignalToCopyResult(data) {
1286
1150
  return {
1287
1151
  success: data.success ?? true,
@@ -1312,11 +1176,10 @@ function normalizeSignalToCopyResult(data) {
1312
1176
  function isPendingSignalResponse(data) {
1313
1177
  return ["triggered", "queued", "processing", "pending", "pending_version", "waiting_for_deploy"].includes(String(data.status || "").toLowerCase());
1314
1178
  }
1315
- function isCompletedSignalRun(data) {
1316
- return ["completed", "success", "succeeded"].includes(String(data.status || "").toLowerCase());
1317
- }
1318
- function isFailedSignalRun(data) {
1319
- return ["failed", "error", "cancelled", "canceled", "crashed", "timed_out"].includes(String(data.status || "").toLowerCase());
1179
+ function assertTerminalSignalResponse(data, capability) {
1180
+ if (isPendingSignalResponse(data) || data.job_id) {
1181
+ throw new Error(`${capability} violated the synchronous response contract.`);
1182
+ }
1320
1183
  }
1321
1184
  function signalPollDelayMs(data, override) {
1322
1185
  if (override !== void 0) return Math.max(250, override);
package/dist/index.d.mts CHANGED
@@ -92,11 +92,21 @@ interface FindEmailResult {
92
92
  verifiedAt: string | null;
93
93
  needsReverification: boolean;
94
94
  providerUsed: string;
95
+ creditsUsed?: number;
96
+ estimatedExternalCostUsd?: number;
97
+ billingMetadata?: Record<string, unknown>;
98
+ resultReturnedFrom?: string;
99
+ cacheHit?: boolean;
100
+ freshEnrichmentUsed?: boolean;
101
+ liveProviderCalled?: boolean;
102
+ openrouterCalled?: boolean;
103
+ byoOpenrouterUsed?: boolean;
95
104
  error?: string;
96
105
  errorCode?: string;
97
106
  raw: Record<string, unknown>;
98
107
  }
99
108
  interface VerifyEmailResult {
109
+ success: boolean;
100
110
  email: string;
101
111
  /** Execution state. Processing results are never send-safe. */
102
112
  status: 'processing' | 'completed';
@@ -107,9 +117,21 @@ interface VerifyEmailResult {
107
117
  isValid: boolean;
108
118
  isDeliverable: boolean;
109
119
  isCatchAll: boolean;
120
+ /** Explicit send-safety verdict. Missing or false never becomes true by inference. */
121
+ verifiedForSending: boolean;
122
+ verificationVerdict: string;
110
123
  confidenceScore: number;
111
124
  provider?: string;
112
125
  verificationSource?: string;
126
+ billingReplayed?: boolean;
127
+ creditsUsed?: number;
128
+ billingMetadata?: Record<string, unknown>;
129
+ resultReturnedFrom?: string;
130
+ cacheHit?: boolean;
131
+ freshEnrichmentUsed?: boolean;
132
+ liveProviderCalled?: boolean;
133
+ openrouterCalled?: boolean;
134
+ byoOpenrouterUsed?: boolean;
113
135
  raw: Record<string, unknown>;
114
136
  }
115
137
  interface VerifyEmailOptions {
@@ -141,15 +163,17 @@ interface CompanySignalEnrichmentParams {
141
163
  lookbackDays?: number;
142
164
  online?: boolean;
143
165
  enableDeepSearch?: boolean;
166
+ /** Web coverage policy. Balanced targets an average all-in search budget below one cent. */
167
+ searchMode?: 'fast' | 'balanced' | 'coverage';
144
168
  includeSummary?: boolean;
145
169
  enableOutreachIntelligence?: boolean;
146
170
  enablePredictiveIntelligence?: boolean;
147
171
  skipCache?: boolean;
148
- /** Wait for a queued Trigger.dev enrichment to finish. Defaults to true. */
172
+ /** @deprecated Company Signals is always synchronous; this option is ignored. */
149
173
  waitForResult?: boolean;
150
- /** Maximum time to wait for a queued enrichment. Defaults to 20 minutes. */
174
+ /** Maximum server-side synchronous execution window. */
151
175
  maxWaitMs?: number;
152
- /** Delay between queued-enrichment status checks. Defaults to 2 seconds. */
176
+ /** @deprecated Company Signals never returns a polling contract. */
153
177
  pollIntervalMs?: number;
154
178
  /** Return a no-spend plan without provider calls or jobs. */
155
179
  dryRun?: boolean;
@@ -162,7 +186,9 @@ interface CompanySignal {
162
186
  type: string;
163
187
  content: string;
164
188
  date?: string | null;
189
+ detectedAt?: string | null;
165
190
  datePrecision?: string;
191
+ hasSpecificDate?: boolean;
166
192
  sourceUrl?: string | null;
167
193
  sourceType?: string;
168
194
  sourceProvenance?: string;
@@ -199,7 +225,10 @@ interface CompanySignalEnrichmentResult {
199
225
  billingMetadata?: Record<string, unknown>;
200
226
  resultReturnedFrom?: string;
201
227
  cacheHit?: boolean;
228
+ freshEnrichmentUsed?: boolean;
202
229
  liveProviderCalled?: boolean;
230
+ aiProviderCalled?: boolean;
231
+ byoAiUsed?: boolean;
203
232
  metadata?: Record<string, unknown>;
204
233
  raw: Record<string, unknown>;
205
234
  }
@@ -214,8 +243,10 @@ interface SignalToCopyParams {
214
243
  skipCache?: boolean;
215
244
  /** Opt into slower deep research. Defaults to false. */
216
245
  enableDeepSearch?: boolean;
246
+ /** @deprecated Signal to Copy is always synchronous; this option is ignored. */
217
247
  waitForResult?: boolean;
218
248
  maxWaitMs?: number;
249
+ /** @deprecated Signal to Copy never returns a polling contract. */
219
250
  pollIntervalMs?: number;
220
251
  /** Return a no-spend plan without provider calls or jobs. */
221
252
  dryRun?: boolean;
@@ -306,7 +337,6 @@ declare class Signaliz {
306
337
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
307
338
  private runCoreProductDryRun;
308
339
  private runSignalCopyBatchChunks;
309
- private runAvailableSignalCopyBatchJob;
310
340
  private runRecoverableCoreBatchJob;
311
341
  private runSignalCopyBatchChunk;
312
342
  private runCoreBatchRound;