@signaliz/sdk 1.0.40 → 1.0.43
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 +11 -0
- package/dist/{chunk-A7B63XMZ.mjs → chunk-HQS7N57T.mjs} +69 -11
- package/dist/index.d.mts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +69 -11
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +69 -11
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,17 @@ const copy = await signaliz.signalToCopy({
|
|
|
69
69
|
skipCache: false,
|
|
70
70
|
enableDeepSearch: false,
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
// Persist a queued copy ID and resume with that ID alone. Signaliz restores
|
|
74
|
+
// the recipient, title, offer, prompt, and underlying research run.
|
|
75
|
+
const queuedCopy = await signaliz.signalToCopy({
|
|
76
|
+
companyDomain: 'example.com',
|
|
77
|
+
personName: 'Jane Doe',
|
|
78
|
+
title: 'VP Sales',
|
|
79
|
+
campaignOffer: 'pipeline intelligence',
|
|
80
|
+
waitForResult: false,
|
|
81
|
+
});
|
|
82
|
+
const resumedCopy = await signaliz.signalToCopy({ signalRunId: queuedCopy.runId });
|
|
72
83
|
```
|
|
73
84
|
|
|
74
85
|
All four products support bounded batch execution over the same REST endpoints.
|
|
@@ -32,7 +32,7 @@ function parseError(status, body) {
|
|
|
32
32
|
}
|
|
33
33
|
function mapErrorType(code, status) {
|
|
34
34
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
35
|
-
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
35
|
+
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
36
36
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
37
37
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
38
38
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
@@ -60,6 +60,7 @@ var HttpClient = class {
|
|
|
60
60
|
}
|
|
61
61
|
async request(functionName, body, method = "POST") {
|
|
62
62
|
let lastError;
|
|
63
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
63
64
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
64
65
|
let timer;
|
|
65
66
|
try {
|
|
@@ -77,7 +78,8 @@ var HttpClient = class {
|
|
|
77
78
|
method,
|
|
78
79
|
headers: {
|
|
79
80
|
"Content-Type": "application/json",
|
|
80
|
-
Authorization: `Bearer ${token}
|
|
81
|
+
Authorization: `Bearer ${token}`,
|
|
82
|
+
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
|
|
81
83
|
},
|
|
82
84
|
signal: controller.signal
|
|
83
85
|
};
|
|
@@ -242,6 +244,11 @@ var HttpClient = class {
|
|
|
242
244
|
return this.accessTokenPromise;
|
|
243
245
|
}
|
|
244
246
|
};
|
|
247
|
+
function requestIdempotencyKey(body) {
|
|
248
|
+
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
249
|
+
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
250
|
+
return requested || `sdk_${nonce}`;
|
|
251
|
+
}
|
|
245
252
|
function sleep(ms) {
|
|
246
253
|
return new Promise((r) => setTimeout(r, ms));
|
|
247
254
|
}
|
|
@@ -342,7 +349,7 @@ function mapMcpErrorType(error) {
|
|
|
342
349
|
function mapErrorTypeFromCode(code) {
|
|
343
350
|
if (!code) return "unknown";
|
|
344
351
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
345
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
352
|
+
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
346
353
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
347
354
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
348
355
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -351,6 +358,7 @@ function mapErrorTypeFromCode(code) {
|
|
|
351
358
|
}
|
|
352
359
|
|
|
353
360
|
// src/index.ts
|
|
361
|
+
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
354
362
|
var Signaliz = class {
|
|
355
363
|
constructor(config) {
|
|
356
364
|
this.client = new HttpClient(config);
|
|
@@ -521,17 +529,16 @@ var Signaliz = class {
|
|
|
521
529
|
};
|
|
522
530
|
}
|
|
523
531
|
async signalToCopy(params) {
|
|
532
|
+
validateSignalToCopyParams(params);
|
|
524
533
|
const request = signalToCopyRequestBody(params);
|
|
525
534
|
let data = await this.client.post("api/v1/signal-to-copy", request);
|
|
526
|
-
if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
|
|
535
|
+
if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
|
|
527
536
|
const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
|
|
528
537
|
const startedAt = Date.now();
|
|
529
538
|
while (Date.now() - startedAt < maxWaitMs) {
|
|
530
539
|
if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
signal_run_id: data.run_id
|
|
534
|
-
});
|
|
540
|
+
const signalRunId = data.signal_run_id || data.run_id;
|
|
541
|
+
data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
|
|
535
542
|
if (!isPendingSignalResponse(data)) break;
|
|
536
543
|
}
|
|
537
544
|
if (isPendingSignalResponse(data)) {
|
|
@@ -542,6 +549,7 @@ var Signaliz = class {
|
|
|
542
549
|
}
|
|
543
550
|
async createSignalCopyBatch(requests, options) {
|
|
544
551
|
validateBatchSize(requests);
|
|
552
|
+
requests.forEach(validateSignalToCopyParams);
|
|
545
553
|
const startedAt = Date.now();
|
|
546
554
|
const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
|
|
547
555
|
request: signalToCopyRequestBody(params),
|
|
@@ -551,7 +559,7 @@ var Signaliz = class {
|
|
|
551
559
|
}));
|
|
552
560
|
const uniqueRequests = deduplicated.uniqueItems;
|
|
553
561
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
554
|
-
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
562
|
+
(params) => (!params.signalRunId || !params.signalRunId.startsWith("copy_") && [params.companyDomain, params.personName, params.title, params.campaignOffer].every((value) => typeof value === "string" && Boolean(value.trim()))) && params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
555
563
|
);
|
|
556
564
|
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
557
565
|
const results = requests.map((_, index) => ({
|
|
@@ -738,7 +746,7 @@ var Signaliz = class {
|
|
|
738
746
|
);
|
|
739
747
|
nextPending.push({
|
|
740
748
|
...task,
|
|
741
|
-
request: { ...task.request, signal_run_id: signalRunId }
|
|
749
|
+
request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
|
|
742
750
|
});
|
|
743
751
|
}
|
|
744
752
|
pending = nextPending;
|
|
@@ -746,7 +754,7 @@ var Signaliz = class {
|
|
|
746
754
|
}
|
|
747
755
|
return results;
|
|
748
756
|
}
|
|
749
|
-
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
|
|
757
|
+
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
|
|
750
758
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
751
759
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
752
760
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
@@ -811,6 +819,25 @@ var Signaliz = class {
|
|
|
811
819
|
};
|
|
812
820
|
}
|
|
813
821
|
}
|
|
822
|
+
if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES) {
|
|
823
|
+
const retryTasks = uniqueTasks.filter((_, index) => isRetryableRowRateLimit(uniqueResults[index]));
|
|
824
|
+
if (retryTasks.length > 0) {
|
|
825
|
+
const retryItems = uniqueResults.filter(isRetryableRowRateLimit);
|
|
826
|
+
await sleep2(rowRateLimitDelayMs(retryItems));
|
|
827
|
+
const retried = await this.runCoreBatchRound(
|
|
828
|
+
path,
|
|
829
|
+
retryTasks,
|
|
830
|
+
options,
|
|
831
|
+
void 0,
|
|
832
|
+
rowRateLimitAttempt + 1
|
|
833
|
+
);
|
|
834
|
+
const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
|
|
835
|
+
for (let index = 0; index < uniqueResults.length; index += 1) {
|
|
836
|
+
const replacement = retriedByIndex.get(uniqueResults[index].index);
|
|
837
|
+
if (replacement) uniqueResults[index] = replacement;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
814
841
|
return tasks.map((task, index) => ({
|
|
815
842
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
816
843
|
index: task.index
|
|
@@ -828,6 +855,21 @@ var Signaliz = class {
|
|
|
828
855
|
};
|
|
829
856
|
}
|
|
830
857
|
};
|
|
858
|
+
function isRetryableRowRateLimit(item) {
|
|
859
|
+
if (!item || item.success || !item.raw || item.raw.retry_eligible === false) return false;
|
|
860
|
+
const code = String(item.raw.error_code || item.raw.code || "").trim().toUpperCase();
|
|
861
|
+
return code === "RATE_LIMITED" || /^RATE_\d+$/.test(code);
|
|
862
|
+
}
|
|
863
|
+
function rowRateLimitDelayMs(items) {
|
|
864
|
+
const delays = items.map((item) => {
|
|
865
|
+
const retryAfterMs = Number(item.raw?.retry_after_ms);
|
|
866
|
+
if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) return retryAfterMs;
|
|
867
|
+
const retryAfterSeconds = Number(item.raw?.retry_after_seconds ?? item.raw?.retry_after);
|
|
868
|
+
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) return retryAfterSeconds * 1e3;
|
|
869
|
+
return 6e4;
|
|
870
|
+
});
|
|
871
|
+
return Math.max(1, Math.min(6e4, Math.max(...delays)));
|
|
872
|
+
}
|
|
831
873
|
function recoverableJobRowFailed(row) {
|
|
832
874
|
return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
833
875
|
}
|
|
@@ -1088,11 +1130,24 @@ function signalToCopyRequestBody(params) {
|
|
|
1088
1130
|
enable_deep_search: params.enableDeepSearch
|
|
1089
1131
|
});
|
|
1090
1132
|
}
|
|
1133
|
+
function validateSignalToCopyParams(params) {
|
|
1134
|
+
if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
|
|
1135
|
+
const missing = [
|
|
1136
|
+
["companyDomain", params.companyDomain],
|
|
1137
|
+
["personName", params.personName],
|
|
1138
|
+
["title", params.title],
|
|
1139
|
+
["campaignOffer", params.campaignOffer]
|
|
1140
|
+
].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
|
|
1141
|
+
if (missing.length > 0) {
|
|
1142
|
+
throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1091
1145
|
function normalizeSignalToCopyResult(data) {
|
|
1092
1146
|
return {
|
|
1093
1147
|
success: data.success ?? true,
|
|
1094
1148
|
status: data.status,
|
|
1095
1149
|
runId: data.signal_run_id ?? data.run_id,
|
|
1150
|
+
resumeContextPersisted: data.resume_context_persisted,
|
|
1096
1151
|
strongestSignal: data.strongest_signal ?? "",
|
|
1097
1152
|
whyNow: data.why_now ?? "",
|
|
1098
1153
|
subjectLine: data.subject_line ?? "",
|
|
@@ -1100,6 +1155,9 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1100
1155
|
confidence: data.confidence ?? 0,
|
|
1101
1156
|
evidenceUrl: data.evidence_url ?? "",
|
|
1102
1157
|
researchPrompt: data.research_prompt,
|
|
1158
|
+
empty: data.empty,
|
|
1159
|
+
emptyReason: data.empty_reason,
|
|
1160
|
+
researchMetadata: data.research_metadata,
|
|
1103
1161
|
variations: (data.variations ?? []).map((variation) => ({
|
|
1104
1162
|
style: variation.style,
|
|
1105
1163
|
subjectLine: variation.subject_line ?? "",
|
package/dist/index.d.mts
CHANGED
|
@@ -167,10 +167,10 @@ interface CompanySignalEnrichmentResult {
|
|
|
167
167
|
raw: Record<string, unknown>;
|
|
168
168
|
}
|
|
169
169
|
interface SignalToCopyParams {
|
|
170
|
-
companyDomain
|
|
171
|
-
personName
|
|
172
|
-
title
|
|
173
|
-
campaignOffer
|
|
170
|
+
companyDomain?: string;
|
|
171
|
+
personName?: string;
|
|
172
|
+
title?: string;
|
|
173
|
+
campaignOffer?: string;
|
|
174
174
|
researchPrompt?: string;
|
|
175
175
|
signalRunId?: string;
|
|
176
176
|
/** Bypass cached company signals and start fresh research. */
|
|
@@ -193,6 +193,7 @@ interface SignalToCopyResult {
|
|
|
193
193
|
success: boolean;
|
|
194
194
|
status?: string;
|
|
195
195
|
runId?: string;
|
|
196
|
+
resumeContextPersisted?: boolean;
|
|
196
197
|
strongestSignal: string;
|
|
197
198
|
whyNow: string;
|
|
198
199
|
subjectLine: string;
|
|
@@ -200,6 +201,9 @@ interface SignalToCopyResult {
|
|
|
200
201
|
confidence: number;
|
|
201
202
|
evidenceUrl: string;
|
|
202
203
|
researchPrompt?: string;
|
|
204
|
+
empty?: boolean;
|
|
205
|
+
emptyReason?: string;
|
|
206
|
+
researchMetadata?: Record<string, unknown>;
|
|
203
207
|
variations: SignalToCopyVariation[];
|
|
204
208
|
raw: Record<string, unknown>;
|
|
205
209
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -167,10 +167,10 @@ interface CompanySignalEnrichmentResult {
|
|
|
167
167
|
raw: Record<string, unknown>;
|
|
168
168
|
}
|
|
169
169
|
interface SignalToCopyParams {
|
|
170
|
-
companyDomain
|
|
171
|
-
personName
|
|
172
|
-
title
|
|
173
|
-
campaignOffer
|
|
170
|
+
companyDomain?: string;
|
|
171
|
+
personName?: string;
|
|
172
|
+
title?: string;
|
|
173
|
+
campaignOffer?: string;
|
|
174
174
|
researchPrompt?: string;
|
|
175
175
|
signalRunId?: string;
|
|
176
176
|
/** Bypass cached company signals and start fresh research. */
|
|
@@ -193,6 +193,7 @@ interface SignalToCopyResult {
|
|
|
193
193
|
success: boolean;
|
|
194
194
|
status?: string;
|
|
195
195
|
runId?: string;
|
|
196
|
+
resumeContextPersisted?: boolean;
|
|
196
197
|
strongestSignal: string;
|
|
197
198
|
whyNow: string;
|
|
198
199
|
subjectLine: string;
|
|
@@ -200,6 +201,9 @@ interface SignalToCopyResult {
|
|
|
200
201
|
confidence: number;
|
|
201
202
|
evidenceUrl: string;
|
|
202
203
|
researchPrompt?: string;
|
|
204
|
+
empty?: boolean;
|
|
205
|
+
emptyReason?: string;
|
|
206
|
+
researchMetadata?: Record<string, unknown>;
|
|
203
207
|
variations: SignalToCopyVariation[];
|
|
204
208
|
raw: Record<string, unknown>;
|
|
205
209
|
}
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ function parseError(status, body) {
|
|
|
59
59
|
}
|
|
60
60
|
function mapErrorType(code, status) {
|
|
61
61
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
62
|
-
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
62
|
+
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
63
63
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
64
64
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
65
65
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
@@ -87,6 +87,7 @@ var HttpClient = class {
|
|
|
87
87
|
}
|
|
88
88
|
async request(functionName, body, method = "POST") {
|
|
89
89
|
let lastError;
|
|
90
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
90
91
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
91
92
|
let timer;
|
|
92
93
|
try {
|
|
@@ -104,7 +105,8 @@ var HttpClient = class {
|
|
|
104
105
|
method,
|
|
105
106
|
headers: {
|
|
106
107
|
"Content-Type": "application/json",
|
|
107
|
-
Authorization: `Bearer ${token}
|
|
108
|
+
Authorization: `Bearer ${token}`,
|
|
109
|
+
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
|
|
108
110
|
},
|
|
109
111
|
signal: controller.signal
|
|
110
112
|
};
|
|
@@ -269,6 +271,11 @@ var HttpClient = class {
|
|
|
269
271
|
return this.accessTokenPromise;
|
|
270
272
|
}
|
|
271
273
|
};
|
|
274
|
+
function requestIdempotencyKey(body) {
|
|
275
|
+
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
276
|
+
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
277
|
+
return requested || `sdk_${nonce}`;
|
|
278
|
+
}
|
|
272
279
|
function sleep(ms) {
|
|
273
280
|
return new Promise((r) => setTimeout(r, ms));
|
|
274
281
|
}
|
|
@@ -369,7 +376,7 @@ function mapMcpErrorType(error) {
|
|
|
369
376
|
function mapErrorTypeFromCode(code) {
|
|
370
377
|
if (!code) return "unknown";
|
|
371
378
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
372
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
379
|
+
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
373
380
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
374
381
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
375
382
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -378,6 +385,7 @@ function mapErrorTypeFromCode(code) {
|
|
|
378
385
|
}
|
|
379
386
|
|
|
380
387
|
// src/index.ts
|
|
388
|
+
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
381
389
|
var Signaliz = class {
|
|
382
390
|
constructor(config) {
|
|
383
391
|
this.client = new HttpClient(config);
|
|
@@ -548,17 +556,16 @@ var Signaliz = class {
|
|
|
548
556
|
};
|
|
549
557
|
}
|
|
550
558
|
async signalToCopy(params) {
|
|
559
|
+
validateSignalToCopyParams(params);
|
|
551
560
|
const request = signalToCopyRequestBody(params);
|
|
552
561
|
let data = await this.client.post("api/v1/signal-to-copy", request);
|
|
553
|
-
if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
|
|
562
|
+
if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
|
|
554
563
|
const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
|
|
555
564
|
const startedAt = Date.now();
|
|
556
565
|
while (Date.now() - startedAt < maxWaitMs) {
|
|
557
566
|
if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
signal_run_id: data.run_id
|
|
561
|
-
});
|
|
567
|
+
const signalRunId = data.signal_run_id || data.run_id;
|
|
568
|
+
data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
|
|
562
569
|
if (!isPendingSignalResponse(data)) break;
|
|
563
570
|
}
|
|
564
571
|
if (isPendingSignalResponse(data)) {
|
|
@@ -569,6 +576,7 @@ var Signaliz = class {
|
|
|
569
576
|
}
|
|
570
577
|
async createSignalCopyBatch(requests, options) {
|
|
571
578
|
validateBatchSize(requests);
|
|
579
|
+
requests.forEach(validateSignalToCopyParams);
|
|
572
580
|
const startedAt = Date.now();
|
|
573
581
|
const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
|
|
574
582
|
request: signalToCopyRequestBody(params),
|
|
@@ -578,7 +586,7 @@ var Signaliz = class {
|
|
|
578
586
|
}));
|
|
579
587
|
const uniqueRequests = deduplicated.uniqueItems;
|
|
580
588
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
581
|
-
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
589
|
+
(params) => (!params.signalRunId || !params.signalRunId.startsWith("copy_") && [params.companyDomain, params.personName, params.title, params.campaignOffer].every((value) => typeof value === "string" && Boolean(value.trim()))) && params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
582
590
|
);
|
|
583
591
|
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
584
592
|
const results = requests.map((_, index) => ({
|
|
@@ -765,7 +773,7 @@ var Signaliz = class {
|
|
|
765
773
|
);
|
|
766
774
|
nextPending.push({
|
|
767
775
|
...task,
|
|
768
|
-
request: { ...task.request, signal_run_id: signalRunId }
|
|
776
|
+
request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
|
|
769
777
|
});
|
|
770
778
|
}
|
|
771
779
|
pending = nextPending;
|
|
@@ -773,7 +781,7 @@ var Signaliz = class {
|
|
|
773
781
|
}
|
|
774
782
|
return results;
|
|
775
783
|
}
|
|
776
|
-
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
|
|
784
|
+
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
|
|
777
785
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
778
786
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
779
787
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
@@ -838,6 +846,25 @@ var Signaliz = class {
|
|
|
838
846
|
};
|
|
839
847
|
}
|
|
840
848
|
}
|
|
849
|
+
if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES) {
|
|
850
|
+
const retryTasks = uniqueTasks.filter((_, index) => isRetryableRowRateLimit(uniqueResults[index]));
|
|
851
|
+
if (retryTasks.length > 0) {
|
|
852
|
+
const retryItems = uniqueResults.filter(isRetryableRowRateLimit);
|
|
853
|
+
await sleep2(rowRateLimitDelayMs(retryItems));
|
|
854
|
+
const retried = await this.runCoreBatchRound(
|
|
855
|
+
path,
|
|
856
|
+
retryTasks,
|
|
857
|
+
options,
|
|
858
|
+
void 0,
|
|
859
|
+
rowRateLimitAttempt + 1
|
|
860
|
+
);
|
|
861
|
+
const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
|
|
862
|
+
for (let index = 0; index < uniqueResults.length; index += 1) {
|
|
863
|
+
const replacement = retriedByIndex.get(uniqueResults[index].index);
|
|
864
|
+
if (replacement) uniqueResults[index] = replacement;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
841
868
|
return tasks.map((task, index) => ({
|
|
842
869
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
843
870
|
index: task.index
|
|
@@ -855,6 +882,21 @@ var Signaliz = class {
|
|
|
855
882
|
};
|
|
856
883
|
}
|
|
857
884
|
};
|
|
885
|
+
function isRetryableRowRateLimit(item) {
|
|
886
|
+
if (!item || item.success || !item.raw || item.raw.retry_eligible === false) return false;
|
|
887
|
+
const code = String(item.raw.error_code || item.raw.code || "").trim().toUpperCase();
|
|
888
|
+
return code === "RATE_LIMITED" || /^RATE_\d+$/.test(code);
|
|
889
|
+
}
|
|
890
|
+
function rowRateLimitDelayMs(items) {
|
|
891
|
+
const delays = items.map((item) => {
|
|
892
|
+
const retryAfterMs = Number(item.raw?.retry_after_ms);
|
|
893
|
+
if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) return retryAfterMs;
|
|
894
|
+
const retryAfterSeconds = Number(item.raw?.retry_after_seconds ?? item.raw?.retry_after);
|
|
895
|
+
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) return retryAfterSeconds * 1e3;
|
|
896
|
+
return 6e4;
|
|
897
|
+
});
|
|
898
|
+
return Math.max(1, Math.min(6e4, Math.max(...delays)));
|
|
899
|
+
}
|
|
858
900
|
function recoverableJobRowFailed(row) {
|
|
859
901
|
return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
860
902
|
}
|
|
@@ -1115,11 +1157,24 @@ function signalToCopyRequestBody(params) {
|
|
|
1115
1157
|
enable_deep_search: params.enableDeepSearch
|
|
1116
1158
|
});
|
|
1117
1159
|
}
|
|
1160
|
+
function validateSignalToCopyParams(params) {
|
|
1161
|
+
if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
|
|
1162
|
+
const missing = [
|
|
1163
|
+
["companyDomain", params.companyDomain],
|
|
1164
|
+
["personName", params.personName],
|
|
1165
|
+
["title", params.title],
|
|
1166
|
+
["campaignOffer", params.campaignOffer]
|
|
1167
|
+
].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
|
|
1168
|
+
if (missing.length > 0) {
|
|
1169
|
+
throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1118
1172
|
function normalizeSignalToCopyResult(data) {
|
|
1119
1173
|
return {
|
|
1120
1174
|
success: data.success ?? true,
|
|
1121
1175
|
status: data.status,
|
|
1122
1176
|
runId: data.signal_run_id ?? data.run_id,
|
|
1177
|
+
resumeContextPersisted: data.resume_context_persisted,
|
|
1123
1178
|
strongestSignal: data.strongest_signal ?? "",
|
|
1124
1179
|
whyNow: data.why_now ?? "",
|
|
1125
1180
|
subjectLine: data.subject_line ?? "",
|
|
@@ -1127,6 +1182,9 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1127
1182
|
confidence: data.confidence ?? 0,
|
|
1128
1183
|
evidenceUrl: data.evidence_url ?? "",
|
|
1129
1184
|
researchPrompt: data.research_prompt,
|
|
1185
|
+
empty: data.empty,
|
|
1186
|
+
emptyReason: data.empty_reason,
|
|
1187
|
+
researchMetadata: data.research_metadata,
|
|
1130
1188
|
variations: (data.variations ?? []).map((variation) => ({
|
|
1131
1189
|
style: variation.style,
|
|
1132
1190
|
subjectLine: variation.subject_line ?? "",
|
package/dist/index.mjs
CHANGED
package/dist/mcp-config.js
CHANGED
|
@@ -63,7 +63,7 @@ function parseError(status, body) {
|
|
|
63
63
|
}
|
|
64
64
|
function mapErrorType(code, status) {
|
|
65
65
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
66
|
-
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
66
|
+
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
67
67
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
68
68
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
69
69
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
@@ -91,6 +91,7 @@ var HttpClient = class {
|
|
|
91
91
|
}
|
|
92
92
|
async request(functionName, body, method = "POST") {
|
|
93
93
|
let lastError;
|
|
94
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
94
95
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
95
96
|
let timer;
|
|
96
97
|
try {
|
|
@@ -108,7 +109,8 @@ var HttpClient = class {
|
|
|
108
109
|
method,
|
|
109
110
|
headers: {
|
|
110
111
|
"Content-Type": "application/json",
|
|
111
|
-
Authorization: `Bearer ${token}
|
|
112
|
+
Authorization: `Bearer ${token}`,
|
|
113
|
+
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
|
|
112
114
|
},
|
|
113
115
|
signal: controller.signal
|
|
114
116
|
};
|
|
@@ -273,6 +275,11 @@ var HttpClient = class {
|
|
|
273
275
|
return this.accessTokenPromise;
|
|
274
276
|
}
|
|
275
277
|
};
|
|
278
|
+
function requestIdempotencyKey(body) {
|
|
279
|
+
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
280
|
+
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
281
|
+
return requested || `sdk_${nonce}`;
|
|
282
|
+
}
|
|
276
283
|
function sleep(ms) {
|
|
277
284
|
return new Promise((r) => setTimeout(r, ms));
|
|
278
285
|
}
|
|
@@ -373,7 +380,7 @@ function mapMcpErrorType(error) {
|
|
|
373
380
|
function mapErrorTypeFromCode(code) {
|
|
374
381
|
if (!code) return "unknown";
|
|
375
382
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
376
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
383
|
+
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
377
384
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
378
385
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
379
386
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -382,6 +389,7 @@ function mapErrorTypeFromCode(code) {
|
|
|
382
389
|
}
|
|
383
390
|
|
|
384
391
|
// src/index.ts
|
|
392
|
+
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
385
393
|
var Signaliz = class {
|
|
386
394
|
constructor(config) {
|
|
387
395
|
this.client = new HttpClient(config);
|
|
@@ -552,17 +560,16 @@ var Signaliz = class {
|
|
|
552
560
|
};
|
|
553
561
|
}
|
|
554
562
|
async signalToCopy(params) {
|
|
563
|
+
validateSignalToCopyParams(params);
|
|
555
564
|
const request = signalToCopyRequestBody(params);
|
|
556
565
|
let data = await this.client.post("api/v1/signal-to-copy", request);
|
|
557
|
-
if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
|
|
566
|
+
if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
|
|
558
567
|
const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
|
|
559
568
|
const startedAt = Date.now();
|
|
560
569
|
while (Date.now() - startedAt < maxWaitMs) {
|
|
561
570
|
if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
signal_run_id: data.run_id
|
|
565
|
-
});
|
|
571
|
+
const signalRunId = data.signal_run_id || data.run_id;
|
|
572
|
+
data = await this.client.post("api/v1/signal-to-copy", data.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...request, signal_run_id: signalRunId });
|
|
566
573
|
if (!isPendingSignalResponse(data)) break;
|
|
567
574
|
}
|
|
568
575
|
if (isPendingSignalResponse(data)) {
|
|
@@ -573,6 +580,7 @@ var Signaliz = class {
|
|
|
573
580
|
}
|
|
574
581
|
async createSignalCopyBatch(requests, options) {
|
|
575
582
|
validateBatchSize(requests);
|
|
583
|
+
requests.forEach(validateSignalToCopyParams);
|
|
576
584
|
const startedAt = Date.now();
|
|
577
585
|
const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
|
|
578
586
|
request: signalToCopyRequestBody(params),
|
|
@@ -582,7 +590,7 @@ var Signaliz = class {
|
|
|
582
590
|
}));
|
|
583
591
|
const uniqueRequests = deduplicated.uniqueItems;
|
|
584
592
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
585
|
-
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
593
|
+
(params) => (!params.signalRunId || !params.signalRunId.startsWith("copy_") && [params.companyDomain, params.personName, params.title, params.campaignOffer].every((value) => typeof value === "string" && Boolean(value.trim()))) && params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
586
594
|
);
|
|
587
595
|
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
588
596
|
const results = requests.map((_, index) => ({
|
|
@@ -769,7 +777,7 @@ var Signaliz = class {
|
|
|
769
777
|
);
|
|
770
778
|
nextPending.push({
|
|
771
779
|
...task,
|
|
772
|
-
request: { ...task.request, signal_run_id: signalRunId }
|
|
780
|
+
request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
|
|
773
781
|
});
|
|
774
782
|
}
|
|
775
783
|
pending = nextPending;
|
|
@@ -777,7 +785,7 @@ var Signaliz = class {
|
|
|
777
785
|
}
|
|
778
786
|
return results;
|
|
779
787
|
}
|
|
780
|
-
async runCoreBatchRound(path2, tasks, options, companyRecoverableBatch) {
|
|
788
|
+
async runCoreBatchRound(path2, tasks, options, companyRecoverableBatch, rowRateLimitAttempt = 0) {
|
|
781
789
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
782
790
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
783
791
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
@@ -842,6 +850,25 @@ var Signaliz = class {
|
|
|
842
850
|
};
|
|
843
851
|
}
|
|
844
852
|
}
|
|
853
|
+
if (rowRateLimitAttempt < MAX_ROW_RATE_LIMIT_RETRIES) {
|
|
854
|
+
const retryTasks = uniqueTasks.filter((_, index) => isRetryableRowRateLimit(uniqueResults[index]));
|
|
855
|
+
if (retryTasks.length > 0) {
|
|
856
|
+
const retryItems = uniqueResults.filter(isRetryableRowRateLimit);
|
|
857
|
+
await sleep2(rowRateLimitDelayMs(retryItems));
|
|
858
|
+
const retried = await this.runCoreBatchRound(
|
|
859
|
+
path2,
|
|
860
|
+
retryTasks,
|
|
861
|
+
options,
|
|
862
|
+
void 0,
|
|
863
|
+
rowRateLimitAttempt + 1
|
|
864
|
+
);
|
|
865
|
+
const retriedByIndex = new Map(retried.map((item) => [item.index, item]));
|
|
866
|
+
for (let index = 0; index < uniqueResults.length; index += 1) {
|
|
867
|
+
const replacement = retriedByIndex.get(uniqueResults[index].index);
|
|
868
|
+
if (replacement) uniqueResults[index] = replacement;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
845
872
|
return tasks.map((task, index) => ({
|
|
846
873
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
847
874
|
index: task.index
|
|
@@ -859,6 +886,21 @@ var Signaliz = class {
|
|
|
859
886
|
};
|
|
860
887
|
}
|
|
861
888
|
};
|
|
889
|
+
function isRetryableRowRateLimit(item) {
|
|
890
|
+
if (!item || item.success || !item.raw || item.raw.retry_eligible === false) return false;
|
|
891
|
+
const code = String(item.raw.error_code || item.raw.code || "").trim().toUpperCase();
|
|
892
|
+
return code === "RATE_LIMITED" || /^RATE_\d+$/.test(code);
|
|
893
|
+
}
|
|
894
|
+
function rowRateLimitDelayMs(items) {
|
|
895
|
+
const delays = items.map((item) => {
|
|
896
|
+
const retryAfterMs = Number(item.raw?.retry_after_ms);
|
|
897
|
+
if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) return retryAfterMs;
|
|
898
|
+
const retryAfterSeconds = Number(item.raw?.retry_after_seconds ?? item.raw?.retry_after);
|
|
899
|
+
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) return retryAfterSeconds * 1e3;
|
|
900
|
+
return 6e4;
|
|
901
|
+
});
|
|
902
|
+
return Math.max(1, Math.min(6e4, Math.max(...delays)));
|
|
903
|
+
}
|
|
862
904
|
function recoverableJobRowFailed(row) {
|
|
863
905
|
return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
864
906
|
}
|
|
@@ -1119,11 +1161,24 @@ function signalToCopyRequestBody(params) {
|
|
|
1119
1161
|
enable_deep_search: params.enableDeepSearch
|
|
1120
1162
|
});
|
|
1121
1163
|
}
|
|
1164
|
+
function validateSignalToCopyParams(params) {
|
|
1165
|
+
if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
|
|
1166
|
+
const missing = [
|
|
1167
|
+
["companyDomain", params.companyDomain],
|
|
1168
|
+
["personName", params.personName],
|
|
1169
|
+
["title", params.title],
|
|
1170
|
+
["campaignOffer", params.campaignOffer]
|
|
1171
|
+
].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
|
|
1172
|
+
if (missing.length > 0) {
|
|
1173
|
+
throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1122
1176
|
function normalizeSignalToCopyResult(data) {
|
|
1123
1177
|
return {
|
|
1124
1178
|
success: data.success ?? true,
|
|
1125
1179
|
status: data.status,
|
|
1126
1180
|
runId: data.signal_run_id ?? data.run_id,
|
|
1181
|
+
resumeContextPersisted: data.resume_context_persisted,
|
|
1127
1182
|
strongestSignal: data.strongest_signal ?? "",
|
|
1128
1183
|
whyNow: data.why_now ?? "",
|
|
1129
1184
|
subjectLine: data.subject_line ?? "",
|
|
@@ -1131,6 +1186,9 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1131
1186
|
confidence: data.confidence ?? 0,
|
|
1132
1187
|
evidenceUrl: data.evidence_url ?? "",
|
|
1133
1188
|
researchPrompt: data.research_prompt,
|
|
1189
|
+
empty: data.empty,
|
|
1190
|
+
emptyReason: data.empty_reason,
|
|
1191
|
+
researchMetadata: data.research_metadata,
|
|
1134
1192
|
variations: (data.variations ?? []).map((variation) => ({
|
|
1135
1193
|
style: variation.style,
|
|
1136
1194
|
subjectLine: variation.subject_line ?? "",
|
package/dist/mcp-config.mjs
CHANGED
package/package.json
CHANGED