@signaliz/sdk 1.0.39 → 1.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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";
@@ -521,17 +528,16 @@ var Signaliz = class {
521
528
  };
522
529
  }
523
530
  async signalToCopy(params) {
531
+ validateSignalToCopyParams(params);
524
532
  const request = signalToCopyRequestBody(params);
525
533
  let data = await this.client.post("api/v1/signal-to-copy", request);
526
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
534
+ if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
527
535
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
528
536
  const startedAt = Date.now();
529
537
  while (Date.now() - startedAt < maxWaitMs) {
530
538
  if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
531
- data = await this.client.post("api/v1/signal-to-copy", {
532
- ...request,
533
- signal_run_id: data.run_id
534
- });
539
+ const signalRunId = data.signal_run_id || data.run_id;
540
+ 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
541
  if (!isPendingSignalResponse(data)) break;
536
542
  }
537
543
  if (isPendingSignalResponse(data)) {
@@ -542,6 +548,7 @@ var Signaliz = class {
542
548
  }
543
549
  async createSignalCopyBatch(requests, options) {
544
550
  validateBatchSize(requests);
551
+ requests.forEach(validateSignalToCopyParams);
545
552
  const startedAt = Date.now();
546
553
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
547
554
  request: signalToCopyRequestBody(params),
@@ -551,7 +558,7 @@ var Signaliz = class {
551
558
  }));
552
559
  const uniqueRequests = deduplicated.uniqueItems;
553
560
  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
561
+ (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
562
  );
556
563
  const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
557
564
  const results = requests.map((_, index) => ({
@@ -738,7 +745,7 @@ var Signaliz = class {
738
745
  );
739
746
  nextPending.push({
740
747
  ...task,
741
- request: { ...task.request, signal_run_id: signalRunId }
748
+ request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
742
749
  });
743
750
  }
744
751
  pending = nextPending;
@@ -944,15 +951,16 @@ function companySignalRecoverableBatchOptions(companies, tasks) {
944
951
  return { label: "Company Signals", pageSize: 25, maxWaitMs };
945
952
  }
946
953
  function normalizeCompanySignalResult(params, data) {
954
+ const rawSignals = data.signals ?? data.signal_feed ?? [];
947
955
  return {
948
956
  success: data.success ?? true,
949
957
  status: data.status,
950
- signalRunId: data.signal_run_id ?? data.run_id,
958
+ signalRunId: data.signal_run_id ?? data.run_id ?? params.signalRunId,
951
959
  company: {
952
960
  name: data.company?.name ?? params.companyName ?? null,
953
961
  domain: data.company?.domain ?? params.companyDomain ?? null
954
962
  },
955
- signals: (data.signals ?? []).map((signal) => {
963
+ signals: rawSignals.map((signal) => {
956
964
  const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : void 0;
957
965
  return {
958
966
  id: signal.id,
@@ -976,9 +984,20 @@ function normalizeCompanySignalResult(params, data) {
976
984
  metadata
977
985
  };
978
986
  }),
979
- signalCount: data.signal_count ?? data.total_signals ?? data.signals?.length ?? 0,
987
+ signalCount: data.signal_count ?? data.total_signals ?? rawSignals.length,
988
+ signalSummary: data.signal_summary ?? null,
989
+ signalSummarySource: data.signal_summary_source,
980
990
  intelligence: data.intelligence ?? null,
991
+ copyFuel: data.copy_fuel ?? null,
992
+ evidenceValidation: data.evidence_validation,
993
+ evidenceSources: data.evidence_sources,
981
994
  credits: data.credits,
995
+ creditsUsed: data.credits_used,
996
+ costUsd: data.cost_usd,
997
+ billingMetadata: data.billing_metadata,
998
+ resultReturnedFrom: data.result_returned_from,
999
+ cacheHit: data.cache_hit,
1000
+ liveProviderCalled: data.live_provider_called,
982
1001
  metadata: data.metadata,
983
1002
  raw: data
984
1003
  };
@@ -1076,11 +1095,24 @@ function signalToCopyRequestBody(params) {
1076
1095
  enable_deep_search: params.enableDeepSearch
1077
1096
  });
1078
1097
  }
1098
+ function validateSignalToCopyParams(params) {
1099
+ if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1100
+ const missing = [
1101
+ ["companyDomain", params.companyDomain],
1102
+ ["personName", params.personName],
1103
+ ["title", params.title],
1104
+ ["campaignOffer", params.campaignOffer]
1105
+ ].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
1106
+ if (missing.length > 0) {
1107
+ throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
1108
+ }
1109
+ }
1079
1110
  function normalizeSignalToCopyResult(data) {
1080
1111
  return {
1081
1112
  success: data.success ?? true,
1082
1113
  status: data.status,
1083
1114
  runId: data.signal_run_id ?? data.run_id,
1115
+ resumeContextPersisted: data.resume_context_persisted,
1084
1116
  strongestSignal: data.strongest_signal ?? "",
1085
1117
  whyNow: data.why_now ?? "",
1086
1118
  subjectLine: data.subject_line ?? "",
@@ -1088,6 +1120,9 @@ function normalizeSignalToCopyResult(data) {
1088
1120
  confidence: data.confidence ?? 0,
1089
1121
  evidenceUrl: data.evidence_url ?? "",
1090
1122
  researchPrompt: data.research_prompt,
1123
+ empty: data.empty,
1124
+ emptyReason: data.empty_reason,
1125
+ researchMetadata: data.research_metadata,
1091
1126
  variations: (data.variations ?? []).map((variation) => ({
1092
1127
  style: variation.style,
1093
1128
  subjectLine: variation.subject_line ?? "",
package/dist/index.d.mts CHANGED
@@ -150,16 +150,27 @@ interface CompanySignalEnrichmentResult {
150
150
  };
151
151
  signals: CompanySignal[];
152
152
  signalCount: number;
153
+ signalSummary: string | null;
154
+ signalSummarySource?: string;
153
155
  intelligence?: Record<string, unknown> | null;
156
+ copyFuel?: Record<string, unknown> | null;
157
+ evidenceValidation?: Record<string, unknown>;
158
+ evidenceSources?: Record<string, unknown>[];
154
159
  credits?: Record<string, unknown>;
160
+ creditsUsed?: number;
161
+ costUsd?: number;
162
+ billingMetadata?: Record<string, unknown>;
163
+ resultReturnedFrom?: string;
164
+ cacheHit?: boolean;
165
+ liveProviderCalled?: boolean;
155
166
  metadata?: Record<string, unknown>;
156
167
  raw: Record<string, unknown>;
157
168
  }
158
169
  interface SignalToCopyParams {
159
- companyDomain: string;
160
- personName: string;
161
- title: string;
162
- campaignOffer: string;
170
+ companyDomain?: string;
171
+ personName?: string;
172
+ title?: string;
173
+ campaignOffer?: string;
163
174
  researchPrompt?: string;
164
175
  signalRunId?: string;
165
176
  /** Bypass cached company signals and start fresh research. */
@@ -182,6 +193,7 @@ interface SignalToCopyResult {
182
193
  success: boolean;
183
194
  status?: string;
184
195
  runId?: string;
196
+ resumeContextPersisted?: boolean;
185
197
  strongestSignal: string;
186
198
  whyNow: string;
187
199
  subjectLine: string;
@@ -189,6 +201,9 @@ interface SignalToCopyResult {
189
201
  confidence: number;
190
202
  evidenceUrl: string;
191
203
  researchPrompt?: string;
204
+ empty?: boolean;
205
+ emptyReason?: string;
206
+ researchMetadata?: Record<string, unknown>;
192
207
  variations: SignalToCopyVariation[];
193
208
  raw: Record<string, unknown>;
194
209
  }
package/dist/index.d.ts CHANGED
@@ -150,16 +150,27 @@ interface CompanySignalEnrichmentResult {
150
150
  };
151
151
  signals: CompanySignal[];
152
152
  signalCount: number;
153
+ signalSummary: string | null;
154
+ signalSummarySource?: string;
153
155
  intelligence?: Record<string, unknown> | null;
156
+ copyFuel?: Record<string, unknown> | null;
157
+ evidenceValidation?: Record<string, unknown>;
158
+ evidenceSources?: Record<string, unknown>[];
154
159
  credits?: Record<string, unknown>;
160
+ creditsUsed?: number;
161
+ costUsd?: number;
162
+ billingMetadata?: Record<string, unknown>;
163
+ resultReturnedFrom?: string;
164
+ cacheHit?: boolean;
165
+ liveProviderCalled?: boolean;
155
166
  metadata?: Record<string, unknown>;
156
167
  raw: Record<string, unknown>;
157
168
  }
158
169
  interface SignalToCopyParams {
159
- companyDomain: string;
160
- personName: string;
161
- title: string;
162
- campaignOffer: string;
170
+ companyDomain?: string;
171
+ personName?: string;
172
+ title?: string;
173
+ campaignOffer?: string;
163
174
  researchPrompt?: string;
164
175
  signalRunId?: string;
165
176
  /** Bypass cached company signals and start fresh research. */
@@ -182,6 +193,7 @@ interface SignalToCopyResult {
182
193
  success: boolean;
183
194
  status?: string;
184
195
  runId?: string;
196
+ resumeContextPersisted?: boolean;
185
197
  strongestSignal: string;
186
198
  whyNow: string;
187
199
  subjectLine: string;
@@ -189,6 +201,9 @@ interface SignalToCopyResult {
189
201
  confidence: number;
190
202
  evidenceUrl: string;
191
203
  researchPrompt?: string;
204
+ empty?: boolean;
205
+ emptyReason?: string;
206
+ researchMetadata?: Record<string, unknown>;
192
207
  variations: SignalToCopyVariation[];
193
208
  raw: Record<string, unknown>;
194
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";
@@ -548,17 +555,16 @@ var Signaliz = class {
548
555
  };
549
556
  }
550
557
  async signalToCopy(params) {
558
+ validateSignalToCopyParams(params);
551
559
  const request = signalToCopyRequestBody(params);
552
560
  let data = await this.client.post("api/v1/signal-to-copy", request);
553
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
561
+ if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
554
562
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
555
563
  const startedAt = Date.now();
556
564
  while (Date.now() - startedAt < maxWaitMs) {
557
565
  if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
558
- data = await this.client.post("api/v1/signal-to-copy", {
559
- ...request,
560
- signal_run_id: data.run_id
561
- });
566
+ const signalRunId = data.signal_run_id || data.run_id;
567
+ 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
568
  if (!isPendingSignalResponse(data)) break;
563
569
  }
564
570
  if (isPendingSignalResponse(data)) {
@@ -569,6 +575,7 @@ var Signaliz = class {
569
575
  }
570
576
  async createSignalCopyBatch(requests, options) {
571
577
  validateBatchSize(requests);
578
+ requests.forEach(validateSignalToCopyParams);
572
579
  const startedAt = Date.now();
573
580
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
574
581
  request: signalToCopyRequestBody(params),
@@ -578,7 +585,7 @@ var Signaliz = class {
578
585
  }));
579
586
  const uniqueRequests = deduplicated.uniqueItems;
580
587
  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
588
+ (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
589
  );
583
590
  const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
584
591
  const results = requests.map((_, index) => ({
@@ -765,7 +772,7 @@ var Signaliz = class {
765
772
  );
766
773
  nextPending.push({
767
774
  ...task,
768
- request: { ...task.request, signal_run_id: signalRunId }
775
+ request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
769
776
  });
770
777
  }
771
778
  pending = nextPending;
@@ -971,15 +978,16 @@ function companySignalRecoverableBatchOptions(companies, tasks) {
971
978
  return { label: "Company Signals", pageSize: 25, maxWaitMs };
972
979
  }
973
980
  function normalizeCompanySignalResult(params, data) {
981
+ const rawSignals = data.signals ?? data.signal_feed ?? [];
974
982
  return {
975
983
  success: data.success ?? true,
976
984
  status: data.status,
977
- signalRunId: data.signal_run_id ?? data.run_id,
985
+ signalRunId: data.signal_run_id ?? data.run_id ?? params.signalRunId,
978
986
  company: {
979
987
  name: data.company?.name ?? params.companyName ?? null,
980
988
  domain: data.company?.domain ?? params.companyDomain ?? null
981
989
  },
982
- signals: (data.signals ?? []).map((signal) => {
990
+ signals: rawSignals.map((signal) => {
983
991
  const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : void 0;
984
992
  return {
985
993
  id: signal.id,
@@ -1003,9 +1011,20 @@ function normalizeCompanySignalResult(params, data) {
1003
1011
  metadata
1004
1012
  };
1005
1013
  }),
1006
- signalCount: data.signal_count ?? data.total_signals ?? data.signals?.length ?? 0,
1014
+ signalCount: data.signal_count ?? data.total_signals ?? rawSignals.length,
1015
+ signalSummary: data.signal_summary ?? null,
1016
+ signalSummarySource: data.signal_summary_source,
1007
1017
  intelligence: data.intelligence ?? null,
1018
+ copyFuel: data.copy_fuel ?? null,
1019
+ evidenceValidation: data.evidence_validation,
1020
+ evidenceSources: data.evidence_sources,
1008
1021
  credits: data.credits,
1022
+ creditsUsed: data.credits_used,
1023
+ costUsd: data.cost_usd,
1024
+ billingMetadata: data.billing_metadata,
1025
+ resultReturnedFrom: data.result_returned_from,
1026
+ cacheHit: data.cache_hit,
1027
+ liveProviderCalled: data.live_provider_called,
1009
1028
  metadata: data.metadata,
1010
1029
  raw: data
1011
1030
  };
@@ -1103,11 +1122,24 @@ function signalToCopyRequestBody(params) {
1103
1122
  enable_deep_search: params.enableDeepSearch
1104
1123
  });
1105
1124
  }
1125
+ function validateSignalToCopyParams(params) {
1126
+ if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1127
+ const missing = [
1128
+ ["companyDomain", params.companyDomain],
1129
+ ["personName", params.personName],
1130
+ ["title", params.title],
1131
+ ["campaignOffer", params.campaignOffer]
1132
+ ].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
1133
+ if (missing.length > 0) {
1134
+ throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
1135
+ }
1136
+ }
1106
1137
  function normalizeSignalToCopyResult(data) {
1107
1138
  return {
1108
1139
  success: data.success ?? true,
1109
1140
  status: data.status,
1110
1141
  runId: data.signal_run_id ?? data.run_id,
1142
+ resumeContextPersisted: data.resume_context_persisted,
1111
1143
  strongestSignal: data.strongest_signal ?? "",
1112
1144
  whyNow: data.why_now ?? "",
1113
1145
  subjectLine: data.subject_line ?? "",
@@ -1115,6 +1147,9 @@ function normalizeSignalToCopyResult(data) {
1115
1147
  confidence: data.confidence ?? 0,
1116
1148
  evidenceUrl: data.evidence_url ?? "",
1117
1149
  researchPrompt: data.research_prompt,
1150
+ empty: data.empty,
1151
+ emptyReason: data.empty_reason,
1152
+ researchMetadata: data.research_metadata,
1118
1153
  variations: (data.variations ?? []).map((variation) => ({
1119
1154
  style: variation.style,
1120
1155
  subjectLine: variation.subject_line ?? "",
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-UVW5N4LW.mjs";
4
+ } from "./chunk-B4OMGBD5.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -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";
@@ -552,17 +559,16 @@ var Signaliz = class {
552
559
  };
553
560
  }
554
561
  async signalToCopy(params) {
562
+ validateSignalToCopyParams(params);
555
563
  const request = signalToCopyRequestBody(params);
556
564
  let data = await this.client.post("api/v1/signal-to-copy", request);
557
- if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
565
+ if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
558
566
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
559
567
  const startedAt = Date.now();
560
568
  while (Date.now() - startedAt < maxWaitMs) {
561
569
  if (!await waitForNextSignalPoll(data, params.pollIntervalMs, startedAt, maxWaitMs)) break;
562
- data = await this.client.post("api/v1/signal-to-copy", {
563
- ...request,
564
- signal_run_id: data.run_id
565
- });
570
+ const signalRunId = data.signal_run_id || data.run_id;
571
+ 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
572
  if (!isPendingSignalResponse(data)) break;
567
573
  }
568
574
  if (isPendingSignalResponse(data)) {
@@ -573,6 +579,7 @@ var Signaliz = class {
573
579
  }
574
580
  async createSignalCopyBatch(requests, options) {
575
581
  validateBatchSize(requests);
582
+ requests.forEach(validateSignalToCopyParams);
576
583
  const startedAt = Date.now();
577
584
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
578
585
  request: signalToCopyRequestBody(params),
@@ -582,7 +589,7 @@ var Signaliz = class {
582
589
  }));
583
590
  const uniqueRequests = deduplicated.uniqueItems;
584
591
  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
592
+ (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
593
  );
587
594
  const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
588
595
  const results = requests.map((_, index) => ({
@@ -769,7 +776,7 @@ var Signaliz = class {
769
776
  );
770
777
  nextPending.push({
771
778
  ...task,
772
- request: { ...task.request, signal_run_id: signalRunId }
779
+ request: item.resume_context_persisted === true ? { signal_run_id: signalRunId } : { ...task.request, signal_run_id: signalRunId }
773
780
  });
774
781
  }
775
782
  pending = nextPending;
@@ -975,15 +982,16 @@ function companySignalRecoverableBatchOptions(companies, tasks) {
975
982
  return { label: "Company Signals", pageSize: 25, maxWaitMs };
976
983
  }
977
984
  function normalizeCompanySignalResult(params, data) {
985
+ const rawSignals = data.signals ?? data.signal_feed ?? [];
978
986
  return {
979
987
  success: data.success ?? true,
980
988
  status: data.status,
981
- signalRunId: data.signal_run_id ?? data.run_id,
989
+ signalRunId: data.signal_run_id ?? data.run_id ?? params.signalRunId,
982
990
  company: {
983
991
  name: data.company?.name ?? params.companyName ?? null,
984
992
  domain: data.company?.domain ?? params.companyDomain ?? null
985
993
  },
986
- signals: (data.signals ?? []).map((signal) => {
994
+ signals: rawSignals.map((signal) => {
987
995
  const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : void 0;
988
996
  return {
989
997
  id: signal.id,
@@ -1007,9 +1015,20 @@ function normalizeCompanySignalResult(params, data) {
1007
1015
  metadata
1008
1016
  };
1009
1017
  }),
1010
- signalCount: data.signal_count ?? data.total_signals ?? data.signals?.length ?? 0,
1018
+ signalCount: data.signal_count ?? data.total_signals ?? rawSignals.length,
1019
+ signalSummary: data.signal_summary ?? null,
1020
+ signalSummarySource: data.signal_summary_source,
1011
1021
  intelligence: data.intelligence ?? null,
1022
+ copyFuel: data.copy_fuel ?? null,
1023
+ evidenceValidation: data.evidence_validation,
1024
+ evidenceSources: data.evidence_sources,
1012
1025
  credits: data.credits,
1026
+ creditsUsed: data.credits_used,
1027
+ costUsd: data.cost_usd,
1028
+ billingMetadata: data.billing_metadata,
1029
+ resultReturnedFrom: data.result_returned_from,
1030
+ cacheHit: data.cache_hit,
1031
+ liveProviderCalled: data.live_provider_called,
1013
1032
  metadata: data.metadata,
1014
1033
  raw: data
1015
1034
  };
@@ -1107,11 +1126,24 @@ function signalToCopyRequestBody(params) {
1107
1126
  enable_deep_search: params.enableDeepSearch
1108
1127
  });
1109
1128
  }
1129
+ function validateSignalToCopyParams(params) {
1130
+ if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1131
+ const missing = [
1132
+ ["companyDomain", params.companyDomain],
1133
+ ["personName", params.personName],
1134
+ ["title", params.title],
1135
+ ["campaignOffer", params.campaignOffer]
1136
+ ].filter(([, value]) => typeof value !== "string" || !value.trim()).map(([field]) => field);
1137
+ if (missing.length > 0) {
1138
+ throw new TypeError(`Signal to Copy requires signalRunId or: ${missing.join(", ")}`);
1139
+ }
1140
+ }
1110
1141
  function normalizeSignalToCopyResult(data) {
1111
1142
  return {
1112
1143
  success: data.success ?? true,
1113
1144
  status: data.status,
1114
1145
  runId: data.signal_run_id ?? data.run_id,
1146
+ resumeContextPersisted: data.resume_context_persisted,
1115
1147
  strongestSignal: data.strongest_signal ?? "",
1116
1148
  whyNow: data.why_now ?? "",
1117
1149
  subjectLine: data.subject_line ?? "",
@@ -1119,6 +1151,9 @@ function normalizeSignalToCopyResult(data) {
1119
1151
  confidence: data.confidence ?? 0,
1120
1152
  evidenceUrl: data.evidence_url ?? "",
1121
1153
  researchPrompt: data.research_prompt,
1154
+ empty: data.empty,
1155
+ emptyReason: data.empty_reason,
1156
+ researchMetadata: data.research_metadata,
1122
1157
  variations: (data.variations ?? []).map((variation) => ({
1123
1158
  style: variation.style,
1124
1159
  subjectLine: variation.subject_line ?? "",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-UVW5N4LW.mjs";
4
+ } from "./chunk-B4OMGBD5.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.39",
3
+ "version": "1.0.42",
4
4
  "description": "Signaliz SDK for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",