lumnisai 0.5.39 → 0.5.41

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
@@ -3435,6 +3435,76 @@ class ResponsesResource {
3435
3435
  throw new ValidationError("excludeUrls must be an array of strings for engagement_expansion");
3436
3436
  }
3437
3437
  }
3438
+ _validateInfluencerEngagementParams(params) {
3439
+ const seedProfiles = this._getParamValue(params, "seedProfiles", "seed_profiles");
3440
+ if (!Array.isArray(seedProfiles) || seedProfiles.some((url) => typeof url !== "string") || !seedProfiles.some((url) => url.trim().length > 0)) {
3441
+ throw new ValidationError(
3442
+ "seedProfiles is required for influencer_engagement and must contain at least one non-empty string"
3443
+ );
3444
+ }
3445
+ const engagementTypes = this._getParamValue(
3446
+ params,
3447
+ "engagementTypes",
3448
+ "engagement_types"
3449
+ );
3450
+ if (engagementTypes !== void 0) {
3451
+ if (!Array.isArray(engagementTypes) || engagementTypes.length === 0)
3452
+ throw new ValidationError("engagementTypes must contain at least one value");
3453
+ const validTypes = ["reactor", "commenter"];
3454
+ for (const type of engagementTypes) {
3455
+ if (!validTypes.includes(type)) {
3456
+ throw new ValidationError(
3457
+ `Invalid engagementTypes value: ${String(type)}. Expected 'reactor' and/or 'commenter'.`
3458
+ );
3459
+ }
3460
+ }
3461
+ }
3462
+ const postsDateRange = this._getParamValue(
3463
+ params,
3464
+ "postsDateRange",
3465
+ "posts_date_range"
3466
+ );
3467
+ if (postsDateRange !== void 0) {
3468
+ const validRanges = [
3469
+ "past-24h",
3470
+ "past-week",
3471
+ "past-month",
3472
+ "past-quarter",
3473
+ "past-6-months",
3474
+ "past-year",
3475
+ "past-2-years",
3476
+ "past-3-years"
3477
+ ];
3478
+ if (!validRanges.includes(postsDateRange))
3479
+ throw new ValidationError(`Invalid postsDateRange value: ${String(postsDateRange)}`);
3480
+ }
3481
+ for (const [camel, snake, maximum] of [
3482
+ ["limit", "limit", 1e3],
3483
+ ["maxReactorsPerPost", "max_reactors_per_post", 5e3],
3484
+ ["maxCommentsPerPost", "max_comments_per_post", 5e3]
3485
+ ]) {
3486
+ const value = this._getParamValue(params, camel, snake);
3487
+ if (value !== void 0 && (!Number.isInteger(value) || value < 1 || value > maximum)) {
3488
+ throw new ValidationError(
3489
+ `${camel} must be an integer between 1 and ${maximum} for influencer_engagement`
3490
+ );
3491
+ }
3492
+ }
3493
+ for (const [camel, snake] of [
3494
+ ["includeReactedPosts", "include_reacted_posts"],
3495
+ ["postsEnableFiltering", "posts_enable_filtering"],
3496
+ ["thoroughEnrichment", "thorough_enrichment"],
3497
+ ["deepValidationUseRelevanceReranker", "deep_validation_use_relevance_reranker"]
3498
+ ]) {
3499
+ const value = this._getParamValue(params, camel, snake);
3500
+ if (value !== void 0 && typeof value !== "boolean")
3501
+ throw new ValidationError(`${camel} must be a boolean for influencer_engagement`);
3502
+ }
3503
+ const excludeUrls = this._getParamValue(params, "excludeUrls", "exclude_urls");
3504
+ if (excludeUrls !== void 0 && (!Array.isArray(excludeUrls) || excludeUrls.some((url) => typeof url !== "string"))) {
3505
+ throw new ValidationError("excludeUrls must be an array of strings for influencer_engagement");
3506
+ }
3507
+ }
3438
3508
  _validateSinceDays(value, agent) {
3439
3509
  if (value === void 0)
3440
3510
  return;
@@ -3480,6 +3550,60 @@ class ResponsesResource {
3480
3550
  );
3481
3551
  }
3482
3552
  }
3553
+ _validateSalesNavigatorRequest(request) {
3554
+ const directParams = request.specializedAgentParams;
3555
+ const directUrl = directParams ? this._getParamValue(directParams, "salesNavigatorUrl", "sales_navigator_url") : void 0;
3556
+ const requestOptions = request.options;
3557
+ const nestedParams = requestOptions ? this._getParamValue(
3558
+ requestOptions,
3559
+ "specializedAgentParams",
3560
+ "specialized_agent_params"
3561
+ ) : void 0;
3562
+ const nestedUrl = this._isPlainObject(nestedParams) ? this._getParamValue(nestedParams, "salesNavigatorUrl", "sales_navigator_url") : void 0;
3563
+ const optionsUrl = requestOptions ? this._getParamValue(requestOptions, "salesNavigatorUrl", "sales_navigator_url") : void 0;
3564
+ const salesNavigatorUrl = directUrl ?? nestedUrl ?? optionsUrl;
3565
+ if (salesNavigatorUrl === void 0 || salesNavigatorUrl === null)
3566
+ return;
3567
+ if (typeof salesNavigatorUrl !== "string" || salesNavigatorUrl.length > 8192 || !salesNavigatorUrl.trim()) {
3568
+ throw new ValidationError(
3569
+ "salesNavigatorUrl must be a non-empty string no longer than 8192 characters"
3570
+ );
3571
+ }
3572
+ if ([...salesNavigatorUrl].some((character) => character.charCodeAt(0) < 32) || salesNavigatorUrl.includes("\\")) {
3573
+ throw new ValidationError("salesNavigatorUrl contains unsafe characters");
3574
+ }
3575
+ let parsed;
3576
+ try {
3577
+ parsed = new URL(salesNavigatorUrl);
3578
+ } catch {
3579
+ throw new ValidationError("salesNavigatorUrl is malformed");
3580
+ }
3581
+ const authorityMatch = salesNavigatorUrl.match(/^[a-z][a-z\d+.-]*:\/\/([^/?#]*)/i);
3582
+ const rawAuthority = authorityMatch?.[1];
3583
+ if (parsed.protocol !== "https:")
3584
+ throw new ValidationError("salesNavigatorUrl must use https");
3585
+ if (parsed.hostname !== "www.linkedin.com")
3586
+ throw new ValidationError("salesNavigatorUrl must use www.linkedin.com");
3587
+ if (parsed.username || parsed.password || parsed.port || rawAuthority?.toLowerCase() !== "www.linkedin.com") {
3588
+ throw new ValidationError("salesNavigatorUrl cannot contain credentials or a custom port");
3589
+ }
3590
+ if (parsed.hash)
3591
+ throw new ValidationError("salesNavigatorUrl cannot contain a fragment");
3592
+ const rawSuffix = authorityMatch ? salesNavigatorUrl.slice(authorityMatch[0].length) : "";
3593
+ const rawPath = rawSuffix.split(/[?#]/, 1)[0];
3594
+ if (rawPath === "/sales/search/company") {
3595
+ throw new ValidationError(
3596
+ "Company Sales Navigator searches are not supported yet; use a people search or people list URL"
3597
+ );
3598
+ }
3599
+ if (rawPath !== "/sales/search/people" && !/^\/sales\/lists\/people\/\d+$/.test(rawPath)) {
3600
+ throw new ValidationError(
3601
+ "salesNavigatorUrl must be a Sales Navigator people-search or people-list URL"
3602
+ );
3603
+ }
3604
+ if (typeof request.userId !== "string" || !request.userId.trim())
3605
+ throw new ValidationError("userId is required when salesNavigatorUrl is provided");
3606
+ }
3483
3607
  _validateCriteriaParams(params, specializedAgent) {
3484
3608
  if (!params)
3485
3609
  return;
@@ -3595,6 +3719,8 @@ class ResponsesResource {
3595
3719
  this._validateContentIntelligenceParams(rawParams);
3596
3720
  if (specializedAgent === "engagement_expansion")
3597
3721
  this._validateEngagementExpansionParams(rawParams);
3722
+ if (specializedAgent === "influencer_engagement")
3723
+ this._validateInfluencerEngagementParams(rawParams);
3598
3724
  if (specializedAgent === "company_intelligence")
3599
3725
  this._validateCompanyIntelligenceParams(rawParams);
3600
3726
  if (specializedAgent === "person_intelligence")
@@ -3640,6 +3766,7 @@ class ResponsesResource {
3640
3766
  for (const file of request.files)
3641
3767
  this._validateFileReference(file.uri);
3642
3768
  }
3769
+ this._validateSalesNavigatorRequest(request);
3643
3770
  if (request.specializedAgentParams)
3644
3771
  this._validateCriteriaParams(request.specializedAgentParams, request.specializedAgent);
3645
3772
  return this.http.post("/responses", request);
@@ -3775,10 +3902,13 @@ class ResponsesResource {
3775
3902
  * @param options.excludeCrmContacts - Exclude people in the acting user's synced CRM ledger; @default true
3776
3903
  * @param options.crmExclusionOwners - Granted owner ledgers to exclude against (user id or email)
3777
3904
  * @param options.crmNameCompanyMatch - Also exclude by exact name+company; @default true
3905
+ * @param options.salesNavigatorUrl - Sales Navigator people-search or people-list URL to use as the only discovery source
3906
+ * @param options.userId - Acting user whose owned Sales Navigator connection should be used; required with `salesNavigatorUrl`
3778
3907
  * @param options.searchJobSignal - CrustData job-listing signal search (decision makers at hiring companies); true | false | 'auto'
3779
3908
  * @param options.deepVerify - Web verification for org/location/third-party criteria: 'auto' (default), 'always', or 'off'
3780
3909
  * @param options.deepValidationUseRelevanceReranker - SLM relevance reranker for surfaced candidates (ranking-only); @default true
3781
3910
  * @param options.deepValidationBackfillBelowCriteria - Pad with criteria-failed candidates when under count; @default true
3911
+ * @param options.enrichEngagementHistory - Add recent LinkedIn engagement evidence before validation; forced off for Sales Navigator V1
3782
3912
  * @param options.deepSearchCriteriaModel - Override criteria decomposition model (e.g. 'openai:gpt-5.4')
3783
3913
  * @returns Response with structured_response containing:
3784
3914
  * - candidates: Validated and scored candidates
@@ -3790,6 +3920,8 @@ class ResponsesResource {
3790
3920
  messages: [{ role: "user", content: query }],
3791
3921
  specializedAgent: "deep_people_search"
3792
3922
  };
3923
+ if (options?.userId !== void 0)
3924
+ request.userId = options.userId;
3793
3925
  const params = {
3794
3926
  deepValidationUseRelevanceReranker: options?.deepValidationUseRelevanceReranker ?? true,
3795
3927
  deepValidationBackfillBelowCriteria: options?.deepValidationBackfillBelowCriteria ?? true
@@ -3823,6 +3955,8 @@ class ResponsesResource {
3823
3955
  params.crmExclusionOwners = options.crmExclusionOwners;
3824
3956
  if (options.crmNameCompanyMatch !== void 0)
3825
3957
  params.crmNameCompanyMatch = options.crmNameCompanyMatch;
3958
+ if (options.salesNavigatorUrl !== void 0)
3959
+ params.salesNavigatorUrl = options.salesNavigatorUrl;
3826
3960
  if (options.searchProfiles !== void 0)
3827
3961
  params.searchProfiles = options.searchProfiles;
3828
3962
  if (options.searchPosts !== void 0)
@@ -3857,8 +3991,22 @@ class ResponsesResource {
3857
3991
  params.searchJobSignal = options.searchJobSignal;
3858
3992
  if (options.deepVerify !== void 0)
3859
3993
  params.deepVerify = options.deepVerify;
3994
+ if (options.enrichEngagementHistory !== void 0)
3995
+ params.enrichEngagementHistory = options.enrichEngagementHistory;
3860
3996
  if (options.deepSearchCriteriaModel)
3861
3997
  params.deepSearchCriteriaModel = options.deepSearchCriteriaModel;
3998
+ if (options.salesNavigatorUrl !== void 0) {
3999
+ params.searchProfiles = false;
4000
+ params.searchPosts = false;
4001
+ params.searchConnections = false;
4002
+ params.searchJobSignal = false;
4003
+ params.includeEngagementInScore = false;
4004
+ params.postsEnableEnrichment = false;
4005
+ params.postsEnableFiltering = false;
4006
+ params.deepValidationUseRelevanceReranker = false;
4007
+ params.deepValidationBackfillBelowCriteria = false;
4008
+ params.enrichEngagementHistory = false;
4009
+ }
3862
4010
  }
3863
4011
  request.specializedAgentParams = params;
3864
4012
  return this.create(request);
@@ -3995,6 +4143,52 @@ class ResponsesResource {
3995
4143
  specializedAgentParams: params
3996
4144
  });
3997
4145
  }
4146
+ /**
4147
+ * Score people who react to or comment on posts connected to named LinkedIn profiles.
4148
+ *
4149
+ * The backend reads posts each seed authored within the requested window and can
4150
+ * optionally include posts they reacted to. It selects posts relevant to the
4151
+ * persona prompt, then runs their engagers through the deep people-search scoring
4152
+ * chain. Seed profiles are always excluded from delivered candidates.
4153
+ *
4154
+ * @param query - Persona prompt used to select posts and score discovered people.
4155
+ * @param options - Seed profiles plus optional window, extraction, and cost controls.
4156
+ * @returns Response; poll with `get()` and read `structuredResponse` as
4157
+ * {@link InfluencerEngagementOutput}.
4158
+ */
4159
+ async influencerEngagement(query, options) {
4160
+ if (!query.trim())
4161
+ throw new ValidationError("influencerEngagement requires a non-empty persona query");
4162
+ const params = {
4163
+ seedProfiles: options.seedProfiles
4164
+ };
4165
+ if (options.includeReactedPosts !== void 0)
4166
+ params.includeReactedPosts = options.includeReactedPosts;
4167
+ if (options.postsDateRange !== void 0)
4168
+ params.postsDateRange = options.postsDateRange;
4169
+ if (options.engagementTypes !== void 0)
4170
+ params.engagementTypes = options.engagementTypes;
4171
+ if (options.postsEnableFiltering !== void 0)
4172
+ params.postsEnableFiltering = options.postsEnableFiltering;
4173
+ if (options.thoroughEnrichment !== void 0)
4174
+ params.thoroughEnrichment = options.thoroughEnrichment;
4175
+ if (options.deepValidationUseRelevanceReranker !== void 0) {
4176
+ params.deepValidationUseRelevanceReranker = options.deepValidationUseRelevanceReranker;
4177
+ }
4178
+ if (options.maxReactorsPerPost !== void 0)
4179
+ params.maxReactorsPerPost = options.maxReactorsPerPost;
4180
+ if (options.maxCommentsPerPost !== void 0)
4181
+ params.maxCommentsPerPost = options.maxCommentsPerPost;
4182
+ if (options.excludeUrls !== void 0)
4183
+ params.excludeUrls = options.excludeUrls;
4184
+ if (options.limit !== void 0)
4185
+ params.limit = options.limit;
4186
+ return this.create({
4187
+ messages: [{ role: "user", content: query }],
4188
+ specializedAgent: "influencer_engagement",
4189
+ specializedAgentParams: params
4190
+ });
4191
+ }
3998
4192
  /**
3999
4193
  * Score people who reacted to or commented on competitor LinkedIn posts.
4000
4194
  *