@the_ro_show/agent-ads-sdk 0.4.3 → 0.6.0

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.js CHANGED
@@ -261,11 +261,13 @@ function createClickEvent(params) {
261
261
  if (params.metadata !== void 0) {
262
262
  event.metadata = {
263
263
  ...params.metadata,
264
- href: params.href
264
+ href: params.href,
265
+ click_context: params.click_context
265
266
  };
266
267
  } else {
267
268
  event.metadata = {
268
- href: params.href
269
+ href: params.href,
270
+ click_context: params.click_context
269
271
  };
270
272
  }
271
273
  return event;
@@ -358,8 +360,10 @@ var DEFAULT_MAX_RETRIES = 2;
358
360
  var AttentionMarketClient = class {
359
361
  http;
360
362
  agentId;
363
+ appId;
361
364
  constructor(config) {
362
365
  this.agentId = config.agentId;
366
+ this.appId = config.appId;
363
367
  this.validateConfig(config);
364
368
  const httpConfig = {
365
369
  apiKey: config.apiKey,
@@ -517,6 +521,332 @@ var AttentionMarketClient = class {
517
521
  body: request
518
522
  });
519
523
  }
524
+ // ============================================================================
525
+ // Intenture Network APIs (Intent-Key Based Matching)
526
+ // ============================================================================
527
+ /**
528
+ * Validate intent-key format.
529
+ * Format: vertical.category[.subcategory][.intent]
530
+ * Examples: "coffee", "coffee.purchase", "legal.estate_planning.wills"
531
+ */
532
+ validateIntentKey(intentKey) {
533
+ if (!intentKey || intentKey.trim().length === 0) {
534
+ throw new Error("intentKey cannot be empty");
535
+ }
536
+ const validPattern = /^[a-z0-9_]+(\.[a-z0-9_]+)*$/;
537
+ if (!validPattern.test(intentKey)) {
538
+ throw new Error(
539
+ `Invalid intentKey format: "${intentKey}". Use lowercase letters, numbers, underscores, and dots only. Example: "coffee.purchase.delivery"`
540
+ );
541
+ }
542
+ const segments = intentKey.split(".");
543
+ if (segments.length === 0) {
544
+ throw new Error("intentKey must have at least one segment");
545
+ }
546
+ }
547
+ /**
548
+ * Validate placement ID is not empty
549
+ */
550
+ validatePlacementId(placementId) {
551
+ if (!placementId || placementId.trim().length === 0) {
552
+ throw new Error("placementId cannot be empty");
553
+ }
554
+ }
555
+ /**
556
+ * Validate revenue share percentage (0-50)
557
+ */
558
+ validateRevenueShare(pct) {
559
+ if (pct !== void 0) {
560
+ if (typeof pct !== "number" || isNaN(pct)) {
561
+ throw new Error("revenueSharePct must be a number");
562
+ }
563
+ if (pct < 0 || pct > 50) {
564
+ throw new Error(
565
+ `revenueSharePct must be between 0 and 50, got ${pct}. Revenue share above 50% is not supported.`
566
+ );
567
+ }
568
+ }
569
+ }
570
+ /**
571
+ * Normalize and validate locale string
572
+ */
573
+ normalizeLocale(locale) {
574
+ if (!locale || locale.trim().length === 0) {
575
+ return "en";
576
+ }
577
+ const normalized = locale.trim();
578
+ const languageCode = normalized.split("-")[0]?.toLowerCase();
579
+ if (!languageCode || languageCode.length < 2) {
580
+ return "en";
581
+ }
582
+ return languageCode;
583
+ }
584
+ /**
585
+ * Request an offer using explicit intent-key matching.
586
+ *
587
+ * Use this API when you have HIGH CONFIDENCE about user intent.
588
+ * Intent-keys enable deterministic matching and agent-to-agent coordination.
589
+ *
590
+ * **Current Limitations:**
591
+ * - Backend uses semantic matching (intentKey mapped to taxonomy field)
592
+ * - `campaign_id` not yet available from backend (returns `unit_id` as placeholder)
593
+ * - `click_url` equals `direct_url` (click redirector not implemented)
594
+ * - Revenue share tracked but payouts not active (preview mode)
595
+ *
596
+ * @example
597
+ * ```typescript
598
+ * // User explicitly said "order coffee for delivery"
599
+ * const offer = await client.requestOffer({
600
+ * placementId: 'order_card',
601
+ * intentKey: 'coffee.purchase.delivery',
602
+ * context: { geo: { city: 'SF', country: 'US' } }
603
+ * });
604
+ *
605
+ * if (offer) {
606
+ * // Use tracked click URL for attribution
607
+ * window.open(offer.click_url);
608
+ * }
609
+ * ```
610
+ *
611
+ * @throws {Error} If agentId or appId not provided in SDKConfig
612
+ * @throws {Error} If intentKey format is invalid
613
+ * @throws {Error} If revenueSharePct out of range (0-50)
614
+ */
615
+ async requestOffer(params, options) {
616
+ if (!this.agentId) {
617
+ throw new Error(
618
+ 'requestOffer() requires agentId in SDKConfig. Provide agentId in constructor: new AttentionMarketClient({ agentId: "agt_123", ... })'
619
+ );
620
+ }
621
+ if (!this.appId) {
622
+ throw new Error(
623
+ 'requestOffer() requires appId in SDKConfig. Provide appId in constructor: new AttentionMarketClient({ appId: "app_456", ... })'
624
+ );
625
+ }
626
+ this.validateIntentKey(params.intentKey);
627
+ this.validatePlacementId(params.placementId);
628
+ this.validateRevenueShare(params.revenueSharePct);
629
+ const semanticContext = params.context?.semanticContext;
630
+ const country = params.context?.geo?.country || "US";
631
+ const language = this.normalizeLocale(params.context?.locale);
632
+ if (params.sourceAgentId && console.warn) {
633
+ console.warn(
634
+ "Revenue share is in PREVIEW mode. sourceAgentId and revenueSharePct are logged for analytics but payouts are not active yet. Expected: Q2 2026"
635
+ );
636
+ }
637
+ const idempotencyKey = options?.idempotencyKey || generateUUID();
638
+ const request = {
639
+ request_id: idempotencyKey,
640
+ agent_id: this.agentId,
641
+ placement: {
642
+ type: "sponsored_suggestion",
643
+ // Default placement type
644
+ surface: params.placementId
645
+ },
646
+ opportunity: {
647
+ intent: {
648
+ taxonomy: params.intentKey,
649
+ // TEMPORARY: Maps to taxonomy until intent_key field added
650
+ query: semanticContext || params.intentKey
651
+ },
652
+ context: {
653
+ country,
654
+ language,
655
+ platform: "web",
656
+ ...params.context?.geo?.region ? { region: params.context.geo.region } : {},
657
+ ...params.context?.geo?.city ? { city: params.context.geo.city } : {}
658
+ },
659
+ constraints: {
660
+ max_units: 1,
661
+ allowed_unit_types: ["sponsored_suggestion"]
662
+ },
663
+ privacy: {
664
+ data_policy: "coarse_only"
665
+ }
666
+ },
667
+ // Add semantic context if provided (for fallback matching)
668
+ ...semanticContext ? { context: semanticContext } : {},
669
+ user_intent: params.intentKey
670
+ };
671
+ const response = await this.decideRaw(request, { idempotencyKey });
672
+ if (response.status === "no_fill" || response.units.length === 0) {
673
+ return null;
674
+ }
675
+ const adUnit = response.units[0];
676
+ if (!adUnit || adUnit.unit_type !== "sponsored_suggestion") {
677
+ return null;
678
+ }
679
+ const impressionId = generateUUID();
680
+ const matchMethod = semanticContext ? "hybrid" : "semantic";
681
+ return {
682
+ offer_id: adUnit.unit_id,
683
+ request_id: response.request_id,
684
+ impression_id: impressionId,
685
+ // LIMITATION: Backend doesn't return campaign_id yet - use unit_id as placeholder
686
+ campaign_id: adUnit.unit_id,
687
+ creative: {
688
+ title: adUnit.suggestion.title,
689
+ body: adUnit.suggestion.body,
690
+ cta: adUnit.suggestion.cta
691
+ },
692
+ // LIMITATION: Click redirector not implemented yet
693
+ // Both URLs are identical until /c/{token} endpoint is deployed
694
+ click_url: adUnit.suggestion.action_url,
695
+ direct_url: adUnit.suggestion.action_url,
696
+ disclosure: {
697
+ label: adUnit.disclosure.label,
698
+ sponsor_name: adUnit.disclosure.sponsor_name
699
+ },
700
+ tracking_token: adUnit.tracking.token,
701
+ match_info: {
702
+ match_method: matchMethod,
703
+ // Accurately reports semantic or hybrid
704
+ matched_intent_key: params.intentKey
705
+ },
706
+ // Revenue share: Logged for analytics but payouts not active
707
+ ...params.sourceAgentId ? {
708
+ revenue_share: {
709
+ status: "preview",
710
+ source_agent_id: params.sourceAgentId,
711
+ ...params.revenueSharePct !== void 0 ? { source_agent_pct: params.revenueSharePct } : {}
712
+ }
713
+ } : {},
714
+ ttl_ms: response.ttl_ms
715
+ };
716
+ }
717
+ /**
718
+ * Request an offer using semantic context matching.
719
+ *
720
+ * This is the fuzzy, discovery API for when you're NOT certain what
721
+ * the user wants. Pass conversation context and let semantic search
722
+ * figure out the best match.
723
+ *
724
+ * **Current Limitations:**
725
+ * - `campaign_id` not yet available from backend (returns `unit_id` as placeholder)
726
+ * - `click_url` equals `direct_url` (click redirector not implemented)
727
+ * - Revenue share tracked but payouts not active (preview mode)
728
+ * - Conversation history auto-limited to last 5 messages
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * const offer = await client.requestOfferFromContext({
733
+ * placementId: 'chat_suggestion',
734
+ * userMessage: "I'm so tired, long day at work...",
735
+ * conversationHistory: ["How was your day?", "Exhausting"],
736
+ * context: { geo: { city: 'NYC' } }
737
+ * });
738
+ *
739
+ * if (offer) {
740
+ * console.log(`Maybe you'd like: ${offer.creative.title}`);
741
+ * }
742
+ * ```
743
+ *
744
+ * @throws {Error} If agentId was not provided in SDKConfig
745
+ * @throws {Error} If revenueSharePct out of range (0-50)
746
+ */
747
+ async requestOfferFromContext(params, options) {
748
+ if (!this.agentId) {
749
+ throw new Error(
750
+ 'requestOfferFromContext() requires agentId in SDKConfig. Provide agentId in constructor: new AttentionMarketClient({ agentId: "agt_123", ... })'
751
+ );
752
+ }
753
+ if (!this.appId) {
754
+ throw new Error(
755
+ 'requestOfferFromContext() requires appId in SDKConfig. Provide appId in constructor: new AttentionMarketClient({ appId: "app_456", ... })'
756
+ );
757
+ }
758
+ this.validatePlacementId(params.placementId);
759
+ this.validateRevenueShare(params.revenueSharePct);
760
+ if (params.sourceAgentId && console.warn) {
761
+ console.warn(
762
+ "Revenue share is in PREVIEW mode. sourceAgentId and revenueSharePct are logged for analytics but payouts are not active yet. Expected: Q2 2026"
763
+ );
764
+ }
765
+ const historyLimit = 5;
766
+ const history = params.conversationHistory || [];
767
+ const limitedHistory = history.slice(-historyLimit);
768
+ const contextParts = [...limitedHistory, params.userMessage];
769
+ const context = contextParts.join("\n");
770
+ const country = params.context?.geo?.country || "US";
771
+ const language = this.normalizeLocale(params.context?.locale);
772
+ const taxonomy = params.suggestedCategory || "unknown";
773
+ const idempotencyKey = options?.idempotencyKey || generateUUID();
774
+ const request = {
775
+ request_id: idempotencyKey,
776
+ agent_id: this.agentId,
777
+ placement: {
778
+ type: "sponsored_suggestion",
779
+ surface: params.placementId
780
+ },
781
+ opportunity: {
782
+ intent: {
783
+ taxonomy,
784
+ query: params.userMessage
785
+ },
786
+ context: {
787
+ country,
788
+ language,
789
+ platform: "web",
790
+ ...params.context?.geo?.region ? { region: params.context.geo.region } : {},
791
+ ...params.context?.geo?.city ? { city: params.context.geo.city } : {}
792
+ },
793
+ constraints: {
794
+ max_units: 1,
795
+ allowed_unit_types: ["sponsored_suggestion"]
796
+ },
797
+ privacy: {
798
+ data_policy: "coarse_only"
799
+ }
800
+ },
801
+ ...context ? { context } : {},
802
+ user_intent: params.userMessage
803
+ };
804
+ const response = await this.decideRaw(request, { idempotencyKey });
805
+ if (response.status === "no_fill" || response.units.length === 0) {
806
+ return null;
807
+ }
808
+ const adUnit = response.units[0];
809
+ if (!adUnit || adUnit.unit_type !== "sponsored_suggestion") {
810
+ return null;
811
+ }
812
+ const impressionId = generateUUID();
813
+ const similarity = adUnit._score?.relevance;
814
+ return {
815
+ offer_id: adUnit.unit_id,
816
+ request_id: response.request_id,
817
+ impression_id: impressionId,
818
+ // LIMITATION: Backend doesn't return campaign_id yet - use unit_id as placeholder
819
+ campaign_id: adUnit.unit_id,
820
+ creative: {
821
+ title: adUnit.suggestion.title,
822
+ body: adUnit.suggestion.body,
823
+ cta: adUnit.suggestion.cta
824
+ },
825
+ // LIMITATION: Click redirector not implemented yet
826
+ // Both URLs are identical until /c/{token} endpoint is deployed
827
+ click_url: adUnit.suggestion.action_url,
828
+ direct_url: adUnit.suggestion.action_url,
829
+ disclosure: {
830
+ label: adUnit.disclosure.label,
831
+ sponsor_name: adUnit.disclosure.sponsor_name
832
+ },
833
+ tracking_token: adUnit.tracking.token,
834
+ match_info: {
835
+ match_method: "semantic",
836
+ // Always semantic for this API
837
+ ...similarity !== void 0 ? { similarity } : {}
838
+ },
839
+ // Revenue share: Logged for analytics but payouts not active
840
+ ...params.sourceAgentId ? {
841
+ revenue_share: {
842
+ status: "preview",
843
+ source_agent_id: params.sourceAgentId,
844
+ ...params.revenueSharePct !== void 0 ? { source_agent_pct: params.revenueSharePct } : {}
845
+ }
846
+ } : {},
847
+ ttl_ms: response.ttl_ms
848
+ };
849
+ }
520
850
  };
521
851
 
522
852
  // src/mock-client.ts