@the_ro_show/agent-ads-sdk 0.4.1 โ†’ 0.4.2

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
@@ -1,6 +1,15 @@
1
1
  # AttentionMarket Agent Ads SDK
2
2
 
3
- TypeScript SDK for monetizing AI agents with relevant sponsored content. Earn $5-50 per click.
3
+ [![npm version](https://badge.fury.io/js/@the_ro_show%2Fagent-ads-sdk.svg)](https://www.npmjs.com/package/@the_ro_show/agent-ads-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ **The first ad network built for AI agents.** Monetize your AI agent with contextual, high-intent sponsored suggestions. Open source, transparent, developer-first.
8
+
9
+ - ๐Ÿš€ **5-minute integration** - npm install + 5 lines of code
10
+ - ๐Ÿ’ฐ **70% revenue share** - You keep the majority, we only win when you do
11
+ - ๐ŸŽฏ **10-15% CTR** - High-intent placements, not banner ads
12
+ - ๐Ÿ”“ **100% Open Source** - Audit every line, full transparency
4
13
 
5
14
  ## Quick Start
6
15
 
@@ -7,26 +7,19 @@ Your bot answers user questions. Sometimes those answers could include a helpful
7
7
 
8
8
  ## Step-by-Step Guide
9
9
 
10
- ### **Step 1: Sign Up for API Keys** (One-time, 2 minutes)
10
+ ### **Step 1: Sign Up for API Keys** (One-time, 30 seconds)
11
11
 
12
12
  You need permission to request ads. Think of this like getting a key to a vending machine.
13
13
 
14
14
  **How to do it:**
15
- ```bash
16
- # Run this command once (it's like filling out a registration form)
17
- curl -X POST https://api.attentionmarket.ai/v1/agent-signup \
18
- -H 'Content-Type: application/json' \
19
- -d '{
20
- "owner_email": "your-email@example.com",
21
- "agent_name": "My Cool Bot",
22
- "sdk_type": "typescript"
23
- }'
24
- ```
15
+ 1. Go to **[attentionmarket.com/signup](https://attentionmarket.com/signup)**
16
+ 2. Enter your email and agent name
17
+ 3. Click "Generate API Keys"
25
18
 
26
19
  **You'll get back:**
27
- - An **Agent ID** (like `agt_abc123`) - your username
28
20
  - A **Test Key** (like `am_test_xyz789`) - for testing
29
21
  - A **Live Key** (like `am_live_xyz789`) - for when you go live
22
+ - An **Agent ID** (you'll use this for tracking)
30
23
 
31
24
  **Write these down!** You'll need them.
32
25
 
@@ -96,7 +89,7 @@ async function handleUserMessage(userMessage) {
96
89
  surface: 'chat'
97
90
  },
98
91
  opportunity: createOpportunity({
99
- taxonomy: 'shopping.ecommerce.platform', // This matches Pietra!
92
+ taxonomy: 'business.ecommerce.platform.trial', // E-commerce platforms
100
93
  country: 'US',
101
94
  language: 'en',
102
95
  platform: 'web',
@@ -204,8 +197,10 @@ await adClient.trackClick({
204
197
 
205
198
  ### **Taxonomy** = Topic Category
206
199
  When you request an ad, you tell us what the user is asking about:
207
- - `shopping.ecommerce.platform` = Online store questions
208
- - `local_services.movers.quote` = Moving company questions
200
+ - `business.ecommerce.platform.trial` = Online store questions
201
+ - `home_services.moving.local.quote` = Moving company questions
202
+ - `insurance.auto.full_coverage.quote` = Car insurance questions
203
+ - `legal.family.divorce.consultation` = Divorce lawyer questions
209
204
  - `business.productivity.tools` = Productivity software questions
210
205
 
211
206
  **You pick the taxonomy** based on what the user asked.
@@ -255,7 +250,7 @@ async function handleMessage(userMessage) {
255
250
  agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
256
251
  placement: { type: 'sponsored_suggestion', surface: 'chat' },
257
252
  opportunity: createOpportunity({
258
- taxonomy: 'shopping.ecommerce.platform',
253
+ taxonomy: 'business.ecommerce.platform.trial',
259
254
  country: 'US',
260
255
  language: 'en',
261
256
  platform: 'web',
package/dist/index.d.mts CHANGED
@@ -21,6 +21,48 @@ interface DecideRequest {
21
21
  agent_id: string;
22
22
  placement: Placement;
23
23
  opportunity: Opportunity;
24
+ /** Full conversation context for semantic matching (optional) */
25
+ context?: string;
26
+ /** Detected or inferred user intent for semantic matching (optional) */
27
+ user_intent?: string;
28
+ }
29
+ /**
30
+ * Simplified request for semantic context-based ad matching.
31
+ * Uses conversation context instead of manual taxonomy selection.
32
+ *
33
+ * The SDK automatically limits conversationHistory to the last 5 messages
34
+ * to avoid token overflow. Only userMessage is required.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const ad = await client.decideFromContext({
39
+ * userMessage: "I need help with estate planning",
40
+ * conversationHistory: ["User: My father passed away recently"],
41
+ * placement: 'sponsored_suggestion'
42
+ * });
43
+ * ```
44
+ */
45
+ interface DecideFromContextRequest {
46
+ /** The user's current message (required) */
47
+ userMessage: string;
48
+ /**
49
+ * Optional conversation history (last few messages for context).
50
+ * SDK automatically limits to last 5 messages to avoid token overflow.
51
+ */
52
+ conversationHistory?: string[];
53
+ /** Ad placement type. Default: 'sponsored_suggestion' */
54
+ placement?: PlacementType;
55
+ /**
56
+ * Optional category hint (e.g., 'legal', 'insurance', 'travel').
57
+ * Used as fallback if semantic matching fails.
58
+ */
59
+ suggestedCategory?: string;
60
+ /** User's country code. Default: 'US' */
61
+ country?: string;
62
+ /** User's language code. Default: 'en' */
63
+ language?: string;
64
+ /** User's platform. Default: 'web' */
65
+ platform?: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';
24
66
  }
25
67
  interface DecideResponse {
26
68
  request_id: string;
@@ -154,6 +196,7 @@ interface APIError {
154
196
  }
155
197
  interface SDKConfig {
156
198
  apiKey: string;
199
+ agentId?: string;
157
200
  supabaseAnonKey?: string;
158
201
  baseUrl?: string;
159
202
  timeoutMs?: number;
@@ -313,6 +356,7 @@ declare function sanitizeURL(url: string | null | undefined, options?: SanitizeU
313
356
 
314
357
  declare class AttentionMarketClient {
315
358
  private http;
359
+ private agentId;
316
360
  constructor(config: SDKConfig);
317
361
  /**
318
362
  * Validate SDK configuration for security
@@ -332,6 +376,23 @@ declare class AttentionMarketClient {
332
376
  decide(request: DecideRequest, options?: {
333
377
  idempotencyKey?: string;
334
378
  }): Promise<AdUnit | null>;
379
+ /**
380
+ * Simplified ad matching using conversation context and semantic search.
381
+ * Automatically handles request construction, taxonomy fallback, and defaults.
382
+ *
383
+ * Requires: agentId in SDKConfig constructor
384
+ *
385
+ * @example
386
+ * const ad = await client.decideFromContext({
387
+ * userMessage: "My father passed away and I need help organizing his estate",
388
+ * placement: 'sponsored_suggestion'
389
+ * });
390
+ *
391
+ * @throws {Error} If agentId was not provided in SDKConfig
392
+ */
393
+ decideFromContext(params: DecideFromContextRequest, options?: {
394
+ idempotencyKey?: string;
395
+ }): Promise<AdUnit | null>;
335
396
  /**
336
397
  * Report an event (impression, click, action, conversion, feedback).
337
398
  */
@@ -644,4 +705,4 @@ declare function getVertical(taxonomy: string): string | null;
644
705
  */
645
706
  declare function suggestTaxonomies(query: string): string[];
646
707
 
647
- export { type APIError, APIRequestError, type AdScore, type AdUnit, type AgentSignupRequest, type AgentSignupResponse, AttentionMarketClient, AttentionMarketError, type Constraints, type Context, type CreateClickEventParams, type CreateImpressionEventParams, type CreateOpportunityParams, type DecideRequest, type DecideResponse, type Disclosure, type EventIngestRequest, type EventIngestResponse, type EventType, type Intent, MockAttentionMarketClient, type MockClientConfig, NetworkError, type Opportunity, type ParsedTaxonomy, type Placement, type PlacementType, type PolicyResponse, type Privacy, type SDKConfig, type SanitizeURLOptions, type SponsoredSuggestion, type SponsoredTool, type TaxonomyIntent, TimeoutError, type ToolCall, type Tracking, buildTaxonomy, createClickEvent, createImpressionEvent, createOpportunity, detectIntent, escapeHTML, generateTimestamp, generateUUID, getBaseTaxonomy, getVertical, isValidTaxonomy, matchesTaxonomy, parseTaxonomy, sanitizeURL, suggestTaxonomies };
708
+ export { type APIError, APIRequestError, type AdScore, type AdUnit, type AgentSignupRequest, type AgentSignupResponse, AttentionMarketClient, AttentionMarketError, type Constraints, type Context, type CreateClickEventParams, type CreateImpressionEventParams, type CreateOpportunityParams, type DecideFromContextRequest, type DecideRequest, type DecideResponse, type Disclosure, type EventIngestRequest, type EventIngestResponse, type EventType, type Intent, MockAttentionMarketClient, type MockClientConfig, NetworkError, type Opportunity, type ParsedTaxonomy, type Placement, type PlacementType, type PolicyResponse, type Privacy, type SDKConfig, type SanitizeURLOptions, type SponsoredSuggestion, type SponsoredTool, type TaxonomyIntent, TimeoutError, type ToolCall, type Tracking, buildTaxonomy, createClickEvent, createImpressionEvent, createOpportunity, detectIntent, escapeHTML, generateTimestamp, generateUUID, getBaseTaxonomy, getVertical, isValidTaxonomy, matchesTaxonomy, parseTaxonomy, sanitizeURL, suggestTaxonomies };
package/dist/index.d.ts CHANGED
@@ -21,6 +21,48 @@ interface DecideRequest {
21
21
  agent_id: string;
22
22
  placement: Placement;
23
23
  opportunity: Opportunity;
24
+ /** Full conversation context for semantic matching (optional) */
25
+ context?: string;
26
+ /** Detected or inferred user intent for semantic matching (optional) */
27
+ user_intent?: string;
28
+ }
29
+ /**
30
+ * Simplified request for semantic context-based ad matching.
31
+ * Uses conversation context instead of manual taxonomy selection.
32
+ *
33
+ * The SDK automatically limits conversationHistory to the last 5 messages
34
+ * to avoid token overflow. Only userMessage is required.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const ad = await client.decideFromContext({
39
+ * userMessage: "I need help with estate planning",
40
+ * conversationHistory: ["User: My father passed away recently"],
41
+ * placement: 'sponsored_suggestion'
42
+ * });
43
+ * ```
44
+ */
45
+ interface DecideFromContextRequest {
46
+ /** The user's current message (required) */
47
+ userMessage: string;
48
+ /**
49
+ * Optional conversation history (last few messages for context).
50
+ * SDK automatically limits to last 5 messages to avoid token overflow.
51
+ */
52
+ conversationHistory?: string[];
53
+ /** Ad placement type. Default: 'sponsored_suggestion' */
54
+ placement?: PlacementType;
55
+ /**
56
+ * Optional category hint (e.g., 'legal', 'insurance', 'travel').
57
+ * Used as fallback if semantic matching fails.
58
+ */
59
+ suggestedCategory?: string;
60
+ /** User's country code. Default: 'US' */
61
+ country?: string;
62
+ /** User's language code. Default: 'en' */
63
+ language?: string;
64
+ /** User's platform. Default: 'web' */
65
+ platform?: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';
24
66
  }
25
67
  interface DecideResponse {
26
68
  request_id: string;
@@ -154,6 +196,7 @@ interface APIError {
154
196
  }
155
197
  interface SDKConfig {
156
198
  apiKey: string;
199
+ agentId?: string;
157
200
  supabaseAnonKey?: string;
158
201
  baseUrl?: string;
159
202
  timeoutMs?: number;
@@ -313,6 +356,7 @@ declare function sanitizeURL(url: string | null | undefined, options?: SanitizeU
313
356
 
314
357
  declare class AttentionMarketClient {
315
358
  private http;
359
+ private agentId;
316
360
  constructor(config: SDKConfig);
317
361
  /**
318
362
  * Validate SDK configuration for security
@@ -332,6 +376,23 @@ declare class AttentionMarketClient {
332
376
  decide(request: DecideRequest, options?: {
333
377
  idempotencyKey?: string;
334
378
  }): Promise<AdUnit | null>;
379
+ /**
380
+ * Simplified ad matching using conversation context and semantic search.
381
+ * Automatically handles request construction, taxonomy fallback, and defaults.
382
+ *
383
+ * Requires: agentId in SDKConfig constructor
384
+ *
385
+ * @example
386
+ * const ad = await client.decideFromContext({
387
+ * userMessage: "My father passed away and I need help organizing his estate",
388
+ * placement: 'sponsored_suggestion'
389
+ * });
390
+ *
391
+ * @throws {Error} If agentId was not provided in SDKConfig
392
+ */
393
+ decideFromContext(params: DecideFromContextRequest, options?: {
394
+ idempotencyKey?: string;
395
+ }): Promise<AdUnit | null>;
335
396
  /**
336
397
  * Report an event (impression, click, action, conversion, feedback).
337
398
  */
@@ -644,4 +705,4 @@ declare function getVertical(taxonomy: string): string | null;
644
705
  */
645
706
  declare function suggestTaxonomies(query: string): string[];
646
707
 
647
- export { type APIError, APIRequestError, type AdScore, type AdUnit, type AgentSignupRequest, type AgentSignupResponse, AttentionMarketClient, AttentionMarketError, type Constraints, type Context, type CreateClickEventParams, type CreateImpressionEventParams, type CreateOpportunityParams, type DecideRequest, type DecideResponse, type Disclosure, type EventIngestRequest, type EventIngestResponse, type EventType, type Intent, MockAttentionMarketClient, type MockClientConfig, NetworkError, type Opportunity, type ParsedTaxonomy, type Placement, type PlacementType, type PolicyResponse, type Privacy, type SDKConfig, type SanitizeURLOptions, type SponsoredSuggestion, type SponsoredTool, type TaxonomyIntent, TimeoutError, type ToolCall, type Tracking, buildTaxonomy, createClickEvent, createImpressionEvent, createOpportunity, detectIntent, escapeHTML, generateTimestamp, generateUUID, getBaseTaxonomy, getVertical, isValidTaxonomy, matchesTaxonomy, parseTaxonomy, sanitizeURL, suggestTaxonomies };
708
+ export { type APIError, APIRequestError, type AdScore, type AdUnit, type AgentSignupRequest, type AgentSignupResponse, AttentionMarketClient, AttentionMarketError, type Constraints, type Context, type CreateClickEventParams, type CreateImpressionEventParams, type CreateOpportunityParams, type DecideFromContextRequest, type DecideRequest, type DecideResponse, type Disclosure, type EventIngestRequest, type EventIngestResponse, type EventType, type Intent, MockAttentionMarketClient, type MockClientConfig, NetworkError, type Opportunity, type ParsedTaxonomy, type Placement, type PlacementType, type PolicyResponse, type Privacy, type SDKConfig, type SanitizeURLOptions, type SponsoredSuggestion, type SponsoredTool, type TaxonomyIntent, TimeoutError, type ToolCall, type Tracking, buildTaxonomy, createClickEvent, createImpressionEvent, createOpportunity, detectIntent, escapeHTML, generateTimestamp, generateUUID, getBaseTaxonomy, getVertical, isValidTaxonomy, matchesTaxonomy, parseTaxonomy, sanitizeURL, suggestTaxonomies };
package/dist/index.js CHANGED
@@ -354,7 +354,9 @@ var DEFAULT_TIMEOUT_MS = 4e3;
354
354
  var DEFAULT_MAX_RETRIES = 2;
355
355
  var AttentionMarketClient = class {
356
356
  http;
357
+ agentId;
357
358
  constructor(config) {
359
+ this.agentId = config.agentId;
358
360
  this.validateConfig(config);
359
361
  const httpConfig = {
360
362
  apiKey: config.apiKey,
@@ -408,6 +410,66 @@ var AttentionMarketClient = class {
408
410
  }
409
411
  return response.units[0] ?? null;
410
412
  }
413
+ /**
414
+ * Simplified ad matching using conversation context and semantic search.
415
+ * Automatically handles request construction, taxonomy fallback, and defaults.
416
+ *
417
+ * Requires: agentId in SDKConfig constructor
418
+ *
419
+ * @example
420
+ * const ad = await client.decideFromContext({
421
+ * userMessage: "My father passed away and I need help organizing his estate",
422
+ * placement: 'sponsored_suggestion'
423
+ * });
424
+ *
425
+ * @throws {Error} If agentId was not provided in SDKConfig
426
+ */
427
+ async decideFromContext(params, options) {
428
+ if (!this.agentId) {
429
+ throw new Error(
430
+ "decideFromContext() requires agentId to be set in SDKConfig. Either provide agentId in the constructor or use decide() directly."
431
+ );
432
+ }
433
+ const historyLimit = 5;
434
+ const history = params.conversationHistory || [];
435
+ const limitedHistory = history.slice(-historyLimit);
436
+ const contextParts = [...limitedHistory, params.userMessage];
437
+ const context = contextParts.join("\n");
438
+ const country = params.country || "US";
439
+ const language = params.language || "en";
440
+ const platform = params.platform || "web";
441
+ const placementType = params.placement || "sponsored_suggestion";
442
+ const taxonomy = params.suggestedCategory || "unknown";
443
+ const request = {
444
+ request_id: generateUUID(),
445
+ agent_id: this.agentId,
446
+ placement: {
447
+ type: placementType,
448
+ surface: "chat"
449
+ },
450
+ opportunity: {
451
+ intent: {
452
+ taxonomy,
453
+ query: params.userMessage
454
+ },
455
+ context: {
456
+ country,
457
+ language,
458
+ platform
459
+ },
460
+ constraints: {
461
+ max_units: 1,
462
+ allowed_unit_types: [placementType]
463
+ },
464
+ privacy: {
465
+ data_policy: "coarse_only"
466
+ }
467
+ },
468
+ context,
469
+ user_intent: params.userMessage
470
+ };
471
+ return await this.decide(request, options);
472
+ }
411
473
  /**
412
474
  * Report an event (impression, click, action, conversion, feedback).
413
475
  */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.ts","../src/taxonomy-utils.ts"],"sourcesContent":["/**\n * AttentionMarket Agent Ads SDK\n * TypeScript SDK for integrating agent-native sponsored units\n */\n\n// Main client\nexport { AttentionMarketClient } from './client.js';\n\n// Mock client for testing\nexport { MockAttentionMarketClient } from './mock-client.js';\nexport type { MockClientConfig } from './mock-client.js';\n\n// Types\nexport type {\n // Configuration\n SDKConfig,\n // Agent Signup\n AgentSignupRequest,\n AgentSignupResponse,\n // Decide\n DecideRequest,\n DecideResponse,\n // Placement\n PlacementType,\n Placement,\n // Opportunity\n Opportunity,\n Intent,\n Context,\n Constraints,\n Privacy,\n // Ad Units\n AdUnit,\n AdScore,\n Disclosure,\n Tracking,\n SponsoredSuggestion,\n SponsoredTool,\n ToolCall,\n // Events\n EventType,\n EventIngestRequest,\n EventIngestResponse,\n // Policy\n PolicyResponse,\n // Errors\n APIError,\n} from './types.js';\n\n// Errors\nexport {\n AttentionMarketError,\n APIRequestError,\n NetworkError,\n TimeoutError,\n} from './errors.js';\n\n// Utilities\nexport {\n generateUUID,\n generateTimestamp,\n createOpportunity,\n createImpressionEvent,\n createClickEvent,\n escapeHTML,\n sanitizeURL,\n} from './utils.js';\n\nexport type {\n CreateOpportunityParams,\n CreateImpressionEventParams,\n CreateClickEventParams,\n SanitizeURLOptions,\n} from './utils.js';\n\n// Taxonomy utilities\nexport {\n buildTaxonomy,\n detectIntent,\n isValidTaxonomy,\n parseTaxonomy,\n getBaseTaxonomy,\n matchesTaxonomy,\n getVertical,\n suggestTaxonomies,\n} from './taxonomy-utils.js';\n\nexport type {\n TaxonomyIntent,\n ParsedTaxonomy,\n} from './taxonomy-utils.js';\n","/**\n * Error classes for AttentionMarket SDK.\n * Surfaces HTTP status, API error codes, messages, and request_id.\n */\n\nimport type { APIError } from './types.js';\n\nexport class AttentionMarketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AttentionMarketError';\n Object.setPrototypeOf(this, AttentionMarketError.prototype);\n }\n}\n\nexport class APIRequestError extends AttentionMarketError {\n public readonly statusCode: number;\n public readonly errorCode: string;\n public readonly requestId?: string;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n statusCode: number,\n apiError: APIError,\n ) {\n super(apiError.message);\n this.name = 'APIRequestError';\n this.statusCode = statusCode;\n this.errorCode = apiError.error;\n if (apiError.request_id !== undefined) {\n this.requestId = apiError.request_id;\n }\n if (apiError.details !== undefined) {\n this.details = apiError.details;\n }\n Object.setPrototypeOf(this, APIRequestError.prototype);\n }\n}\n\nexport class NetworkError extends AttentionMarketError {\n public readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'NetworkError';\n if (cause !== undefined) {\n this.cause = cause;\n }\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class TimeoutError extends AttentionMarketError {\n constructor(message: string = 'Request timed out') {\n super(message);\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n","/**\n * HTTP client with retry logic, timeout handling, and authentication.\n * Retry policy: only on 408, 429, 500, 502, 503, 504\n * Max retries: 2 (configurable)\n * Backoff: exponential with jitter\n */\n\nimport { APIRequestError, NetworkError, TimeoutError } from './errors.js';\nimport type { APIError } from './types.js';\n\nexport interface HTTPClientConfig {\n apiKey?: string; // AttentionMarket API key (am_live_* or am_test_*)\n supabaseAnonKey?: string; // Supabase anon key for infrastructure auth\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n}\n\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class HTTPClient {\n constructor(private config: HTTPClientConfig) {}\n\n async request<T>(\n method: 'GET' | 'POST',\n path: string,\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n } = {},\n ): Promise<T> {\n const url = `${this.config.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(url, method, options);\n return response as T;\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry if it's not a retryable error\n if (!this.shouldRetry(error, attempt)) {\n throw error;\n }\n\n // Wait before retrying\n if (attempt < this.config.maxRetries) {\n await this.backoff(attempt);\n }\n }\n }\n\n // All retries exhausted\n throw lastError;\n }\n\n private async makeRequest(\n url: string,\n method: 'GET' | 'POST',\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n },\n ): Promise<unknown> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n };\n\n // Send Supabase anon key in Authorization (for infrastructure)\n if (this.config.supabaseAnonKey) {\n headers['Authorization'] = `Bearer ${this.config.supabaseAnonKey}`;\n }\n\n // Send AttentionMarket API key in custom header (for app-level auth)\n if (this.config.apiKey) {\n headers['X-AM-API-Key'] = this.config.apiKey;\n }\n\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Handle non-OK responses\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({\n error: 'unknown_error',\n message: 'Failed to parse error response',\n request_id: 'unknown',\n }));\n\n throw new APIRequestError(response.status, errorBody as APIError);\n }\n\n // Parse successful response\n return await response.json();\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle abort (timeout)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n\n // Re-throw API errors\n if (error instanceof APIRequestError) {\n throw error;\n }\n\n // Network errors\n throw new NetworkError('Network request failed', error as Error);\n }\n }\n\n private shouldRetry(error: unknown, attempt: number): boolean {\n // Don't retry if we've exhausted attempts\n if (attempt >= this.config.maxRetries) {\n return false;\n }\n\n // Retry on timeout\n if (error instanceof TimeoutError) {\n return true;\n }\n\n // Retry on network errors\n if (error instanceof NetworkError) {\n return true;\n }\n\n // Retry on specific HTTP status codes\n if (error instanceof APIRequestError) {\n return RETRYABLE_STATUS_CODES.has(error.statusCode);\n }\n\n return false;\n }\n\n private async backoff(attempt: number): Promise<void> {\n // Exponential backoff: 100ms * 2^attempt + jitter\n const baseDelay = 100 * Math.pow(2, attempt);\n const jitter = Math.random() * 100;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","/**\n * Utility functions for the AttentionMarket SDK.\n * Includes UUID generation, opportunity helpers, and event builders.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { EventIngestRequest, Opportunity } from './types.js';\n\n/**\n * Generate a UUID v4 for use as event_id, request_id, etc.\n */\nexport function generateUUID(): string {\n return randomUUID();\n}\n\n/**\n * Generate an ISO 8601 timestamp for the current moment.\n */\nexport function generateTimestamp(): string {\n return new Date().toISOString();\n}\n\n// ============================================================================\n// Opportunity Helper\n// ============================================================================\n\nexport interface CreateOpportunityParams {\n // Required\n taxonomy: string;\n country: string;\n language: string;\n platform: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';\n\n // Optional\n query?: string;\n region?: string;\n city?: string;\n\n // Optional overrides (with defaults)\n constraints?: {\n max_units?: number;\n allowed_unit_types?: ('sponsored_suggestion' | 'sponsored_block' | 'sponsored_tool')[];\n blocked_categories?: string[];\n max_title_chars?: number;\n max_body_chars?: number;\n };\n privacy?: {\n data_policy?: 'coarse_only' | 'none' | 'extended';\n };\n}\n\n/**\n * Create a valid Opportunity object with safe defaults.\n *\n * Defaults:\n * - constraints.max_units = 1\n * - constraints.allowed_unit_types = ['sponsored_suggestion']\n * - privacy.data_policy = 'coarse_only'\n */\nexport function createOpportunity(params: CreateOpportunityParams): Opportunity {\n const opportunity: Opportunity = {\n intent: {\n taxonomy: params.taxonomy,\n },\n context: {\n country: params.country,\n language: params.language,\n platform: params.platform,\n },\n constraints: {\n max_units: params.constraints?.max_units ?? 1,\n allowed_unit_types: params.constraints?.allowed_unit_types ?? ['sponsored_suggestion'],\n },\n privacy: {\n data_policy: params.privacy?.data_policy ?? 'coarse_only',\n },\n };\n\n // Add optional intent fields\n if (params.query !== undefined) {\n opportunity.intent.query = params.query;\n }\n\n // Add optional context fields\n if (params.region !== undefined) {\n opportunity.context.region = params.region;\n }\n if (params.city !== undefined) {\n opportunity.context.city = params.city;\n }\n\n // Add optional constraint fields\n if (params.constraints?.blocked_categories !== undefined) {\n opportunity.constraints.blocked_categories = params.constraints.blocked_categories;\n }\n if (params.constraints?.max_title_chars !== undefined) {\n opportunity.constraints.max_title_chars = params.constraints.max_title_chars;\n }\n if (params.constraints?.max_body_chars !== undefined) {\n opportunity.constraints.max_body_chars = params.constraints.max_body_chars;\n }\n\n return opportunity;\n}\n\n// ============================================================================\n// Event Helpers\n// ============================================================================\n\nexport interface CreateImpressionEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create an impression event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createImpressionEvent(params: CreateImpressionEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return event;\n}\n\nexport interface CreateClickEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create a click event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createClickEvent(params: CreateClickEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n };\n\n // Include href in metadata\n if (params.metadata !== undefined) {\n event.metadata = {\n ...params.metadata,\n href: params.href,\n };\n } else {\n event.metadata = {\n href: params.href,\n };\n }\n\n return event;\n}\n\n// ============================================================================\n// Security & Sanitization Helpers\n// ============================================================================\n\n/**\n * HTML escape map - created once to avoid recreation on each call\n */\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#96;',\n};\n\n/**\n * Regex for matching HTML special characters - created once for performance\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`\\/]/g;\n\n/**\n * Maximum URL length to prevent DoS attacks\n */\nconst MAX_URL_LENGTH = 2048;\n\n/**\n * Dangerous URL protocols that must always be blocked\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'file:', 'vbscript:', 'blob:'];\n\n/**\n * Options for URL sanitization\n */\nexport interface SanitizeURLOptions {\n /**\n * Allow HTTP URLs (default: false, HTTPS only)\n */\n allowHttp?: boolean;\n /**\n * Allow tel: links (default: true)\n */\n allowTel?: boolean;\n /**\n * Allow mailto: links (default: true)\n */\n allowMailto?: boolean;\n /**\n * Callback for validation warnings (instead of console.warn)\n */\n onWarning?: (message: string, context: { url?: string; protocol?: string }) => void;\n}\n\n/**\n * Escape HTML special characters to prevent XSS attacks.\n *\n * Handles null/undefined safely by returning empty string.\n * Escapes: & < > \" ' / `\n *\n * Use this when displaying ad content (title, body, cta) in HTML contexts.\n *\n * @param text - Text to escape (can be null/undefined)\n * @returns Escaped HTML string (empty string if input is null/undefined)\n *\n * @example\n * ```typescript\n * const safeTitle = escapeHTML(unit.suggestion.title);\n * element.innerHTML = safeTitle; // Safe from XSS\n *\n * escapeHTML(null); // Returns ''\n * escapeHTML('<script>alert(1)</script>'); // Returns '&lt;script&gt;alert(1)&lt;/script&gt;'\n * ```\n */\nexport function escapeHTML(text: string | null | undefined): string {\n // Handle null/undefined safely\n if (text == null) {\n return '';\n }\n\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ESCAPES[char] || char);\n}\n\n/**\n * Sanitize and validate a URL to prevent XSS and phishing attacks.\n *\n * Handles null/undefined safely by returning null.\n * Blocks dangerous protocols like javascript:, data:, file:, blob:, vbscript:.\n * Validates URL length to prevent DoS attacks (max 2048 chars).\n * Handles protocol-relative URLs (//example.com).\n *\n * @param url - URL to sanitize (can be null/undefined)\n * @param options - Sanitization options\n * @returns Sanitized URL string, or null if invalid/dangerous\n *\n * @example\n * ```typescript\n * const safeURL = sanitizeURL(unit.suggestion.action_url);\n * if (safeURL) {\n * window.open(safeURL, '_blank');\n * }\n *\n * sanitizeURL(null); // Returns null\n * sanitizeURL('javascript:alert(1)'); // Returns null (blocked)\n * sanitizeURL('https://example.com'); // Returns 'https://example.com'\n * sanitizeURL('http://example.com', { allowHttp: true }); // Returns 'http://example.com'\n * ```\n */\nexport function sanitizeURL(\n url: string | null | undefined,\n options?: SanitizeURLOptions\n): string | null {\n // Handle null/undefined safely\n if (url == null) {\n return null;\n }\n\n const opts = {\n allowHttp: options?.allowHttp ?? false,\n allowTel: options?.allowTel ?? true,\n allowMailto: options?.allowMailto ?? true,\n onWarning: options?.onWarning ?? undefined,\n };\n\n try {\n const trimmedURL = url.trim();\n\n // Block empty URLs\n if (!trimmedURL) {\n return null;\n }\n\n // Validate URL length to prevent DoS\n if (trimmedURL.length > MAX_URL_LENGTH) {\n opts.onWarning?.('URL exceeds maximum length', { url: trimmedURL });\n return null;\n }\n\n // Handle protocol-relative URLs (//example.com)\n let parsedURL: URL;\n if (trimmedURL.startsWith('//')) {\n // Protocol-relative URLs need a base URL to parse\n try {\n parsedURL = new URL(trimmedURL, 'https://dummy-base.com');\n // Convert to absolute HTTPS URL\n return `https:${trimmedURL}`;\n } catch {\n opts.onWarning?.('Invalid protocol-relative URL', { url: trimmedURL });\n return null;\n }\n }\n\n // Parse URL\n parsedURL = new URL(trimmedURL);\n\n // Check protocol\n const protocol = parsedURL.protocol.toLowerCase();\n\n // Always block dangerous protocols\n if (DANGEROUS_PROTOCOLS.includes(protocol)) {\n opts.onWarning?.('Blocked dangerous URL protocol', { url: trimmedURL, protocol });\n return null;\n }\n\n // Allow HTTPS\n if (protocol === 'https:') {\n return trimmedURL;\n }\n\n // Conditionally allow HTTP\n if (protocol === 'http:') {\n if (opts.allowHttp) {\n return trimmedURL;\n } else {\n opts.onWarning?.('HTTP URL blocked. Use HTTPS or set allowHttp: true', {\n url: trimmedURL,\n protocol\n });\n return null;\n }\n }\n\n // Conditionally allow tel:\n if (protocol === 'tel:') {\n return opts.allowTel ? trimmedURL : null;\n }\n\n // Conditionally allow mailto:\n if (protocol === 'mailto:') {\n return opts.allowMailto ? trimmedURL : null;\n }\n\n // Block unknown protocols\n opts.onWarning?.('Unknown URL protocol blocked', { url: trimmedURL, protocol });\n return null;\n } catch (error) {\n // Invalid URL format\n opts.onWarning?.('Invalid URL format', { url });\n return null;\n }\n}\n","/**\n * Main SDK client for AttentionMarket Agent Ads API.\n * Provides public methods: decide(), decideRaw(), track(), getPolicy(), signupAgent()\n */\n\nimport { HTTPClient } from './http.js';\nimport { createImpressionEvent, createClickEvent } from './utils.js';\nimport type {\n SDKConfig,\n DecideRequest,\n DecideResponse,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n AgentSignupRequest,\n AgentSignupResponse,\n AdUnit,\n} from './types.js';\nimport type { CreateImpressionEventParams, CreateClickEventParams } from './utils.js';\n\n// Default configuration (points to AttentionMarket production API)\n// Developers can override with their own backend if self-hosting\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai/v1';\nconst DEFAULT_TIMEOUT_MS = 4000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nexport class AttentionMarketClient {\n private http: HTTPClient;\n\n constructor(config: SDKConfig) {\n // Validate configuration\n this.validateConfig(config);\n\n const httpConfig: {\n apiKey?: string;\n supabaseAnonKey?: string;\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n } = {\n apiKey: config.apiKey,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n\n // Only add supabaseAnonKey if provided\n if (config.supabaseAnonKey !== undefined) {\n httpConfig.supabaseAnonKey = config.supabaseAnonKey;\n }\n\n this.http = new HTTPClient(httpConfig);\n }\n\n /**\n * Validate SDK configuration for security\n */\n private validateConfig(config: SDKConfig): void {\n // Validate base URL uses HTTPS\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n if (!baseUrl.startsWith('https://')) {\n throw new Error(\n 'Security Error: baseUrl must use HTTPS. ' +\n 'HTTP connections expose your API key to man-in-the-middle attacks. ' +\n `Received: ${baseUrl}`\n );\n }\n\n // Validate API key format (if provided)\n if (config.apiKey) {\n const apiKeyPattern = /^am_(live|test)_[a-zA-Z0-9]+$/;\n if (!apiKeyPattern.test(config.apiKey)) {\n console.warn(\n 'Warning: API key does not match expected format (am_live_... or am_test_...). ' +\n 'This may indicate an invalid or compromised key.'\n );\n }\n }\n }\n\n /**\n * Request a sponsored unit decision for an agent opportunity.\n * Returns the full DecideResponse including status, ttl_ms, and all units.\n */\n async decideRaw(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<DecideResponse> {\n const requestOptions: {\n body: DecideRequest;\n idempotencyKey?: string;\n } = { body: request };\n\n if (options?.idempotencyKey !== undefined) {\n requestOptions.idempotencyKey = options.idempotencyKey;\n }\n\n return await this.http.request<DecideResponse>('POST', '/v1/decide', requestOptions);\n }\n\n /**\n * Convenience wrapper around decideRaw().\n * Returns null if status is no_fill, otherwise returns the first ad unit.\n */\n async decide(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n const response = await this.decideRaw(request, options);\n\n if (response.status === 'no_fill') {\n return null;\n }\n\n // Return first unit if available\n return response.units[0] ?? null;\n }\n\n /**\n * Report an event (impression, click, action, conversion, feedback).\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n return await this.http.request<EventIngestResponse>('POST', '/v1/event', {\n body: event,\n });\n }\n\n /**\n * Convenience method to track an impression event.\n * Creates an impression event using createImpressionEvent() and calls track().\n */\n async trackImpression(\n params: Omit<CreateImpressionEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createImpressionEvent(params);\n return await this.track(event);\n }\n\n /**\n * Convenience method to track a click event.\n * Creates a click event using createClickEvent() and calls track().\n */\n async trackClick(\n params: Omit<CreateClickEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createClickEvent(params);\n return await this.track(event);\n }\n\n /**\n * Fetch default policy constraints and formatting requirements.\n */\n async getPolicy(): Promise<PolicyResponse> {\n return await this.http.request<PolicyResponse>('GET', '/v1/policy');\n }\n\n /**\n * Create or register an agent (optional helper).\n * Note: This endpoint is unauthenticated in v1.\n */\n static async signupAgent(\n request: AgentSignupRequest,\n options?: { baseUrl?: string },\n ): Promise<AgentSignupResponse> {\n // Create temporary HTTP client without authentication\n const http = new HTTPClient({\n baseUrl: options?.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxRetries: DEFAULT_MAX_RETRIES,\n });\n\n return await http.request<AgentSignupResponse>('POST', '/v1/agent-signup', {\n body: request,\n });\n }\n}\n","/**\n * Mock AttentionMarket Client for Testing\n *\n * Use this client during development to test your integration without real advertiser data.\n * Returns realistic mock ad units for common taxonomies.\n */\n\nimport type {\n DecideRequest,\n DecideResponse,\n AdUnit,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n} from './types.js';\nimport { generateUUID, generateTimestamp } from './utils.js';\n\nexport interface MockClientConfig {\n /**\n * Simulate API latency (milliseconds)\n * @default 100\n */\n latencyMs?: number;\n\n /**\n * Fill rate (0.0 - 1.0) - probability of returning an ad\n * @default 1.0 (always fill)\n */\n fillRate?: number;\n\n /**\n * Log mock calls to console\n * @default true\n */\n verbose?: boolean;\n}\n\n/**\n * Mock client for testing SDK integration without real advertiser data\n *\n * @example\n * ```typescript\n * import { MockAttentionMarketClient } from '@the_ro_show/agent-ads-sdk';\n *\n * const client = new MockAttentionMarketClient();\n * const unit = await client.decide({...});\n * ```\n */\nexport class MockAttentionMarketClient {\n private config: Required<MockClientConfig>;\n private mockUnits: Record<string, AdUnit>;\n\n constructor(config: MockClientConfig = {}) {\n this.config = {\n latencyMs: config.latencyMs ?? 100,\n fillRate: config.fillRate ?? 1.0,\n verbose: config.verbose ?? true,\n };\n\n // Seed data for common taxonomies\n this.mockUnits = {\n 'local_services.movers.quote': {\n unit_id: 'unit_mock_movers_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Brooklyn Premium Movers',\n },\n tracking: {\n token: 'trk_mock_movers_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/movers',\n click_url: 'https://mock.attentionmarket.com/click/movers',\n },\n suggestion: {\n title: 'Professional Moving Services - Same Day Available',\n body: 'Licensed & insured movers serving Brooklyn since 2015. Free on-site estimates, packing services, and storage options. Rated 4.9/5 stars.',\n cta: 'Get Free Quote โ†’',\n action_url: 'https://demo-movers.example.com/quote?ref=am_mock',\n },\n },\n 'local_services.restaurants.search': {\n unit_id: 'unit_mock_restaurant_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: \"Tony's Italian Kitchen\",\n },\n tracking: {\n token: 'trk_mock_restaurant_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/restaurant',\n click_url: 'https://mock.attentionmarket.com/click/restaurant',\n },\n suggestion: {\n title: \"Tony's Italian Kitchen - Authentic NYC Italian\",\n body: 'โญ๏ธ 4.8/5 stars โ€ข Midtown Manhattan โ€ข Reservations available tonight. Try our signature wood-fired pizza and homemade pasta.',\n cta: 'Reserve Table โ†’',\n action_url: 'https://demo-restaurant.example.com/reserve?ref=am_mock',\n },\n },\n 'local_services.plumbers.quote': {\n unit_id: 'unit_mock_plumber_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: '24/7 Emergency Plumbing',\n },\n tracking: {\n token: 'trk_mock_plumber_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/plumber',\n click_url: 'https://mock.attentionmarket.com/click/plumber',\n },\n suggestion: {\n title: 'Emergency Plumber - 30 Min Response Time',\n body: 'Licensed plumbers available 24/7 across NYC. Free estimates. No overtime charges. Same-day service guaranteed.',\n cta: 'Call Now โ†’',\n action_url: 'tel:+1-555-PLUMBER',\n },\n },\n 'local_services.electricians.quote': {\n unit_id: 'unit_mock_electrician_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Spark Electric Co',\n },\n tracking: {\n token: 'trk_mock_electrician_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electrician',\n click_url: 'https://mock.attentionmarket.com/click/electrician',\n },\n suggestion: {\n title: 'Licensed Electrician - Free Safety Inspection',\n body: 'Certified electricians for residential & commercial. Emergency service available. 10-year warranty on all work.',\n cta: 'Schedule Service โ†’',\n action_url: 'https://demo-electrician.example.com/schedule?ref=am_mock',\n },\n },\n 'local_services.cleaners.quote': {\n unit_id: 'unit_mock_cleaner_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Sparkle Clean NYC',\n },\n tracking: {\n token: 'trk_mock_cleaner_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/cleaner',\n click_url: 'https://mock.attentionmarket.com/click/cleaner',\n },\n suggestion: {\n title: 'Professional Home Cleaning - $99 First Visit',\n body: 'Eco-friendly cleaning products. Background-checked staff. Satisfaction guaranteed or your money back.',\n cta: 'Book Cleaning โ†’',\n action_url: 'https://demo-cleaners.example.com/book?ref=am_mock',\n },\n },\n 'shopping.electronics.search': {\n unit_id: 'unit_mock_electronics_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'TechDeals Pro',\n },\n tracking: {\n token: 'trk_mock_electronics_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electronics',\n click_url: 'https://mock.attentionmarket.com/click/electronics',\n },\n suggestion: {\n title: 'Latest Laptops & Electronics - Up to 40% Off',\n body: 'Free shipping on orders over $50. 30-day returns. Price match guarantee. Shop top brands.',\n cta: 'Shop Deals โ†’',\n action_url: 'https://demo-electronics.example.com/deals?ref=am_mock',\n },\n },\n };\n }\n\n /**\n * Simulate network latency\n */\n private async simulateLatency(): Promise<void> {\n if (this.config.latencyMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, this.config.latencyMs));\n }\n }\n\n /**\n * Check if should fill based on fill rate\n */\n private shouldFill(): boolean {\n return Math.random() < this.config.fillRate;\n }\n\n /**\n * Log mock activity\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.verbose) {\n console.log(`๐Ÿงช [MockClient] ${message}`, ...args);\n }\n }\n\n /**\n * Request a sponsored unit (convenience method)\n */\n async decide(request: DecideRequest): Promise<AdUnit | null> {\n const response = await this.decideRaw(request);\n if (response.status === 'filled' && response.units.length > 0) {\n return response.units[0] || null;\n }\n return null;\n }\n\n /**\n * Request sponsored units (full response)\n */\n async decideRaw(request: DecideRequest): Promise<DecideResponse> {\n await this.simulateLatency();\n\n const taxonomy = request.opportunity.intent.taxonomy;\n this.log('decide()', {\n taxonomy,\n placement: request.placement.type,\n });\n\n // Check fill rate\n if (!this.shouldFill()) {\n this.log('No fill (simulated by fillRate)', { fillRate: this.config.fillRate });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n // Get mock unit for taxonomy\n const unit = this.mockUnits[taxonomy];\n\n if (unit) {\n const title = unit.unit_type === 'sponsored_suggestion'\n ? unit.suggestion.title\n : unit.tool.tool_name;\n\n this.log('Returning mock ad', {\n sponsor: unit.disclosure.sponsor_name,\n title,\n });\n\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'filled',\n units: [unit],\n ttl_ms: 60000,\n };\n }\n\n this.log('No mock data for taxonomy', { taxonomy });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n /**\n * Track an event\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n await this.simulateLatency();\n this.log('track()', { event_type: event.event_type, unit_id: event.unit_id });\n\n return {\n accepted: true,\n };\n }\n\n /**\n * Track impression (convenience method)\n */\n async trackImpression(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n }): Promise<EventIngestResponse> {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return this.track(event);\n }\n\n /**\n * Track click (convenience method)\n */\n async trackClick(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n }): Promise<EventIngestResponse> {\n return this.track({\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n metadata: {\n href: params.href,\n },\n });\n }\n\n /**\n * Get policy\n */\n async getPolicy(): Promise<PolicyResponse> {\n await this.simulateLatency();\n this.log('getPolicy()');\n\n return {\n version: '1.0.0',\n defaults: {\n max_units_per_response: 1,\n blocked_categories: [],\n },\n disclosure: {\n required: true,\n label: 'Sponsored',\n require_sponsor_name: true,\n },\n unit_rules: {},\n };\n }\n\n /**\n * Add custom mock ad unit for testing\n */\n addMockUnit(taxonomy: string, unit: AdUnit): void {\n this.mockUnits[taxonomy] = unit;\n this.log('Added custom mock unit', { taxonomy });\n }\n\n /**\n * Get list of available mock taxonomies\n */\n getAvailableTaxonomies(): string[] {\n return Object.keys(this.mockUnits);\n }\n}\n","/**\n * Taxonomy helper utilities for AttentionMarket SDK\n * Helps with building, validating, and working with the 4-tier taxonomy system\n */\n\n// Valid intent modifiers\nexport type TaxonomyIntent =\n | 'research' // Learning, browsing (low intent)\n | 'compare' // Evaluating options (medium intent)\n | 'quote' // Getting prices (high intent)\n | 'trial' // Free trial signup (high intent)\n | 'book' // Schedule/purchase (very high intent)\n | 'apply' // Application process (very high intent)\n | 'consultation'; // Schedule meeting (very high intent)\n\n/**\n * Build a valid taxonomy string\n *\n * @param vertical - Industry vertical (e.g., \"insurance\", \"business\", \"healthcare\")\n * @param category - Product/service category (e.g., \"auto\", \"saas\", \"dental\")\n * @param subcategory - Specific offering (e.g., \"full_coverage\", \"crm\", \"cosmetic\")\n * @param intent - Optional user journey stage\n * @returns Properly formatted taxonomy string\n *\n * @example\n * buildTaxonomy('insurance', 'auto', 'full_coverage', 'quote')\n * // Returns: 'insurance.auto.full_coverage.quote'\n *\n * @example\n * buildTaxonomy('business', 'saas', 'crm', 'trial')\n * // Returns: 'business.saas.crm.trial'\n */\nexport function buildTaxonomy(\n vertical: string,\n category: string,\n subcategory: string,\n intent?: TaxonomyIntent\n): string {\n const parts = [vertical, category, subcategory];\n if (intent) {\n parts.push(intent);\n }\n return parts.join('.');\n}\n\n/**\n * Detect user intent from query string\n *\n * Analyzes the user's query to determine their stage in the buying journey.\n * Returns the most appropriate intent modifier.\n *\n * @param query - User's search query or question\n * @returns Detected intent modifier\n *\n * @example\n * detectIntent(\"What is term life insurance?\")\n * // Returns: 'research'\n *\n * @example\n * detectIntent(\"Get car insurance quote\")\n * // Returns: 'quote'\n *\n * @example\n * detectIntent(\"Best CRM software comparison\")\n * // Returns: 'compare'\n */\nexport function detectIntent(query: string): TaxonomyIntent {\n const lowerQuery = query.toLowerCase();\n\n // Research intent - learning/information gathering\n if (/what is|how does|how do|learn about|tell me about|explain|understand|definition/i.test(lowerQuery)) {\n return 'research';\n }\n\n // Compare intent - evaluating options\n if (/best|compare|vs|versus|which|top|options|alternatives|review|recommend/i.test(lowerQuery)) {\n return 'compare';\n }\n\n // Quote intent - ready to get prices\n if (/price|cost|how much|quote|estimate|pricing|rate|afford/i.test(lowerQuery)) {\n return 'quote';\n }\n\n // Trial intent - wants to try before buying\n if (/try|demo|free trial|test|preview|sample/i.test(lowerQuery)) {\n return 'trial';\n }\n\n // Book intent - ready to schedule/purchase\n if (/book|schedule|appointment|reserve|set up|make appointment/i.test(lowerQuery)) {\n return 'book';\n }\n\n // Apply intent - application/signup\n if (/apply|sign up|get started|register|enroll|join/i.test(lowerQuery)) {\n return 'apply';\n }\n\n // Consultation intent - wants to talk to someone\n if (/talk to|speak with|consult|meet with|call|contact|discuss/i.test(lowerQuery)) {\n return 'consultation';\n }\n\n // Default to compare (most common)\n return 'compare';\n}\n\n/**\n * Validate taxonomy format\n *\n * Checks if a taxonomy string follows the correct format:\n * - 3 or 4 parts separated by dots\n * - All parts are lowercase alphanumeric with underscores\n * - If 4 parts, last part must be a valid intent\n *\n * @param taxonomy - Taxonomy string to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * isValidTaxonomy('insurance.auto.full_coverage.quote') // true\n * isValidTaxonomy('insurance.auto') // false (too short)\n * isValidTaxonomy('Insurance.Auto.Quote') // false (uppercase)\n * isValidTaxonomy('insurance.auto.full_coverage.invalid') // false (invalid intent)\n */\nexport function isValidTaxonomy(taxonomy: string): boolean {\n const parts = taxonomy.split('.');\n\n // Must be 3 or 4 parts\n if (parts.length < 3 || parts.length > 4) {\n return false;\n }\n\n // Check valid intent (if present)\n const validIntents: TaxonomyIntent[] = [\n 'research', 'compare', 'quote', 'trial',\n 'book', 'apply', 'consultation'\n ];\n\n if (parts.length === 4 && !validIntents.includes(parts[3] as TaxonomyIntent)) {\n return false;\n }\n\n // Check each part is non-empty and alphanumeric + underscore\n return parts.every(part => /^[a-z0-9_]+$/.test(part));\n}\n\n/**\n * Parsed taxonomy components\n */\nexport interface ParsedTaxonomy {\n /** Industry vertical (tier 1) */\n vertical: string;\n /** Product/service category (tier 2) */\n category: string;\n /** Specific offering (tier 3) */\n subcategory: string;\n /** User journey stage (tier 4, optional) */\n intent?: TaxonomyIntent;\n /** Full taxonomy string */\n full: string;\n}\n\n/**\n * Parse taxonomy into components\n *\n * Breaks down a taxonomy string into its constituent parts for analysis.\n *\n * @param taxonomy - Taxonomy string to parse\n * @returns Parsed components or null if invalid\n *\n * @example\n * parseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: {\n * // vertical: 'insurance',\n * // category: 'auto',\n * // subcategory: 'full_coverage',\n * // intent: 'quote',\n * // full: 'insurance.auto.full_coverage.quote'\n * // }\n */\nexport function parseTaxonomy(taxonomy: string): ParsedTaxonomy | null {\n if (!isValidTaxonomy(taxonomy)) {\n return null;\n }\n\n const parts = taxonomy.split('.');\n const result: ParsedTaxonomy = {\n vertical: parts[0]!,\n category: parts[1]!,\n subcategory: parts[2]!,\n full: taxonomy\n };\n\n if (parts[3]) {\n result.intent = parts[3] as TaxonomyIntent;\n }\n\n return result;\n}\n\n/**\n * Get taxonomy without intent modifier\n *\n * Useful for broader matching or grouping taxonomies by product/service.\n *\n * @param taxonomy - Full taxonomy string\n * @returns Taxonomy without intent, or null if invalid\n *\n * @example\n * getBaseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance.auto.full_coverage'\n */\nexport function getBaseTaxonomy(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n if (!parsed) return null;\n\n return `${parsed.vertical}.${parsed.category}.${parsed.subcategory}`;\n}\n\n/**\n * Check if two taxonomies match hierarchically\n *\n * Returns true if they share the same vertical, category, and subcategory\n * (regardless of intent).\n *\n * @param taxonomy1 - First taxonomy\n * @param taxonomy2 - Second taxonomy\n * @returns True if they match hierarchically\n *\n * @example\n * matchesTaxonomy(\n * 'insurance.auto.full_coverage.quote',\n * 'insurance.auto.full_coverage.apply'\n * )\n * // Returns: true (same base, different intent)\n */\nexport function matchesTaxonomy(taxonomy1: string, taxonomy2: string): boolean {\n const base1 = getBaseTaxonomy(taxonomy1);\n const base2 = getBaseTaxonomy(taxonomy2);\n\n return base1 !== null && base2 !== null && base1 === base2;\n}\n\n/**\n * Get vertical from taxonomy\n *\n * Extracts just the industry vertical (tier 1).\n *\n * @param taxonomy - Taxonomy string\n * @returns Vertical or null if invalid\n *\n * @example\n * getVertical('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance'\n */\nexport function getVertical(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n return parsed ? parsed.vertical : null;\n}\n\n/**\n * Suggest taxonomies based on user query\n *\n * Analyzes a user query and suggests appropriate taxonomies.\n * This is a basic implementation - you may want to enhance it\n * with ML/NLP for better accuracy.\n *\n * @param query - User's search query\n * @returns Array of suggested taxonomy strings\n *\n * @example\n * suggestTaxonomies(\"I need car insurance\")\n * // Returns: ['insurance.auto.full_coverage.quote', 'insurance.auto.liability.quote']\n */\nexport function suggestTaxonomies(query: string): string[] {\n const lowerQuery = query.toLowerCase();\n const intent = detectIntent(query);\n const suggestions: string[] = [];\n\n // Insurance-related\n if (/car|auto|vehicle|drive|driving/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'auto', 'full_coverage', intent));\n suggestions.push(buildTaxonomy('insurance', 'auto', 'liability', intent));\n }\n if (/health|medical|doctor/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'health', 'individual', intent));\n }\n if (/life insurance/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'life', 'term', intent));\n }\n\n // Business/SaaS\n if (/crm|customer|sales|lead/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'crm', intent));\n }\n if (/online store|ecommerce|sell online/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'ecommerce', 'platform', intent));\n }\n if (/project management|task|team/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'project_management', intent));\n }\n\n // Legal\n if (/accident|injury|hurt/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'personal_injury', 'accident', intent));\n }\n if (/divorce|custody|family law/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'family_law', 'divorce', intent));\n }\n\n // Healthcare\n if (/dentist|teeth|dental/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'dental', 'general', intent));\n }\n if (/therapist|therapy|counseling/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'mental_health', 'therapy', intent));\n }\n\n // Home services\n if (/mover|moving|relocate/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'moving', 'local', intent));\n }\n if (/plumber|plumbing|leak/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'plumbing', 'emergency', intent));\n }\n if (/clean|cleaning|maid/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'cleaning', 'regular', intent));\n }\n\n return suggestions;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,uBAAN,MAAM,8BAA6B,MAAM;AAAA,EAC9C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,sBAAqB,SAAS;AAAA,EAC5D;AACF;AAEO,IAAM,kBAAN,MAAM,yBAAwB,qBAAqB;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEhB,YACE,YACA,UACA;AACA,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,YAAY,SAAS;AAC1B,QAAI,SAAS,eAAe,QAAW;AACrC,WAAK,YAAY,SAAS;AAAA,IAC5B;AACA,QAAI,SAAS,YAAY,QAAW;AAClC,WAAK,UAAU,SAAS;AAAA,IAC1B;AACA,WAAO,eAAe,MAAM,iBAAgB,SAAS;AAAA,EACvD;AACF;AAEO,IAAM,eAAN,MAAM,sBAAqB,qBAAqB;AAAA,EACrC;AAAA,EAEhB,YAAY,SAAiB,OAAe;AAC1C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,QAAI,UAAU,QAAW;AACvB,WAAK,QAAQ;AAAA,IACf;AACA,WAAO,eAAe,MAAM,cAAa,SAAS;AAAA,EACpD;AACF;AAEO,IAAM,eAAN,MAAM,sBAAqB,qBAAqB;AAAA,EACrD,YAAY,UAAkB,qBAAqB;AACjD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,cAAa,SAAS;AAAA,EACpD;AACF;;;ACxCA,IAAM,yBAAyB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAE9D,IAAM,aAAN,MAAiB;AAAA,EACtB,YAAoB,QAA0B;AAA1B;AAAA,EAA2B;AAAA,EAE/C,MAAM,QACJ,QACA,MACA,UAII,CAAC,GACO;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,OAAO,GAAG,IAAI;AACzC,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,KAAK,OAAO,YAAY,WAAW;AAClE,UAAI;AACF,cAAM,WAAW,MAAM,KAAK,YAAY,KAAK,QAAQ,OAAO;AAC5D,eAAO;AAAA,MACT,SAAS,OAAO;AACd,oBAAY;AAGZ,YAAI,CAAC,KAAK,YAAY,OAAO,OAAO,GAAG;AACrC,gBAAM;AAAA,QACR;AAGA,YAAI,UAAU,KAAK,OAAO,YAAY;AACpC,gBAAM,KAAK,QAAQ,OAAO;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAGA,UAAM;AAAA,EACR;AAAA,EAEA,MAAc,YACZ,KACA,QACA,SAKkB;AAClB,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AAE5E,QAAI;AACF,YAAM,UAAkC;AAAA,QACtC,gBAAgB;AAAA,QAChB,GAAG,QAAQ;AAAA,MACb;AAGA,UAAI,KAAK,OAAO,iBAAiB;AAC/B,gBAAQ,eAAe,IAAI,UAAU,KAAK,OAAO,eAAe;AAAA,MAClE;AAGA,UAAI,KAAK,OAAO,QAAQ;AACtB,gBAAQ,cAAc,IAAI,KAAK,OAAO;AAAA,MACxC;AAEA,UAAI,QAAQ,gBAAgB;AAC1B,gBAAQ,iBAAiB,IAAI,QAAQ;AAAA,MACvC;AAEA,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,QACA,MAAM,QAAQ,OAAO,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,QACpD,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAGtB,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO;AAAA,UACnD,OAAO;AAAA,UACP,SAAS;AAAA,UACT,YAAY;AAAA,QACd,EAAE;AAEF,cAAM,IAAI,gBAAgB,SAAS,QAAQ,SAAqB;AAAA,MAClE;AAGA,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,SAAS,OAAO;AACd,mBAAa,SAAS;AAGtB,UAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;AACzD,cAAM,IAAI,aAAa;AAAA,MACzB;AAGA,UAAI,iBAAiB,iBAAiB;AACpC,cAAM;AAAA,MACR;AAGA,YAAM,IAAI,aAAa,0BAA0B,KAAc;AAAA,IACjE;AAAA,EACF;AAAA,EAEQ,YAAY,OAAgB,SAA0B;AAE5D,QAAI,WAAW,KAAK,OAAO,YAAY;AACrC,aAAO;AAAA,IACT;AAGA,QAAI,iBAAiB,cAAc;AACjC,aAAO;AAAA,IACT;AAGA,QAAI,iBAAiB,cAAc;AACjC,aAAO;AAAA,IACT;AAGA,QAAI,iBAAiB,iBAAiB;AACpC,aAAO,uBAAuB,IAAI,MAAM,UAAU;AAAA,IACpD;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QAAQ,SAAgC;AAEpD,UAAM,YAAY,MAAM,KAAK,IAAI,GAAG,OAAO;AAC3C,UAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,UAAM,QAAQ,YAAY;AAE1B,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAAA,EAC3D;AACF;;;AC7JA,yBAA2B;AAMpB,SAAS,eAAuB;AACrC,aAAO,+BAAW;AACpB;AAKO,SAAS,oBAA4B;AAC1C,UAAO,oBAAI,KAAK,GAAE,YAAY;AAChC;AAuCO,SAAS,kBAAkB,QAA8C;AAC9E,QAAM,cAA2B;AAAA,IAC/B,QAAQ;AAAA,MACN,UAAU,OAAO;AAAA,IACnB;AAAA,IACA,SAAS;AAAA,MACP,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,MACX,WAAW,OAAO,aAAa,aAAa;AAAA,MAC5C,oBAAoB,OAAO,aAAa,sBAAsB,CAAC,sBAAsB;AAAA,IACvF;AAAA,IACA,SAAS;AAAA,MACP,aAAa,OAAO,SAAS,eAAe;AAAA,IAC9C;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,QAAW;AAC9B,gBAAY,OAAO,QAAQ,OAAO;AAAA,EACpC;AAGA,MAAI,OAAO,WAAW,QAAW;AAC/B,gBAAY,QAAQ,SAAS,OAAO;AAAA,EACtC;AACA,MAAI,OAAO,SAAS,QAAW;AAC7B,gBAAY,QAAQ,OAAO,OAAO;AAAA,EACpC;AAGA,MAAI,OAAO,aAAa,uBAAuB,QAAW;AACxD,gBAAY,YAAY,qBAAqB,OAAO,YAAY;AAAA,EAClE;AACA,MAAI,OAAO,aAAa,oBAAoB,QAAW;AACrD,gBAAY,YAAY,kBAAkB,OAAO,YAAY;AAAA,EAC/D;AACA,MAAI,OAAO,aAAa,mBAAmB,QAAW;AACpD,gBAAY,YAAY,iBAAiB,OAAO,YAAY;AAAA,EAC9D;AAEA,SAAO;AACT;AAsBO,SAAS,sBAAsB,QAAyD;AAC7F,QAAM,QAA4B;AAAA,IAChC,UAAU,aAAa;AAAA,IACvB,aAAa,OAAO,eAAe,kBAAkB;AAAA,IACrD,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,IACnB,aAAa,OAAO;AAAA,IACpB,SAAS,OAAO;AAAA,IAChB,YAAY;AAAA,IACZ,gBAAgB,OAAO;AAAA,EACzB;AAEA,MAAI,OAAO,aAAa,QAAW;AACjC,UAAM,WAAW,OAAO;AAAA,EAC1B;AAEA,SAAO;AACT;AAmBO,SAAS,iBAAiB,QAAoD;AACnF,QAAM,QAA4B;AAAA,IAChC,UAAU,aAAa;AAAA,IACvB,aAAa,OAAO,eAAe,kBAAkB;AAAA,IACrD,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,IACnB,aAAa,OAAO;AAAA,IACpB,SAAS,OAAO;AAAA,IAChB,YAAY;AAAA,IACZ,gBAAgB,OAAO;AAAA,EACzB;AAGA,MAAI,OAAO,aAAa,QAAW;AACjC,UAAM,WAAW;AAAA,MACf,GAAG,OAAO;AAAA,MACV,MAAM,OAAO;AAAA,IACf;AAAA,EACF,OAAO;AACL,UAAM,WAAW;AAAA,MACf,MAAM,OAAO;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AASA,IAAM,eAAiD;AAAA,EACrD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAKA,IAAM,oBAAoB;AAK1B,IAAM,iBAAiB;AAKvB,IAAM,sBAAsB,CAAC,eAAe,SAAS,SAAS,aAAa,OAAO;AA4C3E,SAAS,WAAW,MAAyC;AAElE,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,QAAQ,mBAAmB,CAAC,SAAS,aAAa,IAAI,KAAK,IAAI;AAC7E;AA2BO,SAAS,YACd,KACA,SACe;AAEf,MAAI,OAAO,MAAM;AACf,WAAO;AAAA,EACT;AAEA,QAAM,OAAO;AAAA,IACX,WAAW,SAAS,aAAa;AAAA,IACjC,UAAU,SAAS,YAAY;AAAA,IAC/B,aAAa,SAAS,eAAe;AAAA,IACrC,WAAW,SAAS,aAAa;AAAA,EACnC;AAEA,MAAI;AACF,UAAM,aAAa,IAAI,KAAK;AAG5B,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAGA,QAAI,WAAW,SAAS,gBAAgB;AACtC,WAAK,YAAY,8BAA8B,EAAE,KAAK,WAAW,CAAC;AAClE,aAAO;AAAA,IACT;AAGA,QAAI;AACJ,QAAI,WAAW,WAAW,IAAI,GAAG;AAE/B,UAAI;AACF,oBAAY,IAAI,IAAI,YAAY,wBAAwB;AAExD,eAAO,SAAS,UAAU;AAAA,MAC5B,QAAQ;AACN,aAAK,YAAY,iCAAiC,EAAE,KAAK,WAAW,CAAC;AACrE,eAAO;AAAA,MACT;AAAA,IACF;AAGA,gBAAY,IAAI,IAAI,UAAU;AAG9B,UAAM,WAAW,UAAU,SAAS,YAAY;AAGhD,QAAI,oBAAoB,SAAS,QAAQ,GAAG;AAC1C,WAAK,YAAY,kCAAkC,EAAE,KAAK,YAAY,SAAS,CAAC;AAChF,aAAO;AAAA,IACT;AAGA,QAAI,aAAa,UAAU;AACzB,aAAO;AAAA,IACT;AAGA,QAAI,aAAa,SAAS;AACxB,UAAI,KAAK,WAAW;AAClB,eAAO;AAAA,MACT,OAAO;AACL,aAAK,YAAY,sDAAsD;AAAA,UACrE,KAAK;AAAA,UACL;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,aAAa,QAAQ;AACvB,aAAO,KAAK,WAAW,aAAa;AAAA,IACtC;AAGA,QAAI,aAAa,WAAW;AAC1B,aAAO,KAAK,cAAc,aAAa;AAAA,IACzC;AAGA,SAAK,YAAY,gCAAgC,EAAE,KAAK,YAAY,SAAS,CAAC;AAC9E,WAAO;AAAA,EACT,SAAS,OAAO;AAEd,SAAK,YAAY,sBAAsB,EAAE,IAAI,CAAC;AAC9C,WAAO;AAAA,EACT;AACF;;;AC9WA,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAErB,IAAM,wBAAN,MAA4B;AAAA,EACzB;AAAA,EAER,YAAY,QAAmB;AAE7B,SAAK,eAAe,MAAM;AAE1B,UAAM,aAMF;AAAA,MACF,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO,WAAW;AAAA,MAC3B,WAAW,OAAO,aAAa;AAAA,MAC/B,YAAY,OAAO,cAAc;AAAA,IACnC;AAGA,QAAI,OAAO,oBAAoB,QAAW;AACxC,iBAAW,kBAAkB,OAAO;AAAA,IACtC;AAEA,SAAK,OAAO,IAAI,WAAW,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAAyB;AAE9C,UAAM,UAAU,OAAO,WAAW;AAClC,QAAI,CAAC,QAAQ,WAAW,UAAU,GAAG;AACnC,YAAM,IAAI;AAAA,QACR,wHAEe,OAAO;AAAA,MACxB;AAAA,IACF;AAGA,QAAI,OAAO,QAAQ;AACjB,YAAM,gBAAgB;AACtB,UAAI,CAAC,cAAc,KAAK,OAAO,MAAM,GAAG;AACtC,gBAAQ;AAAA,UACN;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UACJ,SACA,SACyB;AACzB,UAAM,iBAGF,EAAE,MAAM,QAAQ;AAEpB,QAAI,SAAS,mBAAmB,QAAW;AACzC,qBAAe,iBAAiB,QAAQ;AAAA,IAC1C;AAEA,WAAO,MAAM,KAAK,KAAK,QAAwB,QAAQ,cAAc,cAAc;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OACJ,SACA,SACwB;AACxB,UAAM,WAAW,MAAM,KAAK,UAAU,SAAS,OAAO;AAEtD,QAAI,SAAS,WAAW,WAAW;AACjC,aAAO;AAAA,IACT;AAGA,WAAO,SAAS,MAAM,CAAC,KAAK;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,OAAyD;AACnE,WAAO,MAAM,KAAK,KAAK,QAA6B,QAAQ,aAAa;AAAA,MACvE,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBACJ,QAC8B;AAC9B,UAAM,QAAQ,sBAAsB,MAAM;AAC1C,WAAO,MAAM,KAAK,MAAM,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WACJ,QAC8B;AAC9B,UAAM,QAAQ,iBAAiB,MAAM;AACrC,WAAO,MAAM,KAAK,MAAM,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAqC;AACzC,WAAO,MAAM,KAAK,KAAK,QAAwB,OAAO,YAAY;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,YACX,SACA,SAC8B;AAE9B,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B,SAAS,SAAS,WAAW;AAAA,MAC7B,WAAW;AAAA,MACX,YAAY;AAAA,IACd,CAAC;AAED,WAAO,MAAM,KAAK,QAA6B,QAAQ,oBAAoB;AAAA,MACzE,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;;;AC/HO,IAAM,4BAAN,MAAgC;AAAA,EAC7B;AAAA,EACA;AAAA,EAER,YAAY,SAA2B,CAAC,GAAG;AACzC,SAAK,SAAS;AAAA,MACZ,WAAW,OAAO,aAAa;AAAA,MAC/B,UAAU,OAAO,YAAY;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,IAC7B;AAGA,SAAK,YAAY;AAAA,MACf,+BAA+B;AAAA,QAC7B,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,qBAAqB,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UACzD,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,MACA,qCAAqC;AAAA,QACnC,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,yBAAyB,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UAC7D,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,MACA,iCAAiC;AAAA,QAC/B,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,sBAAsB,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UAC1D,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,MACA,qCAAqC;AAAA,QACnC,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,0BAA0B,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UAC9D,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,MACA,iCAAiC;AAAA,QAC/B,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,sBAAsB,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UAC1D,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,MACA,+BAA+B;AAAA,QAC7B,SAAS;AAAA,QACT,WAAW;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,UACP,aAAa;AAAA,UACb,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,UACR,OAAO,0BAA0B,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,UAC9D,gBAAgB;AAAA,UAChB,WAAW;AAAA,QACb;AAAA,QACA,YAAY;AAAA,UACV,OAAO;AAAA,UACP,MAAM;AAAA,UACN,KAAK;AAAA,UACL,YAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAiC;AAC7C,QAAI,KAAK,OAAO,YAAY,GAAG;AAC7B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,OAAO,SAAS,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAsB;AAC5B,WAAO,KAAK,OAAO,IAAI,KAAK,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKQ,IAAI,YAAoB,MAAuB;AACrD,QAAI,KAAK,OAAO,SAAS;AACvB,cAAQ,IAAI,0BAAmB,OAAO,IAAI,GAAG,IAAI;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,SAAgD;AAC3D,UAAM,WAAW,MAAM,KAAK,UAAU,OAAO;AAC7C,QAAI,SAAS,WAAW,YAAY,SAAS,MAAM,SAAS,GAAG;AAC7D,aAAO,SAAS,MAAM,CAAC,KAAK;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,SAAiD;AAC/D,UAAM,KAAK,gBAAgB;AAE3B,UAAM,WAAW,QAAQ,YAAY,OAAO;AAC5C,SAAK,IAAI,YAAY;AAAA,MACnB;AAAA,MACA,WAAW,QAAQ,UAAU;AAAA,IAC/B,CAAC;AAGD,QAAI,CAAC,KAAK,WAAW,GAAG;AACtB,WAAK,IAAI,mCAAmC,EAAE,UAAU,KAAK,OAAO,SAAS,CAAC;AAC9E,aAAO;AAAA,QACL,YAAY,QAAQ;AAAA,QACpB,aAAa,cAAc,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,QACxD,QAAQ;AAAA,QACR,OAAO,CAAC;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAGA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,QAAI,MAAM;AACR,YAAM,QAAQ,KAAK,cAAc,yBAC7B,KAAK,WAAW,QAChB,KAAK,KAAK;AAEd,WAAK,IAAI,qBAAqB;AAAA,QAC5B,SAAS,KAAK,WAAW;AAAA,QACzB;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,YAAY,QAAQ;AAAA,QACpB,aAAa,cAAc,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,QACxD,QAAQ;AAAA,QACR,OAAO,CAAC,IAAI;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,SAAK,IAAI,6BAA6B,EAAE,SAAS,CAAC;AAClD,WAAO;AAAA,MACL,YAAY,QAAQ;AAAA,MACpB,aAAa,cAAc,aAAa,EAAE,UAAU,GAAG,CAAC;AAAA,MACxD,QAAQ;AAAA,MACR,OAAO,CAAC;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,OAAyD;AACnE,UAAM,KAAK,gBAAgB;AAC3B,SAAK,IAAI,WAAW,EAAE,YAAY,MAAM,YAAY,SAAS,MAAM,QAAQ,CAAC;AAE5E,WAAO;AAAA,MACL,UAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,QAQW;AAC/B,UAAM,QAA4B;AAAA,MAChC,UAAU,aAAa;AAAA,MACvB,aAAa,OAAO,eAAe,kBAAkB;AAAA,MACrD,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,MAChB,YAAY;AAAA,MACZ,gBAAgB,OAAO;AAAA,IACzB;AAEA,QAAI,OAAO,aAAa,QAAW;AACjC,YAAM,WAAW,OAAO;AAAA,IAC1B;AAEA,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,QAQgB;AAC/B,WAAO,KAAK,MAAM;AAAA,MAChB,UAAU,aAAa;AAAA,MACvB,aAAa,OAAO,eAAe,kBAAkB;AAAA,MACrD,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,SAAS,OAAO;AAAA,MAChB,YAAY;AAAA,MACZ,gBAAgB,OAAO;AAAA,MACvB,UAAU;AAAA,QACR,MAAM,OAAO;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAqC;AACzC,UAAM,KAAK,gBAAgB;AAC3B,SAAK,IAAI,aAAa;AAEtB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU;AAAA,QACR,wBAAwB;AAAA,QACxB,oBAAoB,CAAC;AAAA,MACvB;AAAA,MACA,YAAY;AAAA,QACV,UAAU;AAAA,QACV,OAAO;AAAA,QACP,sBAAsB;AAAA,MACxB;AAAA,MACA,YAAY,CAAC;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAkB,MAAoB;AAChD,SAAK,UAAU,QAAQ,IAAI;AAC3B,SAAK,IAAI,0BAA0B,EAAE,SAAS,CAAC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,yBAAmC;AACjC,WAAO,OAAO,KAAK,KAAK,SAAS;AAAA,EACnC;AACF;;;AC7VO,SAAS,cACd,UACA,UACA,aACA,QACQ;AACR,QAAM,QAAQ,CAAC,UAAU,UAAU,WAAW;AAC9C,MAAI,QAAQ;AACV,UAAM,KAAK,MAAM;AAAA,EACnB;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAuBO,SAAS,aAAa,OAA+B;AAC1D,QAAM,aAAa,MAAM,YAAY;AAGrC,MAAI,mFAAmF,KAAK,UAAU,GAAG;AACvG,WAAO;AAAA,EACT;AAGA,MAAI,0EAA0E,KAAK,UAAU,GAAG;AAC9F,WAAO;AAAA,EACT;AAGA,MAAI,0DAA0D,KAAK,UAAU,GAAG;AAC9E,WAAO;AAAA,EACT;AAGA,MAAI,2CAA2C,KAAK,UAAU,GAAG;AAC/D,WAAO;AAAA,EACT;AAGA,MAAI,6DAA6D,KAAK,UAAU,GAAG;AACjF,WAAO;AAAA,EACT;AAGA,MAAI,kDAAkD,KAAK,UAAU,GAAG;AACtE,WAAO;AAAA,EACT;AAGA,MAAI,6DAA6D,KAAK,UAAU,GAAG;AACjF,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAmBO,SAAS,gBAAgB,UAA2B;AACzD,QAAM,QAAQ,SAAS,MAAM,GAAG;AAGhC,MAAI,MAAM,SAAS,KAAK,MAAM,SAAS,GAAG;AACxC,WAAO;AAAA,EACT;AAGA,QAAM,eAAiC;AAAA,IACrC;AAAA,IAAY;AAAA,IAAW;AAAA,IAAS;AAAA,IAChC;AAAA,IAAQ;AAAA,IAAS;AAAA,EACnB;AAEA,MAAI,MAAM,WAAW,KAAK,CAAC,aAAa,SAAS,MAAM,CAAC,CAAmB,GAAG;AAC5E,WAAO;AAAA,EACT;AAGA,SAAO,MAAM,MAAM,UAAQ,eAAe,KAAK,IAAI,CAAC;AACtD;AAoCO,SAAS,cAAc,UAAyC;AACrE,MAAI,CAAC,gBAAgB,QAAQ,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,QAAM,SAAyB;AAAA,IAC7B,UAAU,MAAM,CAAC;AAAA,IACjB,UAAU,MAAM,CAAC;AAAA,IACjB,aAAa,MAAM,CAAC;AAAA,IACpB,MAAM;AAAA,EACR;AAEA,MAAI,MAAM,CAAC,GAAG;AACZ,WAAO,SAAS,MAAM,CAAC;AAAA,EACzB;AAEA,SAAO;AACT;AAcO,SAAS,gBAAgB,UAAiC;AAC/D,QAAM,SAAS,cAAc,QAAQ;AACrC,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,GAAG,OAAO,QAAQ,IAAI,OAAO,QAAQ,IAAI,OAAO,WAAW;AACpE;AAmBO,SAAS,gBAAgB,WAAmB,WAA4B;AAC7E,QAAM,QAAQ,gBAAgB,SAAS;AACvC,QAAM,QAAQ,gBAAgB,SAAS;AAEvC,SAAO,UAAU,QAAQ,UAAU,QAAQ,UAAU;AACvD;AAcO,SAAS,YAAY,UAAiC;AAC3D,QAAM,SAAS,cAAc,QAAQ;AACrC,SAAO,SAAS,OAAO,WAAW;AACpC;AAgBO,SAAS,kBAAkB,OAAyB;AACzD,QAAM,aAAa,MAAM,YAAY;AACrC,QAAM,SAAS,aAAa,KAAK;AACjC,QAAM,cAAwB,CAAC;AAG/B,MAAI,iCAAiC,KAAK,UAAU,GAAG;AACrD,gBAAY,KAAK,cAAc,aAAa,QAAQ,iBAAiB,MAAM,CAAC;AAC5E,gBAAY,KAAK,cAAc,aAAa,QAAQ,aAAa,MAAM,CAAC;AAAA,EAC1E;AACA,MAAI,wBAAwB,KAAK,UAAU,GAAG;AAC5C,gBAAY,KAAK,cAAc,aAAa,UAAU,cAAc,MAAM,CAAC;AAAA,EAC7E;AACA,MAAI,iBAAiB,KAAK,UAAU,GAAG;AACrC,gBAAY,KAAK,cAAc,aAAa,QAAQ,QAAQ,MAAM,CAAC;AAAA,EACrE;AAGA,MAAI,0BAA0B,KAAK,UAAU,GAAG;AAC9C,gBAAY,KAAK,cAAc,YAAY,QAAQ,OAAO,MAAM,CAAC;AAAA,EACnE;AACA,MAAI,qCAAqC,KAAK,UAAU,GAAG;AACzD,gBAAY,KAAK,cAAc,YAAY,aAAa,YAAY,MAAM,CAAC;AAAA,EAC7E;AACA,MAAI,+BAA+B,KAAK,UAAU,GAAG;AACnD,gBAAY,KAAK,cAAc,YAAY,QAAQ,sBAAsB,MAAM,CAAC;AAAA,EAClF;AAGA,MAAI,uBAAuB,KAAK,UAAU,GAAG;AAC3C,gBAAY,KAAK,cAAc,SAAS,mBAAmB,YAAY,MAAM,CAAC;AAAA,EAChF;AACA,MAAI,6BAA6B,KAAK,UAAU,GAAG;AACjD,gBAAY,KAAK,cAAc,SAAS,cAAc,WAAW,MAAM,CAAC;AAAA,EAC1E;AAGA,MAAI,uBAAuB,KAAK,UAAU,GAAG;AAC3C,gBAAY,KAAK,cAAc,cAAc,UAAU,WAAW,MAAM,CAAC;AAAA,EAC3E;AACA,MAAI,+BAA+B,KAAK,UAAU,GAAG;AACnD,gBAAY,KAAK,cAAc,cAAc,iBAAiB,WAAW,MAAM,CAAC;AAAA,EAClF;AAGA,MAAI,wBAAwB,KAAK,UAAU,GAAG;AAC5C,gBAAY,KAAK,cAAc,iBAAiB,UAAU,SAAS,MAAM,CAAC;AAAA,EAC5E;AACA,MAAI,wBAAwB,KAAK,UAAU,GAAG;AAC5C,gBAAY,KAAK,cAAc,iBAAiB,YAAY,aAAa,MAAM,CAAC;AAAA,EAClF;AACA,MAAI,sBAAsB,KAAK,UAAU,GAAG;AAC1C,gBAAY,KAAK,cAAc,iBAAiB,YAAY,WAAW,MAAM,CAAC;AAAA,EAChF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.ts","../src/taxonomy-utils.ts"],"sourcesContent":["/**\n * AttentionMarket Agent Ads SDK\n * TypeScript SDK for integrating agent-native sponsored units\n */\n\n// Main client\nexport { AttentionMarketClient } from './client.js';\n\n// Mock client for testing\nexport { MockAttentionMarketClient } from './mock-client.js';\nexport type { MockClientConfig } from './mock-client.js';\n\n// Types\nexport type {\n // Configuration\n SDKConfig,\n // Agent Signup\n AgentSignupRequest,\n AgentSignupResponse,\n // Decide\n DecideRequest,\n DecideFromContextRequest,\n DecideResponse,\n // Placement\n PlacementType,\n Placement,\n // Opportunity\n Opportunity,\n Intent,\n Context,\n Constraints,\n Privacy,\n // Ad Units\n AdUnit,\n AdScore,\n Disclosure,\n Tracking,\n SponsoredSuggestion,\n SponsoredTool,\n ToolCall,\n // Events\n EventType,\n EventIngestRequest,\n EventIngestResponse,\n // Policy\n PolicyResponse,\n // Errors\n APIError,\n} from './types.js';\n\n// Errors\nexport {\n AttentionMarketError,\n APIRequestError,\n NetworkError,\n TimeoutError,\n} from './errors.js';\n\n// Utilities\nexport {\n generateUUID,\n generateTimestamp,\n createOpportunity,\n createImpressionEvent,\n createClickEvent,\n escapeHTML,\n sanitizeURL,\n} from './utils.js';\n\nexport type {\n CreateOpportunityParams,\n CreateImpressionEventParams,\n CreateClickEventParams,\n SanitizeURLOptions,\n} from './utils.js';\n\n// Taxonomy utilities\nexport {\n buildTaxonomy,\n detectIntent,\n isValidTaxonomy,\n parseTaxonomy,\n getBaseTaxonomy,\n matchesTaxonomy,\n getVertical,\n suggestTaxonomies,\n} from './taxonomy-utils.js';\n\nexport type {\n TaxonomyIntent,\n ParsedTaxonomy,\n} from './taxonomy-utils.js';\n","/**\n * Error classes for AttentionMarket SDK.\n * Surfaces HTTP status, API error codes, messages, and request_id.\n */\n\nimport type { APIError } from './types.js';\n\nexport class AttentionMarketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AttentionMarketError';\n Object.setPrototypeOf(this, AttentionMarketError.prototype);\n }\n}\n\nexport class APIRequestError extends AttentionMarketError {\n public readonly statusCode: number;\n public readonly errorCode: string;\n public readonly requestId?: string;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n statusCode: number,\n apiError: APIError,\n ) {\n super(apiError.message);\n this.name = 'APIRequestError';\n this.statusCode = statusCode;\n this.errorCode = apiError.error;\n if (apiError.request_id !== undefined) {\n this.requestId = apiError.request_id;\n }\n if (apiError.details !== undefined) {\n this.details = apiError.details;\n }\n Object.setPrototypeOf(this, APIRequestError.prototype);\n }\n}\n\nexport class NetworkError extends AttentionMarketError {\n public readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'NetworkError';\n if (cause !== undefined) {\n this.cause = cause;\n }\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class TimeoutError extends AttentionMarketError {\n constructor(message: string = 'Request timed out') {\n super(message);\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n","/**\n * HTTP client with retry logic, timeout handling, and authentication.\n * Retry policy: only on 408, 429, 500, 502, 503, 504\n * Max retries: 2 (configurable)\n * Backoff: exponential with jitter\n */\n\nimport { APIRequestError, NetworkError, TimeoutError } from './errors.js';\nimport type { APIError } from './types.js';\n\nexport interface HTTPClientConfig {\n apiKey?: string; // AttentionMarket API key (am_live_* or am_test_*)\n supabaseAnonKey?: string; // Supabase anon key for infrastructure auth\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n}\n\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class HTTPClient {\n constructor(private config: HTTPClientConfig) {}\n\n async request<T>(\n method: 'GET' | 'POST',\n path: string,\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n } = {},\n ): Promise<T> {\n const url = `${this.config.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(url, method, options);\n return response as T;\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry if it's not a retryable error\n if (!this.shouldRetry(error, attempt)) {\n throw error;\n }\n\n // Wait before retrying\n if (attempt < this.config.maxRetries) {\n await this.backoff(attempt);\n }\n }\n }\n\n // All retries exhausted\n throw lastError;\n }\n\n private async makeRequest(\n url: string,\n method: 'GET' | 'POST',\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n },\n ): Promise<unknown> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n };\n\n // Send Supabase anon key in Authorization (for infrastructure)\n if (this.config.supabaseAnonKey) {\n headers['Authorization'] = `Bearer ${this.config.supabaseAnonKey}`;\n }\n\n // Send AttentionMarket API key in custom header (for app-level auth)\n if (this.config.apiKey) {\n headers['X-AM-API-Key'] = this.config.apiKey;\n }\n\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Handle non-OK responses\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({\n error: 'unknown_error',\n message: 'Failed to parse error response',\n request_id: 'unknown',\n }));\n\n throw new APIRequestError(response.status, errorBody as APIError);\n }\n\n // Parse successful response\n return await response.json();\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle abort (timeout)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n\n // Re-throw API errors\n if (error instanceof APIRequestError) {\n throw error;\n }\n\n // Network errors\n throw new NetworkError('Network request failed', error as Error);\n }\n }\n\n private shouldRetry(error: unknown, attempt: number): boolean {\n // Don't retry if we've exhausted attempts\n if (attempt >= this.config.maxRetries) {\n return false;\n }\n\n // Retry on timeout\n if (error instanceof TimeoutError) {\n return true;\n }\n\n // Retry on network errors\n if (error instanceof NetworkError) {\n return true;\n }\n\n // Retry on specific HTTP status codes\n if (error instanceof APIRequestError) {\n return RETRYABLE_STATUS_CODES.has(error.statusCode);\n }\n\n return false;\n }\n\n private async backoff(attempt: number): Promise<void> {\n // Exponential backoff: 100ms * 2^attempt + jitter\n const baseDelay = 100 * Math.pow(2, attempt);\n const jitter = Math.random() * 100;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","/**\n * Utility functions for the AttentionMarket SDK.\n * Includes UUID generation, opportunity helpers, and event builders.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { EventIngestRequest, Opportunity } from './types.js';\n\n/**\n * Generate a UUID v4 for use as event_id, request_id, etc.\n */\nexport function generateUUID(): string {\n return randomUUID();\n}\n\n/**\n * Generate an ISO 8601 timestamp for the current moment.\n */\nexport function generateTimestamp(): string {\n return new Date().toISOString();\n}\n\n// ============================================================================\n// Opportunity Helper\n// ============================================================================\n\nexport interface CreateOpportunityParams {\n // Required\n taxonomy: string;\n country: string;\n language: string;\n platform: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';\n\n // Optional\n query?: string;\n region?: string;\n city?: string;\n\n // Optional overrides (with defaults)\n constraints?: {\n max_units?: number;\n allowed_unit_types?: ('sponsored_suggestion' | 'sponsored_block' | 'sponsored_tool')[];\n blocked_categories?: string[];\n max_title_chars?: number;\n max_body_chars?: number;\n };\n privacy?: {\n data_policy?: 'coarse_only' | 'none' | 'extended';\n };\n}\n\n/**\n * Create a valid Opportunity object with safe defaults.\n *\n * Defaults:\n * - constraints.max_units = 1\n * - constraints.allowed_unit_types = ['sponsored_suggestion']\n * - privacy.data_policy = 'coarse_only'\n */\nexport function createOpportunity(params: CreateOpportunityParams): Opportunity {\n const opportunity: Opportunity = {\n intent: {\n taxonomy: params.taxonomy,\n },\n context: {\n country: params.country,\n language: params.language,\n platform: params.platform,\n },\n constraints: {\n max_units: params.constraints?.max_units ?? 1,\n allowed_unit_types: params.constraints?.allowed_unit_types ?? ['sponsored_suggestion'],\n },\n privacy: {\n data_policy: params.privacy?.data_policy ?? 'coarse_only',\n },\n };\n\n // Add optional intent fields\n if (params.query !== undefined) {\n opportunity.intent.query = params.query;\n }\n\n // Add optional context fields\n if (params.region !== undefined) {\n opportunity.context.region = params.region;\n }\n if (params.city !== undefined) {\n opportunity.context.city = params.city;\n }\n\n // Add optional constraint fields\n if (params.constraints?.blocked_categories !== undefined) {\n opportunity.constraints.blocked_categories = params.constraints.blocked_categories;\n }\n if (params.constraints?.max_title_chars !== undefined) {\n opportunity.constraints.max_title_chars = params.constraints.max_title_chars;\n }\n if (params.constraints?.max_body_chars !== undefined) {\n opportunity.constraints.max_body_chars = params.constraints.max_body_chars;\n }\n\n return opportunity;\n}\n\n// ============================================================================\n// Event Helpers\n// ============================================================================\n\nexport interface CreateImpressionEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create an impression event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createImpressionEvent(params: CreateImpressionEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return event;\n}\n\nexport interface CreateClickEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create a click event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createClickEvent(params: CreateClickEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n };\n\n // Include href in metadata\n if (params.metadata !== undefined) {\n event.metadata = {\n ...params.metadata,\n href: params.href,\n };\n } else {\n event.metadata = {\n href: params.href,\n };\n }\n\n return event;\n}\n\n// ============================================================================\n// Security & Sanitization Helpers\n// ============================================================================\n\n/**\n * HTML escape map - created once to avoid recreation on each call\n */\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#96;',\n};\n\n/**\n * Regex for matching HTML special characters - created once for performance\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`\\/]/g;\n\n/**\n * Maximum URL length to prevent DoS attacks\n */\nconst MAX_URL_LENGTH = 2048;\n\n/**\n * Dangerous URL protocols that must always be blocked\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'file:', 'vbscript:', 'blob:'];\n\n/**\n * Options for URL sanitization\n */\nexport interface SanitizeURLOptions {\n /**\n * Allow HTTP URLs (default: false, HTTPS only)\n */\n allowHttp?: boolean;\n /**\n * Allow tel: links (default: true)\n */\n allowTel?: boolean;\n /**\n * Allow mailto: links (default: true)\n */\n allowMailto?: boolean;\n /**\n * Callback for validation warnings (instead of console.warn)\n */\n onWarning?: (message: string, context: { url?: string; protocol?: string }) => void;\n}\n\n/**\n * Escape HTML special characters to prevent XSS attacks.\n *\n * Handles null/undefined safely by returning empty string.\n * Escapes: & < > \" ' / `\n *\n * Use this when displaying ad content (title, body, cta) in HTML contexts.\n *\n * @param text - Text to escape (can be null/undefined)\n * @returns Escaped HTML string (empty string if input is null/undefined)\n *\n * @example\n * ```typescript\n * const safeTitle = escapeHTML(unit.suggestion.title);\n * element.innerHTML = safeTitle; // Safe from XSS\n *\n * escapeHTML(null); // Returns ''\n * escapeHTML('<script>alert(1)</script>'); // Returns '&lt;script&gt;alert(1)&lt;/script&gt;'\n * ```\n */\nexport function escapeHTML(text: string | null | undefined): string {\n // Handle null/undefined safely\n if (text == null) {\n return '';\n }\n\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ESCAPES[char] || char);\n}\n\n/**\n * Sanitize and validate a URL to prevent XSS and phishing attacks.\n *\n * Handles null/undefined safely by returning null.\n * Blocks dangerous protocols like javascript:, data:, file:, blob:, vbscript:.\n * Validates URL length to prevent DoS attacks (max 2048 chars).\n * Handles protocol-relative URLs (//example.com).\n *\n * @param url - URL to sanitize (can be null/undefined)\n * @param options - Sanitization options\n * @returns Sanitized URL string, or null if invalid/dangerous\n *\n * @example\n * ```typescript\n * const safeURL = sanitizeURL(unit.suggestion.action_url);\n * if (safeURL) {\n * window.open(safeURL, '_blank');\n * }\n *\n * sanitizeURL(null); // Returns null\n * sanitizeURL('javascript:alert(1)'); // Returns null (blocked)\n * sanitizeURL('https://example.com'); // Returns 'https://example.com'\n * sanitizeURL('http://example.com', { allowHttp: true }); // Returns 'http://example.com'\n * ```\n */\nexport function sanitizeURL(\n url: string | null | undefined,\n options?: SanitizeURLOptions\n): string | null {\n // Handle null/undefined safely\n if (url == null) {\n return null;\n }\n\n const opts = {\n allowHttp: options?.allowHttp ?? false,\n allowTel: options?.allowTel ?? true,\n allowMailto: options?.allowMailto ?? true,\n onWarning: options?.onWarning ?? undefined,\n };\n\n try {\n const trimmedURL = url.trim();\n\n // Block empty URLs\n if (!trimmedURL) {\n return null;\n }\n\n // Validate URL length to prevent DoS\n if (trimmedURL.length > MAX_URL_LENGTH) {\n opts.onWarning?.('URL exceeds maximum length', { url: trimmedURL });\n return null;\n }\n\n // Handle protocol-relative URLs (//example.com)\n let parsedURL: URL;\n if (trimmedURL.startsWith('//')) {\n // Protocol-relative URLs need a base URL to parse\n try {\n parsedURL = new URL(trimmedURL, 'https://dummy-base.com');\n // Convert to absolute HTTPS URL\n return `https:${trimmedURL}`;\n } catch {\n opts.onWarning?.('Invalid protocol-relative URL', { url: trimmedURL });\n return null;\n }\n }\n\n // Parse URL\n parsedURL = new URL(trimmedURL);\n\n // Check protocol\n const protocol = parsedURL.protocol.toLowerCase();\n\n // Always block dangerous protocols\n if (DANGEROUS_PROTOCOLS.includes(protocol)) {\n opts.onWarning?.('Blocked dangerous URL protocol', { url: trimmedURL, protocol });\n return null;\n }\n\n // Allow HTTPS\n if (protocol === 'https:') {\n return trimmedURL;\n }\n\n // Conditionally allow HTTP\n if (protocol === 'http:') {\n if (opts.allowHttp) {\n return trimmedURL;\n } else {\n opts.onWarning?.('HTTP URL blocked. Use HTTPS or set allowHttp: true', {\n url: trimmedURL,\n protocol\n });\n return null;\n }\n }\n\n // Conditionally allow tel:\n if (protocol === 'tel:') {\n return opts.allowTel ? trimmedURL : null;\n }\n\n // Conditionally allow mailto:\n if (protocol === 'mailto:') {\n return opts.allowMailto ? trimmedURL : null;\n }\n\n // Block unknown protocols\n opts.onWarning?.('Unknown URL protocol blocked', { url: trimmedURL, protocol });\n return null;\n } catch (error) {\n // Invalid URL format\n opts.onWarning?.('Invalid URL format', { url });\n return null;\n }\n}\n","/**\n * Main SDK client for AttentionMarket Agent Ads API.\n * Provides public methods: decide(), decideRaw(), track(), getPolicy(), signupAgent()\n */\n\nimport { HTTPClient } from './http.js';\nimport { createImpressionEvent, createClickEvent, generateUUID } from './utils.js';\nimport type {\n SDKConfig,\n DecideRequest,\n DecideFromContextRequest,\n DecideResponse,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n AgentSignupRequest,\n AgentSignupResponse,\n AdUnit,\n} from './types.js';\nimport type { CreateImpressionEventParams, CreateClickEventParams } from './utils.js';\n\n// Default configuration (points to AttentionMarket production API)\n// Developers can override with their own backend if self-hosting\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai/v1';\nconst DEFAULT_TIMEOUT_MS = 4000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nexport class AttentionMarketClient {\n private http: HTTPClient;\n private agentId: string | undefined;\n\n constructor(config: SDKConfig) {\n this.agentId = config.agentId;\n // Validate configuration\n this.validateConfig(config);\n\n const httpConfig: {\n apiKey?: string;\n supabaseAnonKey?: string;\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n } = {\n apiKey: config.apiKey,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n\n // Only add supabaseAnonKey if provided\n if (config.supabaseAnonKey !== undefined) {\n httpConfig.supabaseAnonKey = config.supabaseAnonKey;\n }\n\n this.http = new HTTPClient(httpConfig);\n }\n\n /**\n * Validate SDK configuration for security\n */\n private validateConfig(config: SDKConfig): void {\n // Validate base URL uses HTTPS\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n if (!baseUrl.startsWith('https://')) {\n throw new Error(\n 'Security Error: baseUrl must use HTTPS. ' +\n 'HTTP connections expose your API key to man-in-the-middle attacks. ' +\n `Received: ${baseUrl}`\n );\n }\n\n // Validate API key format (if provided)\n if (config.apiKey) {\n const apiKeyPattern = /^am_(live|test)_[a-zA-Z0-9]+$/;\n if (!apiKeyPattern.test(config.apiKey)) {\n console.warn(\n 'Warning: API key does not match expected format (am_live_... or am_test_...). ' +\n 'This may indicate an invalid or compromised key.'\n );\n }\n }\n }\n\n /**\n * Request a sponsored unit decision for an agent opportunity.\n * Returns the full DecideResponse including status, ttl_ms, and all units.\n */\n async decideRaw(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<DecideResponse> {\n const requestOptions: {\n body: DecideRequest;\n idempotencyKey?: string;\n } = { body: request };\n\n if (options?.idempotencyKey !== undefined) {\n requestOptions.idempotencyKey = options.idempotencyKey;\n }\n\n return await this.http.request<DecideResponse>('POST', '/v1/decide', requestOptions);\n }\n\n /**\n * Convenience wrapper around decideRaw().\n * Returns null if status is no_fill, otherwise returns the first ad unit.\n */\n async decide(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n const response = await this.decideRaw(request, options);\n\n if (response.status === 'no_fill') {\n return null;\n }\n\n // Return first unit if available\n return response.units[0] ?? null;\n }\n\n /**\n * Simplified ad matching using conversation context and semantic search.\n * Automatically handles request construction, taxonomy fallback, and defaults.\n *\n * Requires: agentId in SDKConfig constructor\n *\n * @example\n * const ad = await client.decideFromContext({\n * userMessage: \"My father passed away and I need help organizing his estate\",\n * placement: 'sponsored_suggestion'\n * });\n *\n * @throws {Error} If agentId was not provided in SDKConfig\n */\n async decideFromContext(\n params: DecideFromContextRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n // Validate agentId is available\n if (!this.agentId) {\n throw new Error(\n 'decideFromContext() requires agentId to be set in SDKConfig. ' +\n 'Either provide agentId in the constructor or use decide() directly.'\n );\n }\n\n // Limit conversation history to last 5 messages to avoid token overflow\n const historyLimit = 5;\n const history = params.conversationHistory || [];\n const limitedHistory = history.slice(-historyLimit);\n\n // Build context string from user message + limited history\n const contextParts = [...limitedHistory, params.userMessage];\n const context = contextParts.join('\\n');\n\n // Use provided values or sensible defaults\n const country = params.country || 'US';\n const language = params.language || 'en';\n const platform = params.platform || 'web';\n const placementType = params.placement || 'sponsored_suggestion';\n\n // Build taxonomy from suggestedCategory or use fallback\n // Backend semantic matching doesn't strictly require valid taxonomy\n const taxonomy = params.suggestedCategory || 'unknown';\n\n // Build full DecideRequest with semantic context\n const request: DecideRequest = {\n request_id: generateUUID(),\n agent_id: this.agentId,\n placement: {\n type: placementType,\n surface: 'chat'\n },\n opportunity: {\n intent: {\n taxonomy,\n query: params.userMessage\n },\n context: {\n country,\n language,\n platform\n },\n constraints: {\n max_units: 1,\n allowed_unit_types: [placementType]\n },\n privacy: {\n data_policy: 'coarse_only'\n }\n },\n context,\n user_intent: params.userMessage\n };\n\n return await this.decide(request, options);\n }\n\n /**\n * Report an event (impression, click, action, conversion, feedback).\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n return await this.http.request<EventIngestResponse>('POST', '/v1/event', {\n body: event,\n });\n }\n\n /**\n * Convenience method to track an impression event.\n * Creates an impression event using createImpressionEvent() and calls track().\n */\n async trackImpression(\n params: Omit<CreateImpressionEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createImpressionEvent(params);\n return await this.track(event);\n }\n\n /**\n * Convenience method to track a click event.\n * Creates a click event using createClickEvent() and calls track().\n */\n async trackClick(\n params: Omit<CreateClickEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createClickEvent(params);\n return await this.track(event);\n }\n\n /**\n * Fetch default policy constraints and formatting requirements.\n */\n async getPolicy(): Promise<PolicyResponse> {\n return await this.http.request<PolicyResponse>('GET', '/v1/policy');\n }\n\n /**\n * Create or register an agent (optional helper).\n * Note: This endpoint is unauthenticated in v1.\n */\n static async signupAgent(\n request: AgentSignupRequest,\n options?: { baseUrl?: string },\n ): Promise<AgentSignupResponse> {\n // Create temporary HTTP client without authentication\n const http = new HTTPClient({\n baseUrl: options?.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxRetries: DEFAULT_MAX_RETRIES,\n });\n\n return await http.request<AgentSignupResponse>('POST', '/v1/agent-signup', {\n body: request,\n });\n }\n}\n","/**\n * Mock AttentionMarket Client for Testing\n *\n * Use this client during development to test your integration without real advertiser data.\n * Returns realistic mock ad units for common taxonomies.\n */\n\nimport type {\n DecideRequest,\n DecideResponse,\n AdUnit,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n} from './types.js';\nimport { generateUUID, generateTimestamp } from './utils.js';\n\nexport interface MockClientConfig {\n /**\n * Simulate API latency (milliseconds)\n * @default 100\n */\n latencyMs?: number;\n\n /**\n * Fill rate (0.0 - 1.0) - probability of returning an ad\n * @default 1.0 (always fill)\n */\n fillRate?: number;\n\n /**\n * Log mock calls to console\n * @default true\n */\n verbose?: boolean;\n}\n\n/**\n * Mock client for testing SDK integration without real advertiser data\n *\n * @example\n * ```typescript\n * import { MockAttentionMarketClient } from '@the_ro_show/agent-ads-sdk';\n *\n * const client = new MockAttentionMarketClient();\n * const unit = await client.decide({...});\n * ```\n */\nexport class MockAttentionMarketClient {\n private config: Required<MockClientConfig>;\n private mockUnits: Record<string, AdUnit>;\n\n constructor(config: MockClientConfig = {}) {\n this.config = {\n latencyMs: config.latencyMs ?? 100,\n fillRate: config.fillRate ?? 1.0,\n verbose: config.verbose ?? true,\n };\n\n // Seed data for common taxonomies\n this.mockUnits = {\n 'local_services.movers.quote': {\n unit_id: 'unit_mock_movers_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Brooklyn Premium Movers',\n },\n tracking: {\n token: 'trk_mock_movers_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/movers',\n click_url: 'https://mock.attentionmarket.com/click/movers',\n },\n suggestion: {\n title: 'Professional Moving Services - Same Day Available',\n body: 'Licensed & insured movers serving Brooklyn since 2015. Free on-site estimates, packing services, and storage options. Rated 4.9/5 stars.',\n cta: 'Get Free Quote โ†’',\n action_url: 'https://demo-movers.example.com/quote?ref=am_mock',\n },\n },\n 'local_services.restaurants.search': {\n unit_id: 'unit_mock_restaurant_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: \"Tony's Italian Kitchen\",\n },\n tracking: {\n token: 'trk_mock_restaurant_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/restaurant',\n click_url: 'https://mock.attentionmarket.com/click/restaurant',\n },\n suggestion: {\n title: \"Tony's Italian Kitchen - Authentic NYC Italian\",\n body: 'โญ๏ธ 4.8/5 stars โ€ข Midtown Manhattan โ€ข Reservations available tonight. Try our signature wood-fired pizza and homemade pasta.',\n cta: 'Reserve Table โ†’',\n action_url: 'https://demo-restaurant.example.com/reserve?ref=am_mock',\n },\n },\n 'local_services.plumbers.quote': {\n unit_id: 'unit_mock_plumber_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: '24/7 Emergency Plumbing',\n },\n tracking: {\n token: 'trk_mock_plumber_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/plumber',\n click_url: 'https://mock.attentionmarket.com/click/plumber',\n },\n suggestion: {\n title: 'Emergency Plumber - 30 Min Response Time',\n body: 'Licensed plumbers available 24/7 across NYC. Free estimates. No overtime charges. Same-day service guaranteed.',\n cta: 'Call Now โ†’',\n action_url: 'tel:+1-555-PLUMBER',\n },\n },\n 'local_services.electricians.quote': {\n unit_id: 'unit_mock_electrician_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Spark Electric Co',\n },\n tracking: {\n token: 'trk_mock_electrician_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electrician',\n click_url: 'https://mock.attentionmarket.com/click/electrician',\n },\n suggestion: {\n title: 'Licensed Electrician - Free Safety Inspection',\n body: 'Certified electricians for residential & commercial. Emergency service available. 10-year warranty on all work.',\n cta: 'Schedule Service โ†’',\n action_url: 'https://demo-electrician.example.com/schedule?ref=am_mock',\n },\n },\n 'local_services.cleaners.quote': {\n unit_id: 'unit_mock_cleaner_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Sparkle Clean NYC',\n },\n tracking: {\n token: 'trk_mock_cleaner_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/cleaner',\n click_url: 'https://mock.attentionmarket.com/click/cleaner',\n },\n suggestion: {\n title: 'Professional Home Cleaning - $99 First Visit',\n body: 'Eco-friendly cleaning products. Background-checked staff. Satisfaction guaranteed or your money back.',\n cta: 'Book Cleaning โ†’',\n action_url: 'https://demo-cleaners.example.com/book?ref=am_mock',\n },\n },\n 'shopping.electronics.search': {\n unit_id: 'unit_mock_electronics_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'TechDeals Pro',\n },\n tracking: {\n token: 'trk_mock_electronics_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electronics',\n click_url: 'https://mock.attentionmarket.com/click/electronics',\n },\n suggestion: {\n title: 'Latest Laptops & Electronics - Up to 40% Off',\n body: 'Free shipping on orders over $50. 30-day returns. Price match guarantee. Shop top brands.',\n cta: 'Shop Deals โ†’',\n action_url: 'https://demo-electronics.example.com/deals?ref=am_mock',\n },\n },\n };\n }\n\n /**\n * Simulate network latency\n */\n private async simulateLatency(): Promise<void> {\n if (this.config.latencyMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, this.config.latencyMs));\n }\n }\n\n /**\n * Check if should fill based on fill rate\n */\n private shouldFill(): boolean {\n return Math.random() < this.config.fillRate;\n }\n\n /**\n * Log mock activity\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.verbose) {\n console.log(`๐Ÿงช [MockClient] ${message}`, ...args);\n }\n }\n\n /**\n * Request a sponsored unit (convenience method)\n */\n async decide(request: DecideRequest): Promise<AdUnit | null> {\n const response = await this.decideRaw(request);\n if (response.status === 'filled' && response.units.length > 0) {\n return response.units[0] || null;\n }\n return null;\n }\n\n /**\n * Request sponsored units (full response)\n */\n async decideRaw(request: DecideRequest): Promise<DecideResponse> {\n await this.simulateLatency();\n\n const taxonomy = request.opportunity.intent.taxonomy;\n this.log('decide()', {\n taxonomy,\n placement: request.placement.type,\n });\n\n // Check fill rate\n if (!this.shouldFill()) {\n this.log('No fill (simulated by fillRate)', { fillRate: this.config.fillRate });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n // Get mock unit for taxonomy\n const unit = this.mockUnits[taxonomy];\n\n if (unit) {\n const title = unit.unit_type === 'sponsored_suggestion'\n ? unit.suggestion.title\n : unit.tool.tool_name;\n\n this.log('Returning mock ad', {\n sponsor: unit.disclosure.sponsor_name,\n title,\n });\n\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'filled',\n units: [unit],\n ttl_ms: 60000,\n };\n }\n\n this.log('No mock data for taxonomy', { taxonomy });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n /**\n * Track an event\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n await this.simulateLatency();\n this.log('track()', { event_type: event.event_type, unit_id: event.unit_id });\n\n return {\n accepted: true,\n };\n }\n\n /**\n * Track impression (convenience method)\n */\n async trackImpression(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n }): Promise<EventIngestResponse> {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return this.track(event);\n }\n\n /**\n * Track click (convenience method)\n */\n async trackClick(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n }): Promise<EventIngestResponse> {\n return this.track({\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n metadata: {\n href: params.href,\n },\n });\n }\n\n /**\n * Get policy\n */\n async getPolicy(): Promise<PolicyResponse> {\n await this.simulateLatency();\n this.log('getPolicy()');\n\n return {\n version: '1.0.0',\n defaults: {\n max_units_per_response: 1,\n blocked_categories: [],\n },\n disclosure: {\n required: true,\n label: 'Sponsored',\n require_sponsor_name: true,\n },\n unit_rules: {},\n };\n }\n\n /**\n * Add custom mock ad unit for testing\n */\n addMockUnit(taxonomy: string, unit: AdUnit): void {\n this.mockUnits[taxonomy] = unit;\n this.log('Added custom mock unit', { taxonomy });\n }\n\n /**\n * Get list of available mock taxonomies\n */\n getAvailableTaxonomies(): string[] {\n return Object.keys(this.mockUnits);\n }\n}\n","/**\n * Taxonomy helper utilities for AttentionMarket SDK\n * Helps with building, validating, and working with the 4-tier taxonomy system\n */\n\n// Valid intent modifiers\nexport type TaxonomyIntent =\n | 'research' // Learning, browsing (low intent)\n | 'compare' // Evaluating options (medium intent)\n | 'quote' // Getting prices (high intent)\n | 'trial' // Free trial signup (high intent)\n | 'book' // Schedule/purchase (very high intent)\n | 'apply' // Application process (very high intent)\n | 'consultation'; // Schedule meeting (very high intent)\n\n/**\n * Build a valid taxonomy string\n *\n * @param vertical - Industry vertical (e.g., \"insurance\", \"business\", \"healthcare\")\n * @param category - Product/service category (e.g., \"auto\", \"saas\", \"dental\")\n * @param subcategory - Specific offering (e.g., \"full_coverage\", \"crm\", \"cosmetic\")\n * @param intent - Optional user journey stage\n * @returns Properly formatted taxonomy string\n *\n * @example\n * buildTaxonomy('insurance', 'auto', 'full_coverage', 'quote')\n * // Returns: 'insurance.auto.full_coverage.quote'\n *\n * @example\n * buildTaxonomy('business', 'saas', 'crm', 'trial')\n * // Returns: 'business.saas.crm.trial'\n */\nexport function buildTaxonomy(\n vertical: string,\n category: string,\n subcategory: string,\n intent?: TaxonomyIntent\n): string {\n const parts = [vertical, category, subcategory];\n if (intent) {\n parts.push(intent);\n }\n return parts.join('.');\n}\n\n/**\n * Detect user intent from query string\n *\n * Analyzes the user's query to determine their stage in the buying journey.\n * Returns the most appropriate intent modifier.\n *\n * @param query - User's search query or question\n * @returns Detected intent modifier\n *\n * @example\n * detectIntent(\"What is term life insurance?\")\n * // Returns: 'research'\n *\n * @example\n * detectIntent(\"Get car insurance quote\")\n * // Returns: 'quote'\n *\n * @example\n * detectIntent(\"Best CRM software comparison\")\n * // Returns: 'compare'\n */\nexport function detectIntent(query: string): TaxonomyIntent {\n const lowerQuery = query.toLowerCase();\n\n // Research intent - learning/information gathering\n if (/what is|how does|how do|learn about|tell me about|explain|understand|definition/i.test(lowerQuery)) {\n return 'research';\n }\n\n // Compare intent - evaluating options\n if (/best|compare|vs|versus|which|top|options|alternatives|review|recommend/i.test(lowerQuery)) {\n return 'compare';\n }\n\n // Quote intent - ready to get prices\n if (/price|cost|how much|quote|estimate|pricing|rate|afford/i.test(lowerQuery)) {\n return 'quote';\n }\n\n // Trial intent - wants to try before buying\n if (/try|demo|free trial|test|preview|sample/i.test(lowerQuery)) {\n return 'trial';\n }\n\n // Book intent - ready to schedule/purchase\n if (/book|schedule|appointment|reserve|set up|make appointment/i.test(lowerQuery)) {\n return 'book';\n }\n\n // Apply intent - application/signup\n if (/apply|sign up|get started|register|enroll|join/i.test(lowerQuery)) {\n return 'apply';\n }\n\n // Consultation intent - wants to talk to someone\n if (/talk to|speak with|consult|meet with|call|contact|discuss/i.test(lowerQuery)) {\n return 'consultation';\n }\n\n // Default to compare (most common)\n return 'compare';\n}\n\n/**\n * Validate taxonomy format\n *\n * Checks if a taxonomy string follows the correct format:\n * - 3 or 4 parts separated by dots\n * - All parts are lowercase alphanumeric with underscores\n * - If 4 parts, last part must be a valid intent\n *\n * @param taxonomy - Taxonomy string to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * isValidTaxonomy('insurance.auto.full_coverage.quote') // true\n * isValidTaxonomy('insurance.auto') // false (too short)\n * isValidTaxonomy('Insurance.Auto.Quote') // false (uppercase)\n * isValidTaxonomy('insurance.auto.full_coverage.invalid') // false (invalid intent)\n */\nexport function isValidTaxonomy(taxonomy: string): boolean {\n const parts = taxonomy.split('.');\n\n // Must be 3 or 4 parts\n if (parts.length < 3 || parts.length > 4) {\n return false;\n }\n\n // Check valid intent (if present)\n const validIntents: TaxonomyIntent[] = [\n 'research', 'compare', 'quote', 'trial',\n 'book', 'apply', 'consultation'\n ];\n\n if (parts.length === 4 && !validIntents.includes(parts[3] as TaxonomyIntent)) {\n return false;\n }\n\n // Check each part is non-empty and alphanumeric + underscore\n return parts.every(part => /^[a-z0-9_]+$/.test(part));\n}\n\n/**\n * Parsed taxonomy components\n */\nexport interface ParsedTaxonomy {\n /** Industry vertical (tier 1) */\n vertical: string;\n /** Product/service category (tier 2) */\n category: string;\n /** Specific offering (tier 3) */\n subcategory: string;\n /** User journey stage (tier 4, optional) */\n intent?: TaxonomyIntent;\n /** Full taxonomy string */\n full: string;\n}\n\n/**\n * Parse taxonomy into components\n *\n * Breaks down a taxonomy string into its constituent parts for analysis.\n *\n * @param taxonomy - Taxonomy string to parse\n * @returns Parsed components or null if invalid\n *\n * @example\n * parseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: {\n * // vertical: 'insurance',\n * // category: 'auto',\n * // subcategory: 'full_coverage',\n * // intent: 'quote',\n * // full: 'insurance.auto.full_coverage.quote'\n * // }\n */\nexport function parseTaxonomy(taxonomy: string): ParsedTaxonomy | null {\n if (!isValidTaxonomy(taxonomy)) {\n return null;\n }\n\n const parts = taxonomy.split('.');\n const result: ParsedTaxonomy = {\n vertical: parts[0]!,\n category: parts[1]!,\n subcategory: parts[2]!,\n full: taxonomy\n };\n\n if (parts[3]) {\n result.intent = parts[3] as TaxonomyIntent;\n }\n\n return result;\n}\n\n/**\n * Get taxonomy without intent modifier\n *\n * Useful for broader matching or grouping taxonomies by product/service.\n *\n * @param taxonomy - Full taxonomy string\n * @returns Taxonomy without intent, or null if invalid\n *\n * @example\n * getBaseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance.auto.full_coverage'\n */\nexport function getBaseTaxonomy(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n if (!parsed) return null;\n\n return `${parsed.vertical}.${parsed.category}.${parsed.subcategory}`;\n}\n\n/**\n * Check if two taxonomies match hierarchically\n *\n * Returns true if they share the same vertical, category, and subcategory\n * (regardless of intent).\n *\n * @param taxonomy1 - First taxonomy\n * @param taxonomy2 - Second taxonomy\n * @returns True if they match hierarchically\n *\n * @example\n * matchesTaxonomy(\n * 'insurance.auto.full_coverage.quote',\n * 'insurance.auto.full_coverage.apply'\n * )\n * // Returns: true (same base, different intent)\n */\nexport function matchesTaxonomy(taxonomy1: string, taxonomy2: string): boolean {\n const base1 = getBaseTaxonomy(taxonomy1);\n const base2 = getBaseTaxonomy(taxonomy2);\n\n return base1 !== null && base2 !== null && base1 === base2;\n}\n\n/**\n * Get vertical from taxonomy\n *\n * Extracts just the industry vertical (tier 1).\n *\n * @param taxonomy - Taxonomy string\n * @returns Vertical or null if invalid\n *\n * @example\n * getVertical('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance'\n */\nexport function getVertical(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n return parsed ? parsed.vertical : null;\n}\n\n/**\n * Suggest taxonomies based on user query\n *\n * Analyzes a user query and suggests appropriate taxonomies.\n * This is a basic implementation - you may want to enhance it\n * with ML/NLP for better accuracy.\n *\n * @param query - User's search query\n * @returns Array of suggested taxonomy strings\n *\n * @example\n * suggestTaxonomies(\"I need car insurance\")\n * // Returns: ['insurance.auto.full_coverage.quote', 'insurance.auto.liability.quote']\n */\nexport function suggestTaxonomies(query: string): string[] {\n const lowerQuery = query.toLowerCase();\n const intent = detectIntent(query);\n const suggestions: string[] = [];\n\n // Insurance-related\n if (/car|auto|vehicle|drive|driving/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'auto', 'full_coverage', intent));\n suggestions.push(buildTaxonomy('insurance', 'auto', 'liability', intent));\n }\n if (/health|medical|doctor/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'health', 'individual', intent));\n }\n if (/life insurance/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'life', 'term', intent));\n }\n\n // Business/SaaS\n if (/crm|customer|sales|lead/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'crm', intent));\n }\n if (/online store|ecommerce|sell online/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'ecommerce', 'platform', intent));\n }\n if (/project management|task|team/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'project_management', intent));\n }\n\n // Legal\n if (/accident|injury|hurt/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'personal_injury', 'accident', intent));\n }\n if (/divorce|custody|family law/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'family_law', 'divorce', intent));\n }\n\n // Healthcare\n if (/dentist|teeth|dental/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'dental', 'general', intent));\n }\n if (/therapist|therapy|counseling/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'mental_health', 'therapy', intent));\n }\n\n // Home services\n if (/mover|moving|relocate/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'moving', 'local', intent));\n }\n if (/plumber|plumbing|leak/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'plumbing', 'emergency', intent));\n }\n if (/clean|cleaning|maid/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'cleaning', 'regular', intent));\n }\n\n return suggestions;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -308,7 +308,9 @@ var DEFAULT_TIMEOUT_MS = 4e3;
308
308
  var DEFAULT_MAX_RETRIES = 2;
309
309
  var AttentionMarketClient = class {
310
310
  http;
311
+ agentId;
311
312
  constructor(config) {
313
+ this.agentId = config.agentId;
312
314
  this.validateConfig(config);
313
315
  const httpConfig = {
314
316
  apiKey: config.apiKey,
@@ -362,6 +364,66 @@ var AttentionMarketClient = class {
362
364
  }
363
365
  return response.units[0] ?? null;
364
366
  }
367
+ /**
368
+ * Simplified ad matching using conversation context and semantic search.
369
+ * Automatically handles request construction, taxonomy fallback, and defaults.
370
+ *
371
+ * Requires: agentId in SDKConfig constructor
372
+ *
373
+ * @example
374
+ * const ad = await client.decideFromContext({
375
+ * userMessage: "My father passed away and I need help organizing his estate",
376
+ * placement: 'sponsored_suggestion'
377
+ * });
378
+ *
379
+ * @throws {Error} If agentId was not provided in SDKConfig
380
+ */
381
+ async decideFromContext(params, options) {
382
+ if (!this.agentId) {
383
+ throw new Error(
384
+ "decideFromContext() requires agentId to be set in SDKConfig. Either provide agentId in the constructor or use decide() directly."
385
+ );
386
+ }
387
+ const historyLimit = 5;
388
+ const history = params.conversationHistory || [];
389
+ const limitedHistory = history.slice(-historyLimit);
390
+ const contextParts = [...limitedHistory, params.userMessage];
391
+ const context = contextParts.join("\n");
392
+ const country = params.country || "US";
393
+ const language = params.language || "en";
394
+ const platform = params.platform || "web";
395
+ const placementType = params.placement || "sponsored_suggestion";
396
+ const taxonomy = params.suggestedCategory || "unknown";
397
+ const request = {
398
+ request_id: generateUUID(),
399
+ agent_id: this.agentId,
400
+ placement: {
401
+ type: placementType,
402
+ surface: "chat"
403
+ },
404
+ opportunity: {
405
+ intent: {
406
+ taxonomy,
407
+ query: params.userMessage
408
+ },
409
+ context: {
410
+ country,
411
+ language,
412
+ platform
413
+ },
414
+ constraints: {
415
+ max_units: 1,
416
+ allowed_unit_types: [placementType]
417
+ },
418
+ privacy: {
419
+ data_policy: "coarse_only"
420
+ }
421
+ },
422
+ context,
423
+ user_intent: params.userMessage
424
+ };
425
+ return await this.decide(request, options);
426
+ }
365
427
  /**
366
428
  * Report an event (impression, click, action, conversion, feedback).
367
429
  */
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.ts","../src/taxonomy-utils.ts"],"sourcesContent":["/**\n * Error classes for AttentionMarket SDK.\n * Surfaces HTTP status, API error codes, messages, and request_id.\n */\n\nimport type { APIError } from './types.js';\n\nexport class AttentionMarketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AttentionMarketError';\n Object.setPrototypeOf(this, AttentionMarketError.prototype);\n }\n}\n\nexport class APIRequestError extends AttentionMarketError {\n public readonly statusCode: number;\n public readonly errorCode: string;\n public readonly requestId?: string;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n statusCode: number,\n apiError: APIError,\n ) {\n super(apiError.message);\n this.name = 'APIRequestError';\n this.statusCode = statusCode;\n this.errorCode = apiError.error;\n if (apiError.request_id !== undefined) {\n this.requestId = apiError.request_id;\n }\n if (apiError.details !== undefined) {\n this.details = apiError.details;\n }\n Object.setPrototypeOf(this, APIRequestError.prototype);\n }\n}\n\nexport class NetworkError extends AttentionMarketError {\n public readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'NetworkError';\n if (cause !== undefined) {\n this.cause = cause;\n }\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class TimeoutError extends AttentionMarketError {\n constructor(message: string = 'Request timed out') {\n super(message);\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n","/**\n * HTTP client with retry logic, timeout handling, and authentication.\n * Retry policy: only on 408, 429, 500, 502, 503, 504\n * Max retries: 2 (configurable)\n * Backoff: exponential with jitter\n */\n\nimport { APIRequestError, NetworkError, TimeoutError } from './errors.js';\nimport type { APIError } from './types.js';\n\nexport interface HTTPClientConfig {\n apiKey?: string; // AttentionMarket API key (am_live_* or am_test_*)\n supabaseAnonKey?: string; // Supabase anon key for infrastructure auth\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n}\n\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class HTTPClient {\n constructor(private config: HTTPClientConfig) {}\n\n async request<T>(\n method: 'GET' | 'POST',\n path: string,\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n } = {},\n ): Promise<T> {\n const url = `${this.config.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(url, method, options);\n return response as T;\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry if it's not a retryable error\n if (!this.shouldRetry(error, attempt)) {\n throw error;\n }\n\n // Wait before retrying\n if (attempt < this.config.maxRetries) {\n await this.backoff(attempt);\n }\n }\n }\n\n // All retries exhausted\n throw lastError;\n }\n\n private async makeRequest(\n url: string,\n method: 'GET' | 'POST',\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n },\n ): Promise<unknown> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n };\n\n // Send Supabase anon key in Authorization (for infrastructure)\n if (this.config.supabaseAnonKey) {\n headers['Authorization'] = `Bearer ${this.config.supabaseAnonKey}`;\n }\n\n // Send AttentionMarket API key in custom header (for app-level auth)\n if (this.config.apiKey) {\n headers['X-AM-API-Key'] = this.config.apiKey;\n }\n\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Handle non-OK responses\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({\n error: 'unknown_error',\n message: 'Failed to parse error response',\n request_id: 'unknown',\n }));\n\n throw new APIRequestError(response.status, errorBody as APIError);\n }\n\n // Parse successful response\n return await response.json();\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle abort (timeout)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n\n // Re-throw API errors\n if (error instanceof APIRequestError) {\n throw error;\n }\n\n // Network errors\n throw new NetworkError('Network request failed', error as Error);\n }\n }\n\n private shouldRetry(error: unknown, attempt: number): boolean {\n // Don't retry if we've exhausted attempts\n if (attempt >= this.config.maxRetries) {\n return false;\n }\n\n // Retry on timeout\n if (error instanceof TimeoutError) {\n return true;\n }\n\n // Retry on network errors\n if (error instanceof NetworkError) {\n return true;\n }\n\n // Retry on specific HTTP status codes\n if (error instanceof APIRequestError) {\n return RETRYABLE_STATUS_CODES.has(error.statusCode);\n }\n\n return false;\n }\n\n private async backoff(attempt: number): Promise<void> {\n // Exponential backoff: 100ms * 2^attempt + jitter\n const baseDelay = 100 * Math.pow(2, attempt);\n const jitter = Math.random() * 100;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","/**\n * Utility functions for the AttentionMarket SDK.\n * Includes UUID generation, opportunity helpers, and event builders.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { EventIngestRequest, Opportunity } from './types.js';\n\n/**\n * Generate a UUID v4 for use as event_id, request_id, etc.\n */\nexport function generateUUID(): string {\n return randomUUID();\n}\n\n/**\n * Generate an ISO 8601 timestamp for the current moment.\n */\nexport function generateTimestamp(): string {\n return new Date().toISOString();\n}\n\n// ============================================================================\n// Opportunity Helper\n// ============================================================================\n\nexport interface CreateOpportunityParams {\n // Required\n taxonomy: string;\n country: string;\n language: string;\n platform: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';\n\n // Optional\n query?: string;\n region?: string;\n city?: string;\n\n // Optional overrides (with defaults)\n constraints?: {\n max_units?: number;\n allowed_unit_types?: ('sponsored_suggestion' | 'sponsored_block' | 'sponsored_tool')[];\n blocked_categories?: string[];\n max_title_chars?: number;\n max_body_chars?: number;\n };\n privacy?: {\n data_policy?: 'coarse_only' | 'none' | 'extended';\n };\n}\n\n/**\n * Create a valid Opportunity object with safe defaults.\n *\n * Defaults:\n * - constraints.max_units = 1\n * - constraints.allowed_unit_types = ['sponsored_suggestion']\n * - privacy.data_policy = 'coarse_only'\n */\nexport function createOpportunity(params: CreateOpportunityParams): Opportunity {\n const opportunity: Opportunity = {\n intent: {\n taxonomy: params.taxonomy,\n },\n context: {\n country: params.country,\n language: params.language,\n platform: params.platform,\n },\n constraints: {\n max_units: params.constraints?.max_units ?? 1,\n allowed_unit_types: params.constraints?.allowed_unit_types ?? ['sponsored_suggestion'],\n },\n privacy: {\n data_policy: params.privacy?.data_policy ?? 'coarse_only',\n },\n };\n\n // Add optional intent fields\n if (params.query !== undefined) {\n opportunity.intent.query = params.query;\n }\n\n // Add optional context fields\n if (params.region !== undefined) {\n opportunity.context.region = params.region;\n }\n if (params.city !== undefined) {\n opportunity.context.city = params.city;\n }\n\n // Add optional constraint fields\n if (params.constraints?.blocked_categories !== undefined) {\n opportunity.constraints.blocked_categories = params.constraints.blocked_categories;\n }\n if (params.constraints?.max_title_chars !== undefined) {\n opportunity.constraints.max_title_chars = params.constraints.max_title_chars;\n }\n if (params.constraints?.max_body_chars !== undefined) {\n opportunity.constraints.max_body_chars = params.constraints.max_body_chars;\n }\n\n return opportunity;\n}\n\n// ============================================================================\n// Event Helpers\n// ============================================================================\n\nexport interface CreateImpressionEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create an impression event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createImpressionEvent(params: CreateImpressionEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return event;\n}\n\nexport interface CreateClickEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create a click event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createClickEvent(params: CreateClickEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n };\n\n // Include href in metadata\n if (params.metadata !== undefined) {\n event.metadata = {\n ...params.metadata,\n href: params.href,\n };\n } else {\n event.metadata = {\n href: params.href,\n };\n }\n\n return event;\n}\n\n// ============================================================================\n// Security & Sanitization Helpers\n// ============================================================================\n\n/**\n * HTML escape map - created once to avoid recreation on each call\n */\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#96;',\n};\n\n/**\n * Regex for matching HTML special characters - created once for performance\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`\\/]/g;\n\n/**\n * Maximum URL length to prevent DoS attacks\n */\nconst MAX_URL_LENGTH = 2048;\n\n/**\n * Dangerous URL protocols that must always be blocked\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'file:', 'vbscript:', 'blob:'];\n\n/**\n * Options for URL sanitization\n */\nexport interface SanitizeURLOptions {\n /**\n * Allow HTTP URLs (default: false, HTTPS only)\n */\n allowHttp?: boolean;\n /**\n * Allow tel: links (default: true)\n */\n allowTel?: boolean;\n /**\n * Allow mailto: links (default: true)\n */\n allowMailto?: boolean;\n /**\n * Callback for validation warnings (instead of console.warn)\n */\n onWarning?: (message: string, context: { url?: string; protocol?: string }) => void;\n}\n\n/**\n * Escape HTML special characters to prevent XSS attacks.\n *\n * Handles null/undefined safely by returning empty string.\n * Escapes: & < > \" ' / `\n *\n * Use this when displaying ad content (title, body, cta) in HTML contexts.\n *\n * @param text - Text to escape (can be null/undefined)\n * @returns Escaped HTML string (empty string if input is null/undefined)\n *\n * @example\n * ```typescript\n * const safeTitle = escapeHTML(unit.suggestion.title);\n * element.innerHTML = safeTitle; // Safe from XSS\n *\n * escapeHTML(null); // Returns ''\n * escapeHTML('<script>alert(1)</script>'); // Returns '&lt;script&gt;alert(1)&lt;/script&gt;'\n * ```\n */\nexport function escapeHTML(text: string | null | undefined): string {\n // Handle null/undefined safely\n if (text == null) {\n return '';\n }\n\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ESCAPES[char] || char);\n}\n\n/**\n * Sanitize and validate a URL to prevent XSS and phishing attacks.\n *\n * Handles null/undefined safely by returning null.\n * Blocks dangerous protocols like javascript:, data:, file:, blob:, vbscript:.\n * Validates URL length to prevent DoS attacks (max 2048 chars).\n * Handles protocol-relative URLs (//example.com).\n *\n * @param url - URL to sanitize (can be null/undefined)\n * @param options - Sanitization options\n * @returns Sanitized URL string, or null if invalid/dangerous\n *\n * @example\n * ```typescript\n * const safeURL = sanitizeURL(unit.suggestion.action_url);\n * if (safeURL) {\n * window.open(safeURL, '_blank');\n * }\n *\n * sanitizeURL(null); // Returns null\n * sanitizeURL('javascript:alert(1)'); // Returns null (blocked)\n * sanitizeURL('https://example.com'); // Returns 'https://example.com'\n * sanitizeURL('http://example.com', { allowHttp: true }); // Returns 'http://example.com'\n * ```\n */\nexport function sanitizeURL(\n url: string | null | undefined,\n options?: SanitizeURLOptions\n): string | null {\n // Handle null/undefined safely\n if (url == null) {\n return null;\n }\n\n const opts = {\n allowHttp: options?.allowHttp ?? false,\n allowTel: options?.allowTel ?? true,\n allowMailto: options?.allowMailto ?? true,\n onWarning: options?.onWarning ?? undefined,\n };\n\n try {\n const trimmedURL = url.trim();\n\n // Block empty URLs\n if (!trimmedURL) {\n return null;\n }\n\n // Validate URL length to prevent DoS\n if (trimmedURL.length > MAX_URL_LENGTH) {\n opts.onWarning?.('URL exceeds maximum length', { url: trimmedURL });\n return null;\n }\n\n // Handle protocol-relative URLs (//example.com)\n let parsedURL: URL;\n if (trimmedURL.startsWith('//')) {\n // Protocol-relative URLs need a base URL to parse\n try {\n parsedURL = new URL(trimmedURL, 'https://dummy-base.com');\n // Convert to absolute HTTPS URL\n return `https:${trimmedURL}`;\n } catch {\n opts.onWarning?.('Invalid protocol-relative URL', { url: trimmedURL });\n return null;\n }\n }\n\n // Parse URL\n parsedURL = new URL(trimmedURL);\n\n // Check protocol\n const protocol = parsedURL.protocol.toLowerCase();\n\n // Always block dangerous protocols\n if (DANGEROUS_PROTOCOLS.includes(protocol)) {\n opts.onWarning?.('Blocked dangerous URL protocol', { url: trimmedURL, protocol });\n return null;\n }\n\n // Allow HTTPS\n if (protocol === 'https:') {\n return trimmedURL;\n }\n\n // Conditionally allow HTTP\n if (protocol === 'http:') {\n if (opts.allowHttp) {\n return trimmedURL;\n } else {\n opts.onWarning?.('HTTP URL blocked. Use HTTPS or set allowHttp: true', {\n url: trimmedURL,\n protocol\n });\n return null;\n }\n }\n\n // Conditionally allow tel:\n if (protocol === 'tel:') {\n return opts.allowTel ? trimmedURL : null;\n }\n\n // Conditionally allow mailto:\n if (protocol === 'mailto:') {\n return opts.allowMailto ? trimmedURL : null;\n }\n\n // Block unknown protocols\n opts.onWarning?.('Unknown URL protocol blocked', { url: trimmedURL, protocol });\n return null;\n } catch (error) {\n // Invalid URL format\n opts.onWarning?.('Invalid URL format', { url });\n return null;\n }\n}\n","/**\n * Main SDK client for AttentionMarket Agent Ads API.\n * Provides public methods: decide(), decideRaw(), track(), getPolicy(), signupAgent()\n */\n\nimport { HTTPClient } from './http.js';\nimport { createImpressionEvent, createClickEvent } from './utils.js';\nimport type {\n SDKConfig,\n DecideRequest,\n DecideResponse,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n AgentSignupRequest,\n AgentSignupResponse,\n AdUnit,\n} from './types.js';\nimport type { CreateImpressionEventParams, CreateClickEventParams } from './utils.js';\n\n// Default configuration (points to AttentionMarket production API)\n// Developers can override with their own backend if self-hosting\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai/v1';\nconst DEFAULT_TIMEOUT_MS = 4000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nexport class AttentionMarketClient {\n private http: HTTPClient;\n\n constructor(config: SDKConfig) {\n // Validate configuration\n this.validateConfig(config);\n\n const httpConfig: {\n apiKey?: string;\n supabaseAnonKey?: string;\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n } = {\n apiKey: config.apiKey,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n\n // Only add supabaseAnonKey if provided\n if (config.supabaseAnonKey !== undefined) {\n httpConfig.supabaseAnonKey = config.supabaseAnonKey;\n }\n\n this.http = new HTTPClient(httpConfig);\n }\n\n /**\n * Validate SDK configuration for security\n */\n private validateConfig(config: SDKConfig): void {\n // Validate base URL uses HTTPS\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n if (!baseUrl.startsWith('https://')) {\n throw new Error(\n 'Security Error: baseUrl must use HTTPS. ' +\n 'HTTP connections expose your API key to man-in-the-middle attacks. ' +\n `Received: ${baseUrl}`\n );\n }\n\n // Validate API key format (if provided)\n if (config.apiKey) {\n const apiKeyPattern = /^am_(live|test)_[a-zA-Z0-9]+$/;\n if (!apiKeyPattern.test(config.apiKey)) {\n console.warn(\n 'Warning: API key does not match expected format (am_live_... or am_test_...). ' +\n 'This may indicate an invalid or compromised key.'\n );\n }\n }\n }\n\n /**\n * Request a sponsored unit decision for an agent opportunity.\n * Returns the full DecideResponse including status, ttl_ms, and all units.\n */\n async decideRaw(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<DecideResponse> {\n const requestOptions: {\n body: DecideRequest;\n idempotencyKey?: string;\n } = { body: request };\n\n if (options?.idempotencyKey !== undefined) {\n requestOptions.idempotencyKey = options.idempotencyKey;\n }\n\n return await this.http.request<DecideResponse>('POST', '/v1/decide', requestOptions);\n }\n\n /**\n * Convenience wrapper around decideRaw().\n * Returns null if status is no_fill, otherwise returns the first ad unit.\n */\n async decide(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n const response = await this.decideRaw(request, options);\n\n if (response.status === 'no_fill') {\n return null;\n }\n\n // Return first unit if available\n return response.units[0] ?? null;\n }\n\n /**\n * Report an event (impression, click, action, conversion, feedback).\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n return await this.http.request<EventIngestResponse>('POST', '/v1/event', {\n body: event,\n });\n }\n\n /**\n * Convenience method to track an impression event.\n * Creates an impression event using createImpressionEvent() and calls track().\n */\n async trackImpression(\n params: Omit<CreateImpressionEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createImpressionEvent(params);\n return await this.track(event);\n }\n\n /**\n * Convenience method to track a click event.\n * Creates a click event using createClickEvent() and calls track().\n */\n async trackClick(\n params: Omit<CreateClickEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createClickEvent(params);\n return await this.track(event);\n }\n\n /**\n * Fetch default policy constraints and formatting requirements.\n */\n async getPolicy(): Promise<PolicyResponse> {\n return await this.http.request<PolicyResponse>('GET', '/v1/policy');\n }\n\n /**\n * Create or register an agent (optional helper).\n * Note: This endpoint is unauthenticated in v1.\n */\n static async signupAgent(\n request: AgentSignupRequest,\n options?: { baseUrl?: string },\n ): Promise<AgentSignupResponse> {\n // Create temporary HTTP client without authentication\n const http = new HTTPClient({\n baseUrl: options?.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxRetries: DEFAULT_MAX_RETRIES,\n });\n\n return await http.request<AgentSignupResponse>('POST', '/v1/agent-signup', {\n body: request,\n });\n }\n}\n","/**\n * Mock AttentionMarket Client for Testing\n *\n * Use this client during development to test your integration without real advertiser data.\n * Returns realistic mock ad units for common taxonomies.\n */\n\nimport type {\n DecideRequest,\n DecideResponse,\n AdUnit,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n} from './types.js';\nimport { generateUUID, generateTimestamp } from './utils.js';\n\nexport interface MockClientConfig {\n /**\n * Simulate API latency (milliseconds)\n * @default 100\n */\n latencyMs?: number;\n\n /**\n * Fill rate (0.0 - 1.0) - probability of returning an ad\n * @default 1.0 (always fill)\n */\n fillRate?: number;\n\n /**\n * Log mock calls to console\n * @default true\n */\n verbose?: boolean;\n}\n\n/**\n * Mock client for testing SDK integration without real advertiser data\n *\n * @example\n * ```typescript\n * import { MockAttentionMarketClient } from '@the_ro_show/agent-ads-sdk';\n *\n * const client = new MockAttentionMarketClient();\n * const unit = await client.decide({...});\n * ```\n */\nexport class MockAttentionMarketClient {\n private config: Required<MockClientConfig>;\n private mockUnits: Record<string, AdUnit>;\n\n constructor(config: MockClientConfig = {}) {\n this.config = {\n latencyMs: config.latencyMs ?? 100,\n fillRate: config.fillRate ?? 1.0,\n verbose: config.verbose ?? true,\n };\n\n // Seed data for common taxonomies\n this.mockUnits = {\n 'local_services.movers.quote': {\n unit_id: 'unit_mock_movers_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Brooklyn Premium Movers',\n },\n tracking: {\n token: 'trk_mock_movers_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/movers',\n click_url: 'https://mock.attentionmarket.com/click/movers',\n },\n suggestion: {\n title: 'Professional Moving Services - Same Day Available',\n body: 'Licensed & insured movers serving Brooklyn since 2015. Free on-site estimates, packing services, and storage options. Rated 4.9/5 stars.',\n cta: 'Get Free Quote โ†’',\n action_url: 'https://demo-movers.example.com/quote?ref=am_mock',\n },\n },\n 'local_services.restaurants.search': {\n unit_id: 'unit_mock_restaurant_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: \"Tony's Italian Kitchen\",\n },\n tracking: {\n token: 'trk_mock_restaurant_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/restaurant',\n click_url: 'https://mock.attentionmarket.com/click/restaurant',\n },\n suggestion: {\n title: \"Tony's Italian Kitchen - Authentic NYC Italian\",\n body: 'โญ๏ธ 4.8/5 stars โ€ข Midtown Manhattan โ€ข Reservations available tonight. Try our signature wood-fired pizza and homemade pasta.',\n cta: 'Reserve Table โ†’',\n action_url: 'https://demo-restaurant.example.com/reserve?ref=am_mock',\n },\n },\n 'local_services.plumbers.quote': {\n unit_id: 'unit_mock_plumber_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: '24/7 Emergency Plumbing',\n },\n tracking: {\n token: 'trk_mock_plumber_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/plumber',\n click_url: 'https://mock.attentionmarket.com/click/plumber',\n },\n suggestion: {\n title: 'Emergency Plumber - 30 Min Response Time',\n body: 'Licensed plumbers available 24/7 across NYC. Free estimates. No overtime charges. Same-day service guaranteed.',\n cta: 'Call Now โ†’',\n action_url: 'tel:+1-555-PLUMBER',\n },\n },\n 'local_services.electricians.quote': {\n unit_id: 'unit_mock_electrician_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Spark Electric Co',\n },\n tracking: {\n token: 'trk_mock_electrician_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electrician',\n click_url: 'https://mock.attentionmarket.com/click/electrician',\n },\n suggestion: {\n title: 'Licensed Electrician - Free Safety Inspection',\n body: 'Certified electricians for residential & commercial. Emergency service available. 10-year warranty on all work.',\n cta: 'Schedule Service โ†’',\n action_url: 'https://demo-electrician.example.com/schedule?ref=am_mock',\n },\n },\n 'local_services.cleaners.quote': {\n unit_id: 'unit_mock_cleaner_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Sparkle Clean NYC',\n },\n tracking: {\n token: 'trk_mock_cleaner_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/cleaner',\n click_url: 'https://mock.attentionmarket.com/click/cleaner',\n },\n suggestion: {\n title: 'Professional Home Cleaning - $99 First Visit',\n body: 'Eco-friendly cleaning products. Background-checked staff. Satisfaction guaranteed or your money back.',\n cta: 'Book Cleaning โ†’',\n action_url: 'https://demo-cleaners.example.com/book?ref=am_mock',\n },\n },\n 'shopping.electronics.search': {\n unit_id: 'unit_mock_electronics_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'TechDeals Pro',\n },\n tracking: {\n token: 'trk_mock_electronics_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electronics',\n click_url: 'https://mock.attentionmarket.com/click/electronics',\n },\n suggestion: {\n title: 'Latest Laptops & Electronics - Up to 40% Off',\n body: 'Free shipping on orders over $50. 30-day returns. Price match guarantee. Shop top brands.',\n cta: 'Shop Deals โ†’',\n action_url: 'https://demo-electronics.example.com/deals?ref=am_mock',\n },\n },\n };\n }\n\n /**\n * Simulate network latency\n */\n private async simulateLatency(): Promise<void> {\n if (this.config.latencyMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, this.config.latencyMs));\n }\n }\n\n /**\n * Check if should fill based on fill rate\n */\n private shouldFill(): boolean {\n return Math.random() < this.config.fillRate;\n }\n\n /**\n * Log mock activity\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.verbose) {\n console.log(`๐Ÿงช [MockClient] ${message}`, ...args);\n }\n }\n\n /**\n * Request a sponsored unit (convenience method)\n */\n async decide(request: DecideRequest): Promise<AdUnit | null> {\n const response = await this.decideRaw(request);\n if (response.status === 'filled' && response.units.length > 0) {\n return response.units[0] || null;\n }\n return null;\n }\n\n /**\n * Request sponsored units (full response)\n */\n async decideRaw(request: DecideRequest): Promise<DecideResponse> {\n await this.simulateLatency();\n\n const taxonomy = request.opportunity.intent.taxonomy;\n this.log('decide()', {\n taxonomy,\n placement: request.placement.type,\n });\n\n // Check fill rate\n if (!this.shouldFill()) {\n this.log('No fill (simulated by fillRate)', { fillRate: this.config.fillRate });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n // Get mock unit for taxonomy\n const unit = this.mockUnits[taxonomy];\n\n if (unit) {\n const title = unit.unit_type === 'sponsored_suggestion'\n ? unit.suggestion.title\n : unit.tool.tool_name;\n\n this.log('Returning mock ad', {\n sponsor: unit.disclosure.sponsor_name,\n title,\n });\n\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'filled',\n units: [unit],\n ttl_ms: 60000,\n };\n }\n\n this.log('No mock data for taxonomy', { taxonomy });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n /**\n * Track an event\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n await this.simulateLatency();\n this.log('track()', { event_type: event.event_type, unit_id: event.unit_id });\n\n return {\n accepted: true,\n };\n }\n\n /**\n * Track impression (convenience method)\n */\n async trackImpression(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n }): Promise<EventIngestResponse> {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return this.track(event);\n }\n\n /**\n * Track click (convenience method)\n */\n async trackClick(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n }): Promise<EventIngestResponse> {\n return this.track({\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n metadata: {\n href: params.href,\n },\n });\n }\n\n /**\n * Get policy\n */\n async getPolicy(): Promise<PolicyResponse> {\n await this.simulateLatency();\n this.log('getPolicy()');\n\n return {\n version: '1.0.0',\n defaults: {\n max_units_per_response: 1,\n blocked_categories: [],\n },\n disclosure: {\n required: true,\n label: 'Sponsored',\n require_sponsor_name: true,\n },\n unit_rules: {},\n };\n }\n\n /**\n * Add custom mock ad unit for testing\n */\n addMockUnit(taxonomy: string, unit: AdUnit): void {\n this.mockUnits[taxonomy] = unit;\n this.log('Added custom mock unit', { taxonomy });\n }\n\n /**\n * Get list of available mock taxonomies\n */\n getAvailableTaxonomies(): string[] {\n return Object.keys(this.mockUnits);\n }\n}\n","/**\n * Taxonomy helper utilities for AttentionMarket SDK\n * Helps with building, validating, and working with the 4-tier taxonomy system\n */\n\n// Valid intent modifiers\nexport type TaxonomyIntent =\n | 'research' // Learning, browsing (low intent)\n | 'compare' // Evaluating options (medium intent)\n | 'quote' // Getting prices (high intent)\n | 'trial' // Free trial signup (high intent)\n | 'book' // Schedule/purchase (very high intent)\n | 'apply' // Application process (very high intent)\n | 'consultation'; // Schedule meeting (very high intent)\n\n/**\n * Build a valid taxonomy string\n *\n * @param vertical - Industry vertical (e.g., \"insurance\", \"business\", \"healthcare\")\n * @param category - Product/service category (e.g., \"auto\", \"saas\", \"dental\")\n * @param subcategory - Specific offering (e.g., \"full_coverage\", \"crm\", \"cosmetic\")\n * @param intent - Optional user journey stage\n * @returns Properly formatted taxonomy string\n *\n * @example\n * buildTaxonomy('insurance', 'auto', 'full_coverage', 'quote')\n * // Returns: 'insurance.auto.full_coverage.quote'\n *\n * @example\n * buildTaxonomy('business', 'saas', 'crm', 'trial')\n * // Returns: 'business.saas.crm.trial'\n */\nexport function buildTaxonomy(\n vertical: string,\n category: string,\n subcategory: string,\n intent?: TaxonomyIntent\n): string {\n const parts = [vertical, category, subcategory];\n if (intent) {\n parts.push(intent);\n }\n return parts.join('.');\n}\n\n/**\n * Detect user intent from query string\n *\n * Analyzes the user's query to determine their stage in the buying journey.\n * Returns the most appropriate intent modifier.\n *\n * @param query - User's search query or question\n * @returns Detected intent modifier\n *\n * @example\n * detectIntent(\"What is term life insurance?\")\n * // Returns: 'research'\n *\n * @example\n * detectIntent(\"Get car insurance quote\")\n * // Returns: 'quote'\n *\n * @example\n * detectIntent(\"Best CRM software comparison\")\n * // Returns: 'compare'\n */\nexport function detectIntent(query: string): TaxonomyIntent {\n const lowerQuery = query.toLowerCase();\n\n // Research intent - learning/information gathering\n if (/what is|how does|how do|learn about|tell me about|explain|understand|definition/i.test(lowerQuery)) {\n return 'research';\n }\n\n // Compare intent - evaluating options\n if (/best|compare|vs|versus|which|top|options|alternatives|review|recommend/i.test(lowerQuery)) {\n return 'compare';\n }\n\n // Quote intent - ready to get prices\n if (/price|cost|how much|quote|estimate|pricing|rate|afford/i.test(lowerQuery)) {\n return 'quote';\n }\n\n // Trial intent - wants to try before buying\n if (/try|demo|free trial|test|preview|sample/i.test(lowerQuery)) {\n return 'trial';\n }\n\n // Book intent - ready to schedule/purchase\n if (/book|schedule|appointment|reserve|set up|make appointment/i.test(lowerQuery)) {\n return 'book';\n }\n\n // Apply intent - application/signup\n if (/apply|sign up|get started|register|enroll|join/i.test(lowerQuery)) {\n return 'apply';\n }\n\n // Consultation intent - wants to talk to someone\n if (/talk to|speak with|consult|meet with|call|contact|discuss/i.test(lowerQuery)) {\n return 'consultation';\n }\n\n // Default to compare (most common)\n return 'compare';\n}\n\n/**\n * Validate taxonomy format\n *\n * Checks if a taxonomy string follows the correct format:\n * - 3 or 4 parts separated by dots\n * - All parts are lowercase alphanumeric with underscores\n * - If 4 parts, last part must be a valid intent\n *\n * @param taxonomy - Taxonomy string to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * isValidTaxonomy('insurance.auto.full_coverage.quote') // true\n * isValidTaxonomy('insurance.auto') // false (too short)\n * isValidTaxonomy('Insurance.Auto.Quote') // false (uppercase)\n * isValidTaxonomy('insurance.auto.full_coverage.invalid') // false (invalid intent)\n */\nexport function isValidTaxonomy(taxonomy: string): boolean {\n const parts = taxonomy.split('.');\n\n // Must be 3 or 4 parts\n if (parts.length < 3 || parts.length > 4) {\n return false;\n }\n\n // Check valid intent (if present)\n const validIntents: TaxonomyIntent[] = [\n 'research', 'compare', 'quote', 'trial',\n 'book', 'apply', 'consultation'\n ];\n\n if (parts.length === 4 && !validIntents.includes(parts[3] as TaxonomyIntent)) {\n return false;\n }\n\n // Check each part is non-empty and alphanumeric + underscore\n return parts.every(part => /^[a-z0-9_]+$/.test(part));\n}\n\n/**\n * Parsed taxonomy components\n */\nexport interface ParsedTaxonomy {\n /** Industry vertical (tier 1) */\n vertical: string;\n /** Product/service category (tier 2) */\n category: string;\n /** Specific offering (tier 3) */\n subcategory: string;\n /** User journey stage (tier 4, optional) */\n intent?: TaxonomyIntent;\n /** Full taxonomy string */\n full: string;\n}\n\n/**\n * Parse taxonomy into components\n *\n * Breaks down a taxonomy string into its constituent parts for analysis.\n *\n * @param taxonomy - Taxonomy string to parse\n * @returns Parsed components or null if invalid\n *\n * @example\n * parseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: {\n * // vertical: 'insurance',\n * // category: 'auto',\n * // subcategory: 'full_coverage',\n * // intent: 'quote',\n * // full: 'insurance.auto.full_coverage.quote'\n * // }\n */\nexport function parseTaxonomy(taxonomy: string): ParsedTaxonomy | null {\n if (!isValidTaxonomy(taxonomy)) {\n return null;\n }\n\n const parts = taxonomy.split('.');\n const result: ParsedTaxonomy = {\n vertical: parts[0]!,\n category: parts[1]!,\n subcategory: parts[2]!,\n full: taxonomy\n };\n\n if (parts[3]) {\n result.intent = parts[3] as TaxonomyIntent;\n }\n\n return result;\n}\n\n/**\n * Get taxonomy without intent modifier\n *\n * Useful for broader matching or grouping taxonomies by product/service.\n *\n * @param taxonomy - Full taxonomy string\n * @returns Taxonomy without intent, or null if invalid\n *\n * @example\n * getBaseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance.auto.full_coverage'\n */\nexport function getBaseTaxonomy(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n if (!parsed) return null;\n\n return `${parsed.vertical}.${parsed.category}.${parsed.subcategory}`;\n}\n\n/**\n * Check if two taxonomies match hierarchically\n *\n * Returns true if they share the same vertical, category, and subcategory\n * (regardless of intent).\n *\n * @param taxonomy1 - First taxonomy\n * @param taxonomy2 - Second taxonomy\n * @returns True if they match hierarchically\n *\n * @example\n * matchesTaxonomy(\n * 'insurance.auto.full_coverage.quote',\n * 'insurance.auto.full_coverage.apply'\n * )\n * // Returns: true (same base, different intent)\n */\nexport function matchesTaxonomy(taxonomy1: string, taxonomy2: string): boolean {\n const base1 = getBaseTaxonomy(taxonomy1);\n const base2 = getBaseTaxonomy(taxonomy2);\n\n return base1 !== null && base2 !== null && base1 === base2;\n}\n\n/**\n * Get vertical from taxonomy\n *\n * Extracts just the industry vertical (tier 1).\n *\n * @param taxonomy - Taxonomy string\n * @returns Vertical or null if invalid\n *\n * @example\n * getVertical('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance'\n */\nexport function getVertical(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n return parsed ? parsed.vertical : null;\n}\n\n/**\n * Suggest taxonomies based on user query\n *\n * Analyzes a user query and suggests appropriate taxonomies.\n * This is a basic implementation - you may want to enhance it\n * with ML/NLP for better accuracy.\n *\n * @param query - User's search query\n * @returns Array of suggested taxonomy strings\n *\n * @example\n * suggestTaxonomies(\"I need car insurance\")\n * // Returns: ['insurance.auto.full_coverage.quote', 'insurance.auto.liability.quote']\n */\nexport function suggestTaxonomies(query: string): string[] {\n const lowerQuery = query.toLowerCase();\n const intent = detectIntent(query);\n const suggestions: string[] = [];\n\n // Insurance-related\n if (/car|auto|vehicle|drive|driving/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'auto', 'full_coverage', intent));\n suggestions.push(buildTaxonomy('insurance', 'auto', 'liability', intent));\n }\n if (/health|medical|doctor/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'health', 'individual', intent));\n }\n if (/life insurance/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'life', 'term', intent));\n }\n\n // Business/SaaS\n if (/crm|customer|sales|lead/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'crm', intent));\n }\n if (/online store|ecommerce|sell online/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'ecommerce', 'platform', intent));\n }\n if (/project management|task|team/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'project_management', intent));\n }\n\n // Legal\n if (/accident|injury|hurt/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'personal_injury', 'accident', intent));\n }\n if (/divorce|custody|family law/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'family_law', 'divorce', intent));\n }\n\n // Healthcare\n if (/dentist|teeth|dental/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'dental', 'general', intent));\n }\n if (/therapist|therapy|counseling/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'mental_health', 'therapy', intent));\n }\n\n // Home services\n if (/mover|moving|relocate/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'moving', 'local', intent));\n }\n if (/plumber|plumbing|leak/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'plumbing', 'emergency', intent));\n }\n if (/clean|cleaning|maid/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'cleaning', 'regular', intent));\n }\n\n return suggestions;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.ts","../src/taxonomy-utils.ts"],"sourcesContent":["/**\n * Error classes for AttentionMarket SDK.\n * Surfaces HTTP status, API error codes, messages, and request_id.\n */\n\nimport type { APIError } from './types.js';\n\nexport class AttentionMarketError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AttentionMarketError';\n Object.setPrototypeOf(this, AttentionMarketError.prototype);\n }\n}\n\nexport class APIRequestError extends AttentionMarketError {\n public readonly statusCode: number;\n public readonly errorCode: string;\n public readonly requestId?: string;\n public readonly details?: Record<string, unknown>;\n\n constructor(\n statusCode: number,\n apiError: APIError,\n ) {\n super(apiError.message);\n this.name = 'APIRequestError';\n this.statusCode = statusCode;\n this.errorCode = apiError.error;\n if (apiError.request_id !== undefined) {\n this.requestId = apiError.request_id;\n }\n if (apiError.details !== undefined) {\n this.details = apiError.details;\n }\n Object.setPrototypeOf(this, APIRequestError.prototype);\n }\n}\n\nexport class NetworkError extends AttentionMarketError {\n public readonly cause?: Error;\n\n constructor(message: string, cause?: Error) {\n super(message);\n this.name = 'NetworkError';\n if (cause !== undefined) {\n this.cause = cause;\n }\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class TimeoutError extends AttentionMarketError {\n constructor(message: string = 'Request timed out') {\n super(message);\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n","/**\n * HTTP client with retry logic, timeout handling, and authentication.\n * Retry policy: only on 408, 429, 500, 502, 503, 504\n * Max retries: 2 (configurable)\n * Backoff: exponential with jitter\n */\n\nimport { APIRequestError, NetworkError, TimeoutError } from './errors.js';\nimport type { APIError } from './types.js';\n\nexport interface HTTPClientConfig {\n apiKey?: string; // AttentionMarket API key (am_live_* or am_test_*)\n supabaseAnonKey?: string; // Supabase anon key for infrastructure auth\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n}\n\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class HTTPClient {\n constructor(private config: HTTPClientConfig) {}\n\n async request<T>(\n method: 'GET' | 'POST',\n path: string,\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n } = {},\n ): Promise<T> {\n const url = `${this.config.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.makeRequest(url, method, options);\n return response as T;\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry if it's not a retryable error\n if (!this.shouldRetry(error, attempt)) {\n throw error;\n }\n\n // Wait before retrying\n if (attempt < this.config.maxRetries) {\n await this.backoff(attempt);\n }\n }\n }\n\n // All retries exhausted\n throw lastError;\n }\n\n private async makeRequest(\n url: string,\n method: 'GET' | 'POST',\n options: {\n body?: unknown;\n headers?: Record<string, string>;\n idempotencyKey?: string;\n },\n ): Promise<unknown> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n };\n\n // Send Supabase anon key in Authorization (for infrastructure)\n if (this.config.supabaseAnonKey) {\n headers['Authorization'] = `Bearer ${this.config.supabaseAnonKey}`;\n }\n\n // Send AttentionMarket API key in custom header (for app-level auth)\n if (this.config.apiKey) {\n headers['X-AM-API-Key'] = this.config.apiKey;\n }\n\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const response = await fetch(url, {\n method,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Handle non-OK responses\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({\n error: 'unknown_error',\n message: 'Failed to parse error response',\n request_id: 'unknown',\n }));\n\n throw new APIRequestError(response.status, errorBody as APIError);\n }\n\n // Parse successful response\n return await response.json();\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Handle abort (timeout)\n if (error instanceof Error && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n\n // Re-throw API errors\n if (error instanceof APIRequestError) {\n throw error;\n }\n\n // Network errors\n throw new NetworkError('Network request failed', error as Error);\n }\n }\n\n private shouldRetry(error: unknown, attempt: number): boolean {\n // Don't retry if we've exhausted attempts\n if (attempt >= this.config.maxRetries) {\n return false;\n }\n\n // Retry on timeout\n if (error instanceof TimeoutError) {\n return true;\n }\n\n // Retry on network errors\n if (error instanceof NetworkError) {\n return true;\n }\n\n // Retry on specific HTTP status codes\n if (error instanceof APIRequestError) {\n return RETRYABLE_STATUS_CODES.has(error.statusCode);\n }\n\n return false;\n }\n\n private async backoff(attempt: number): Promise<void> {\n // Exponential backoff: 100ms * 2^attempt + jitter\n const baseDelay = 100 * Math.pow(2, attempt);\n const jitter = Math.random() * 100;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","/**\n * Utility functions for the AttentionMarket SDK.\n * Includes UUID generation, opportunity helpers, and event builders.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { EventIngestRequest, Opportunity } from './types.js';\n\n/**\n * Generate a UUID v4 for use as event_id, request_id, etc.\n */\nexport function generateUUID(): string {\n return randomUUID();\n}\n\n/**\n * Generate an ISO 8601 timestamp for the current moment.\n */\nexport function generateTimestamp(): string {\n return new Date().toISOString();\n}\n\n// ============================================================================\n// Opportunity Helper\n// ============================================================================\n\nexport interface CreateOpportunityParams {\n // Required\n taxonomy: string;\n country: string;\n language: string;\n platform: 'web' | 'ios' | 'android' | 'desktop' | 'voice' | 'other';\n\n // Optional\n query?: string;\n region?: string;\n city?: string;\n\n // Optional overrides (with defaults)\n constraints?: {\n max_units?: number;\n allowed_unit_types?: ('sponsored_suggestion' | 'sponsored_block' | 'sponsored_tool')[];\n blocked_categories?: string[];\n max_title_chars?: number;\n max_body_chars?: number;\n };\n privacy?: {\n data_policy?: 'coarse_only' | 'none' | 'extended';\n };\n}\n\n/**\n * Create a valid Opportunity object with safe defaults.\n *\n * Defaults:\n * - constraints.max_units = 1\n * - constraints.allowed_unit_types = ['sponsored_suggestion']\n * - privacy.data_policy = 'coarse_only'\n */\nexport function createOpportunity(params: CreateOpportunityParams): Opportunity {\n const opportunity: Opportunity = {\n intent: {\n taxonomy: params.taxonomy,\n },\n context: {\n country: params.country,\n language: params.language,\n platform: params.platform,\n },\n constraints: {\n max_units: params.constraints?.max_units ?? 1,\n allowed_unit_types: params.constraints?.allowed_unit_types ?? ['sponsored_suggestion'],\n },\n privacy: {\n data_policy: params.privacy?.data_policy ?? 'coarse_only',\n },\n };\n\n // Add optional intent fields\n if (params.query !== undefined) {\n opportunity.intent.query = params.query;\n }\n\n // Add optional context fields\n if (params.region !== undefined) {\n opportunity.context.region = params.region;\n }\n if (params.city !== undefined) {\n opportunity.context.city = params.city;\n }\n\n // Add optional constraint fields\n if (params.constraints?.blocked_categories !== undefined) {\n opportunity.constraints.blocked_categories = params.constraints.blocked_categories;\n }\n if (params.constraints?.max_title_chars !== undefined) {\n opportunity.constraints.max_title_chars = params.constraints.max_title_chars;\n }\n if (params.constraints?.max_body_chars !== undefined) {\n opportunity.constraints.max_body_chars = params.constraints.max_body_chars;\n }\n\n return opportunity;\n}\n\n// ============================================================================\n// Event Helpers\n// ============================================================================\n\nexport interface CreateImpressionEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create an impression event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createImpressionEvent(params: CreateImpressionEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return event;\n}\n\nexport interface CreateClickEventParams {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Helper to create a click event payload.\n *\n * @param params - Event parameters (snake_case to match API)\n * @returns EventIngestRequest ready to pass to client.track()\n */\nexport function createClickEvent(params: CreateClickEventParams): EventIngestRequest {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at ?? generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n };\n\n // Include href in metadata\n if (params.metadata !== undefined) {\n event.metadata = {\n ...params.metadata,\n href: params.href,\n };\n } else {\n event.metadata = {\n href: params.href,\n };\n }\n\n return event;\n}\n\n// ============================================================================\n// Security & Sanitization Helpers\n// ============================================================================\n\n/**\n * HTML escape map - created once to avoid recreation on each call\n */\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '/': '&#x2F;',\n '`': '&#96;',\n};\n\n/**\n * Regex for matching HTML special characters - created once for performance\n */\nconst HTML_ESCAPE_REGEX = /[&<>\"'`\\/]/g;\n\n/**\n * Maximum URL length to prevent DoS attacks\n */\nconst MAX_URL_LENGTH = 2048;\n\n/**\n * Dangerous URL protocols that must always be blocked\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'file:', 'vbscript:', 'blob:'];\n\n/**\n * Options for URL sanitization\n */\nexport interface SanitizeURLOptions {\n /**\n * Allow HTTP URLs (default: false, HTTPS only)\n */\n allowHttp?: boolean;\n /**\n * Allow tel: links (default: true)\n */\n allowTel?: boolean;\n /**\n * Allow mailto: links (default: true)\n */\n allowMailto?: boolean;\n /**\n * Callback for validation warnings (instead of console.warn)\n */\n onWarning?: (message: string, context: { url?: string; protocol?: string }) => void;\n}\n\n/**\n * Escape HTML special characters to prevent XSS attacks.\n *\n * Handles null/undefined safely by returning empty string.\n * Escapes: & < > \" ' / `\n *\n * Use this when displaying ad content (title, body, cta) in HTML contexts.\n *\n * @param text - Text to escape (can be null/undefined)\n * @returns Escaped HTML string (empty string if input is null/undefined)\n *\n * @example\n * ```typescript\n * const safeTitle = escapeHTML(unit.suggestion.title);\n * element.innerHTML = safeTitle; // Safe from XSS\n *\n * escapeHTML(null); // Returns ''\n * escapeHTML('<script>alert(1)</script>'); // Returns '&lt;script&gt;alert(1)&lt;/script&gt;'\n * ```\n */\nexport function escapeHTML(text: string | null | undefined): string {\n // Handle null/undefined safely\n if (text == null) {\n return '';\n }\n\n return text.replace(HTML_ESCAPE_REGEX, (char) => HTML_ESCAPES[char] || char);\n}\n\n/**\n * Sanitize and validate a URL to prevent XSS and phishing attacks.\n *\n * Handles null/undefined safely by returning null.\n * Blocks dangerous protocols like javascript:, data:, file:, blob:, vbscript:.\n * Validates URL length to prevent DoS attacks (max 2048 chars).\n * Handles protocol-relative URLs (//example.com).\n *\n * @param url - URL to sanitize (can be null/undefined)\n * @param options - Sanitization options\n * @returns Sanitized URL string, or null if invalid/dangerous\n *\n * @example\n * ```typescript\n * const safeURL = sanitizeURL(unit.suggestion.action_url);\n * if (safeURL) {\n * window.open(safeURL, '_blank');\n * }\n *\n * sanitizeURL(null); // Returns null\n * sanitizeURL('javascript:alert(1)'); // Returns null (blocked)\n * sanitizeURL('https://example.com'); // Returns 'https://example.com'\n * sanitizeURL('http://example.com', { allowHttp: true }); // Returns 'http://example.com'\n * ```\n */\nexport function sanitizeURL(\n url: string | null | undefined,\n options?: SanitizeURLOptions\n): string | null {\n // Handle null/undefined safely\n if (url == null) {\n return null;\n }\n\n const opts = {\n allowHttp: options?.allowHttp ?? false,\n allowTel: options?.allowTel ?? true,\n allowMailto: options?.allowMailto ?? true,\n onWarning: options?.onWarning ?? undefined,\n };\n\n try {\n const trimmedURL = url.trim();\n\n // Block empty URLs\n if (!trimmedURL) {\n return null;\n }\n\n // Validate URL length to prevent DoS\n if (trimmedURL.length > MAX_URL_LENGTH) {\n opts.onWarning?.('URL exceeds maximum length', { url: trimmedURL });\n return null;\n }\n\n // Handle protocol-relative URLs (//example.com)\n let parsedURL: URL;\n if (trimmedURL.startsWith('//')) {\n // Protocol-relative URLs need a base URL to parse\n try {\n parsedURL = new URL(trimmedURL, 'https://dummy-base.com');\n // Convert to absolute HTTPS URL\n return `https:${trimmedURL}`;\n } catch {\n opts.onWarning?.('Invalid protocol-relative URL', { url: trimmedURL });\n return null;\n }\n }\n\n // Parse URL\n parsedURL = new URL(trimmedURL);\n\n // Check protocol\n const protocol = parsedURL.protocol.toLowerCase();\n\n // Always block dangerous protocols\n if (DANGEROUS_PROTOCOLS.includes(protocol)) {\n opts.onWarning?.('Blocked dangerous URL protocol', { url: trimmedURL, protocol });\n return null;\n }\n\n // Allow HTTPS\n if (protocol === 'https:') {\n return trimmedURL;\n }\n\n // Conditionally allow HTTP\n if (protocol === 'http:') {\n if (opts.allowHttp) {\n return trimmedURL;\n } else {\n opts.onWarning?.('HTTP URL blocked. Use HTTPS or set allowHttp: true', {\n url: trimmedURL,\n protocol\n });\n return null;\n }\n }\n\n // Conditionally allow tel:\n if (protocol === 'tel:') {\n return opts.allowTel ? trimmedURL : null;\n }\n\n // Conditionally allow mailto:\n if (protocol === 'mailto:') {\n return opts.allowMailto ? trimmedURL : null;\n }\n\n // Block unknown protocols\n opts.onWarning?.('Unknown URL protocol blocked', { url: trimmedURL, protocol });\n return null;\n } catch (error) {\n // Invalid URL format\n opts.onWarning?.('Invalid URL format', { url });\n return null;\n }\n}\n","/**\n * Main SDK client for AttentionMarket Agent Ads API.\n * Provides public methods: decide(), decideRaw(), track(), getPolicy(), signupAgent()\n */\n\nimport { HTTPClient } from './http.js';\nimport { createImpressionEvent, createClickEvent, generateUUID } from './utils.js';\nimport type {\n SDKConfig,\n DecideRequest,\n DecideFromContextRequest,\n DecideResponse,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n AgentSignupRequest,\n AgentSignupResponse,\n AdUnit,\n} from './types.js';\nimport type { CreateImpressionEventParams, CreateClickEventParams } from './utils.js';\n\n// Default configuration (points to AttentionMarket production API)\n// Developers can override with their own backend if self-hosting\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai/v1';\nconst DEFAULT_TIMEOUT_MS = 4000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nexport class AttentionMarketClient {\n private http: HTTPClient;\n private agentId: string | undefined;\n\n constructor(config: SDKConfig) {\n this.agentId = config.agentId;\n // Validate configuration\n this.validateConfig(config);\n\n const httpConfig: {\n apiKey?: string;\n supabaseAnonKey?: string;\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n } = {\n apiKey: config.apiKey,\n baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n };\n\n // Only add supabaseAnonKey if provided\n if (config.supabaseAnonKey !== undefined) {\n httpConfig.supabaseAnonKey = config.supabaseAnonKey;\n }\n\n this.http = new HTTPClient(httpConfig);\n }\n\n /**\n * Validate SDK configuration for security\n */\n private validateConfig(config: SDKConfig): void {\n // Validate base URL uses HTTPS\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n if (!baseUrl.startsWith('https://')) {\n throw new Error(\n 'Security Error: baseUrl must use HTTPS. ' +\n 'HTTP connections expose your API key to man-in-the-middle attacks. ' +\n `Received: ${baseUrl}`\n );\n }\n\n // Validate API key format (if provided)\n if (config.apiKey) {\n const apiKeyPattern = /^am_(live|test)_[a-zA-Z0-9]+$/;\n if (!apiKeyPattern.test(config.apiKey)) {\n console.warn(\n 'Warning: API key does not match expected format (am_live_... or am_test_...). ' +\n 'This may indicate an invalid or compromised key.'\n );\n }\n }\n }\n\n /**\n * Request a sponsored unit decision for an agent opportunity.\n * Returns the full DecideResponse including status, ttl_ms, and all units.\n */\n async decideRaw(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<DecideResponse> {\n const requestOptions: {\n body: DecideRequest;\n idempotencyKey?: string;\n } = { body: request };\n\n if (options?.idempotencyKey !== undefined) {\n requestOptions.idempotencyKey = options.idempotencyKey;\n }\n\n return await this.http.request<DecideResponse>('POST', '/v1/decide', requestOptions);\n }\n\n /**\n * Convenience wrapper around decideRaw().\n * Returns null if status is no_fill, otherwise returns the first ad unit.\n */\n async decide(\n request: DecideRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n const response = await this.decideRaw(request, options);\n\n if (response.status === 'no_fill') {\n return null;\n }\n\n // Return first unit if available\n return response.units[0] ?? null;\n }\n\n /**\n * Simplified ad matching using conversation context and semantic search.\n * Automatically handles request construction, taxonomy fallback, and defaults.\n *\n * Requires: agentId in SDKConfig constructor\n *\n * @example\n * const ad = await client.decideFromContext({\n * userMessage: \"My father passed away and I need help organizing his estate\",\n * placement: 'sponsored_suggestion'\n * });\n *\n * @throws {Error} If agentId was not provided in SDKConfig\n */\n async decideFromContext(\n params: DecideFromContextRequest,\n options?: { idempotencyKey?: string },\n ): Promise<AdUnit | null> {\n // Validate agentId is available\n if (!this.agentId) {\n throw new Error(\n 'decideFromContext() requires agentId to be set in SDKConfig. ' +\n 'Either provide agentId in the constructor or use decide() directly.'\n );\n }\n\n // Limit conversation history to last 5 messages to avoid token overflow\n const historyLimit = 5;\n const history = params.conversationHistory || [];\n const limitedHistory = history.slice(-historyLimit);\n\n // Build context string from user message + limited history\n const contextParts = [...limitedHistory, params.userMessage];\n const context = contextParts.join('\\n');\n\n // Use provided values or sensible defaults\n const country = params.country || 'US';\n const language = params.language || 'en';\n const platform = params.platform || 'web';\n const placementType = params.placement || 'sponsored_suggestion';\n\n // Build taxonomy from suggestedCategory or use fallback\n // Backend semantic matching doesn't strictly require valid taxonomy\n const taxonomy = params.suggestedCategory || 'unknown';\n\n // Build full DecideRequest with semantic context\n const request: DecideRequest = {\n request_id: generateUUID(),\n agent_id: this.agentId,\n placement: {\n type: placementType,\n surface: 'chat'\n },\n opportunity: {\n intent: {\n taxonomy,\n query: params.userMessage\n },\n context: {\n country,\n language,\n platform\n },\n constraints: {\n max_units: 1,\n allowed_unit_types: [placementType]\n },\n privacy: {\n data_policy: 'coarse_only'\n }\n },\n context,\n user_intent: params.userMessage\n };\n\n return await this.decide(request, options);\n }\n\n /**\n * Report an event (impression, click, action, conversion, feedback).\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n return await this.http.request<EventIngestResponse>('POST', '/v1/event', {\n body: event,\n });\n }\n\n /**\n * Convenience method to track an impression event.\n * Creates an impression event using createImpressionEvent() and calls track().\n */\n async trackImpression(\n params: Omit<CreateImpressionEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createImpressionEvent(params);\n return await this.track(event);\n }\n\n /**\n * Convenience method to track a click event.\n * Creates a click event using createClickEvent() and calls track().\n */\n async trackClick(\n params: Omit<CreateClickEventParams, 'occurred_at'> & { occurred_at?: string },\n ): Promise<EventIngestResponse> {\n const event = createClickEvent(params);\n return await this.track(event);\n }\n\n /**\n * Fetch default policy constraints and formatting requirements.\n */\n async getPolicy(): Promise<PolicyResponse> {\n return await this.http.request<PolicyResponse>('GET', '/v1/policy');\n }\n\n /**\n * Create or register an agent (optional helper).\n * Note: This endpoint is unauthenticated in v1.\n */\n static async signupAgent(\n request: AgentSignupRequest,\n options?: { baseUrl?: string },\n ): Promise<AgentSignupResponse> {\n // Create temporary HTTP client without authentication\n const http = new HTTPClient({\n baseUrl: options?.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n maxRetries: DEFAULT_MAX_RETRIES,\n });\n\n return await http.request<AgentSignupResponse>('POST', '/v1/agent-signup', {\n body: request,\n });\n }\n}\n","/**\n * Mock AttentionMarket Client for Testing\n *\n * Use this client during development to test your integration without real advertiser data.\n * Returns realistic mock ad units for common taxonomies.\n */\n\nimport type {\n DecideRequest,\n DecideResponse,\n AdUnit,\n EventIngestRequest,\n EventIngestResponse,\n PolicyResponse,\n} from './types.js';\nimport { generateUUID, generateTimestamp } from './utils.js';\n\nexport interface MockClientConfig {\n /**\n * Simulate API latency (milliseconds)\n * @default 100\n */\n latencyMs?: number;\n\n /**\n * Fill rate (0.0 - 1.0) - probability of returning an ad\n * @default 1.0 (always fill)\n */\n fillRate?: number;\n\n /**\n * Log mock calls to console\n * @default true\n */\n verbose?: boolean;\n}\n\n/**\n * Mock client for testing SDK integration without real advertiser data\n *\n * @example\n * ```typescript\n * import { MockAttentionMarketClient } from '@the_ro_show/agent-ads-sdk';\n *\n * const client = new MockAttentionMarketClient();\n * const unit = await client.decide({...});\n * ```\n */\nexport class MockAttentionMarketClient {\n private config: Required<MockClientConfig>;\n private mockUnits: Record<string, AdUnit>;\n\n constructor(config: MockClientConfig = {}) {\n this.config = {\n latencyMs: config.latencyMs ?? 100,\n fillRate: config.fillRate ?? 1.0,\n verbose: config.verbose ?? true,\n };\n\n // Seed data for common taxonomies\n this.mockUnits = {\n 'local_services.movers.quote': {\n unit_id: 'unit_mock_movers_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Brooklyn Premium Movers',\n },\n tracking: {\n token: 'trk_mock_movers_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/movers',\n click_url: 'https://mock.attentionmarket.com/click/movers',\n },\n suggestion: {\n title: 'Professional Moving Services - Same Day Available',\n body: 'Licensed & insured movers serving Brooklyn since 2015. Free on-site estimates, packing services, and storage options. Rated 4.9/5 stars.',\n cta: 'Get Free Quote โ†’',\n action_url: 'https://demo-movers.example.com/quote?ref=am_mock',\n },\n },\n 'local_services.restaurants.search': {\n unit_id: 'unit_mock_restaurant_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: \"Tony's Italian Kitchen\",\n },\n tracking: {\n token: 'trk_mock_restaurant_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/restaurant',\n click_url: 'https://mock.attentionmarket.com/click/restaurant',\n },\n suggestion: {\n title: \"Tony's Italian Kitchen - Authentic NYC Italian\",\n body: 'โญ๏ธ 4.8/5 stars โ€ข Midtown Manhattan โ€ข Reservations available tonight. Try our signature wood-fired pizza and homemade pasta.',\n cta: 'Reserve Table โ†’',\n action_url: 'https://demo-restaurant.example.com/reserve?ref=am_mock',\n },\n },\n 'local_services.plumbers.quote': {\n unit_id: 'unit_mock_plumber_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: '24/7 Emergency Plumbing',\n },\n tracking: {\n token: 'trk_mock_plumber_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/plumber',\n click_url: 'https://mock.attentionmarket.com/click/plumber',\n },\n suggestion: {\n title: 'Emergency Plumber - 30 Min Response Time',\n body: 'Licensed plumbers available 24/7 across NYC. Free estimates. No overtime charges. Same-day service guaranteed.',\n cta: 'Call Now โ†’',\n action_url: 'tel:+1-555-PLUMBER',\n },\n },\n 'local_services.electricians.quote': {\n unit_id: 'unit_mock_electrician_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Spark Electric Co',\n },\n tracking: {\n token: 'trk_mock_electrician_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electrician',\n click_url: 'https://mock.attentionmarket.com/click/electrician',\n },\n suggestion: {\n title: 'Licensed Electrician - Free Safety Inspection',\n body: 'Certified electricians for residential & commercial. Emergency service available. 10-year warranty on all work.',\n cta: 'Schedule Service โ†’',\n action_url: 'https://demo-electrician.example.com/schedule?ref=am_mock',\n },\n },\n 'local_services.cleaners.quote': {\n unit_id: 'unit_mock_cleaner_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'Sparkle Clean NYC',\n },\n tracking: {\n token: 'trk_mock_cleaner_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/cleaner',\n click_url: 'https://mock.attentionmarket.com/click/cleaner',\n },\n suggestion: {\n title: 'Professional Home Cleaning - $99 First Visit',\n body: 'Eco-friendly cleaning products. Background-checked staff. Satisfaction guaranteed or your money back.',\n cta: 'Book Cleaning โ†’',\n action_url: 'https://demo-cleaners.example.com/book?ref=am_mock',\n },\n },\n 'shopping.electronics.search': {\n unit_id: 'unit_mock_electronics_001',\n unit_type: 'sponsored_suggestion',\n disclosure: {\n label: 'Sponsored',\n explanation: 'This is a paid advertisement',\n sponsor_name: 'TechDeals Pro',\n },\n tracking: {\n token: 'trk_mock_electronics_' + generateUUID().substring(0, 8),\n impression_url: 'https://mock.attentionmarket.com/imp/electronics',\n click_url: 'https://mock.attentionmarket.com/click/electronics',\n },\n suggestion: {\n title: 'Latest Laptops & Electronics - Up to 40% Off',\n body: 'Free shipping on orders over $50. 30-day returns. Price match guarantee. Shop top brands.',\n cta: 'Shop Deals โ†’',\n action_url: 'https://demo-electronics.example.com/deals?ref=am_mock',\n },\n },\n };\n }\n\n /**\n * Simulate network latency\n */\n private async simulateLatency(): Promise<void> {\n if (this.config.latencyMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, this.config.latencyMs));\n }\n }\n\n /**\n * Check if should fill based on fill rate\n */\n private shouldFill(): boolean {\n return Math.random() < this.config.fillRate;\n }\n\n /**\n * Log mock activity\n */\n private log(message: string, ...args: unknown[]): void {\n if (this.config.verbose) {\n console.log(`๐Ÿงช [MockClient] ${message}`, ...args);\n }\n }\n\n /**\n * Request a sponsored unit (convenience method)\n */\n async decide(request: DecideRequest): Promise<AdUnit | null> {\n const response = await this.decideRaw(request);\n if (response.status === 'filled' && response.units.length > 0) {\n return response.units[0] || null;\n }\n return null;\n }\n\n /**\n * Request sponsored units (full response)\n */\n async decideRaw(request: DecideRequest): Promise<DecideResponse> {\n await this.simulateLatency();\n\n const taxonomy = request.opportunity.intent.taxonomy;\n this.log('decide()', {\n taxonomy,\n placement: request.placement.type,\n });\n\n // Check fill rate\n if (!this.shouldFill()) {\n this.log('No fill (simulated by fillRate)', { fillRate: this.config.fillRate });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n // Get mock unit for taxonomy\n const unit = this.mockUnits[taxonomy];\n\n if (unit) {\n const title = unit.unit_type === 'sponsored_suggestion'\n ? unit.suggestion.title\n : unit.tool.tool_name;\n\n this.log('Returning mock ad', {\n sponsor: unit.disclosure.sponsor_name,\n title,\n });\n\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'filled',\n units: [unit],\n ttl_ms: 60000,\n };\n }\n\n this.log('No mock data for taxonomy', { taxonomy });\n return {\n request_id: request.request_id,\n decision_id: 'dec_mock_' + generateUUID().substring(0, 8),\n status: 'no_fill',\n units: [],\n ttl_ms: 60000,\n };\n }\n\n /**\n * Track an event\n */\n async track(event: EventIngestRequest): Promise<EventIngestResponse> {\n await this.simulateLatency();\n this.log('track()', { event_type: event.event_type, unit_id: event.unit_id });\n\n return {\n accepted: true,\n };\n }\n\n /**\n * Track impression (convenience method)\n */\n async trackImpression(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n occurred_at?: string;\n metadata?: Record<string, unknown>;\n }): Promise<EventIngestResponse> {\n const event: EventIngestRequest = {\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'impression',\n tracking_token: params.tracking_token,\n };\n\n if (params.metadata !== undefined) {\n event.metadata = params.metadata;\n }\n\n return this.track(event);\n }\n\n /**\n * Track click (convenience method)\n */\n async trackClick(params: {\n agent_id: string;\n request_id: string;\n decision_id: string;\n unit_id: string;\n tracking_token: string;\n href: string;\n occurred_at?: string;\n }): Promise<EventIngestResponse> {\n return this.track({\n event_id: generateUUID(),\n occurred_at: params.occurred_at || generateTimestamp(),\n agent_id: params.agent_id,\n request_id: params.request_id,\n decision_id: params.decision_id,\n unit_id: params.unit_id,\n event_type: 'click',\n tracking_token: params.tracking_token,\n metadata: {\n href: params.href,\n },\n });\n }\n\n /**\n * Get policy\n */\n async getPolicy(): Promise<PolicyResponse> {\n await this.simulateLatency();\n this.log('getPolicy()');\n\n return {\n version: '1.0.0',\n defaults: {\n max_units_per_response: 1,\n blocked_categories: [],\n },\n disclosure: {\n required: true,\n label: 'Sponsored',\n require_sponsor_name: true,\n },\n unit_rules: {},\n };\n }\n\n /**\n * Add custom mock ad unit for testing\n */\n addMockUnit(taxonomy: string, unit: AdUnit): void {\n this.mockUnits[taxonomy] = unit;\n this.log('Added custom mock unit', { taxonomy });\n }\n\n /**\n * Get list of available mock taxonomies\n */\n getAvailableTaxonomies(): string[] {\n return Object.keys(this.mockUnits);\n }\n}\n","/**\n * Taxonomy helper utilities for AttentionMarket SDK\n * Helps with building, validating, and working with the 4-tier taxonomy system\n */\n\n// Valid intent modifiers\nexport type TaxonomyIntent =\n | 'research' // Learning, browsing (low intent)\n | 'compare' // Evaluating options (medium intent)\n | 'quote' // Getting prices (high intent)\n | 'trial' // Free trial signup (high intent)\n | 'book' // Schedule/purchase (very high intent)\n | 'apply' // Application process (very high intent)\n | 'consultation'; // Schedule meeting (very high intent)\n\n/**\n * Build a valid taxonomy string\n *\n * @param vertical - Industry vertical (e.g., \"insurance\", \"business\", \"healthcare\")\n * @param category - Product/service category (e.g., \"auto\", \"saas\", \"dental\")\n * @param subcategory - Specific offering (e.g., \"full_coverage\", \"crm\", \"cosmetic\")\n * @param intent - Optional user journey stage\n * @returns Properly formatted taxonomy string\n *\n * @example\n * buildTaxonomy('insurance', 'auto', 'full_coverage', 'quote')\n * // Returns: 'insurance.auto.full_coverage.quote'\n *\n * @example\n * buildTaxonomy('business', 'saas', 'crm', 'trial')\n * // Returns: 'business.saas.crm.trial'\n */\nexport function buildTaxonomy(\n vertical: string,\n category: string,\n subcategory: string,\n intent?: TaxonomyIntent\n): string {\n const parts = [vertical, category, subcategory];\n if (intent) {\n parts.push(intent);\n }\n return parts.join('.');\n}\n\n/**\n * Detect user intent from query string\n *\n * Analyzes the user's query to determine their stage in the buying journey.\n * Returns the most appropriate intent modifier.\n *\n * @param query - User's search query or question\n * @returns Detected intent modifier\n *\n * @example\n * detectIntent(\"What is term life insurance?\")\n * // Returns: 'research'\n *\n * @example\n * detectIntent(\"Get car insurance quote\")\n * // Returns: 'quote'\n *\n * @example\n * detectIntent(\"Best CRM software comparison\")\n * // Returns: 'compare'\n */\nexport function detectIntent(query: string): TaxonomyIntent {\n const lowerQuery = query.toLowerCase();\n\n // Research intent - learning/information gathering\n if (/what is|how does|how do|learn about|tell me about|explain|understand|definition/i.test(lowerQuery)) {\n return 'research';\n }\n\n // Compare intent - evaluating options\n if (/best|compare|vs|versus|which|top|options|alternatives|review|recommend/i.test(lowerQuery)) {\n return 'compare';\n }\n\n // Quote intent - ready to get prices\n if (/price|cost|how much|quote|estimate|pricing|rate|afford/i.test(lowerQuery)) {\n return 'quote';\n }\n\n // Trial intent - wants to try before buying\n if (/try|demo|free trial|test|preview|sample/i.test(lowerQuery)) {\n return 'trial';\n }\n\n // Book intent - ready to schedule/purchase\n if (/book|schedule|appointment|reserve|set up|make appointment/i.test(lowerQuery)) {\n return 'book';\n }\n\n // Apply intent - application/signup\n if (/apply|sign up|get started|register|enroll|join/i.test(lowerQuery)) {\n return 'apply';\n }\n\n // Consultation intent - wants to talk to someone\n if (/talk to|speak with|consult|meet with|call|contact|discuss/i.test(lowerQuery)) {\n return 'consultation';\n }\n\n // Default to compare (most common)\n return 'compare';\n}\n\n/**\n * Validate taxonomy format\n *\n * Checks if a taxonomy string follows the correct format:\n * - 3 or 4 parts separated by dots\n * - All parts are lowercase alphanumeric with underscores\n * - If 4 parts, last part must be a valid intent\n *\n * @param taxonomy - Taxonomy string to validate\n * @returns True if valid, false otherwise\n *\n * @example\n * isValidTaxonomy('insurance.auto.full_coverage.quote') // true\n * isValidTaxonomy('insurance.auto') // false (too short)\n * isValidTaxonomy('Insurance.Auto.Quote') // false (uppercase)\n * isValidTaxonomy('insurance.auto.full_coverage.invalid') // false (invalid intent)\n */\nexport function isValidTaxonomy(taxonomy: string): boolean {\n const parts = taxonomy.split('.');\n\n // Must be 3 or 4 parts\n if (parts.length < 3 || parts.length > 4) {\n return false;\n }\n\n // Check valid intent (if present)\n const validIntents: TaxonomyIntent[] = [\n 'research', 'compare', 'quote', 'trial',\n 'book', 'apply', 'consultation'\n ];\n\n if (parts.length === 4 && !validIntents.includes(parts[3] as TaxonomyIntent)) {\n return false;\n }\n\n // Check each part is non-empty and alphanumeric + underscore\n return parts.every(part => /^[a-z0-9_]+$/.test(part));\n}\n\n/**\n * Parsed taxonomy components\n */\nexport interface ParsedTaxonomy {\n /** Industry vertical (tier 1) */\n vertical: string;\n /** Product/service category (tier 2) */\n category: string;\n /** Specific offering (tier 3) */\n subcategory: string;\n /** User journey stage (tier 4, optional) */\n intent?: TaxonomyIntent;\n /** Full taxonomy string */\n full: string;\n}\n\n/**\n * Parse taxonomy into components\n *\n * Breaks down a taxonomy string into its constituent parts for analysis.\n *\n * @param taxonomy - Taxonomy string to parse\n * @returns Parsed components or null if invalid\n *\n * @example\n * parseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: {\n * // vertical: 'insurance',\n * // category: 'auto',\n * // subcategory: 'full_coverage',\n * // intent: 'quote',\n * // full: 'insurance.auto.full_coverage.quote'\n * // }\n */\nexport function parseTaxonomy(taxonomy: string): ParsedTaxonomy | null {\n if (!isValidTaxonomy(taxonomy)) {\n return null;\n }\n\n const parts = taxonomy.split('.');\n const result: ParsedTaxonomy = {\n vertical: parts[0]!,\n category: parts[1]!,\n subcategory: parts[2]!,\n full: taxonomy\n };\n\n if (parts[3]) {\n result.intent = parts[3] as TaxonomyIntent;\n }\n\n return result;\n}\n\n/**\n * Get taxonomy without intent modifier\n *\n * Useful for broader matching or grouping taxonomies by product/service.\n *\n * @param taxonomy - Full taxonomy string\n * @returns Taxonomy without intent, or null if invalid\n *\n * @example\n * getBaseTaxonomy('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance.auto.full_coverage'\n */\nexport function getBaseTaxonomy(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n if (!parsed) return null;\n\n return `${parsed.vertical}.${parsed.category}.${parsed.subcategory}`;\n}\n\n/**\n * Check if two taxonomies match hierarchically\n *\n * Returns true if they share the same vertical, category, and subcategory\n * (regardless of intent).\n *\n * @param taxonomy1 - First taxonomy\n * @param taxonomy2 - Second taxonomy\n * @returns True if they match hierarchically\n *\n * @example\n * matchesTaxonomy(\n * 'insurance.auto.full_coverage.quote',\n * 'insurance.auto.full_coverage.apply'\n * )\n * // Returns: true (same base, different intent)\n */\nexport function matchesTaxonomy(taxonomy1: string, taxonomy2: string): boolean {\n const base1 = getBaseTaxonomy(taxonomy1);\n const base2 = getBaseTaxonomy(taxonomy2);\n\n return base1 !== null && base2 !== null && base1 === base2;\n}\n\n/**\n * Get vertical from taxonomy\n *\n * Extracts just the industry vertical (tier 1).\n *\n * @param taxonomy - Taxonomy string\n * @returns Vertical or null if invalid\n *\n * @example\n * getVertical('insurance.auto.full_coverage.quote')\n * // Returns: 'insurance'\n */\nexport function getVertical(taxonomy: string): string | null {\n const parsed = parseTaxonomy(taxonomy);\n return parsed ? parsed.vertical : null;\n}\n\n/**\n * Suggest taxonomies based on user query\n *\n * Analyzes a user query and suggests appropriate taxonomies.\n * This is a basic implementation - you may want to enhance it\n * with ML/NLP for better accuracy.\n *\n * @param query - User's search query\n * @returns Array of suggested taxonomy strings\n *\n * @example\n * suggestTaxonomies(\"I need car insurance\")\n * // Returns: ['insurance.auto.full_coverage.quote', 'insurance.auto.liability.quote']\n */\nexport function suggestTaxonomies(query: string): string[] {\n const lowerQuery = query.toLowerCase();\n const intent = detectIntent(query);\n const suggestions: string[] = [];\n\n // Insurance-related\n if (/car|auto|vehicle|drive|driving/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'auto', 'full_coverage', intent));\n suggestions.push(buildTaxonomy('insurance', 'auto', 'liability', intent));\n }\n if (/health|medical|doctor/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'health', 'individual', intent));\n }\n if (/life insurance/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('insurance', 'life', 'term', intent));\n }\n\n // Business/SaaS\n if (/crm|customer|sales|lead/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'crm', intent));\n }\n if (/online store|ecommerce|sell online/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'ecommerce', 'platform', intent));\n }\n if (/project management|task|team/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('business', 'saas', 'project_management', intent));\n }\n\n // Legal\n if (/accident|injury|hurt/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'personal_injury', 'accident', intent));\n }\n if (/divorce|custody|family law/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('legal', 'family_law', 'divorce', intent));\n }\n\n // Healthcare\n if (/dentist|teeth|dental/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'dental', 'general', intent));\n }\n if (/therapist|therapy|counseling/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('healthcare', 'mental_health', 'therapy', intent));\n }\n\n // Home services\n if (/mover|moving|relocate/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'moving', 'local', intent));\n }\n if (/plumber|plumbing|leak/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'plumbing', 'emergency', intent));\n }\n if (/clean|cleaning|maid/.test(lowerQuery)) {\n suggestions.push(buildTaxonomy('home_services', 'cleaning', 'regular', intent));\n }\n\n return suggestions;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the_ro_show/agent-ads-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "TypeScript SDK for integrating agent-native sponsored units (Sponsored Suggestions and Sponsored Tools) into AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",