@signaliz/sdk 1.0.65 → 1.0.67
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 +5 -0
- package/dist/{chunk-FOHZG634.mjs → chunk-4BUM5AVV.mjs} +90 -11
- package/dist/index.d.mts +45 -7
- package/dist/index.d.ts +45 -7
- package/dist/index.js +90 -11
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +90 -11
- package/dist/mcp-config.mjs +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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,8 +1636,11 @@ 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,
|
|
1642
|
+
billingReplayed: data.billing_replayed,
|
|
1643
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1571
1644
|
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1572
1645
|
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1573
1646
|
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
@@ -1584,6 +1657,11 @@ function normalizeFindEmailResult(data) {
|
|
|
1584
1657
|
runId: data.run_id,
|
|
1585
1658
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1586
1659
|
suggestedAction: data.suggested_action,
|
|
1660
|
+
retryEligible: data.retry_eligible,
|
|
1661
|
+
retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
|
|
1662
|
+
doNotAutoResubmit: data.do_not_auto_resubmit === true,
|
|
1663
|
+
recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
|
|
1664
|
+
recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
|
|
1587
1665
|
cachePublicationStatus: data.cache_publication_status,
|
|
1588
1666
|
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1589
1667
|
raw: data
|
|
@@ -1628,6 +1706,7 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1628
1706
|
verificationSource: data.verification_source,
|
|
1629
1707
|
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1630
1708
|
billingReplayed: data.billing_replayed,
|
|
1709
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1631
1710
|
creditsUsed: data.credits_used,
|
|
1632
1711
|
billingMetadata: data.billing_metadata,
|
|
1633
1712
|
historicalBillingMetadata: data.historical_billing_metadata,
|
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,9 +181,14 @@ 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
|
+
/** True when this response reuses the first request's billing receipt without charging again. */
|
|
188
|
+
billingReplayed?: boolean;
|
|
189
|
+
/** True when the public result came from a completed response-idempotency reservation. */
|
|
190
|
+
idempotencyReplayed?: boolean;
|
|
191
|
+
/** Original provider-work receipt when this response recovered a completed run or idempotency reservation. */
|
|
162
192
|
historicalBillingMetadata?: Record<string, unknown>;
|
|
163
193
|
resultReturnedFrom?: string;
|
|
164
194
|
cacheHit?: boolean;
|
|
@@ -179,6 +209,12 @@ interface FindEmailResult {
|
|
|
179
209
|
runId?: string;
|
|
180
210
|
nextPollAfterSeconds?: number;
|
|
181
211
|
suggestedAction?: string;
|
|
212
|
+
/** True only for same-run recovery, or a fresh idempotency key with explicit zero-spend proof; never retry automatically. */
|
|
213
|
+
retryEligible?: boolean;
|
|
214
|
+
retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
|
|
215
|
+
doNotAutoResubmit?: boolean;
|
|
216
|
+
recoveryMode?: 'same_run';
|
|
217
|
+
recoveryStatus?: 'pending' | 'completed';
|
|
182
218
|
cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
|
|
183
219
|
cachePublicationRetryEligible?: boolean;
|
|
184
220
|
raw: Record<string, unknown>;
|
|
@@ -216,9 +252,11 @@ interface VerifyEmailResult {
|
|
|
216
252
|
/** ISO timestamp when the provider observed the winning verdict. */
|
|
217
253
|
verificationObservedAt?: string;
|
|
218
254
|
billingReplayed?: boolean;
|
|
255
|
+
/** True when the public result came from a completed response-idempotency reservation. */
|
|
256
|
+
idempotencyReplayed?: boolean;
|
|
219
257
|
creditsUsed?: number;
|
|
220
258
|
billingMetadata?: Record<string, unknown>;
|
|
221
|
-
/** Original provider-work
|
|
259
|
+
/** Original provider-work receipt when this response recovered a completed run or idempotency reservation. */
|
|
222
260
|
historicalBillingMetadata?: Record<string, unknown>;
|
|
223
261
|
resultReturnedFrom?: string;
|
|
224
262
|
cacheHit?: boolean;
|
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,9 +181,14 @@ 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
|
+
/** True when this response reuses the first request's billing receipt without charging again. */
|
|
188
|
+
billingReplayed?: boolean;
|
|
189
|
+
/** True when the public result came from a completed response-idempotency reservation. */
|
|
190
|
+
idempotencyReplayed?: boolean;
|
|
191
|
+
/** Original provider-work receipt when this response recovered a completed run or idempotency reservation. */
|
|
162
192
|
historicalBillingMetadata?: Record<string, unknown>;
|
|
163
193
|
resultReturnedFrom?: string;
|
|
164
194
|
cacheHit?: boolean;
|
|
@@ -179,6 +209,12 @@ interface FindEmailResult {
|
|
|
179
209
|
runId?: string;
|
|
180
210
|
nextPollAfterSeconds?: number;
|
|
181
211
|
suggestedAction?: string;
|
|
212
|
+
/** True only for same-run recovery, or a fresh idempotency key with explicit zero-spend proof; never retry automatically. */
|
|
213
|
+
retryEligible?: boolean;
|
|
214
|
+
retryStrategy?: 'new_idempotency_key' | 'same_run_recovery';
|
|
215
|
+
doNotAutoResubmit?: boolean;
|
|
216
|
+
recoveryMode?: 'same_run';
|
|
217
|
+
recoveryStatus?: 'pending' | 'completed';
|
|
182
218
|
cachePublicationStatus?: 'completed' | 'pending' | 'pending_safe_retry' | 'superseded_by_newer_verdict';
|
|
183
219
|
cachePublicationRetryEligible?: boolean;
|
|
184
220
|
raw: Record<string, unknown>;
|
|
@@ -216,9 +252,11 @@ interface VerifyEmailResult {
|
|
|
216
252
|
/** ISO timestamp when the provider observed the winning verdict. */
|
|
217
253
|
verificationObservedAt?: string;
|
|
218
254
|
billingReplayed?: boolean;
|
|
255
|
+
/** True when the public result came from a completed response-idempotency reservation. */
|
|
256
|
+
idempotencyReplayed?: boolean;
|
|
219
257
|
creditsUsed?: number;
|
|
220
258
|
billingMetadata?: Record<string, unknown>;
|
|
221
|
-
/** Original provider-work
|
|
259
|
+
/** Original provider-work receipt when this response recovered a completed run or idempotency reservation. */
|
|
222
260
|
historicalBillingMetadata?: Record<string, unknown>;
|
|
223
261
|
resultReturnedFrom?: string;
|
|
224
262
|
cacheHit?: boolean;
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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,8 +1663,11 @@ 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,
|
|
1669
|
+
billingReplayed: data.billing_replayed,
|
|
1670
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1598
1671
|
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1599
1672
|
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1600
1673
|
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
@@ -1611,6 +1684,11 @@ function normalizeFindEmailResult(data) {
|
|
|
1611
1684
|
runId: data.run_id,
|
|
1612
1685
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1613
1686
|
suggestedAction: data.suggested_action,
|
|
1687
|
+
retryEligible: data.retry_eligible,
|
|
1688
|
+
retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
|
|
1689
|
+
doNotAutoResubmit: data.do_not_auto_resubmit === true,
|
|
1690
|
+
recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
|
|
1691
|
+
recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
|
|
1614
1692
|
cachePublicationStatus: data.cache_publication_status,
|
|
1615
1693
|
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1616
1694
|
raw: data
|
|
@@ -1655,6 +1733,7 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1655
1733
|
verificationSource: data.verification_source,
|
|
1656
1734
|
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1657
1735
|
billingReplayed: data.billing_replayed,
|
|
1736
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1658
1737
|
creditsUsed: data.credits_used,
|
|
1659
1738
|
billingMetadata: data.billing_metadata,
|
|
1660
1739
|
historicalBillingMetadata: data.historical_billing_metadata,
|
package/dist/index.mjs
CHANGED
package/dist/mcp-config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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,8 +1667,11 @@ 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,
|
|
1673
|
+
billingReplayed: data.billing_replayed,
|
|
1674
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1602
1675
|
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1603
1676
|
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1604
1677
|
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
@@ -1615,6 +1688,11 @@ function normalizeFindEmailResult(data) {
|
|
|
1615
1688
|
runId: data.run_id,
|
|
1616
1689
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1617
1690
|
suggestedAction: data.suggested_action,
|
|
1691
|
+
retryEligible: data.retry_eligible,
|
|
1692
|
+
retryStrategy: ["new_idempotency_key", "same_run_recovery"].includes(data.retry_strategy) ? data.retry_strategy : void 0,
|
|
1693
|
+
doNotAutoResubmit: data.do_not_auto_resubmit === true,
|
|
1694
|
+
recoveryMode: data.recovery_mode === "same_run" ? "same_run" : void 0,
|
|
1695
|
+
recoveryStatus: ["pending", "completed"].includes(String(data.recovery_status)) ? data.recovery_status : void 0,
|
|
1618
1696
|
cachePublicationStatus: data.cache_publication_status,
|
|
1619
1697
|
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1620
1698
|
raw: data
|
|
@@ -1659,6 +1737,7 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1659
1737
|
verificationSource: data.verification_source,
|
|
1660
1738
|
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1661
1739
|
billingReplayed: data.billing_replayed,
|
|
1740
|
+
idempotencyReplayed: data.idempotency_replayed,
|
|
1662
1741
|
creditsUsed: data.credits_used,
|
|
1663
1742
|
billingMetadata: data.billing_metadata,
|
|
1664
1743
|
historicalBillingMetadata: data.historical_billing_metadata,
|
package/dist/mcp-config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaliz/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.67",
|
|
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": [
|