@signaliz/sdk 1.0.65 → 1.0.66

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
@@ -39,6 +39,11 @@ const found = await signaliz.findEmail({
39
39
  skipCache: true,
40
40
  });
41
41
 
42
+ // An HTTPS LinkedIn person profile URL (/in/<slug>) is also sufficient on its own.
43
+ const foundFromLinkedIn = await signaliz.findEmail({
44
+ linkedinUrl: 'https://www.linkedin.com/in/jane-doe',
45
+ });
46
+
42
47
  const verified = await signaliz.verifyEmail(found.email!, {
43
48
  // Optional: bypass cached verification proof for a fresh-provider check.
44
49
  skipCache: true,
@@ -47,6 +47,8 @@ function publicRetryDetails(body) {
47
47
  ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
48
48
  ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
49
49
  ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
50
+ ...body?.live_provider_called !== void 0 ? { live_provider_called: body.live_provider_called } : {},
51
+ ...body?.estimated_external_cost_usd !== void 0 ? { estimated_external_cost_usd: body.estimated_external_cost_usd } : {},
50
52
  ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
51
53
  ...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
52
54
  ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
@@ -131,7 +133,8 @@ var HttpClient = class {
131
133
  const err = parseError(res.status, errBody);
132
134
  if (err.isRetryable && attempt < this.maxRetries) {
133
135
  const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
134
- if (retryWithNewKey) throw err;
136
+ const recoverSameRun = err.details?.retry_strategy === "same_run_recovery";
137
+ if (retryWithNewKey || recoverSameRun) throw err;
135
138
  const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
136
139
  await sleep(wait);
137
140
  lastError = err;
@@ -203,6 +206,7 @@ var HttpClient = class {
203
206
  if (!res.ok) {
204
207
  const err = parseError(res.status, data ?? { message: text });
205
208
  if (err.isRetryable && attempt < this.maxRetries) {
209
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
206
210
  lastError = err;
207
211
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
208
212
  continue;
@@ -227,6 +231,7 @@ var HttpClient = class {
227
231
  details: isRecord(responseError.data) ? responseError.data : void 0
228
232
  });
229
233
  if (err.isRetryable && attempt < this.maxRetries) {
234
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
230
235
  lastError = err;
231
236
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
232
237
  continue;
@@ -235,7 +240,10 @@ var HttpClient = class {
235
240
  }
236
241
  return unwrapMcpResponse(data);
237
242
  } catch (e) {
238
- if (e instanceof SignalizError && !e.isRetryable) throw e;
243
+ if (e instanceof SignalizError) {
244
+ if (e.details?.retry_strategy === "same_run_recovery") throw e;
245
+ if (!e.isRetryable) throw e;
246
+ }
239
247
  if (e.name === "AbortError") {
240
248
  lastError = new SignalizError({
241
249
  code: "TIMEOUT",
@@ -346,15 +354,25 @@ function unwrapMcpPayload(payload) {
346
354
  return unwrapMcpPayload(payload.result);
347
355
  }
348
356
  if (payload.ok === false) {
357
+ if (isRecord(payload.result) && (payload.result.success === false || payload.result.ok === false)) {
358
+ return unwrapMcpPayload(payload.result);
359
+ }
349
360
  const first = firstPayloadError(payload);
350
- const code = firstString(first.code, payload.error_code, payload.code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
361
+ const firstDetails = isRecord(first.details) ? first.details : {};
362
+ const code = firstString(
363
+ firstDetails.error_code,
364
+ firstDetails.legacy_error_code,
365
+ first.code,
366
+ payload.error_code,
367
+ payload.code
368
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
351
369
  throw new SignalizError({
352
370
  code,
353
371
  message: firstString(first.message, payload.message, payload.summary, payload.error) || "MCP request failed",
354
372
  errorType: mapErrorTypeFromCode(code),
355
373
  details: {
356
374
  ...payload,
357
- ...isRecord(first.details) ? first.details : {}
375
+ ...firstDetails
358
376
  }
359
377
  });
360
378
  }
@@ -363,14 +381,20 @@ function unwrapMcpPayload(payload) {
363
381
  }
364
382
  if (payload.success === false) {
365
383
  const first = firstPayloadError(payload);
366
- const code = firstString(first.code, payload.error_code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
384
+ const firstDetails = isRecord(first.details) ? first.details : {};
385
+ const code = firstString(
386
+ firstDetails.error_code,
387
+ firstDetails.legacy_error_code,
388
+ first.code,
389
+ payload.error_code
390
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
367
391
  throw new SignalizError({
368
392
  code,
369
393
  message: firstString(first.message, payload.message, payload.summary) || "MCP request failed",
370
394
  errorType: mapErrorTypeFromCode(code),
371
395
  details: {
372
396
  ...payload,
373
- ...isRecord(first.details) ? first.details : {}
397
+ ...firstDetails
374
398
  }
375
399
  });
376
400
  }
@@ -560,6 +584,31 @@ function assertLargeCoreProductInputBounds(path, body) {
560
584
  );
561
585
  }
562
586
 
587
+ // ../../supabase/functions/_shared/find-email-identity.ts
588
+ var LINKEDIN_PERSON_PROFILE_URL_PATTERN = "^[hH][tT][tT][pP][sS]://(?:[A-Za-z0-9-]+\\.)*[lL][iI][nN][kK][eE][dD][iI][nN]\\.[cC][oO][mM]/[iI][nN]/[^/?#]+/?(?:[?#].*)?$";
589
+ var LINKEDIN_PERSON_PROFILE_URL_REGEX = new RegExp(LINKEDIN_PERSON_PROFILE_URL_PATTERN);
590
+ function nonEmptyText(value) {
591
+ return typeof value === "string" && value.trim().length > 0;
592
+ }
593
+ function isLinkedInPersonProfileUrl(value) {
594
+ if (!nonEmptyText(value)) return false;
595
+ const normalized = String(value).trim();
596
+ if (!LINKEDIN_PERSON_PROFILE_URL_REGEX.test(normalized)) return false;
597
+ try {
598
+ const url = new URL(normalized);
599
+ const hostname = url.hostname.toLowerCase();
600
+ return url.protocol === "https:" && !url.username && !url.password && !url.port && (hostname === "linkedin.com" || hostname.endsWith(".linkedin.com")) && /^\/in\/[^/]+\/?$/i.test(url.pathname);
601
+ } catch {
602
+ return false;
603
+ }
604
+ }
605
+ function hasFindEmailIdentity(input) {
606
+ if (isLinkedInPersonProfileUrl(input.linkedin_url)) return true;
607
+ const hasName = nonEmptyText(input.full_name) || nonEmptyText(input.first_name) && nonEmptyText(input.last_name);
608
+ return hasName && nonEmptyText(input.company_domain);
609
+ }
610
+ var FIND_EMAIL_IDENTITY_REQUIREMENT = "Provide an HTTPS LinkedIn person profile URL (/in/<slug>), or provide company_domain with full_name or first_name and last_name.";
611
+
563
612
  // src/index.ts
564
613
  var MAX_ROW_RATE_LIMIT_RETRIES = 2;
565
614
  var Signaliz = class {
@@ -567,22 +616,28 @@ var Signaliz = class {
567
616
  this.client = new HttpClient(config);
568
617
  }
569
618
  async findEmail(params) {
570
- const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
619
+ const request = findEmailRequestBody(params);
620
+ assertFindEmailRequestIdentity(request);
621
+ const data = await this.client.post("api/v1/find-email", request);
571
622
  return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
572
623
  }
573
624
  async findEmails(params, options) {
574
625
  validateBatchSize(params);
626
+ const requests = params.map(findEmailRequestBody);
627
+ if (!requests.some(hasFindEmailRequestIdentity)) {
628
+ throw new TypeError(`Find Email batch must include at least one valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
629
+ }
575
630
  if (options?.dryRun === true) {
576
631
  return this.runCoreProductDryRun(
577
632
  "api/v1/find-email",
578
- params.map(findEmailRequestBody),
633
+ requests,
579
634
  options.idempotencyKey
580
635
  );
581
636
  }
582
637
  const startedAt = Date.now();
583
638
  const round = await this.runCoreBatchRound(
584
639
  "api/v1/find-email",
585
- params.map((item, index) => ({ index, request: findEmailRequestBody(item) })),
640
+ requests.map((request, index) => ({ index, request })),
586
641
  options
587
642
  );
588
643
  return finalizeCoreBatch(
@@ -1408,6 +1463,8 @@ function batchItemFailure(index, error, raw) {
1408
1463
  const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
1409
1464
  const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
1410
1465
  const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
1466
+ const liveProviderCalled = raw?.live_provider_called ?? raw?.liveProviderCalled;
1467
+ const estimatedExternalCostUsd = raw?.estimated_external_cost_usd ?? raw?.estimatedExternalCostUsd;
1411
1468
  const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
1412
1469
  const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
1413
1470
  const jobId = raw?.job_id ?? raw?.jobId;
@@ -1419,13 +1476,18 @@ function batchItemFailure(index, error, raw) {
1419
1476
  ...errorCode ? { errorCode } : {},
1420
1477
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
1421
1478
  ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
1422
- ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
1479
+ ...["new_idempotency_key", "same_run_recovery"].includes(String(retryStrategy)) ? { retryStrategy } : {},
1423
1480
  ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
1424
1481
  ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
1482
+ ...liveProviderCalled === false ? { liveProviderCalled: false } : {},
1483
+ ...estimatedExternalCostUsd === 0 ? { estimatedExternalCostUsd: 0 } : {},
1425
1484
  ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
1426
1485
  ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
1427
1486
  ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
1428
- ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
1487
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {},
1488
+ ...raw?.do_not_auto_resubmit === true ? { doNotAutoResubmit: true } : {},
1489
+ ...raw?.recovery_mode === "same_run" ? { recoveryMode: "same_run" } : {},
1490
+ ...["pending", "completed"].includes(String(raw?.recovery_status)) ? { recoveryStatus: raw?.recovery_status } : {}
1429
1491
  };
1430
1492
  }
1431
1493
  function recoverableJobRowFailed(row) {
@@ -1488,6 +1550,14 @@ function findEmailRequestBody(params) {
1488
1550
  idempotency_key: params.idempotencyKey
1489
1551
  });
1490
1552
  }
1553
+ function hasFindEmailRequestIdentity(request) {
1554
+ return typeof request.run_id === "string" && request.run_id.trim().length > 0 || hasFindEmailIdentity(request);
1555
+ }
1556
+ function assertFindEmailRequestIdentity(request) {
1557
+ if (!hasFindEmailRequestIdentity(request)) {
1558
+ throw new TypeError(`Find Email requires a valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
1559
+ }
1560
+ }
1491
1561
  function verifyEmailBatchRequestBody(input, options) {
1492
1562
  if (typeof input === "string") {
1493
1563
  return compact({ email: input, skip_cache: options?.skipCache });
@@ -1566,6 +1636,7 @@ function normalizeFindEmailResult(data) {
1566
1636
  verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1567
1637
  failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
1568
1638
  creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
1639
+ creditsCharged: data.credits_charged ?? billingMetadata?.credits_charged,
1569
1640
  estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
1570
1641
  billingMetadata,
1571
1642
  historicalBillingMetadata: data.historical_billing_metadata,
@@ -1584,6 +1655,11 @@ function normalizeFindEmailResult(data) {
1584
1655
  runId: data.run_id,
1585
1656
  nextPollAfterSeconds: data.next_poll_after_seconds,
1586
1657
  suggestedAction: data.suggested_action,
1658
+ retryEligible: data.retry_eligible,
1659
+ retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
1660
+ doNotAutoResubmit: data.do_not_auto_resubmit === true,
1661
+ recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
1662
+ recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
1587
1663
  cachePublicationStatus: data.cache_publication_status,
1588
1664
  cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1589
1665
  raw: data
package/dist/index.d.mts CHANGED
@@ -56,11 +56,15 @@ interface BatchItemResult<T> {
56
56
  /** Server-provided delay before retrying the failed row. */
57
57
  retryAfterSeconds?: number;
58
58
  /** Explicit recovery strategy. A fresh key can repeat provider work and is never automatic. */
59
- retryStrategy?: 'new_idempotency_key';
59
+ retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
60
60
  /** Present only when the server proves the failed row consumed zero Signaliz credits. */
61
61
  creditsUsed?: 0;
62
62
  /** Present only when the server proves the failed row charged zero Signaliz credits. */
63
63
  creditsCharged?: 0;
64
+ /** Present only when the server proves no live provider was called for the failed row. */
65
+ liveProviderCalled?: false;
66
+ /** Present only when the server proves the failed row had no external provider cost. */
67
+ estimatedExternalCostUsd?: 0;
64
68
  /** Existing provider run that can be resumed without dispatching new work. */
65
69
  runId?: string;
66
70
  /** Existing Verify Email run that can be resumed without dispatching new work. */
@@ -69,6 +73,9 @@ interface BatchItemResult<T> {
69
73
  jobId?: string;
70
74
  /** Machine-readable recovery action returned by Signaliz. */
71
75
  suggestedAction?: string;
76
+ doNotAutoResubmit?: boolean;
77
+ recoveryMode?: 'same_run';
78
+ recoveryStatus?: 'pending' | 'completed';
72
79
  cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
73
80
  cachePublicationRetryEligible?: boolean;
74
81
  /** Zero-based input index containing the full result for this exact duplicate. */
@@ -111,10 +118,6 @@ interface CoreProductDryRunResult {
111
118
  raw: Record<string, unknown>;
112
119
  }
113
120
  interface FindEmailCommonParams {
114
- fullName?: string;
115
- firstName?: string;
116
- lastName?: string;
117
- linkedinUrl?: string;
118
121
  companyName?: string;
119
122
  /** Bypass cached email and verification data for a fresh-provider run. */
120
123
  skipCache?: boolean;
@@ -124,13 +127,35 @@ interface FindEmailCommonParams {
124
127
  idempotencyKey?: string;
125
128
  }
126
129
  type FindEmailParams = FindEmailCommonParams & ({
130
+ /** Start a new lookup using only the contact's LinkedIn profile. */
131
+ linkedinUrl: string;
132
+ companyDomain?: string;
133
+ fullName?: string;
134
+ firstName?: string;
135
+ lastName?: string;
136
+ /** Omit for a new lookup. */
137
+ runId?: undefined;
138
+ } | ({
127
139
  companyDomain: string;
140
+ linkedinUrl?: string;
128
141
  /** Omit for a new lookup. */
129
142
  runId?: undefined;
143
+ } & ({
144
+ fullName: string;
145
+ firstName?: string;
146
+ lastName?: string;
130
147
  } | {
148
+ fullName?: string;
149
+ firstName: string;
150
+ lastName: string;
151
+ })) | {
131
152
  /** Resume one existing Find Email run without dispatching a new finder. */
132
153
  runId: string;
133
154
  companyDomain?: string;
155
+ linkedinUrl?: string;
156
+ fullName?: string;
157
+ firstName?: string;
158
+ lastName?: string;
134
159
  });
135
160
  interface FindEmailResult {
136
161
  success: boolean;
@@ -156,6 +181,7 @@ interface FindEmailResult {
156
181
  verificationObservedAt?: string;
157
182
  failureReason: string;
158
183
  creditsUsed?: number;
184
+ creditsCharged?: number;
159
185
  estimatedExternalCostUsd?: number;
160
186
  billingMetadata?: Record<string, unknown>;
161
187
  /** Original provider-work billing receipt when this request only recovered a completed run. */
@@ -179,6 +205,12 @@ interface FindEmailResult {
179
205
  runId?: string;
180
206
  nextPollAfterSeconds?: number;
181
207
  suggestedAction?: string;
208
+ /** True only for same-run recovery, or a fresh idempotency key with explicit zero-spend proof; never retry automatically. */
209
+ retryEligible?: boolean;
210
+ retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
211
+ doNotAutoResubmit?: boolean;
212
+ recoveryMode?: 'same_run';
213
+ recoveryStatus?: 'pending' | 'completed';
182
214
  cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
183
215
  cachePublicationRetryEligible?: boolean;
184
216
  raw: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -56,11 +56,15 @@ interface BatchItemResult<T> {
56
56
  /** Server-provided delay before retrying the failed row. */
57
57
  retryAfterSeconds?: number;
58
58
  /** Explicit recovery strategy. A fresh key can repeat provider work and is never automatic. */
59
- retryStrategy?: 'new_idempotency_key';
59
+ retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
60
60
  /** Present only when the server proves the failed row consumed zero Signaliz credits. */
61
61
  creditsUsed?: 0;
62
62
  /** Present only when the server proves the failed row charged zero Signaliz credits. */
63
63
  creditsCharged?: 0;
64
+ /** Present only when the server proves no live provider was called for the failed row. */
65
+ liveProviderCalled?: false;
66
+ /** Present only when the server proves the failed row had no external provider cost. */
67
+ estimatedExternalCostUsd?: 0;
64
68
  /** Existing provider run that can be resumed without dispatching new work. */
65
69
  runId?: string;
66
70
  /** Existing Verify Email run that can be resumed without dispatching new work. */
@@ -69,6 +73,9 @@ interface BatchItemResult<T> {
69
73
  jobId?: string;
70
74
  /** Machine-readable recovery action returned by Signaliz. */
71
75
  suggestedAction?: string;
76
+ doNotAutoResubmit?: boolean;
77
+ recoveryMode?: 'same_run';
78
+ recoveryStatus?: 'pending' | 'completed';
72
79
  cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
73
80
  cachePublicationRetryEligible?: boolean;
74
81
  /** Zero-based input index containing the full result for this exact duplicate. */
@@ -111,10 +118,6 @@ interface CoreProductDryRunResult {
111
118
  raw: Record<string, unknown>;
112
119
  }
113
120
  interface FindEmailCommonParams {
114
- fullName?: string;
115
- firstName?: string;
116
- lastName?: string;
117
- linkedinUrl?: string;
118
121
  companyName?: string;
119
122
  /** Bypass cached email and verification data for a fresh-provider run. */
120
123
  skipCache?: boolean;
@@ -124,13 +127,35 @@ interface FindEmailCommonParams {
124
127
  idempotencyKey?: string;
125
128
  }
126
129
  type FindEmailParams = FindEmailCommonParams & ({
130
+ /** Start a new lookup using only the contact's LinkedIn profile. */
131
+ linkedinUrl: string;
132
+ companyDomain?: string;
133
+ fullName?: string;
134
+ firstName?: string;
135
+ lastName?: string;
136
+ /** Omit for a new lookup. */
137
+ runId?: undefined;
138
+ } | ({
127
139
  companyDomain: string;
140
+ linkedinUrl?: string;
128
141
  /** Omit for a new lookup. */
129
142
  runId?: undefined;
143
+ } & ({
144
+ fullName: string;
145
+ firstName?: string;
146
+ lastName?: string;
130
147
  } | {
148
+ fullName?: string;
149
+ firstName: string;
150
+ lastName: string;
151
+ })) | {
131
152
  /** Resume one existing Find Email run without dispatching a new finder. */
132
153
  runId: string;
133
154
  companyDomain?: string;
155
+ linkedinUrl?: string;
156
+ fullName?: string;
157
+ firstName?: string;
158
+ lastName?: string;
134
159
  });
135
160
  interface FindEmailResult {
136
161
  success: boolean;
@@ -156,6 +181,7 @@ interface FindEmailResult {
156
181
  verificationObservedAt?: string;
157
182
  failureReason: string;
158
183
  creditsUsed?: number;
184
+ creditsCharged?: number;
159
185
  estimatedExternalCostUsd?: number;
160
186
  billingMetadata?: Record<string, unknown>;
161
187
  /** Original provider-work billing receipt when this request only recovered a completed run. */
@@ -179,6 +205,12 @@ interface FindEmailResult {
179
205
  runId?: string;
180
206
  nextPollAfterSeconds?: number;
181
207
  suggestedAction?: string;
208
+ /** True only for same-run recovery, or a fresh idempotency key with explicit zero-spend proof; never retry automatically. */
209
+ retryEligible?: boolean;
210
+ retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
211
+ doNotAutoResubmit?: boolean;
212
+ recoveryMode?: 'same_run';
213
+ recoveryStatus?: 'pending' | 'completed';
182
214
  cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
183
215
  cachePublicationRetryEligible?: boolean;
184
216
  raw: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -74,6 +74,8 @@ function publicRetryDetails(body) {
74
74
  ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
75
75
  ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
76
76
  ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
77
+ ...body?.live_provider_called !== void 0 ? { live_provider_called: body.live_provider_called } : {},
78
+ ...body?.estimated_external_cost_usd !== void 0 ? { estimated_external_cost_usd: body.estimated_external_cost_usd } : {},
77
79
  ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
78
80
  ...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
79
81
  ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
@@ -158,7 +160,8 @@ var HttpClient = class {
158
160
  const err = parseError(res.status, errBody);
159
161
  if (err.isRetryable && attempt < this.maxRetries) {
160
162
  const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
161
- if (retryWithNewKey) throw err;
163
+ const recoverSameRun = err.details?.retry_strategy === "same_run_recovery";
164
+ if (retryWithNewKey || recoverSameRun) throw err;
162
165
  const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
163
166
  await sleep(wait);
164
167
  lastError = err;
@@ -230,6 +233,7 @@ var HttpClient = class {
230
233
  if (!res.ok) {
231
234
  const err = parseError(res.status, data ?? { message: text });
232
235
  if (err.isRetryable && attempt < this.maxRetries) {
236
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
233
237
  lastError = err;
234
238
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
235
239
  continue;
@@ -254,6 +258,7 @@ var HttpClient = class {
254
258
  details: isRecord(responseError.data) ? responseError.data : void 0
255
259
  });
256
260
  if (err.isRetryable && attempt < this.maxRetries) {
261
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
257
262
  lastError = err;
258
263
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
259
264
  continue;
@@ -262,7 +267,10 @@ var HttpClient = class {
262
267
  }
263
268
  return unwrapMcpResponse(data);
264
269
  } catch (e) {
265
- if (e instanceof SignalizError && !e.isRetryable) throw e;
270
+ if (e instanceof SignalizError) {
271
+ if (e.details?.retry_strategy === "same_run_recovery") throw e;
272
+ if (!e.isRetryable) throw e;
273
+ }
266
274
  if (e.name === "AbortError") {
267
275
  lastError = new SignalizError({
268
276
  code: "TIMEOUT",
@@ -373,15 +381,25 @@ function unwrapMcpPayload(payload) {
373
381
  return unwrapMcpPayload(payload.result);
374
382
  }
375
383
  if (payload.ok === false) {
384
+ if (isRecord(payload.result) && (payload.result.success === false || payload.result.ok === false)) {
385
+ return unwrapMcpPayload(payload.result);
386
+ }
376
387
  const first = firstPayloadError(payload);
377
- const code = firstString(first.code, payload.error_code, payload.code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
388
+ const firstDetails = isRecord(first.details) ? first.details : {};
389
+ const code = firstString(
390
+ firstDetails.error_code,
391
+ firstDetails.legacy_error_code,
392
+ first.code,
393
+ payload.error_code,
394
+ payload.code
395
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
378
396
  throw new SignalizError({
379
397
  code,
380
398
  message: firstString(first.message, payload.message, payload.summary, payload.error) || "MCP request failed",
381
399
  errorType: mapErrorTypeFromCode(code),
382
400
  details: {
383
401
  ...payload,
384
- ...isRecord(first.details) ? first.details : {}
402
+ ...firstDetails
385
403
  }
386
404
  });
387
405
  }
@@ -390,14 +408,20 @@ function unwrapMcpPayload(payload) {
390
408
  }
391
409
  if (payload.success === false) {
392
410
  const first = firstPayloadError(payload);
393
- const code = firstString(first.code, payload.error_code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
411
+ const firstDetails = isRecord(first.details) ? first.details : {};
412
+ const code = firstString(
413
+ firstDetails.error_code,
414
+ firstDetails.legacy_error_code,
415
+ first.code,
416
+ payload.error_code
417
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
394
418
  throw new SignalizError({
395
419
  code,
396
420
  message: firstString(first.message, payload.message, payload.summary) || "MCP request failed",
397
421
  errorType: mapErrorTypeFromCode(code),
398
422
  details: {
399
423
  ...payload,
400
- ...isRecord(first.details) ? first.details : {}
424
+ ...firstDetails
401
425
  }
402
426
  });
403
427
  }
@@ -587,6 +611,31 @@ function assertLargeCoreProductInputBounds(path, body) {
587
611
  );
588
612
  }
589
613
 
614
+ // ../../supabase/functions/_shared/find-email-identity.ts
615
+ var LINKEDIN_PERSON_PROFILE_URL_PATTERN = "^[hH][tT][tT][pP][sS]://(?:[A-Za-z0-9-]+\\.)*[lL][iI][nN][kK][eE][dD][iI][nN]\\.[cC][oO][mM]/[iI][nN]/[^/?#]+/?(?:[?#].*)?$";
616
+ var LINKEDIN_PERSON_PROFILE_URL_REGEX = new RegExp(LINKEDIN_PERSON_PROFILE_URL_PATTERN);
617
+ function nonEmptyText(value) {
618
+ return typeof value === "string" && value.trim().length > 0;
619
+ }
620
+ function isLinkedInPersonProfileUrl(value) {
621
+ if (!nonEmptyText(value)) return false;
622
+ const normalized = String(value).trim();
623
+ if (!LINKEDIN_PERSON_PROFILE_URL_REGEX.test(normalized)) return false;
624
+ try {
625
+ const url = new URL(normalized);
626
+ const hostname = url.hostname.toLowerCase();
627
+ return url.protocol === "https:" && !url.username && !url.password && !url.port && (hostname === "linkedin.com" || hostname.endsWith(".linkedin.com")) && /^\/in\/[^/]+\/?$/i.test(url.pathname);
628
+ } catch {
629
+ return false;
630
+ }
631
+ }
632
+ function hasFindEmailIdentity(input) {
633
+ if (isLinkedInPersonProfileUrl(input.linkedin_url)) return true;
634
+ const hasName = nonEmptyText(input.full_name) || nonEmptyText(input.first_name) && nonEmptyText(input.last_name);
635
+ return hasName && nonEmptyText(input.company_domain);
636
+ }
637
+ var FIND_EMAIL_IDENTITY_REQUIREMENT = "Provide an HTTPS LinkedIn person profile URL (/in/<slug>), or provide company_domain with full_name or first_name and last_name.";
638
+
590
639
  // src/index.ts
591
640
  var MAX_ROW_RATE_LIMIT_RETRIES = 2;
592
641
  var Signaliz = class {
@@ -594,22 +643,28 @@ var Signaliz = class {
594
643
  this.client = new HttpClient(config);
595
644
  }
596
645
  async findEmail(params) {
597
- const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
646
+ const request = findEmailRequestBody(params);
647
+ assertFindEmailRequestIdentity(request);
648
+ const data = await this.client.post("api/v1/find-email", request);
598
649
  return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
599
650
  }
600
651
  async findEmails(params, options) {
601
652
  validateBatchSize(params);
653
+ const requests = params.map(findEmailRequestBody);
654
+ if (!requests.some(hasFindEmailRequestIdentity)) {
655
+ throw new TypeError(`Find Email batch must include at least one valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
656
+ }
602
657
  if (options?.dryRun === true) {
603
658
  return this.runCoreProductDryRun(
604
659
  "api/v1/find-email",
605
- params.map(findEmailRequestBody),
660
+ requests,
606
661
  options.idempotencyKey
607
662
  );
608
663
  }
609
664
  const startedAt = Date.now();
610
665
  const round = await this.runCoreBatchRound(
611
666
  "api/v1/find-email",
612
- params.map((item, index) => ({ index, request: findEmailRequestBody(item) })),
667
+ requests.map((request, index) => ({ index, request })),
613
668
  options
614
669
  );
615
670
  return finalizeCoreBatch(
@@ -1435,6 +1490,8 @@ function batchItemFailure(index, error, raw) {
1435
1490
  const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
1436
1491
  const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
1437
1492
  const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
1493
+ const liveProviderCalled = raw?.live_provider_called ?? raw?.liveProviderCalled;
1494
+ const estimatedExternalCostUsd = raw?.estimated_external_cost_usd ?? raw?.estimatedExternalCostUsd;
1438
1495
  const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
1439
1496
  const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
1440
1497
  const jobId = raw?.job_id ?? raw?.jobId;
@@ -1446,13 +1503,18 @@ function batchItemFailure(index, error, raw) {
1446
1503
  ...errorCode ? { errorCode } : {},
1447
1504
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
1448
1505
  ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
1449
- ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
1506
+ ...["new_idempotency_key", "same_run_recovery"].includes(String(retryStrategy)) ? { retryStrategy } : {},
1450
1507
  ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
1451
1508
  ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
1509
+ ...liveProviderCalled === false ? { liveProviderCalled: false } : {},
1510
+ ...estimatedExternalCostUsd === 0 ? { estimatedExternalCostUsd: 0 } : {},
1452
1511
  ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
1453
1512
  ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
1454
1513
  ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
1455
- ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
1514
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {},
1515
+ ...raw?.do_not_auto_resubmit === true ? { doNotAutoResubmit: true } : {},
1516
+ ...raw?.recovery_mode === "same_run" ? { recoveryMode: "same_run" } : {},
1517
+ ...["pending", "completed"].includes(String(raw?.recovery_status)) ? { recoveryStatus: raw?.recovery_status } : {}
1456
1518
  };
1457
1519
  }
1458
1520
  function recoverableJobRowFailed(row) {
@@ -1515,6 +1577,14 @@ function findEmailRequestBody(params) {
1515
1577
  idempotency_key: params.idempotencyKey
1516
1578
  });
1517
1579
  }
1580
+ function hasFindEmailRequestIdentity(request) {
1581
+ return typeof request.run_id === "string" && request.run_id.trim().length > 0 || hasFindEmailIdentity(request);
1582
+ }
1583
+ function assertFindEmailRequestIdentity(request) {
1584
+ if (!hasFindEmailRequestIdentity(request)) {
1585
+ throw new TypeError(`Find Email requires a valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
1586
+ }
1587
+ }
1518
1588
  function verifyEmailBatchRequestBody(input, options) {
1519
1589
  if (typeof input === "string") {
1520
1590
  return compact({ email: input, skip_cache: options?.skipCache });
@@ -1593,6 +1663,7 @@ function normalizeFindEmailResult(data) {
1593
1663
  verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1594
1664
  failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
1595
1665
  creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
1666
+ creditsCharged: data.credits_charged ?? billingMetadata?.credits_charged,
1596
1667
  estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
1597
1668
  billingMetadata,
1598
1669
  historicalBillingMetadata: data.historical_billing_metadata,
@@ -1611,6 +1682,11 @@ function normalizeFindEmailResult(data) {
1611
1682
  runId: data.run_id,
1612
1683
  nextPollAfterSeconds: data.next_poll_after_seconds,
1613
1684
  suggestedAction: data.suggested_action,
1685
+ retryEligible: data.retry_eligible,
1686
+ retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
1687
+ doNotAutoResubmit: data.do_not_auto_resubmit === true,
1688
+ recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
1689
+ recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
1614
1690
  cachePublicationStatus: data.cache_publication_status,
1615
1691
  cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1616
1692
  raw: data
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-FOHZG634.mjs";
4
+ } from "./chunk-ZXSNNBID.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -78,6 +78,8 @@ function publicRetryDetails(body) {
78
78
  ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
79
79
  ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
80
80
  ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
81
+ ...body?.live_provider_called !== void 0 ? { live_provider_called: body.live_provider_called } : {},
82
+ ...body?.estimated_external_cost_usd !== void 0 ? { estimated_external_cost_usd: body.estimated_external_cost_usd } : {},
81
83
  ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
82
84
  ...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
83
85
  ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
@@ -162,7 +164,8 @@ var HttpClient = class {
162
164
  const err = parseError(res.status, errBody);
163
165
  if (err.isRetryable && attempt < this.maxRetries) {
164
166
  const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
165
- if (retryWithNewKey) throw err;
167
+ const recoverSameRun = err.details?.retry_strategy === "same_run_recovery";
168
+ if (retryWithNewKey || recoverSameRun) throw err;
166
169
  const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
167
170
  await sleep(wait);
168
171
  lastError = err;
@@ -234,6 +237,7 @@ var HttpClient = class {
234
237
  if (!res.ok) {
235
238
  const err = parseError(res.status, data ?? { message: text });
236
239
  if (err.isRetryable && attempt < this.maxRetries) {
240
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
237
241
  lastError = err;
238
242
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
239
243
  continue;
@@ -258,6 +262,7 @@ var HttpClient = class {
258
262
  details: isRecord(responseError.data) ? responseError.data : void 0
259
263
  });
260
264
  if (err.isRetryable && attempt < this.maxRetries) {
265
+ if (err.details?.retry_strategy === "same_run_recovery") throw err;
261
266
  lastError = err;
262
267
  await sleep(err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4));
263
268
  continue;
@@ -266,7 +271,10 @@ var HttpClient = class {
266
271
  }
267
272
  return unwrapMcpResponse(data);
268
273
  } catch (e) {
269
- if (e instanceof SignalizError && !e.isRetryable) throw e;
274
+ if (e instanceof SignalizError) {
275
+ if (e.details?.retry_strategy === "same_run_recovery") throw e;
276
+ if (!e.isRetryable) throw e;
277
+ }
270
278
  if (e.name === "AbortError") {
271
279
  lastError = new SignalizError({
272
280
  code: "TIMEOUT",
@@ -377,15 +385,25 @@ function unwrapMcpPayload(payload) {
377
385
  return unwrapMcpPayload(payload.result);
378
386
  }
379
387
  if (payload.ok === false) {
388
+ if (isRecord(payload.result) && (payload.result.success === false || payload.result.ok === false)) {
389
+ return unwrapMcpPayload(payload.result);
390
+ }
380
391
  const first = firstPayloadError(payload);
381
- const code = firstString(first.code, payload.error_code, payload.code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
392
+ const firstDetails = isRecord(first.details) ? first.details : {};
393
+ const code = firstString(
394
+ firstDetails.error_code,
395
+ firstDetails.legacy_error_code,
396
+ first.code,
397
+ payload.error_code,
398
+ payload.code
399
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
382
400
  throw new SignalizError({
383
401
  code,
384
402
  message: firstString(first.message, payload.message, payload.summary, payload.error) || "MCP request failed",
385
403
  errorType: mapErrorTypeFromCode(code),
386
404
  details: {
387
405
  ...payload,
388
- ...isRecord(first.details) ? first.details : {}
406
+ ...firstDetails
389
407
  }
390
408
  });
391
409
  }
@@ -394,14 +412,20 @@ function unwrapMcpPayload(payload) {
394
412
  }
395
413
  if (payload.success === false) {
396
414
  const first = firstPayloadError(payload);
397
- const code = firstString(first.code, payload.error_code) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
415
+ const firstDetails = isRecord(first.details) ? first.details : {};
416
+ const code = firstString(
417
+ firstDetails.error_code,
418
+ firstDetails.legacy_error_code,
419
+ first.code,
420
+ payload.error_code
421
+ ) || (payload.approval_required ? "APPROVAL_REQUIRED" : "MCP_ERROR");
398
422
  throw new SignalizError({
399
423
  code,
400
424
  message: firstString(first.message, payload.message, payload.summary) || "MCP request failed",
401
425
  errorType: mapErrorTypeFromCode(code),
402
426
  details: {
403
427
  ...payload,
404
- ...isRecord(first.details) ? first.details : {}
428
+ ...firstDetails
405
429
  }
406
430
  });
407
431
  }
@@ -591,6 +615,31 @@ function assertLargeCoreProductInputBounds(path2, body) {
591
615
  );
592
616
  }
593
617
 
618
+ // ../../supabase/functions/_shared/find-email-identity.ts
619
+ var LINKEDIN_PERSON_PROFILE_URL_PATTERN = "^[hH][tT][tT][pP][sS]://(?:[A-Za-z0-9-]+\\.)*[lL][iI][nN][kK][eE][dD][iI][nN]\\.[cC][oO][mM]/[iI][nN]/[^/?#]+/?(?:[?#].*)?$";
620
+ var LINKEDIN_PERSON_PROFILE_URL_REGEX = new RegExp(LINKEDIN_PERSON_PROFILE_URL_PATTERN);
621
+ function nonEmptyText(value) {
622
+ return typeof value === "string" && value.trim().length > 0;
623
+ }
624
+ function isLinkedInPersonProfileUrl(value) {
625
+ if (!nonEmptyText(value)) return false;
626
+ const normalized = String(value).trim();
627
+ if (!LINKEDIN_PERSON_PROFILE_URL_REGEX.test(normalized)) return false;
628
+ try {
629
+ const url = new URL(normalized);
630
+ const hostname = url.hostname.toLowerCase();
631
+ return url.protocol === "https:" && !url.username && !url.password && !url.port && (hostname === "linkedin.com" || hostname.endsWith(".linkedin.com")) && /^\/in\/[^/]+\/?$/i.test(url.pathname);
632
+ } catch {
633
+ return false;
634
+ }
635
+ }
636
+ function hasFindEmailIdentity(input) {
637
+ if (isLinkedInPersonProfileUrl(input.linkedin_url)) return true;
638
+ const hasName = nonEmptyText(input.full_name) || nonEmptyText(input.first_name) && nonEmptyText(input.last_name);
639
+ return hasName && nonEmptyText(input.company_domain);
640
+ }
641
+ var FIND_EMAIL_IDENTITY_REQUIREMENT = "Provide an HTTPS LinkedIn person profile URL (/in/<slug>), or provide company_domain with full_name or first_name and last_name.";
642
+
594
643
  // src/index.ts
595
644
  var MAX_ROW_RATE_LIMIT_RETRIES = 2;
596
645
  var Signaliz = class {
@@ -598,22 +647,28 @@ var Signaliz = class {
598
647
  this.client = new HttpClient(config);
599
648
  }
600
649
  async findEmail(params) {
601
- const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
650
+ const request = findEmailRequestBody(params);
651
+ assertFindEmailRequestIdentity(request);
652
+ const data = await this.client.post("api/v1/find-email", request);
602
653
  return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
603
654
  }
604
655
  async findEmails(params, options) {
605
656
  validateBatchSize(params);
657
+ const requests = params.map(findEmailRequestBody);
658
+ if (!requests.some(hasFindEmailRequestIdentity)) {
659
+ throw new TypeError(`Find Email batch must include at least one valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
660
+ }
606
661
  if (options?.dryRun === true) {
607
662
  return this.runCoreProductDryRun(
608
663
  "api/v1/find-email",
609
- params.map(findEmailRequestBody),
664
+ requests,
610
665
  options.idempotencyKey
611
666
  );
612
667
  }
613
668
  const startedAt = Date.now();
614
669
  const round = await this.runCoreBatchRound(
615
670
  "api/v1/find-email",
616
- params.map((item, index) => ({ index, request: findEmailRequestBody(item) })),
671
+ requests.map((request, index) => ({ index, request })),
617
672
  options
618
673
  );
619
674
  return finalizeCoreBatch(
@@ -1439,6 +1494,8 @@ function batchItemFailure(index, error, raw) {
1439
1494
  const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
1440
1495
  const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
1441
1496
  const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
1497
+ const liveProviderCalled = raw?.live_provider_called ?? raw?.liveProviderCalled;
1498
+ const estimatedExternalCostUsd = raw?.estimated_external_cost_usd ?? raw?.estimatedExternalCostUsd;
1442
1499
  const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
1443
1500
  const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
1444
1501
  const jobId = raw?.job_id ?? raw?.jobId;
@@ -1450,13 +1507,18 @@ function batchItemFailure(index, error, raw) {
1450
1507
  ...errorCode ? { errorCode } : {},
1451
1508
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
1452
1509
  ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
1453
- ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
1510
+ ...["new_idempotency_key", "same_run_recovery"].includes(String(retryStrategy)) ? { retryStrategy } : {},
1454
1511
  ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
1455
1512
  ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
1513
+ ...liveProviderCalled === false ? { liveProviderCalled: false } : {},
1514
+ ...estimatedExternalCostUsd === 0 ? { estimatedExternalCostUsd: 0 } : {},
1456
1515
  ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
1457
1516
  ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
1458
1517
  ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
1459
- ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
1518
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {},
1519
+ ...raw?.do_not_auto_resubmit === true ? { doNotAutoResubmit: true } : {},
1520
+ ...raw?.recovery_mode === "same_run" ? { recoveryMode: "same_run" } : {},
1521
+ ...["pending", "completed"].includes(String(raw?.recovery_status)) ? { recoveryStatus: raw?.recovery_status } : {}
1460
1522
  };
1461
1523
  }
1462
1524
  function recoverableJobRowFailed(row) {
@@ -1519,6 +1581,14 @@ function findEmailRequestBody(params) {
1519
1581
  idempotency_key: params.idempotencyKey
1520
1582
  });
1521
1583
  }
1584
+ function hasFindEmailRequestIdentity(request) {
1585
+ return typeof request.run_id === "string" && request.run_id.trim().length > 0 || hasFindEmailIdentity(request);
1586
+ }
1587
+ function assertFindEmailRequestIdentity(request) {
1588
+ if (!hasFindEmailRequestIdentity(request)) {
1589
+ throw new TypeError(`Find Email requires a valid identity. ${FIND_EMAIL_IDENTITY_REQUIREMENT}`);
1590
+ }
1591
+ }
1522
1592
  function verifyEmailBatchRequestBody(input, options) {
1523
1593
  if (typeof input === "string") {
1524
1594
  return compact({ email: input, skip_cache: options?.skipCache });
@@ -1597,6 +1667,7 @@ function normalizeFindEmailResult(data) {
1597
1667
  verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
1598
1668
  failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
1599
1669
  creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
1670
+ creditsCharged: data.credits_charged ?? billingMetadata?.credits_charged,
1600
1671
  estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
1601
1672
  billingMetadata,
1602
1673
  historicalBillingMetadata: data.historical_billing_metadata,
@@ -1615,6 +1686,11 @@ function normalizeFindEmailResult(data) {
1615
1686
  runId: data.run_id,
1616
1687
  nextPollAfterSeconds: data.next_poll_after_seconds,
1617
1688
  suggestedAction: data.suggested_action,
1689
+ retryEligible: data.retry_eligible,
1690
+ retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
1691
+ doNotAutoResubmit: data.do_not_auto_resubmit === true,
1692
+ recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
1693
+ recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
1618
1694
  cachePublicationStatus: data.cache_publication_status,
1619
1695
  cachePublicationRetryEligible: data.cache_publication_retry_eligible,
1620
1696
  raw: data
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-FOHZG634.mjs";
4
+ } from "./chunk-ZXSNNBID.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.65",
3
+ "version": "1.0.66",
4
4
  "description": "Signaliz SDK for Signals Everything, Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,7 +15,7 @@
15
15
  "scripts": {
16
16
  "generate-types": "tsx scripts/generate-types.ts",
17
17
  "build": "npm run generate-types && tsup src/index.ts src/mcp-config.ts --format cjs,esm --dts --clean",
18
- "test": "tsx tests/core-products.test.ts",
18
+ "test": "tsx tests/core-products.test.ts && tsc --noEmit --strict --skipLibCheck --moduleResolution node --module esnext --target es2020 tests/find-email-types.test.ts",
19
19
  "prepublishOnly": "npm run build"
20
20
  },
21
21
  "keywords": [