lumnisai 0.1.19 → 0.1.21

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/dist/index.cjs CHANGED
@@ -11,7 +11,9 @@ const LINKEDIN_LIMITS = {
11
11
  basic: {
12
12
  connectionRequests: {
13
13
  weeklyMax: 100,
14
- dailySafe: 15
14
+ dailySafe: 15,
15
+ personalizedMonthly: 10
16
+ // Very limited personalized requests
15
17
  },
16
18
  messages: {
17
19
  weeklyMax: 100,
@@ -25,13 +27,41 @@ const LINKEDIN_LIMITS = {
25
27
  profileViews: {
26
28
  dailyMax: 500,
27
29
  dailySafe: 250
28
- }
30
+ },
31
+ openProfileMessagesMonthly: null
29
32
  },
30
- premium_career: {
33
+ premium: {
31
34
  connectionRequests: {
35
+ weeklyMax: 150,
36
+ // Same as basic, SSI-dependent
37
+ dailySafe: 20,
38
+ personalizedMonthly: null
39
+ // Unlimited
40
+ },
41
+ messages: {
32
42
  weeklyMax: 150,
33
43
  dailySafe: 20
34
44
  },
45
+ inmail: {
46
+ monthlyCredits: 5,
47
+ maxAccumulation: 15,
48
+ rollover: true
49
+ },
50
+ profileViews: {
51
+ dailyMax: 1e3,
52
+ // Premium (non-SN): 150-1000/day
53
+ dailySafe: 500
54
+ },
55
+ openProfileMessagesMonthly: null
56
+ },
57
+ premium_career: {
58
+ connectionRequests: {
59
+ weeklyMax: 150,
60
+ // Same as basic, SSI-dependent
61
+ dailySafe: 20,
62
+ personalizedMonthly: null
63
+ // Unlimited
64
+ },
35
65
  messages: {
36
66
  weeklyMax: 150,
37
67
  dailySafe: 20
@@ -44,14 +74,16 @@ const LINKEDIN_LIMITS = {
44
74
  profileViews: {
45
75
  dailyMax: 1e3,
46
76
  // Premium (non-SN): 150-1000/day
47
- dailySafe: 300
48
- }
77
+ dailySafe: 500
78
+ },
79
+ openProfileMessagesMonthly: null
49
80
  },
50
81
  premium_business: {
51
82
  connectionRequests: {
52
83
  weeklyMax: 150,
53
84
  // Same as Premium Career, SSI-dependent
54
- dailySafe: 20
85
+ dailySafe: 20,
86
+ personalizedMonthly: null
55
87
  },
56
88
  messages: {
57
89
  weeklyMax: 150,
@@ -65,13 +97,15 @@ const LINKEDIN_LIMITS = {
65
97
  profileViews: {
66
98
  dailyMax: 1e3,
67
99
  // Premium (non-SN): 150-1000/day
68
- dailySafe: 300
69
- }
100
+ dailySafe: 500
101
+ },
102
+ openProfileMessagesMonthly: null
70
103
  },
71
104
  sales_navigator: {
72
105
  connectionRequests: {
73
106
  weeklyMax: 200,
74
- dailySafe: 25
107
+ dailySafe: 25,
108
+ personalizedMonthly: null
75
109
  },
76
110
  messages: {
77
111
  weeklyMax: 150,
@@ -84,13 +118,15 @@ const LINKEDIN_LIMITS = {
84
118
  },
85
119
  profileViews: {
86
120
  dailyMax: 2e3,
87
- dailySafe: 500
88
- }
121
+ dailySafe: 1e3
122
+ },
123
+ openProfileMessagesMonthly: null
89
124
  },
90
125
  recruiter_lite: {
91
126
  connectionRequests: {
92
127
  weeklyMax: 200,
93
- dailySafe: 25
128
+ dailySafe: 25,
129
+ personalizedMonthly: null
94
130
  },
95
131
  messages: {
96
132
  weeklyMax: 200,
@@ -104,13 +140,15 @@ const LINKEDIN_LIMITS = {
104
140
  },
105
141
  profileViews: {
106
142
  dailyMax: 2e3,
107
- dailySafe: 500
108
- }
143
+ dailySafe: 1e3
144
+ },
145
+ openProfileMessagesMonthly: 1e3
109
146
  },
110
147
  recruiter_corporate: {
111
148
  connectionRequests: {
112
149
  weeklyMax: 250,
113
- dailySafe: 30
150
+ dailySafe: 30,
151
+ personalizedMonthly: null
114
152
  },
115
153
  messages: {
116
154
  weeklyMax: 200,
@@ -123,10 +161,63 @@ const LINKEDIN_LIMITS = {
123
161
  },
124
162
  profileViews: {
125
163
  dailyMax: 2e3,
126
- dailySafe: 500
127
- }
164
+ dailySafe: 1e3
165
+ },
166
+ openProfileMessagesMonthly: 1e3
167
+ }
168
+ };
169
+ const UNIPILE_SAFE_LIMITS = {
170
+ // Connection requests per day (Unipile recommends 80-100 for paid)
171
+ connectionRequestsDaily: {
172
+ basic: 15,
173
+ premium: 20,
174
+ premium_career: 20,
175
+ premium_business: 20,
176
+ sales_navigator: 25,
177
+ recruiter_lite: 25,
178
+ recruiter_corporate: 30
179
+ },
180
+ // Messages per day (steady pace recommended)
181
+ messagesDaily: {
182
+ basic: 15,
183
+ premium: 20,
184
+ premium_career: 20,
185
+ premium_business: 20,
186
+ sales_navigator: 20,
187
+ recruiter_lite: 25,
188
+ recruiter_corporate: 30
189
+ },
190
+ // Profile views per day (stay under 50% of max)
191
+ profileViewsDaily: {
192
+ basic: 250,
193
+ premium: 300,
194
+ // 50% of 1000 max, conservative
195
+ premium_career: 300,
196
+ premium_business: 300,
197
+ sales_navigator: 500,
198
+ // 50% of 2000 max
199
+ recruiter_lite: 500,
200
+ recruiter_corporate: 500
128
201
  }
129
202
  };
203
+ const RATE_LIMIT_COOLDOWNS = {
204
+ connectionRequestRejected: 3600,
205
+ // 1 hour after rejection
206
+ messageRateLimited: 1800,
207
+ // 30 min after rate limit
208
+ dailyLimitReached: 86400,
209
+ // 24 hours
210
+ weeklyLimitReached: 604800
211
+ // 7 days
212
+ };
213
+ const UNIPILE_RATE_LIMIT_ERRORS = {
214
+ 422: "cannot_resend_yet",
215
+ // Connection request limit
216
+ 429: "rate_limited",
217
+ // Too many requests
218
+ 500: "server_error_possibly_rate_limited"
219
+ // Sometimes indicates limits
220
+ };
130
221
  const ACTION_DELAYS = {
131
222
  betweenConnectionRequests: 30,
132
223
  betweenMessages: 15,
@@ -153,27 +244,23 @@ function canSendInmail(subscriptionType) {
153
244
  const limits = getLimits(subscriptionType);
154
245
  return limits.inmail.monthlyCredits > 0;
155
246
  }
247
+ function hasOpenProfileMessages(subscriptionType) {
248
+ const limits = getLimits(subscriptionType);
249
+ return limits.openProfileMessagesMonthly !== null;
250
+ }
156
251
  function getBestSubscriptionForAction(subscriptionTypes, action) {
157
252
  if (subscriptionTypes.length === 0)
158
253
  return null;
159
254
  if (subscriptionTypes.length === 1)
160
255
  return subscriptionTypes[0];
161
256
  if (action === "inmail") {
162
- const priority = ["recruiter_corporate", "recruiter_lite", "sales_navigator", "premium_business", "premium_career", "basic"];
163
- for (const priorityType of priority) {
164
- if (subscriptionTypes.includes(priorityType))
165
- return priorityType;
166
- }
167
- }
168
- if (action === "connection_requests") {
169
- const priority = ["recruiter_corporate", "sales_navigator", "recruiter_lite", "premium_business", "premium_career", "basic"];
257
+ const priority = ["recruiter_corporate", "sales_navigator", "recruiter_lite", "premium_business", "premium", "premium_career"];
170
258
  for (const priorityType of priority) {
171
259
  if (subscriptionTypes.includes(priorityType))
172
260
  return priorityType;
173
261
  }
174
- }
175
- if (action === "messages") {
176
- const priority = ["recruiter_corporate", "recruiter_lite", "sales_navigator", "premium_business", "premium_career", "basic"];
262
+ } else {
263
+ const priority = ["recruiter_corporate", "sales_navigator", "recruiter_lite", "premium_business", "premium", "premium_career", "basic"];
177
264
  for (const priorityType of priority) {
178
265
  if (subscriptionTypes.includes(priorityType))
179
266
  return priorityType;
@@ -1029,23 +1116,97 @@ class MessagingResource {
1029
1116
  }
1030
1117
  }
1031
1118
  /**
1032
- * Create drafts for multiple prospects with AI generation
1119
+ * Create drafts for multiple prospects with AI generation.
1120
+ *
1121
+ * **Default behavior (async):**
1122
+ * Returns immediately with a job_id (202 response). Poll `getBatchDraftJobStatus()`
1123
+ * for progress and results. The job continues processing in the background even
1124
+ * if the client disconnects.
1125
+ *
1126
+ * **Legacy behavior (sync):**
1127
+ * Set `wait: true` to block until completion. Not recommended for large batches
1128
+ * as the request may timeout.
1129
+ *
1130
+ * @param userId - User ID or email
1131
+ * @param request - Batch draft creation request
1132
+ * @param options - Optional parameters
1133
+ * @param options.wait - If true, wait for completion (legacy behavior)
1134
+ * @returns BatchDraftJobResponse (async) or BatchDraftResponse (sync with wait=true)
1135
+ *
1136
+ * @example
1137
+ * ```typescript
1138
+ * // Async (recommended) - returns immediately with job_id
1139
+ * const job = await client.messaging.createBatchDrafts(userId, request);
1140
+ * if ('jobId' in job) {
1141
+ * // Poll for status
1142
+ * const status = await client.messaging.getBatchDraftJobStatus(userId, job.jobId);
1143
+ * }
1144
+ *
1145
+ * // Sync (legacy) - blocks until complete
1146
+ * const result = await client.messaging.createBatchDrafts(userId, request, { wait: true });
1147
+ * console.log(result.drafts); // All drafts available immediately
1148
+ * ```
1033
1149
  */
1034
- async createBatchDrafts(userId, request) {
1150
+ async createBatchDrafts(userId, request, options) {
1035
1151
  const queryParams = new URLSearchParams();
1036
1152
  queryParams.append("user_id", userId);
1153
+ if (options?.wait) {
1154
+ queryParams.append("wait", "true");
1155
+ }
1037
1156
  return this.http.post(
1038
1157
  `/messaging/drafts/batch?${queryParams.toString()}`,
1039
1158
  request
1040
1159
  );
1041
1160
  }
1161
+ /**
1162
+ * Get status and results of a batch draft job.
1163
+ *
1164
+ * Poll this endpoint to track progress and retrieve results for batch draft jobs
1165
+ * created via `createBatchDrafts()`. Jobs are retained for 24 hours after creation.
1166
+ *
1167
+ * @param userId - User ID or email
1168
+ * @param jobId - The job ID returned from createBatchDrafts()
1169
+ * @returns BatchDraftJobStatusResponse with progress and results
1170
+ * @throws NotFoundError if job not found or expired
1171
+ *
1172
+ * @example
1173
+ * ```typescript
1174
+ * // Create job
1175
+ * const job = await client.messaging.createBatchDrafts(userId, request);
1176
+ * if ('jobId' in job) {
1177
+ * // Poll until complete
1178
+ * let status;
1179
+ * do {
1180
+ * await new Promise(r => setTimeout(r, 1000)); // Wait 1 second
1181
+ * status = await client.messaging.getBatchDraftJobStatus(userId, job.jobId);
1182
+ * console.log(`Progress: ${status.progress.percentage}%`);
1183
+ * } while (status.status !== 'completed' && status.status !== 'failed');
1184
+ *
1185
+ * console.log(`Created ${status.drafts.length} drafts`);
1186
+ * }
1187
+ * ```
1188
+ */
1189
+ async getBatchDraftJobStatus(userId, jobId) {
1190
+ const queryParams = new URLSearchParams();
1191
+ queryParams.append("user_id", userId);
1192
+ return this.http.get(
1193
+ `/messaging/drafts/batch/jobs/${encodeURIComponent(jobId)}?${queryParams.toString()}`
1194
+ );
1195
+ }
1042
1196
  /**
1043
1197
  * Create drafts for multiple prospects with real-time progress updates via Server-Sent Events (SSE).
1044
1198
  *
1045
1199
  * This method provides real-time progress updates as drafts are being created, significantly
1046
1200
  * improving user experience for large batch operations (30+ prospects).
1047
1201
  *
1202
+ * **Resilience features:**
1203
+ * - Job state is stored in Redis and survives client disconnects
1204
+ * - Background task continues processing even if SSE connection drops
1205
+ * - Client can reconnect using `jobId` option to resume streaming
1206
+ * - Poll `getBatchDraftJobStatus()` as fallback
1207
+ *
1048
1208
  * **Event Types:**
1209
+ * - `job_started`: First event with job_id for reconnection
1049
1210
  * - `progress`: Progress update with percentage and current prospect
1050
1211
  * - `draft_created`: Draft successfully created
1051
1212
  * - `error`: Error occurred for a specific prospect
@@ -1053,6 +1214,8 @@ class MessagingResource {
1053
1214
  *
1054
1215
  * **Example:**
1055
1216
  * ```typescript
1217
+ * let savedJobId: string | undefined;
1218
+ *
1056
1219
  * const result = await client.messaging.createBatchDraftsStream(
1057
1220
  * 'user@example.com',
1058
1221
  * {
@@ -1061,6 +1224,9 @@ class MessagingResource {
1061
1224
  * useAiGeneration: true
1062
1225
  * },
1063
1226
  * {
1227
+ * onJobStarted: (jobId) => {
1228
+ * savedJobId = jobId; // Save for reconnection if disconnected
1229
+ * },
1064
1230
  * onProgress: (processed, total, percentage, prospectName) => {
1065
1231
  * console.log(`${percentage}% - ${prospectName}`)
1066
1232
  * },
@@ -1080,11 +1246,16 @@ class MessagingResource {
1080
1246
  * @param userId - User ID or email
1081
1247
  * @param request - Batch draft creation request
1082
1248
  * @param callbacks - Optional callbacks for stream events
1249
+ * @param options - Optional parameters
1250
+ * @param options.jobId - Existing job ID to resume streaming (for reconnection)
1083
1251
  * @returns Final result with created drafts and error details
1084
1252
  */
1085
- async createBatchDraftsStream(userId, request, callbacks) {
1253
+ async createBatchDraftsStream(userId, request, callbacks, options) {
1086
1254
  const queryParams = new URLSearchParams();
1087
1255
  queryParams.append("user_id", userId);
1256
+ if (options?.jobId) {
1257
+ queryParams.append("job_id", options.jobId);
1258
+ }
1088
1259
  const baseUrl = this.http.options.baseUrl;
1089
1260
  const apiPrefix = this.http.options.apiPrefix || "";
1090
1261
  const path = `/messaging/drafts/batch/stream?${queryParams.toString()}`;
@@ -1150,6 +1321,11 @@ class MessagingResource {
1150
1321
  const eventData = JSON.parse(line.slice(6));
1151
1322
  const { event, data } = eventData;
1152
1323
  switch (event) {
1324
+ case "job_started": {
1325
+ const jobData = toCamelCase(data);
1326
+ callbacks?.onJobStarted?.(jobData.jobId || "");
1327
+ break;
1328
+ }
1153
1329
  case "progress": {
1154
1330
  const progressData = toCamelCase(data);
1155
1331
  callbacks?.onProgress?.(
@@ -1204,8 +1380,15 @@ class MessagingResource {
1204
1380
  *
1205
1381
  * This method yields events as they arrive, providing more control over event handling.
1206
1382
  *
1383
+ * **Resilience features:**
1384
+ * - Job state is stored in Redis and survives client disconnects
1385
+ * - First event is `job_started` with job_id for reconnection
1386
+ * - Client can reconnect using `jobId` option to resume streaming
1387
+ *
1207
1388
  * **Example:**
1208
1389
  * ```typescript
1390
+ * let savedJobId: string | undefined;
1391
+ *
1209
1392
  * for await (const event of client.messaging.createBatchDraftsStreamGenerator(
1210
1393
  * 'user@example.com',
1211
1394
  * {
@@ -1215,6 +1398,9 @@ class MessagingResource {
1215
1398
  * }
1216
1399
  * )) {
1217
1400
  * switch (event.event) {
1401
+ * case 'job_started':
1402
+ * savedJobId = event.data.jobId; // Save for reconnection
1403
+ * break;
1218
1404
  * case 'progress':
1219
1405
  * console.log(`Progress: ${event.data.percentage}%`)
1220
1406
  * break
@@ -1233,12 +1419,17 @@ class MessagingResource {
1233
1419
  *
1234
1420
  * @param userId - User ID or email
1235
1421
  * @param request - Batch draft creation request
1422
+ * @param options - Optional parameters
1423
+ * @param options.jobId - Existing job ID to resume streaming (for reconnection)
1236
1424
  * @yields Stream events with 'event' and 'data' keys
1237
1425
  * @returns Final result with created drafts and error details
1238
1426
  */
1239
- async *createBatchDraftsStreamGenerator(userId, request) {
1427
+ async *createBatchDraftsStreamGenerator(userId, request, options) {
1240
1428
  const queryParams = new URLSearchParams();
1241
1429
  queryParams.append("user_id", userId);
1430
+ if (options?.jobId) {
1431
+ queryParams.append("job_id", options.jobId);
1432
+ }
1242
1433
  const baseUrl = this.http.options.baseUrl;
1243
1434
  const apiPrefix = this.http.options.apiPrefix || "";
1244
1435
  const path = `/messaging/drafts/batch/stream?${queryParams.toString()}`;
@@ -2593,6 +2784,13 @@ var SyncJobStatus = /* @__PURE__ */ ((SyncJobStatus2) => {
2593
2784
  SyncJobStatus2["FAILED"] = "failed";
2594
2785
  return SyncJobStatus2;
2595
2786
  })(SyncJobStatus || {});
2787
+ var BatchJobStatus = /* @__PURE__ */ ((BatchJobStatus2) => {
2788
+ BatchJobStatus2["PENDING"] = "pending";
2789
+ BatchJobStatus2["IN_PROGRESS"] = "in_progress";
2790
+ BatchJobStatus2["COMPLETED"] = "completed";
2791
+ BatchJobStatus2["FAILED"] = "failed";
2792
+ return BatchJobStatus2;
2793
+ })(BatchJobStatus || {});
2596
2794
  var QueueItemStatus = /* @__PURE__ */ ((QueueItemStatus2) => {
2597
2795
  QueueItemStatus2["QUEUED"] = "queued";
2598
2796
  QueueItemStatus2["PROCESSING"] = "processing";
@@ -2728,6 +2926,7 @@ function verifyWebhookSignature(payload, signature, secret) {
2728
2926
 
2729
2927
  exports.ACTION_DELAYS = ACTION_DELAYS;
2730
2928
  exports.AuthenticationError = AuthenticationError;
2929
+ exports.BatchJobStatus = BatchJobStatus;
2731
2930
  exports.ChannelType = ChannelType;
2732
2931
  exports.ConversationStatus = ConversationStatus;
2733
2932
  exports.DraftStatus = DraftStatus;
@@ -2751,9 +2950,12 @@ exports.PeopleDataSource = PeopleDataSource;
2751
2950
  exports.ProgressTracker = ProgressTracker;
2752
2951
  exports.ProviderType = ProviderType;
2753
2952
  exports.QueueItemStatus = QueueItemStatus;
2953
+ exports.RATE_LIMIT_COOLDOWNS = RATE_LIMIT_COOLDOWNS;
2754
2954
  exports.RateLimitError = RateLimitError;
2755
2955
  exports.SourcesNotAvailableError = SourcesNotAvailableError;
2756
2956
  exports.SyncJobStatus = SyncJobStatus;
2957
+ exports.UNIPILE_RATE_LIMIT_ERRORS = UNIPILE_RATE_LIMIT_ERRORS;
2958
+ exports.UNIPILE_SAFE_LIMITS = UNIPILE_SAFE_LIMITS;
2757
2959
  exports.ValidationError = ValidationError;
2758
2960
  exports.canSendInmail = canSendInmail;
2759
2961
  exports.displayProgress = displayProgress;
@@ -2763,4 +2965,5 @@ exports.getConnectionRequestLimit = getConnectionRequestLimit;
2763
2965
  exports.getInmailAllowance = getInmailAllowance;
2764
2966
  exports.getLimits = getLimits;
2765
2967
  exports.getMessageLimit = getMessageLimit;
2968
+ exports.hasOpenProfileMessages = hasOpenProfileMessages;
2766
2969
  exports.verifyWebhookSignature = verifyWebhookSignature;