@signaliz/sdk 1.0.47 → 1.0.48

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,29 +97,27 @@ 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`
@@ -458,34 +458,9 @@ var Signaliz = class {
458
458
  }
459
459
  async enrichCompanySignals(params) {
460
460
  const request = companySignalRequestBody(params);
461
- let data = await this.client.post("api/v1/company-signals", request);
461
+ const data = await this.client.post("api/v1/company-signals", request);
462
462
  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
- }
463
+ assertTerminalSignalResponse(data, "Company Signals");
489
464
  return normalizeCompanySignalResult(params, data);
490
465
  }
491
466
  async enrichCompanies(companies, options) {
@@ -498,68 +473,22 @@ var Signaliz = class {
498
473
  );
499
474
  }
500
475
  const startedAt = Date.now();
501
- const results = new Array(companies.length);
502
476
  const initialTasks = companies.map((params, index) => ({
503
477
  index,
504
478
  request: companySignalRequestBody(params)
505
479
  }));
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
- });
480
+ const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
481
+ const results = round.map((item) => {
482
+ if (!item.success || !item.raw) {
483
+ return batchItemFailure(item.index, item.error || "Company Signal batch request failed", item.raw ?? item);
558
484
  }
559
- if (nextPending.length === 0) break;
560
- await sleep2(Math.max(1, nextDelayMs));
561
- pending = nextPending;
562
- }
485
+ try {
486
+ assertTerminalSignalResponse(item.raw, "Company Signals");
487
+ return { index: item.index, success: true, data: normalizeCompanySignalResult(companies[item.index], item.raw) };
488
+ } catch (error) {
489
+ return batchItemFailure(item.index, error instanceof Error ? error.message : String(error), item.raw);
490
+ }
491
+ });
563
492
  const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
564
493
  const succeeded = compactedResults.filter((item) => item.success).length;
565
494
  return {
@@ -573,21 +502,9 @@ var Signaliz = class {
573
502
  async signalToCopy(params) {
574
503
  validateSignalToCopyParams(params);
575
504
  const request = signalToCopyRequestBody(params);
576
- let data = await this.client.post("api/v1/signal-to-copy", request);
505
+ const data = await this.client.post("api/v1/signal-to-copy", request);
577
506
  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
- }
507
+ assertTerminalSignalResponse(data, "Signal to Copy");
591
508
  return normalizeSignalToCopyResult(data);
592
509
  }
593
510
  async createSignalCopyBatch(requests, options) {
@@ -602,16 +519,10 @@ var Signaliz = class {
602
519
  }
603
520
  const startedAt = Date.now();
604
521
  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
522
+ request: signalToCopyRequestBody(params)
609
523
  }));
610
524
  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);
525
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
615
526
  const results = requests.map((_, index) => ({
616
527
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
617
528
  index
@@ -667,22 +578,6 @@ var Signaliz = class {
667
578
  }
668
579
  return uniqueResults;
669
580
  }
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
581
  async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
687
582
  const startedAt = Date.now();
688
583
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
@@ -754,77 +649,35 @@ var Signaliz = class {
754
649
  return rows;
755
650
  }
756
651
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
757
- const startedAt = Date.now();
758
652
  const results = new Array(requests.length);
759
653
  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" };
654
+ const data = await this.client.post("api/v1/signal-to-copy", {
655
+ requests: requests.map(signalToCopyRequestBody),
656
+ concurrency
657
+ });
658
+ if (!Array.isArray(data.results) || data.results.length !== requests.length) {
659
+ throw new Error("Signal to Copy batch returned an invalid result count");
660
+ }
661
+ for (let index = 0; index < requests.length; index += 1) {
662
+ const item = data.results[index];
663
+ if (item.success === false) {
664
+ if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
665
+ index,
666
+ success: false,
667
+ raw: item
668
+ })) {
669
+ rateLimited.push({ index, params: requests[index], raw: item });
814
670
  continue;
815
671
  }
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
- });
672
+ results[index] = batchItemFailure(index, item.error || item.message || "Signal to Copy failed", item);
673
+ continue;
674
+ }
675
+ try {
676
+ assertTerminalSignalResponse(item, "Signal to Copy");
677
+ results[index] = { index, success: true, data: normalizeSignalToCopyResult(item) };
678
+ } catch (error) {
679
+ results[index] = batchItemFailure(index, error instanceof Error ? error.message : String(error), item);
825
680
  }
826
- pending = nextPending;
827
- if (pending.length > 0) await sleep2(nextDelayMs);
828
681
  }
829
682
  if (rateLimited.length > 0) {
830
683
  await sleep2(rowRateLimitDelayMs(rateLimited.map((item) => ({
@@ -843,12 +696,12 @@ var Signaliz = class {
843
696
  }
844
697
  return results;
845
698
  }
846
- async runCoreBatchRound(path, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
699
+ async runCoreBatchRound(path, tasks, options, rowRateLimitAttempt = 0) {
847
700
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
848
701
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
849
702
  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) {
703
+ 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;
704
+ if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
852
705
  const rows = await this.runRecoverableCoreBatchJob(
853
706
  path,
854
707
  uniqueTasks.map((task) => task.request),
@@ -920,7 +773,6 @@ var Signaliz = class {
920
773
  path,
921
774
  retryTasks,
922
775
  options,
923
- void 0,
924
776
  rowRateLimitAttempt + 1
925
777
  );
926
778
  const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
@@ -1084,41 +936,17 @@ function companySignalRequestBody(params) {
1084
936
  lookback_days: params.lookbackDays,
1085
937
  online,
1086
938
  enable_deep_search: params.enableDeepSearch ?? online,
939
+ search_mode: params.searchMode ?? "balanced",
1087
940
  include_summary: params.includeSummary,
1088
941
  enable_outreach_intelligence: params.enableOutreachIntelligence,
1089
942
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
1090
943
  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,
944
+ wait_for_result: true,
1094
945
  max_wait_ms: params.maxWaitMs,
1095
946
  dry_run: params.dryRun,
1096
947
  idempotency_key: params.idempotencyKey
1097
948
  });
1098
949
  }
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
950
  function normalizeCompanySignalResult(params, data) {
1123
951
  const rawSignals = data.signals ?? data.signal_feed ?? [];
1124
952
  return {
@@ -1266,6 +1094,7 @@ function signalToCopyRequestBody(params) {
1266
1094
  signal_run_id: params.signalRunId,
1267
1095
  skip_cache: params.skipCache,
1268
1096
  enable_deep_search: params.enableDeepSearch,
1097
+ max_wait_ms: params.maxWaitMs,
1269
1098
  dry_run: params.dryRun,
1270
1099
  idempotency_key: params.idempotencyKey
1271
1100
  });
@@ -1312,11 +1141,10 @@ function normalizeSignalToCopyResult(data) {
1312
1141
  function isPendingSignalResponse(data) {
1313
1142
  return ["triggered", "queued", "processing", "pending", "pending_version", "waiting_for_deploy"].includes(String(data.status || "").toLowerCase());
1314
1143
  }
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());
1144
+ function assertTerminalSignalResponse(data, capability) {
1145
+ if (isPendingSignalResponse(data) || data.job_id) {
1146
+ throw new Error(`${capability} violated the synchronous response contract.`);
1147
+ }
1320
1148
  }
1321
1149
  function signalPollDelayMs(data, override) {
1322
1150
  if (override !== void 0) return Math.max(250, override);
package/dist/index.d.mts CHANGED
@@ -141,15 +141,17 @@ interface CompanySignalEnrichmentParams {
141
141
  lookbackDays?: number;
142
142
  online?: boolean;
143
143
  enableDeepSearch?: boolean;
144
+ /** Web coverage policy. Balanced targets an average all-in search budget below one cent. */
145
+ searchMode?: 'fast' | 'balanced' | 'coverage';
144
146
  includeSummary?: boolean;
145
147
  enableOutreachIntelligence?: boolean;
146
148
  enablePredictiveIntelligence?: boolean;
147
149
  skipCache?: boolean;
148
- /** Wait for a queued Trigger.dev enrichment to finish. Defaults to true. */
150
+ /** @deprecated Company Signals is always synchronous; this option is ignored. */
149
151
  waitForResult?: boolean;
150
- /** Maximum time to wait for a queued enrichment. Defaults to 20 minutes. */
152
+ /** Maximum server-side synchronous execution window. */
151
153
  maxWaitMs?: number;
152
- /** Delay between queued-enrichment status checks. Defaults to 2 seconds. */
154
+ /** @deprecated Company Signals never returns a polling contract. */
153
155
  pollIntervalMs?: number;
154
156
  /** Return a no-spend plan without provider calls or jobs. */
155
157
  dryRun?: boolean;
@@ -214,8 +216,10 @@ interface SignalToCopyParams {
214
216
  skipCache?: boolean;
215
217
  /** Opt into slower deep research. Defaults to false. */
216
218
  enableDeepSearch?: boolean;
219
+ /** @deprecated Signal to Copy is always synchronous; this option is ignored. */
217
220
  waitForResult?: boolean;
218
221
  maxWaitMs?: number;
222
+ /** @deprecated Signal to Copy never returns a polling contract. */
219
223
  pollIntervalMs?: number;
220
224
  /** Return a no-spend plan without provider calls or jobs. */
221
225
  dryRun?: boolean;
@@ -306,7 +310,6 @@ declare class Signaliz {
306
310
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
307
311
  private runCoreProductDryRun;
308
312
  private runSignalCopyBatchChunks;
309
- private runAvailableSignalCopyBatchJob;
310
313
  private runRecoverableCoreBatchJob;
311
314
  private runSignalCopyBatchChunk;
312
315
  private runCoreBatchRound;
package/dist/index.d.ts CHANGED
@@ -141,15 +141,17 @@ interface CompanySignalEnrichmentParams {
141
141
  lookbackDays?: number;
142
142
  online?: boolean;
143
143
  enableDeepSearch?: boolean;
144
+ /** Web coverage policy. Balanced targets an average all-in search budget below one cent. */
145
+ searchMode?: 'fast' | 'balanced' | 'coverage';
144
146
  includeSummary?: boolean;
145
147
  enableOutreachIntelligence?: boolean;
146
148
  enablePredictiveIntelligence?: boolean;
147
149
  skipCache?: boolean;
148
- /** Wait for a queued Trigger.dev enrichment to finish. Defaults to true. */
150
+ /** @deprecated Company Signals is always synchronous; this option is ignored. */
149
151
  waitForResult?: boolean;
150
- /** Maximum time to wait for a queued enrichment. Defaults to 20 minutes. */
152
+ /** Maximum server-side synchronous execution window. */
151
153
  maxWaitMs?: number;
152
- /** Delay between queued-enrichment status checks. Defaults to 2 seconds. */
154
+ /** @deprecated Company Signals never returns a polling contract. */
153
155
  pollIntervalMs?: number;
154
156
  /** Return a no-spend plan without provider calls or jobs. */
155
157
  dryRun?: boolean;
@@ -214,8 +216,10 @@ interface SignalToCopyParams {
214
216
  skipCache?: boolean;
215
217
  /** Opt into slower deep research. Defaults to false. */
216
218
  enableDeepSearch?: boolean;
219
+ /** @deprecated Signal to Copy is always synchronous; this option is ignored. */
217
220
  waitForResult?: boolean;
218
221
  maxWaitMs?: number;
222
+ /** @deprecated Signal to Copy never returns a polling contract. */
219
223
  pollIntervalMs?: number;
220
224
  /** Return a no-spend plan without provider calls or jobs. */
221
225
  dryRun?: boolean;
@@ -306,7 +310,6 @@ declare class Signaliz {
306
310
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
307
311
  private runCoreProductDryRun;
308
312
  private runSignalCopyBatchChunks;
309
- private runAvailableSignalCopyBatchJob;
310
313
  private runRecoverableCoreBatchJob;
311
314
  private runSignalCopyBatchChunk;
312
315
  private runCoreBatchRound;
package/dist/index.js CHANGED
@@ -485,34 +485,9 @@ var Signaliz = class {
485
485
  }
486
486
  async enrichCompanySignals(params) {
487
487
  const request = companySignalRequestBody(params);
488
- let data = await this.client.post("api/v1/company-signals", request);
488
+ const data = await this.client.post("api/v1/company-signals", request);
489
489
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
490
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
491
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
492
- const startedAt = Date.now();
493
- while (Date.now() - startedAt < maxWaitMs) {
494
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
495
- const status = await this.client.post("api/v1/company-signals", {
496
- ...request,
497
- signal_run_id: data.run_id
498
- });
499
- if (isCompletedSignalRun(status)) {
500
- data = status.output ?? status.result ?? status.data ?? status;
501
- break;
502
- }
503
- if (isFailedSignalRun(status)) {
504
- throw new Error(`Company signal enrichment ${data.run_id} failed with status ${status.status || "unknown"}`);
505
- }
506
- if (!isPendingSignalResponse(status)) {
507
- data = status.output ?? status.result ?? status.data ?? status;
508
- break;
509
- }
510
- data = status;
511
- }
512
- if (isPendingSignalResponse(data)) {
513
- throw new Error(`Company signal enrichment ${data.run_id} did not complete within ${maxWaitMs}ms`);
514
- }
515
- }
490
+ assertTerminalSignalResponse(data, "Company Signals");
516
491
  return normalizeCompanySignalResult(params, data);
517
492
  }
518
493
  async enrichCompanies(companies, options) {
@@ -525,68 +500,22 @@ var Signaliz = class {
525
500
  );
526
501
  }
527
502
  const startedAt = Date.now();
528
- const results = new Array(companies.length);
529
503
  const initialTasks = companies.map((params, index) => ({
530
504
  index,
531
505
  request: companySignalRequestBody(params)
532
506
  }));
533
- const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
534
- let pending = initialTasks;
535
- let firstRound = true;
536
- while (pending.length > 0) {
537
- const round = await this.runCoreBatchRound(
538
- "api/v1/company-signals",
539
- pending,
540
- options,
541
- firstRound ? recoverableBatch : void 0
542
- );
543
- firstRound = false;
544
- const taskByIndex = new Map(pending.map((task) => [task.index, task]));
545
- const nextPending = [];
546
- let nextDelayMs = 6e4;
547
- for (const item of round) {
548
- const params = companies[item.index];
549
- const task = taskByIndex.get(item.index);
550
- if (!item.success || !item.raw) {
551
- results[item.index] = batchItemFailure(
552
- item.index,
553
- item.error || "Company Signal batch request failed",
554
- item.raw ?? item
555
- );
556
- continue;
557
- }
558
- if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
559
- results[item.index] = { index: item.index, success: true, data: normalizeCompanySignalResult(params, item.raw) };
560
- continue;
561
- }
562
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
563
- if (Date.now() - startedAt >= maxWaitMs) {
564
- results[item.index] = {
565
- index: item.index,
566
- success: false,
567
- error: `Company signal enrichment ${item.raw.signal_run_id || item.raw.run_id || ""} did not complete within ${maxWaitMs}ms`
568
- };
569
- continue;
570
- }
571
- const signalRunId = item.raw.signal_run_id || item.raw.run_id;
572
- if (!signalRunId) {
573
- results[item.index] = { index: item.index, success: false, error: "Company signal enrichment is processing without a signal_run_id" };
574
- continue;
575
- }
576
- nextDelayMs = Math.min(
577
- nextDelayMs,
578
- signalPollDelayMs(item.raw, params.pollIntervalMs),
579
- maxWaitMs - (Date.now() - startedAt)
580
- );
581
- nextPending.push({
582
- ...task,
583
- request: { ...task.request, signal_run_id: signalRunId }
584
- });
507
+ const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
508
+ const results = round.map((item) => {
509
+ if (!item.success || !item.raw) {
510
+ return batchItemFailure(item.index, item.error || "Company Signal batch request failed", item.raw ?? item);
585
511
  }
586
- if (nextPending.length === 0) break;
587
- await sleep2(Math.max(1, nextDelayMs));
588
- pending = nextPending;
589
- }
512
+ try {
513
+ assertTerminalSignalResponse(item.raw, "Company Signals");
514
+ return { index: item.index, success: true, data: normalizeCompanySignalResult(companies[item.index], item.raw) };
515
+ } catch (error) {
516
+ return batchItemFailure(item.index, error instanceof Error ? error.message : String(error), item.raw);
517
+ }
518
+ });
590
519
  const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
591
520
  const succeeded = compactedResults.filter((item) => item.success).length;
592
521
  return {
@@ -600,21 +529,9 @@ var Signaliz = class {
600
529
  async signalToCopy(params) {
601
530
  validateSignalToCopyParams(params);
602
531
  const request = signalToCopyRequestBody(params);
603
- let data = await this.client.post("api/v1/signal-to-copy", request);
532
+ const data = await this.client.post("api/v1/signal-to-copy", request);
604
533
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
605
- if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
606
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
607
- const startedAt = Date.now();
608
- while (Date.now() - startedAt < maxWaitMs) {
609
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
610
- const signalRunId = data.signal_run_id || data.run_id;
611
- data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
612
- if (!isPendingSignalResponse(data)) break;
613
- }
614
- if (isPendingSignalResponse(data)) {
615
- throw new Error(`Signal Copy ${data.run_id} did not complete within ${maxWaitMs}ms`);
616
- }
617
- }
534
+ assertTerminalSignalResponse(data, "Signal to Copy");
618
535
  return normalizeSignalToCopyResult(data);
619
536
  }
620
537
  async createSignalCopyBatch(requests, options) {
@@ -629,16 +546,10 @@ var Signaliz = class {
629
546
  }
630
547
  const startedAt = Date.now();
631
548
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
632
- request: signalToCopyRequestBody(params),
633
- wait_for_result: params.waitForResult,
634
- max_wait_ms: params.maxWaitMs,
635
- poll_interval_ms: params.pollIntervalMs
549
+ request: signalToCopyRequestBody(params)
636
550
  }));
637
551
  const uniqueRequests = deduplicated.uniqueItems;
638
- const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
639
- (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
640
- );
641
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
552
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
642
553
  const results = requests.map((_, index) => ({
643
554
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
644
555
  index
@@ -694,22 +605,6 @@ var Signaliz = class {
694
605
  }
695
606
  return uniqueResults;
696
607
  }
697
- async runAvailableSignalCopyBatchJob(requests, options) {
698
- const rows = await this.runRecoverableCoreBatchJob(
699
- "api/v1/signal-to-copy",
700
- requests.map(signalToCopyRequestBody),
701
- "Signal to Copy",
702
- 500,
703
- 20 * 6e4,
704
- options?.idempotencyKey
705
- );
706
- const results = new Array(requests.length);
707
- for (const row of rows) {
708
- const index = Number(row.index);
709
- results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
710
- }
711
- return results;
712
- }
713
608
  async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
714
609
  const startedAt = Date.now();
715
610
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
@@ -781,77 +676,35 @@ var Signaliz = class {
781
676
  return rows;
782
677
  }
783
678
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
784
- const startedAt = Date.now();
785
679
  const results = new Array(requests.length);
786
680
  const rateLimited = [];
787
- let pending = requests.map((params, index) => ({
788
- index,
789
- params,
790
- request: signalToCopyRequestBody(params)
791
- }));
792
- while (pending.length > 0) {
793
- const data = await this.client.post("api/v1/signal-to-copy", {
794
- requests: pending.map((item) => item.request),
795
- concurrency
796
- });
797
- if (!Array.isArray(data.results) || data.results.length !== pending.length) {
798
- throw new Error("Signal to Copy batch returned an invalid result count");
799
- }
800
- const nextPending = [];
801
- let nextDelayMs = 6e4;
802
- for (let index = 0; index < pending.length; index += 1) {
803
- const task = pending[index];
804
- const item = data.results[index];
805
- if (item.success === false) {
806
- if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
807
- index: task.index,
808
- success: false,
809
- raw: item
810
- })) {
811
- rateLimited.push({ index: task.index, params: task.params, raw: item });
812
- continue;
813
- }
814
- results[task.index] = batchItemFailure(
815
- task.index,
816
- item.error || item.message || "Signal to Copy failed",
817
- item
818
- );
819
- continue;
820
- }
821
- if (!isPendingSignalResponse(item)) {
822
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
823
- continue;
824
- }
825
- if (task.params.waitForResult === false) {
826
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
827
- continue;
828
- }
829
- const maxWaitMs = Math.max(1e3, task.params.maxWaitMs ?? 20 * 6e4);
830
- if (Date.now() - startedAt >= maxWaitMs) {
831
- results[task.index] = {
832
- index: task.index,
833
- success: false,
834
- error: `Signal Copy ${item.signal_run_id || item.run_id || ""} did not complete within ${maxWaitMs}ms`
835
- };
836
- continue;
837
- }
838
- const signalRunId = item.signal_run_id || item.run_id;
839
- if (!signalRunId) {
840
- results[task.index] = { index: task.index, success: false, error: "Signal Copy is processing without a signal_run_id" };
681
+ const data = await this.client.post("api/v1/signal-to-copy", {
682
+ requests: requests.map(signalToCopyRequestBody),
683
+ concurrency
684
+ });
685
+ if (!Array.isArray(data.results) || data.results.length !== requests.length) {
686
+ throw new Error("Signal to Copy batch returned an invalid result count");
687
+ }
688
+ for (let index = 0; index < requests.length; index += 1) {
689
+ const item = data.results[index];
690
+ if (item.success === false) {
691
+ if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
692
+ index,
693
+ success: false,
694
+ raw: item
695
+ })) {
696
+ rateLimited.push({ index, params: requests[index], raw: item });
841
697
  continue;
842
698
  }
843
- nextDelayMs = Math.min(
844
- nextDelayMs,
845
- signalPollDelayMs(item, task.params.pollIntervalMs),
846
- maxWaitMs - (Date.now() - startedAt)
847
- );
848
- nextPending.push({
849
- ...task,
850
- request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
851
- });
699
+ results[index] = batchItemFailure(index, item.error || item.message || "Signal to Copy failed", item);
700
+ continue;
701
+ }
702
+ try {
703
+ assertTerminalSignalResponse(item, "Signal to Copy");
704
+ results[index] = { index, success: true, data: normalizeSignalToCopyResult(item) };
705
+ } catch (error) {
706
+ results[index] = batchItemFailure(index, error instanceof Error ? error.message : String(error), item);
852
707
  }
853
- pending = nextPending;
854
- if (pending.length > 0) await sleep2(nextDelayMs);
855
708
  }
856
709
  if (rateLimited.length > 0) {
857
710
  await sleep2(rowRateLimitDelayMs(rateLimited.map((item) => ({
@@ -870,12 +723,12 @@ var Signaliz = class {
870
723
  }
871
724
  return results;
872
725
  }
873
- async runCoreBatchRound(path, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
726
+ async runCoreBatchRound(path, tasks, options, rowRateLimitAttempt = 0) {
874
727
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
875
728
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
876
729
  const uniqueTasks = deduplicated.uniqueItems;
877
- 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;
878
- if (recoverableBatch && uniqueTasks.length > 25) {
730
+ 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;
731
+ if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
879
732
  const rows = await this.runRecoverableCoreBatchJob(
880
733
  path,
881
734
  uniqueTasks.map((task) => task.request),
@@ -947,7 +800,6 @@ var Signaliz = class {
947
800
  path,
948
801
  retryTasks,
949
802
  options,
950
- void 0,
951
803
  rowRateLimitAttempt + 1
952
804
  );
953
805
  const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
@@ -1111,41 +963,17 @@ function companySignalRequestBody(params) {
1111
963
  lookback_days: params.lookbackDays,
1112
964
  online,
1113
965
  enable_deep_search: params.enableDeepSearch ?? online,
966
+ search_mode: params.searchMode ?? "balanced",
1114
967
  include_summary: params.includeSummary,
1115
968
  enable_outreach_intelligence: params.enableOutreachIntelligence,
1116
969
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
1117
970
  skip_cache: params.skipCache,
1118
- // REST always returns a resumable run instead of holding the connection;
1119
- // waitForResult controls the SDK polling loop.
1120
- wait_for_result: false,
971
+ wait_for_result: true,
1121
972
  max_wait_ms: params.maxWaitMs,
1122
973
  dry_run: params.dryRun,
1123
974
  idempotency_key: params.idempotencyKey
1124
975
  });
1125
976
  }
1126
- function companySignalRecoverableBatchOptions(companies, tasks) {
1127
- if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
1128
- return void 0;
1129
- }
1130
- const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
1131
- if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
1132
- return void 0;
1133
- }
1134
- const configKey = (request) => {
1135
- const {
1136
- company_domain: _companyDomain,
1137
- company_name: _companyName,
1138
- signal_run_id: _signalRunId,
1139
- wait_for_result: _waitForResult,
1140
- max_wait_ms: _maxWaitMs,
1141
- ...config
1142
- } = request;
1143
- return JSON.stringify(config);
1144
- };
1145
- const sharedConfig = configKey(tasks[0].request);
1146
- if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
1147
- return { label: "Company Signals", pageSize: 25, maxWaitMs };
1148
- }
1149
977
  function normalizeCompanySignalResult(params, data) {
1150
978
  const rawSignals = data.signals ?? data.signal_feed ?? [];
1151
979
  return {
@@ -1293,6 +1121,7 @@ function signalToCopyRequestBody(params) {
1293
1121
  signal_run_id: params.signalRunId,
1294
1122
  skip_cache: params.skipCache,
1295
1123
  enable_deep_search: params.enableDeepSearch,
1124
+ max_wait_ms: params.maxWaitMs,
1296
1125
  dry_run: params.dryRun,
1297
1126
  idempotency_key: params.idempotencyKey
1298
1127
  });
@@ -1339,11 +1168,10 @@ function normalizeSignalToCopyResult(data) {
1339
1168
  function isPendingSignalResponse(data) {
1340
1169
  return ["triggered", "queued", "processing", "pending", "pending_version", "waiting_for_deploy"].includes(String(data.status || "").toLowerCase());
1341
1170
  }
1342
- function isCompletedSignalRun(data) {
1343
- return ["completed", "success", "succeeded"].includes(String(data.status || "").toLowerCase());
1344
- }
1345
- function isFailedSignalRun(data) {
1346
- return ["failed", "error", "cancelled", "canceled", "crashed", "timed_out"].includes(String(data.status || "").toLowerCase());
1171
+ function assertTerminalSignalResponse(data, capability) {
1172
+ if (isPendingSignalResponse(data) || data.job_id) {
1173
+ throw new Error(`${capability} violated the synchronous response contract.`);
1174
+ }
1347
1175
  }
1348
1176
  function signalPollDelayMs(data, override) {
1349
1177
  if (override !== void 0) return Math.max(250, override);
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-KTWDTUCI.mjs";
4
+ } from "./chunk-NOOQDYOJ.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -489,34 +489,9 @@ var Signaliz = class {
489
489
  }
490
490
  async enrichCompanySignals(params) {
491
491
  const request = companySignalRequestBody(params);
492
- let data = await this.client.post("api/v1/company-signals", request);
492
+ const data = await this.client.post("api/v1/company-signals", request);
493
493
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
494
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
495
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
496
- const startedAt = Date.now();
497
- while (Date.now() - startedAt < maxWaitMs) {
498
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
499
- const status = await this.client.post("api/v1/company-signals", {
500
- ...request,
501
- signal_run_id: data.run_id
502
- });
503
- if (isCompletedSignalRun(status)) {
504
- data = status.output ?? status.result ?? status.data ?? status;
505
- break;
506
- }
507
- if (isFailedSignalRun(status)) {
508
- throw new Error(`Company signal enrichment ${data.run_id} failed with status ${status.status || "unknown"}`);
509
- }
510
- if (!isPendingSignalResponse(status)) {
511
- data = status.output ?? status.result ?? status.data ?? status;
512
- break;
513
- }
514
- data = status;
515
- }
516
- if (isPendingSignalResponse(data)) {
517
- throw new Error(`Company signal enrichment ${data.run_id} did not complete within ${maxWaitMs}ms`);
518
- }
519
- }
494
+ assertTerminalSignalResponse(data, "Company Signals");
520
495
  return normalizeCompanySignalResult(params, data);
521
496
  }
522
497
  async enrichCompanies(companies, options) {
@@ -529,68 +504,22 @@ var Signaliz = class {
529
504
  );
530
505
  }
531
506
  const startedAt = Date.now();
532
- const results = new Array(companies.length);
533
507
  const initialTasks = companies.map((params, index) => ({
534
508
  index,
535
509
  request: companySignalRequestBody(params)
536
510
  }));
537
- const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
538
- let pending = initialTasks;
539
- let firstRound = true;
540
- while (pending.length > 0) {
541
- const round = await this.runCoreBatchRound(
542
- "api/v1/company-signals",
543
- pending,
544
- options,
545
- firstRound ? recoverableBatch : void 0
546
- );
547
- firstRound = false;
548
- const taskByIndex = new Map(pending.map((task) => [task.index, task]));
549
- const nextPending = [];
550
- let nextDelayMs = 6e4;
551
- for (const item of round) {
552
- const params = companies[item.index];
553
- const task = taskByIndex.get(item.index);
554
- if (!item.success || !item.raw) {
555
- results[item.index] = batchItemFailure(
556
- item.index,
557
- item.error || "Company Signal batch request failed",
558
- item.raw ?? item
559
- );
560
- continue;
561
- }
562
- if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
563
- results[item.index] = { index: item.index, success: true, data: normalizeCompanySignalResult(params, item.raw) };
564
- continue;
565
- }
566
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
567
- if (Date.now() - startedAt >= maxWaitMs) {
568
- results[item.index] = {
569
- index: item.index,
570
- success: false,
571
- error: `Company signal enrichment ${item.raw.signal_run_id || item.raw.run_id || ""} did not complete within ${maxWaitMs}ms`
572
- };
573
- continue;
574
- }
575
- const signalRunId = item.raw.signal_run_id || item.raw.run_id;
576
- if (!signalRunId) {
577
- results[item.index] = { index: item.index, success: false, error: "Company signal enrichment is processing without a signal_run_id" };
578
- continue;
579
- }
580
- nextDelayMs = Math.min(
581
- nextDelayMs,
582
- signalPollDelayMs(item.raw, params.pollIntervalMs),
583
- maxWaitMs - (Date.now() - startedAt)
584
- );
585
- nextPending.push({
586
- ...task,
587
- request: { ...task.request, signal_run_id: signalRunId }
588
- });
511
+ const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
512
+ const results = round.map((item) => {
513
+ if (!item.success || !item.raw) {
514
+ return batchItemFailure(item.index, item.error || "Company Signal batch request failed", item.raw ?? item);
589
515
  }
590
- if (nextPending.length === 0) break;
591
- await sleep2(Math.max(1, nextDelayMs));
592
- pending = nextPending;
593
- }
516
+ try {
517
+ assertTerminalSignalResponse(item.raw, "Company Signals");
518
+ return { index: item.index, success: true, data: normalizeCompanySignalResult(companies[item.index], item.raw) };
519
+ } catch (error) {
520
+ return batchItemFailure(item.index, error instanceof Error ? error.message : String(error), item.raw);
521
+ }
522
+ });
594
523
  const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
595
524
  const succeeded = compactedResults.filter((item) => item.success).length;
596
525
  return {
@@ -604,21 +533,9 @@ var Signaliz = class {
604
533
  async signalToCopy(params) {
605
534
  validateSignalToCopyParams(params);
606
535
  const request = signalToCopyRequestBody(params);
607
- let data = await this.client.post("api/v1/signal-to-copy", request);
536
+ const data = await this.client.post("api/v1/signal-to-copy", request);
608
537
  if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
609
- if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
610
- const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
611
- const startedAt = Date.now();
612
- while (Date.now() - startedAt < maxWaitMs) {
613
- if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
614
- const signalRunId = data.signal_run_id || data.run_id;
615
- data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
616
- if (!isPendingSignalResponse(data)) break;
617
- }
618
- if (isPendingSignalResponse(data)) {
619
- throw new Error(`Signal Copy ${data.run_id} did not complete within ${maxWaitMs}ms`);
620
- }
621
- }
538
+ assertTerminalSignalResponse(data, "Signal to Copy");
622
539
  return normalizeSignalToCopyResult(data);
623
540
  }
624
541
  async createSignalCopyBatch(requests, options) {
@@ -633,16 +550,10 @@ var Signaliz = class {
633
550
  }
634
551
  const startedAt = Date.now();
635
552
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
636
- request: signalToCopyRequestBody(params),
637
- wait_for_result: params.waitForResult,
638
- max_wait_ms: params.maxWaitMs,
639
- poll_interval_ms: params.pollIntervalMs
553
+ request: signalToCopyRequestBody(params)
640
554
  }));
641
555
  const uniqueRequests = deduplicated.uniqueItems;
642
- const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
643
- (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
644
- );
645
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
556
+ const uniqueResults = await this.runSignalCopyBatchChunks(uniqueRequests, options);
646
557
  const results = requests.map((_, index) => ({
647
558
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
648
559
  index
@@ -698,22 +609,6 @@ var Signaliz = class {
698
609
  }
699
610
  return uniqueResults;
700
611
  }
701
- async runAvailableSignalCopyBatchJob(requests, options) {
702
- const rows = await this.runRecoverableCoreBatchJob(
703
- "api/v1/signal-to-copy",
704
- requests.map(signalToCopyRequestBody),
705
- "Signal to Copy",
706
- 500,
707
- 20 * 6e4,
708
- options?.idempotencyKey
709
- );
710
- const results = new Array(requests.length);
711
- for (const row of rows) {
712
- const index = Number(row.index);
713
- results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
714
- }
715
- return results;
716
- }
717
612
  async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
718
613
  const startedAt = Date.now();
719
614
  const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
@@ -785,77 +680,35 @@ var Signaliz = class {
785
680
  return rows;
786
681
  }
787
682
  async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0) {
788
- const startedAt = Date.now();
789
683
  const results = new Array(requests.length);
790
684
  const rateLimited = [];
791
- let pending = requests.map((params, index) => ({
792
- index,
793
- params,
794
- request: signalToCopyRequestBody(params)
795
- }));
796
- while (pending.length > 0) {
797
- const data = await this.client.post("api/v1/signal-to-copy", {
798
- requests: pending.map((item) => item.request),
799
- concurrency
800
- });
801
- if (!Array.isArray(data.results) || data.results.length !== pending.length) {
802
- throw new Error("Signal to Copy batch returned an invalid result count");
803
- }
804
- const nextPending = [];
805
- let nextDelayMs = 6e4;
806
- for (let index = 0; index < pending.length; index += 1) {
807
- const task = pending[index];
808
- const item = data.results[index];
809
- if (item.success === false) {
810
- if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
811
- index: task.index,
812
- success: false,
813
- raw: item
814
- })) {
815
- rateLimited.push({ index: task.index, params: task.params, raw: item });
816
- continue;
817
- }
818
- results[task.index] = batchItemFailure(
819
- task.index,
820
- item.error || item.message || "Signal to Copy failed",
821
- item
822
- );
823
- continue;
824
- }
825
- if (!isPendingSignalResponse(item)) {
826
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
827
- continue;
828
- }
829
- if (task.params.waitForResult === false) {
830
- results[task.index] = { index: task.index, success: true, data: normalizeSignalToCopyResult(item) };
831
- continue;
832
- }
833
- const maxWaitMs = Math.max(1e3, task.params.maxWaitMs ?? 20 * 6e4);
834
- if (Date.now() - startedAt >= maxWaitMs) {
835
- results[task.index] = {
836
- index: task.index,
837
- success: false,
838
- error: `Signal Copy ${item.signal_run_id || item.run_id || ""} did not complete within ${maxWaitMs}ms`
839
- };
840
- continue;
841
- }
842
- const signalRunId = item.signal_run_id || item.run_id;
843
- if (!signalRunId) {
844
- results[task.index] = { index: task.index, success: false, error: "Signal Copy is processing without a signal_run_id" };
685
+ const data = await this.client.post("api/v1/signal-to-copy", {
686
+ requests: requests.map(signalToCopyRequestBody),
687
+ concurrency
688
+ });
689
+ if (!Array.isArray(data.results) || data.results.length !== requests.length) {
690
+ throw new Error("Signal to Copy batch returned an invalid result count");
691
+ }
692
+ for (let index = 0; index < requests.length; index += 1) {
693
+ const item = data.results[index];
694
+ if (item.success === false) {
695
+ if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES && isRetryableRowRateLimit({
696
+ index,
697
+ success: false,
698
+ raw: item
699
+ })) {
700
+ rateLimited.push({ index, params: requests[index], raw: item });
845
701
  continue;
846
702
  }
847
- nextDelayMs = Math.min(
848
- nextDelayMs,
849
- signalPollDelayMs(item, task.params.pollIntervalMs),
850
- maxWaitMs - (Date.now() - startedAt)
851
- );
852
- nextPending.push({
853
- ...task,
854
- request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
855
- });
703
+ results[index] = batchItemFailure(index, item.error || item.message || "Signal to Copy failed", item);
704
+ continue;
705
+ }
706
+ try {
707
+ assertTerminalSignalResponse(item, "Signal to Copy");
708
+ results[index] = { index, success: true, data: normalizeSignalToCopyResult(item) };
709
+ } catch (error) {
710
+ results[index] = batchItemFailure(index, error instanceof Error ? error.message : String(error), item);
856
711
  }
857
- pending = nextPending;
858
- if (pending.length > 0) await sleep2(nextDelayMs);
859
712
  }
860
713
  if (rateLimited.length > 0) {
861
714
  await sleep2(rowRateLimitDelayMs(rateLimited.map((item) => ({
@@ -874,12 +727,12 @@ var Signaliz = class {
874
727
  }
875
728
  return results;
876
729
  }
877
- async runCoreBatchRound(path2, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
730
+ async runCoreBatchRound(path2, tasks, options, rowRateLimitAttempt = 0) {
878
731
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
879
732
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
880
733
  const uniqueTasks = deduplicated.uniqueItems;
881
- const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : companyRecoverableBatch;
882
- if (recoverableBatch && uniqueTasks.length > 25) {
734
+ const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
735
+ if (path2 !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
883
736
  const rows = await this.runRecoverableCoreBatchJob(
884
737
  path2,
885
738
  uniqueTasks.map((task) => task.request),
@@ -951,7 +804,6 @@ var Signaliz = class {
951
804
  path2,
952
805
  retryTasks,
953
806
  options,
954
- void 0,
955
807
  rowRateLimitAttempt + 1
956
808
  );
957
809
  const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
@@ -1115,41 +967,17 @@ function companySignalRequestBody(params) {
1115
967
  lookback_days: params.lookbackDays,
1116
968
  online,
1117
969
  enable_deep_search: params.enableDeepSearch ?? online,
970
+ search_mode: params.searchMode ?? "balanced",
1118
971
  include_summary: params.includeSummary,
1119
972
  enable_outreach_intelligence: params.enableOutreachIntelligence,
1120
973
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
1121
974
  skip_cache: params.skipCache,
1122
- // REST always returns a resumable run instead of holding the connection;
1123
- // waitForResult controls the SDK polling loop.
1124
- wait_for_result: false,
975
+ wait_for_result: true,
1125
976
  max_wait_ms: params.maxWaitMs,
1126
977
  dry_run: params.dryRun,
1127
978
  idempotency_key: params.idempotencyKey
1128
979
  });
1129
980
  }
1130
- function companySignalRecoverableBatchOptions(companies, tasks) {
1131
- if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
1132
- return void 0;
1133
- }
1134
- const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
1135
- if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
1136
- return void 0;
1137
- }
1138
- const configKey = (request) => {
1139
- const {
1140
- company_domain: _companyDomain,
1141
- company_name: _companyName,
1142
- signal_run_id: _signalRunId,
1143
- wait_for_result: _waitForResult,
1144
- max_wait_ms: _maxWaitMs,
1145
- ...config
1146
- } = request;
1147
- return JSON.stringify(config);
1148
- };
1149
- const sharedConfig = configKey(tasks[0].request);
1150
- if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
1151
- return { label: "Company Signals", pageSize: 25, maxWaitMs };
1152
- }
1153
981
  function normalizeCompanySignalResult(params, data) {
1154
982
  const rawSignals = data.signals ?? data.signal_feed ?? [];
1155
983
  return {
@@ -1297,6 +1125,7 @@ function signalToCopyRequestBody(params) {
1297
1125
  signal_run_id: params.signalRunId,
1298
1126
  skip_cache: params.skipCache,
1299
1127
  enable_deep_search: params.enableDeepSearch,
1128
+ max_wait_ms: params.maxWaitMs,
1300
1129
  dry_run: params.dryRun,
1301
1130
  idempotency_key: params.idempotencyKey
1302
1131
  });
@@ -1343,11 +1172,10 @@ function normalizeSignalToCopyResult(data) {
1343
1172
  function isPendingSignalResponse(data) {
1344
1173
  return ["triggered", "queued", "processing", "pending", "pending_version", "waiting_for_deploy"].includes(String(data.status || "").toLowerCase());
1345
1174
  }
1346
- function isCompletedSignalRun(data) {
1347
- return ["completed", "success", "succeeded"].includes(String(data.status || "").toLowerCase());
1348
- }
1349
- function isFailedSignalRun(data) {
1350
- return ["failed", "error", "cancelled", "canceled", "crashed", "timed_out"].includes(String(data.status || "").toLowerCase());
1175
+ function assertTerminalSignalResponse(data, capability) {
1176
+ if (isPendingSignalResponse(data) || data.job_id) {
1177
+ throw new Error(`${capability} violated the synchronous response contract.`);
1178
+ }
1351
1179
  }
1352
1180
  function signalPollDelayMs(data, override) {
1353
1181
  if (override !== void 0) return Math.max(250, override);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-KTWDTUCI.mjs";
4
+ } from "./chunk-NOOQDYOJ.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.47",
3
+ "version": "1.0.48",
4
4
  "description": "Signaliz SDK for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",