@the_ro_show/agent-ads-sdk 0.1.2 โ†’ 0.2.1

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
@@ -2,6 +2,14 @@
2
2
 
3
3
  TypeScript SDK for integrating agent-native sponsored units (Sponsored Suggestions and Sponsored Tools) into AI agents.
4
4
 
5
+ ## ๐Ÿš€ New to this? Start Here
6
+
7
+ **โ†’ [Simple Integration Guide](./SIMPLE_INTEGRATION_GUIDE.md)** - Step-by-step guide for Clawdbot/Moltbot developers
8
+
9
+ This README contains the full API reference. If you're just getting started, the Simple Integration Guide is much easier to follow!
10
+
11
+ ---
12
+
5
13
  ## Installation
6
14
 
7
15
  ```bash
@@ -0,0 +1,324 @@
1
+ # ๐Ÿค– How to Add Ads to Your Clawdbot/Moltbot in Simple English
2
+
3
+ ### **The Big Picture:**
4
+ Your bot answers user questions. Sometimes those answers could include a helpful ad (like suggesting Pietra when someone asks about starting an online store). You get paid when users see/click those ads.
5
+
6
+ ---
7
+
8
+ ## Step-by-Step Guide
9
+
10
+ ### **Step 1: Sign Up for API Keys** (One-time, 2 minutes)
11
+
12
+ You need permission to request ads. Think of this like getting a key to a vending machine.
13
+
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
+ ```
25
+
26
+ **You'll get back:**
27
+ - An **Agent ID** (like `agt_abc123`) - your username
28
+ - A **Test Key** (like `am_test_xyz789`) - for testing
29
+ - A **Live Key** (like `am_live_xyz789`) - for when you go live
30
+
31
+ **Write these down!** You'll need them.
32
+
33
+ ---
34
+
35
+ ### **Step 2: Install the SDK** (One-time, 30 seconds)
36
+
37
+ This is like downloading an app on your phone.
38
+
39
+ **In your bot's code folder:**
40
+ ```bash
41
+ npm install @the_ro_show/agent-ads-sdk
42
+ ```
43
+
44
+ Done!
45
+
46
+ ---
47
+
48
+ ### **Step 3: Set Up Your Credentials** (One-time, 1 minute)
49
+
50
+ Create a file called `.env` in your bot's folder:
51
+
52
+ ```bash
53
+ ATTENTIONMARKET_API_KEY=am_test_xyz789 # Use your test key from Step 1
54
+ ATTENTIONMARKET_AGENT_ID=agt_abc123 # Use your agent ID from Step 1
55
+ ```
56
+
57
+ ---
58
+
59
+ ### **Step 4: Add Code to Your Bot** (10 minutes)
60
+
61
+ This is where your bot learns to request and show ads.
62
+
63
+ #### **A. Import the SDK** (Add to top of your bot's code)
64
+
65
+ ```typescript
66
+ import { AttentionMarketClient, createOpportunity } from '@the_ro_show/agent-ads-sdk';
67
+
68
+ // Initialize the SDK (this connects you to the ad platform)
69
+ const adClient = new AttentionMarketClient({
70
+ apiKey: process.env.ATTENTIONMARKET_API_KEY,
71
+ });
72
+ ```
73
+
74
+ #### **B. Decide WHEN to Show Ads**
75
+
76
+ You choose when! Here's a simple example:
77
+
78
+ ```typescript
79
+ // Your bot's existing code that handles user messages
80
+ async function handleUserMessage(userMessage) {
81
+ // 1. Your bot answers the question (like normal)
82
+ const botAnswer = await yourBot.respond(userMessage);
83
+
84
+ // 2. Check if this is a good time to show an ad
85
+ let ad = null;
86
+
87
+ if (userMessage.toLowerCase().includes('online store') ||
88
+ userMessage.toLowerCase().includes('ecommerce')) {
89
+
90
+ // 3. Request an ad about e-commerce
91
+ ad = await adClient.decide({
92
+ request_id: `req_${Date.now()}`,
93
+ agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
94
+ placement: {
95
+ type: 'sponsored_suggestion',
96
+ surface: 'chat'
97
+ },
98
+ opportunity: createOpportunity({
99
+ taxonomy: 'shopping.ecommerce.platform', // This matches Pietra!
100
+ country: 'US',
101
+ language: 'en',
102
+ platform: 'web',
103
+ }),
104
+ });
105
+ }
106
+
107
+ // 4. Send both the answer AND the ad back to the user
108
+ return {
109
+ answer: botAnswer,
110
+ ad: ad // This will be null if no ad matches
111
+ };
112
+ }
113
+ ```
114
+
115
+ #### **C. Display the Ad to the User**
116
+
117
+ Show it clearly labeled as "Sponsored":
118
+
119
+ ```typescript
120
+ // Example: Showing in chat
121
+ if (ad) {
122
+ console.log('\n--- Sponsored ---');
123
+ console.log(`${ad.suggestion.title}`);
124
+ console.log(`${ad.suggestion.body}`);
125
+ console.log(`๐Ÿ‘‰ ${ad.suggestion.cta}: ${ad.suggestion.action_url}`);
126
+ console.log(`(Sponsored by ${ad.disclosure.sponsor_name})`);
127
+
128
+ // Track that user saw it (you get paid!)
129
+ await adClient.trackImpression({
130
+ agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
131
+ request_id: 'req_...', // Same ID from step B
132
+ decision_id: ad.unit_id,
133
+ unit_id: ad.unit_id,
134
+ tracking_token: ad.tracking.token,
135
+ });
136
+ }
137
+ ```
138
+
139
+ #### **D. Track When User Clicks**
140
+
141
+ If user clicks the ad link:
142
+
143
+ ```typescript
144
+ // When user clicks the ad link
145
+ await adClient.trackClick({
146
+ agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
147
+ request_id: 'req_...',
148
+ decision_id: ad.unit_id,
149
+ unit_id: ad.unit_id,
150
+ tracking_token: ad.tracking.token,
151
+ });
152
+ ```
153
+
154
+ ---
155
+
156
+ ## ๐Ÿ“ฑ **Real Example: Bot Conversation**
157
+
158
+ **User:** "How do I start an online store?"
159
+
160
+ **Your Bot:**
161
+ ```
162
+ ๐Ÿค– Starting an online store involves choosing a platform,
163
+ setting up products, and marketing. Here are the basics...
164
+
165
+ [Your bot's helpful answer here]
166
+
167
+ --- Sponsored ---
168
+ Pietra - E-Commerce Platform for Product Brands
169
+ Launch your online store in minutes. Built-in inventory,
170
+ shipping, and payments. Trusted by 10,000+ brands.
171
+
172
+ ๐Ÿ‘‰ Start Free Trial: https://pietrastudio.com
173
+ (Sponsored by Pietra Inc)
174
+ ```
175
+
176
+ **You get paid** when the user sees this โœ…
177
+ **You get paid more** if they click it โœ…
178
+
179
+ ---
180
+
181
+ ## ๐ŸŽฏ **What You Control:**
182
+
183
+ ### โœ… **You Decide WHEN to Show Ads:**
184
+ - After certain types of questions
185
+ - Once every 5 messages
186
+ - Only for commercial topics
187
+ - Never for sensitive topics
188
+
189
+ ### โœ… **You Decide WHERE:**
190
+ - In chat responses
191
+ - In email summaries
192
+ - In tool suggestions
193
+ - In search results
194
+
195
+ ### โœ… **You Decide HOW:**
196
+ - At the bottom of your response
197
+ - In a special "Sponsored" section
198
+ - As a subtle suggestion
199
+ - With colors/borders (if you have a UI)
200
+
201
+ ---
202
+
203
+ ## ๐Ÿ”‘ **Key Concepts (Simple):**
204
+
205
+ ### **Taxonomy** = Topic Category
206
+ 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
209
+ - `business.productivity.tools` = Productivity software questions
210
+
211
+ **You pick the taxonomy** based on what the user asked.
212
+
213
+ ### **Request ID** = Unique Identifier
214
+ Like a receipt number. You generate a random ID each time you request an ad:
215
+ ```typescript
216
+ request_id: `req_${Date.now()}` // req_1706889234567
217
+ ```
218
+
219
+ ### **Tracking Token** = Proof You Showed the Ad
220
+ Comes back with the ad. You send it when tracking impressions/clicks so we know it's real.
221
+
222
+ ---
223
+
224
+ ## ๐Ÿ’ฐ **How You Make Money:**
225
+
226
+ 1. **User asks question** โ†’ Your bot answers
227
+ 2. **You request ad** โ†’ Get back Pietra ad
228
+ 3. **You show ad** โ†’ Track impression โ†’ **You get paid ~$0.005**
229
+ 4. **User clicks ad** โ†’ Track click โ†’ **You get paid ~$0.50**
230
+
231
+ **That's it!** The more relevant ads you show, the more users click, the more you earn.
232
+
233
+ ---
234
+
235
+ ## โšก **Quick Start (Copy-Paste):**
236
+
237
+ Here's everything in one block:
238
+
239
+ ```typescript
240
+ import { AttentionMarketClient, createOpportunity } from '@the_ro_show/agent-ads-sdk';
241
+
242
+ const adClient = new AttentionMarketClient({
243
+ apiKey: process.env.ATTENTIONMARKET_API_KEY,
244
+ });
245
+
246
+ // In your message handler
247
+ async function handleMessage(userMessage) {
248
+ const botAnswer = await yourBot.respond(userMessage);
249
+
250
+ // Request ad if about e-commerce
251
+ let ad = null;
252
+ if (userMessage.includes('online store')) {
253
+ ad = await adClient.decide({
254
+ request_id: `req_${Date.now()}`,
255
+ agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
256
+ placement: { type: 'sponsored_suggestion', surface: 'chat' },
257
+ opportunity: createOpportunity({
258
+ taxonomy: 'shopping.ecommerce.platform',
259
+ country: 'US',
260
+ language: 'en',
261
+ platform: 'web',
262
+ }),
263
+ });
264
+
265
+ // Track impression
266
+ if (ad) {
267
+ await adClient.trackImpression({
268
+ agent_id: process.env.ATTENTIONMARKET_AGENT_ID,
269
+ request_id: 'req_...',
270
+ decision_id: ad.unit_id,
271
+ unit_id: ad.unit_id,
272
+ tracking_token: ad.tracking.token,
273
+ });
274
+ }
275
+ }
276
+
277
+ return { answer: botAnswer, ad };
278
+ }
279
+ ```
280
+
281
+ ---
282
+
283
+ ## โœ… **Checklist:**
284
+
285
+ - [ ] Run signup command to get API keys
286
+ - [ ] Install SDK: `npm install @the_ro_show/agent-ads-sdk`
287
+ - [ ] Create `.env` file with your keys
288
+ - [ ] Add SDK import to your bot code
289
+ - [ ] Add code to request ads (when appropriate)
290
+ - [ ] Add code to display ads (clearly labeled)
291
+ - [ ] Add tracking for impressions
292
+ - [ ] Add tracking for clicks
293
+ - [ ] Test with a question about "online store"
294
+ - [ ] See Pietra ad appear!
295
+
296
+ ---
297
+
298
+ **That's it!** You're now monetizing your bot with relevant ads. ๐ŸŽ‰
299
+
300
+ Users get helpful suggestions, advertisers reach their audience, and you get paid for providing value to both sides.
301
+
302
+ ---
303
+
304
+ ## ๐Ÿ†˜ **Need Help?**
305
+
306
+ - **Documentation:** See README.md for full API reference
307
+ - **Security:** See SECURITY.md for best practices
308
+ - **Issues:** https://github.com/rtrivedi/agent-ads-sdk/issues
309
+
310
+ ---
311
+
312
+ ## ๐Ÿ”„ **Advanced: Custom Configuration**
313
+
314
+ If you're self-hosting or need custom backend:
315
+
316
+ ```typescript
317
+ const adClient = new AttentionMarketClient({
318
+ apiKey: process.env.ATTENTIONMARKET_API_KEY,
319
+ baseUrl: 'https://your-custom-backend.com/v1', // Optional
320
+ supabaseAnonKey: 'your-key', // Optional (for Supabase)
321
+ });
322
+ ```
323
+
324
+ For most users, just the `apiKey` is enough!
package/dist/index.d.mts CHANGED
@@ -141,6 +141,7 @@ interface APIError {
141
141
  }
142
142
  interface SDKConfig {
143
143
  apiKey: string;
144
+ supabaseAnonKey?: string;
144
145
  baseUrl?: string;
145
146
  timeoutMs?: number;
146
147
  maxRetries?: number;
package/dist/index.d.ts CHANGED
@@ -141,6 +141,7 @@ interface APIError {
141
141
  }
142
142
  interface SDKConfig {
143
143
  apiKey: string;
144
+ supabaseAnonKey?: string;
144
145
  baseUrl?: string;
145
146
  timeoutMs?: number;
146
147
  maxRetries?: number;
package/dist/index.js CHANGED
@@ -115,8 +115,11 @@ var HTTPClient = class {
115
115
  "Content-Type": "application/json",
116
116
  ...options.headers
117
117
  };
118
+ if (this.config.supabaseAnonKey) {
119
+ headers["Authorization"] = `Bearer ${this.config.supabaseAnonKey}`;
120
+ }
118
121
  if (this.config.apiKey) {
119
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
122
+ headers["X-AM-API-Key"] = this.config.apiKey;
120
123
  }
121
124
  if (options.idempotencyKey) {
122
125
  headers["Idempotency-Key"] = options.idempotencyKey;
@@ -338,19 +341,23 @@ function sanitizeURL(url, options) {
338
341
  }
339
342
 
340
343
  // src/client.ts
341
- var DEFAULT_BASE_URL = "https://api.attentionmarket.ai";
344
+ var DEFAULT_BASE_URL = "https://api.attentionmarket.ai/v1";
342
345
  var DEFAULT_TIMEOUT_MS = 4e3;
343
346
  var DEFAULT_MAX_RETRIES = 2;
344
347
  var AttentionMarketClient = class {
345
348
  http;
346
349
  constructor(config) {
347
350
  this.validateConfig(config);
348
- this.http = new HTTPClient({
351
+ const httpConfig = {
349
352
  apiKey: config.apiKey,
350
353
  baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
351
354
  timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
352
355
  maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES
353
- });
356
+ };
357
+ if (config.supabaseAnonKey !== void 0) {
358
+ httpConfig.supabaseAnonKey = config.supabaseAnonKey;
359
+ }
360
+ this.http = new HTTPClient(httpConfig);
354
361
  }
355
362
  /**
356
363
  * Validate SDK configuration for security
@@ -433,7 +440,7 @@ var AttentionMarketClient = class {
433
440
  timeoutMs: DEFAULT_TIMEOUT_MS,
434
441
  maxRetries: DEFAULT_MAX_RETRIES
435
442
  });
436
- return await http.request("POST", "/v1/agent/signup", {
443
+ return await http.request("POST", "/v1/agent-signup", {
437
444
  body: request
438
445
  });
439
446
  }
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"],"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 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 * 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;\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 if (this.config.apiKey) {\n headers['Authorization'] = `Bearer ${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\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai';\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 this.http = new HTTPClient({\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\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.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 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 * 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"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -77,8 +77,11 @@ var HTTPClient = class {
77
77
  "Content-Type": "application/json",
78
78
  ...options.headers
79
79
  };
80
+ if (this.config.supabaseAnonKey) {
81
+ headers["Authorization"] = `Bearer ${this.config.supabaseAnonKey}`;
82
+ }
80
83
  if (this.config.apiKey) {
81
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
84
+ headers["X-AM-API-Key"] = this.config.apiKey;
82
85
  }
83
86
  if (options.idempotencyKey) {
84
87
  headers["Idempotency-Key"] = options.idempotencyKey;
@@ -300,19 +303,23 @@ function sanitizeURL(url, options) {
300
303
  }
301
304
 
302
305
  // src/client.ts
303
- var DEFAULT_BASE_URL = "https://api.attentionmarket.ai";
306
+ var DEFAULT_BASE_URL = "https://api.attentionmarket.ai/v1";
304
307
  var DEFAULT_TIMEOUT_MS = 4e3;
305
308
  var DEFAULT_MAX_RETRIES = 2;
306
309
  var AttentionMarketClient = class {
307
310
  http;
308
311
  constructor(config) {
309
312
  this.validateConfig(config);
310
- this.http = new HTTPClient({
313
+ const httpConfig = {
311
314
  apiKey: config.apiKey,
312
315
  baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
313
316
  timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
314
317
  maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES
315
- });
318
+ };
319
+ if (config.supabaseAnonKey !== void 0) {
320
+ httpConfig.supabaseAnonKey = config.supabaseAnonKey;
321
+ }
322
+ this.http = new HTTPClient(httpConfig);
316
323
  }
317
324
  /**
318
325
  * Validate SDK configuration for security
@@ -395,7 +402,7 @@ var AttentionMarketClient = class {
395
402
  timeoutMs: DEFAULT_TIMEOUT_MS,
396
403
  maxRetries: DEFAULT_MAX_RETRIES
397
404
  });
398
- return await http.request("POST", "/v1/agent/signup", {
405
+ return await http.request("POST", "/v1/agent-signup", {
399
406
  body: request
400
407
  });
401
408
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/utils.ts","../src/client.ts","../src/mock-client.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;\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 if (this.config.apiKey) {\n headers['Authorization'] = `Bearer ${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\nconst DEFAULT_BASE_URL = 'https://api.attentionmarket.ai';\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 this.http = new HTTPClient({\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\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"],"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"],"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"],"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.1.2",
3
+ "version": "0.2.1",
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",
@@ -16,7 +16,8 @@
16
16
  "dist",
17
17
  "README.md",
18
18
  "LICENSE",
19
- "SECURITY.md"
19
+ "SECURITY.md",
20
+ "SIMPLE_INTEGRATION_GUIDE.md"
20
21
  ],
21
22
  "repository": {
22
23
  "type": "git",