@adport/provider-reddit 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +16 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
resolveDateRange
|
|
120
120
|
} from "@adport/core";
|
|
121
121
|
var REDDIT_MICROS = 1e6;
|
|
122
|
-
var BREAKDOWN = { account: void 0, campaign: "
|
|
122
|
+
var BREAKDOWN = { account: void 0, campaign: "CAMPAIGN_ID", ad_group: "AD_GROUP_ID", ad: "AD_ID" };
|
|
123
123
|
var RedditAdsProvider = class {
|
|
124
124
|
constructor(client) {
|
|
125
125
|
this.client = client;
|
|
@@ -166,20 +166,22 @@ var RedditAdsProvider = class {
|
|
|
166
166
|
data: {
|
|
167
167
|
...breakdown ? { breakdowns: [breakdown] } : {},
|
|
168
168
|
fields: [
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
169
|
+
"SPEND",
|
|
170
|
+
"IMPRESSIONS",
|
|
171
|
+
"CLICKS",
|
|
172
|
+
"CONVERSION_PURCHASE_CLICKS",
|
|
173
|
+
"CONVERSION_PURCHASE_VIEWS",
|
|
174
|
+
"CONVERSION_PURCHASE_TOTAL_VALUE"
|
|
175
175
|
],
|
|
176
176
|
starts_at: `${range.start}T00:00:00Z`,
|
|
177
|
-
|
|
177
|
+
// The v3 schema accepts hourly granularity only. Midnight on the
|
|
178
|
+
// following day keeps an inclusive Adport date range intact.
|
|
179
|
+
ends_at: `${nextDay(range.end)}T00:00:00Z`,
|
|
178
180
|
time_zone_id: "UTC"
|
|
179
181
|
}
|
|
180
182
|
};
|
|
181
183
|
const metrics = await this.fetchReportPages(accountId, body, query.limit ?? 1e3);
|
|
182
|
-
for (const metric of metrics) rows.push(this.toReportRow(metric, accountId, query, breakdown));
|
|
184
|
+
for (const metric of metrics) rows.push(this.toReportRow(metric, accountId, query, breakdown?.toLowerCase()));
|
|
183
185
|
}
|
|
184
186
|
return { rows };
|
|
185
187
|
}
|
|
@@ -415,6 +417,11 @@ var RedditAdsProvider = class {
|
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
};
|
|
420
|
+
function nextDay(date) {
|
|
421
|
+
const value = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
422
|
+
value.setUTCDate(value.getUTCDate() + 1);
|
|
423
|
+
return value.toISOString().slice(0, 10);
|
|
424
|
+
}
|
|
418
425
|
function validateRedditPath(path) {
|
|
419
426
|
const normalized = path.replace(/^\/+|\/+$/g, "").trim();
|
|
420
427
|
if (!/^[A-Za-z0-9_.~/-]{3,220}$/.test(normalized) || normalized.includes("..") || normalized.includes("//")) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/provider.ts","../src/tools.ts","../src/index.ts"],"sourcesContent":["import { AdportError } from '@adport/core';\n\nexport interface RedditCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n /** Reddit requires an honest, uniquely identifying User-Agent on every request. */\n userAgent: string;\n onRefreshToken?: (refreshToken: string) => Promise<void>;\n}\n\nexport const REDDIT_API_VERSION = 'v3';\nexport const REDDIT_API_BASE = `https://ads-api.reddit.com/api/${REDDIT_API_VERSION}`;\nexport const REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token';\n\ninterface TokenState {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface RedditEnvelope<T> {\n data: T;\n pagination?: { next_url?: string; previous_url?: string; page_index?: number; total_count?: number };\n}\n\n/** OAuth-refreshing Reddit Ads API v3 client with exact next_url pagination. */\nexport class RedditAdsClient {\n private token?: TokenState;\n private refreshToken: string;\n\n constructor(\n private readonly credentials: RedditCredentials,\n private readonly fetchImpl: typeof fetch = fetch,\n ) {\n this.refreshToken = credentials.refreshToken;\n }\n\n async get<T>(pathOrUrl: string, params: Record<string, unknown> = {}): Promise<RedditEnvelope<T>> {\n const url = this.url(pathOrUrl, params);\n return this.send<T>(url, { method: 'GET' });\n }\n\n async post<T>(pathOrUrl: string, body: Record<string, unknown>): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'POST', body: JSON.stringify(body) });\n }\n\n async patch<T>(pathOrUrl: string, body: Record<string, unknown>): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'PATCH', body: JSON.stringify(body) });\n }\n\n async delete<T>(pathOrUrl: string): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'DELETE' });\n }\n\n async getPaged<T>(path: string, params: Record<string, unknown> = {}, limit = 1000): Promise<T[]> {\n const rows: T[] = [];\n let page = await this.get<T[]>(path, params);\n while (true) {\n rows.push(...page.data.slice(0, Math.max(0, limit - rows.length)));\n if (rows.length >= limit || !page.pagination?.next_url) return rows;\n page = await this.get<T[]>(page.pagination.next_url);\n }\n }\n\n private url(pathOrUrl: string, params: Record<string, unknown> = {}): string {\n const normalized = pathOrUrl.trim();\n const url = normalized.startsWith('https://')\n ? new URL(normalized)\n : new URL(`${REDDIT_API_BASE}/${normalized.replace(/^\\/+/, '')}`);\n if (url.origin !== 'https://ads-api.reddit.com' || !url.pathname.startsWith('/api/v3/')) {\n throw new AdportError('INVALID_INPUT', 'reddit: pagination or API URL must stay within https://ads-api.reddit.com/api/v3/');\n }\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));\n }\n return url.toString();\n }\n\n private async send<T>(url: string, init: { method: string; body?: string }): Promise<RedditEnvelope<T>> {\n const accessToken = await this.accessToken();\n const response = await this.fetchImpl(url, {\n method: init.method,\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'user-agent': this.credentials.userAgent,\n accept: 'application/json',\n ...(init.body ? { 'content-type': 'application/json' } : {}),\n },\n body: init.body,\n });\n const raw = await response.text();\n const parsed = raw ? safeJson(raw) : { data: {} };\n if (!response.ok) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Reddit Ads API ${response.status} ${init.method} ${new URL(url).pathname}: ${redditErrorMessage(parsed)}`,\n parsed,\n );\n }\n return parsed as RedditEnvelope<T>;\n }\n\n private async accessToken(): Promise<string> {\n if (this.token && this.token.expiresAt > Date.now() + 60_000) return this.token.accessToken;\n const response = await this.fetchImpl(REDDIT_TOKEN_URL, {\n method: 'POST',\n headers: {\n authorization: `Basic ${Buffer.from(`${this.credentials.clientId}:${this.credentials.clientSecret}`).toString('base64')}`,\n 'content-type': 'application/x-www-form-urlencoded',\n 'user-agent': this.credentials.userAgent,\n },\n body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.refreshToken }),\n });\n const data = (await response.json()) as {\n access_token?: string;\n expires_in?: number;\n refresh_token?: string;\n error?: string;\n message?: string;\n };\n if (!response.ok || !data.access_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Reddit OAuth refresh failed${data.error ? ` (${data.error})` : ''}: ${data.message ?? 'no access token returned'}`,\n data,\n );\n }\n if (data.refresh_token && data.refresh_token !== this.refreshToken) {\n this.refreshToken = data.refresh_token;\n await this.credentials.onRefreshToken?.(data.refresh_token);\n }\n this.token = {\n accessToken: data.access_token,\n expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,\n };\n return data.access_token;\n }\n}\n\nfunction safeJson(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return { message: raw };\n }\n}\n\nfunction redditErrorMessage(value: unknown): string {\n if (!value || typeof value !== 'object') return String(value);\n const record = value as Record<string, unknown>;\n return String(record.message ?? record.error ?? record.detail ?? 'unknown provider error');\n}\n","import {\n AdportError,\n resolveDateRange,\n type Account,\n type AdProvider,\n type MetricName,\n type NormalizedQuery,\n type ProviderCapabilities,\n type Report,\n type ReportRow,\n type StandardActions,\n type WriteGuard,\n type WriteOperation,\n type WritePreview,\n type WriteResult,\n} from '@adport/core';\nimport { RedditAdsClient, type RedditEnvelope } from './client.js';\n\n/** Reddit budget, bid, spend, CPC, and CPM fields are integer micros. */\nexport const REDDIT_MICROS = 1_000_000;\n\ninterface RedditBusiness { id: string; name?: string }\ninterface RedditAdAccount {\n id: string;\n name?: string;\n currency?: string;\n admin_approval?: string;\n suspension_reason?: string;\n}\nexport interface RedditCampaign {\n id: string;\n ad_account_id: string;\n name: string;\n configured_status: string;\n effective_status?: string;\n objective?: string;\n funding_instrument_id?: string;\n goal_type?: string;\n goal_value?: number;\n spend_cap?: number;\n is_campaign_budget_optimization?: boolean;\n}\n\ninterface WritePlan {\n summary: string;\n changes: string[];\n coercions: string[];\n budgetDeltas: WritePreview['budgetDeltas'];\n execute: () => Promise<string[]>;\n}\n\nconst BREAKDOWN = { account: undefined, campaign: 'campaign_id', ad_group: 'ad_group_id', ad: 'ad_id' } as const;\n\nexport class RedditAdsProvider implements AdProvider {\n readonly id = 'reddit';\n\n constructor(private readonly client: RedditAdsClient) {}\n\n capabilities(): ProviderCapabilities {\n return { serverDryRun: false };\n }\n\n standardActions(): StandardActions {\n return {\n pauseCampaign: (accountId, campaignId) => ({\n tool: 'reddit_set_campaign_status',\n input: { account_id: accountId, campaign_id: campaignId, configured_status: 'PAUSED' },\n }),\n };\n }\n\n async listAccounts(): Promise<Account[]> {\n const businesses = await this.client.getPaged<RedditBusiness>('me/businesses', { 'page.size': 100 }, 1000);\n const byId = new Map<string, RedditAdAccount>();\n for (const business of businesses) {\n const accounts = await this.client.getPaged<RedditAdAccount>(\n `businesses/${encodeURIComponent(business.id)}/ad_accounts`,\n { 'page.size': 100 },\n 1000,\n );\n for (const account of accounts) byId.set(account.id, account);\n }\n return [...byId.values()].map((account) => ({\n provider: this.id,\n id: account.id,\n name: account.name ?? account.id,\n currency: account.currency,\n status: account.suspension_reason ? `SUSPENDED:${account.suspension_reason}` : account.admin_approval,\n }));\n }\n\n async report(query: NormalizedQuery): Promise<Report> {\n const range = resolveDateRange(query.dateRange);\n const accountIds = query.accountIds ?? (await this.listAccounts()).map((account) => account.id);\n const rows: ReportRow[] = [];\n for (const accountId of accountIds) {\n const breakdown = BREAKDOWN[query.level];\n const body = {\n data: {\n ...(breakdown ? { breakdowns: [breakdown] } : {}),\n fields: [\n 'spend',\n 'impressions',\n 'clicks',\n 'conversion_purchase_clicks',\n 'conversion_purchase_views',\n 'conversion_purchase_total_value',\n ],\n starts_at: `${range.start}T00:00:00Z`,\n ends_at: `${range.end}T23:59:59Z`,\n time_zone_id: 'UTC',\n },\n };\n const metrics = await this.fetchReportPages(accountId, body, query.limit ?? 1000);\n for (const metric of metrics) rows.push(this.toReportRow(metric, accountId, query, breakdown));\n }\n return { rows };\n }\n\n async rawReport(input: {\n account_id: string;\n breakdowns?: string[];\n fields: string[];\n starts_at: string;\n ends_at: string;\n time_zone_id?: string;\n filter?: string;\n page_size?: number;\n }): Promise<{ metrics: Array<Record<string, unknown>>; metrics_updated_at?: string }> {\n const body = {\n data: {\n ...(input.breakdowns?.length ? { breakdowns: input.breakdowns } : {}),\n fields: input.fields,\n starts_at: input.starts_at,\n ends_at: input.ends_at,\n time_zone_id: input.time_zone_id ?? 'UTC',\n ...(input.filter ? { filter: input.filter } : {}),\n },\n };\n const first = await this.client.post<{ metrics?: Array<Record<string, unknown>>; metrics_updated_at?: string }>(\n `ad_accounts/${encodeURIComponent(input.account_id)}/reports?page.size=${Math.min(input.page_size ?? 200, 1000)}`,\n body,\n );\n const metrics = [...(first.data.metrics ?? [])];\n let next = first.pagination?.next_url;\n while (next && metrics.length < (input.page_size ?? 200)) {\n const page = await this.client.get<{ metrics?: Array<Record<string, unknown>> }>(next);\n metrics.push(...(page.data.metrics ?? []));\n next = page.pagination?.next_url;\n }\n return { metrics: metrics.slice(0, input.page_size ?? 200), metrics_updated_at: first.data.metrics_updated_at };\n }\n\n async apiRead(input: {\n account_id: string;\n path: string;\n method?: 'GET' | 'POST';\n params?: Record<string, unknown>;\n body?: Record<string, unknown>;\n limit?: number;\n }): Promise<unknown> {\n const path = validateRedditPath(input.path);\n assertReadScoped(path, input.account_id);\n if (input.method === 'POST') {\n if (!/(?:\\/reports|\\/history|\\/query|\\/search)$/.test(path)) {\n throw new AdportError('INVALID_INPUT', 'reddit: read-only POST is limited to documented reports, history, query, and search endpoints');\n }\n return this.client.post(path, input.body ?? {});\n }\n if (input.limit) return this.client.getPaged(path, input.params, input.limit);\n return this.client.get(path, input.params);\n }\n\n async listCampaigns(accountId: string, ids?: string[]): Promise<RedditCampaign[]> {\n return this.client.getPaged<RedditCampaign>(\n `ad_accounts/${encodeURIComponent(accountId)}/campaigns`,\n { ...(ids?.length ? { id: ids } : {}), 'page.size': Math.min(ids?.length ?? 100, 100) },\n ids?.length ?? 1000,\n );\n }\n\n async previewWrite(op: WriteOperation, guard: WriteGuard): Promise<WritePreview> {\n const plan = await this.plan(op, guard);\n return {\n summary: plan.summary,\n changes: plan.changes,\n coercions: plan.coercions,\n budgetDeltas: plan.budgetDeltas,\n serverValidated: false,\n };\n }\n\n async applyWrite(op: WriteOperation, guard: WriteGuard): Promise<WriteResult> {\n const plan = await this.plan(op, guard);\n return { applied: true, resourceIds: await plan.execute() };\n }\n\n private async fetchReportPages(\n accountId: string,\n body: Record<string, unknown>,\n limit: number,\n ): Promise<Array<Record<string, unknown>>> {\n const first = await this.client.post<{ metrics?: Array<Record<string, unknown>> }>(\n `ad_accounts/${encodeURIComponent(accountId)}/reports?page.size=${Math.min(limit, 1000)}`,\n body,\n );\n const rows = [...(first.data.metrics ?? [])];\n let next = first.pagination?.next_url;\n while (next && rows.length < limit) {\n const page = await this.client.get<{ metrics?: Array<Record<string, unknown>> }>(next);\n rows.push(...(page.data.metrics ?? []));\n next = page.pagination?.next_url;\n }\n return rows.slice(0, limit);\n }\n\n private toReportRow(\n raw: Record<string, unknown>,\n accountId: string,\n query: NormalizedQuery,\n breakdown?: string,\n ): ReportRow {\n const value = (key: string) => Number(raw[key] ?? 0);\n const spend = value('spend') / REDDIT_MICROS;\n const impressions = value('impressions');\n const clicks = value('clicks');\n const conversions = value('conversion_purchase_clicks') + value('conversion_purchase_views');\n const conversionValue = value('conversion_purchase_total_value') / 100;\n const all: Record<MetricName, number> = {\n spend: round2(spend), impressions, clicks, conversions, conversion_value: round2(conversionValue),\n ctr: impressions ? round2((clicks / impressions) * 100) : 0,\n cpc: clicks ? round2(spend / clicks) : 0,\n cpm: impressions ? round2((spend / impressions) * 1000) : 0,\n cpa: conversions ? round2(spend / conversions) : 0,\n roas: spend ? round2(conversionValue / spend) : 0,\n };\n const entityId = breakdown ? String(raw[breakdown] ?? '') : accountId;\n return {\n provider: this.id,\n accountId,\n entity: { level: query.level, id: entityId, name: entityId },\n metrics: Object.fromEntries(query.metrics.map((metric) => [metric, all[metric]])),\n };\n }\n\n private async plan(op: WriteOperation, guard: WriteGuard): Promise<WritePlan> {\n const payload = op.payload as never;\n switch (op.tool) {\n case 'reddit_create_campaign': return this.planCreateCampaign(op.accountId, payload, guard);\n case 'reddit_set_campaign_status': return this.planSetStatus(op.accountId, payload);\n case 'reddit_set_budget': return this.planSetBudget(op.accountId, payload);\n case 'reddit_api_create': return this.planApiCreate(op.accountId, payload, guard);\n case 'reddit_api_update': return this.planApiUpdate(op.accountId, payload);\n case 'reddit_api_delete': return this.planApiDelete(op.accountId, payload);\n default: throw new AdportError('PROVIDER_ERROR', `reddit: unsupported write tool ${op.tool}`);\n }\n }\n\n private async planCreateCampaign(\n accountId: string,\n payload: {\n name: string;\n objective: string;\n funding_instrument_id: string;\n configured_status?: 'ACTIVE' | 'PAUSED';\n budget_micros?: number;\n budget_type?: 'DAILY_SPEND' | 'LIFETIME_SPEND';\n conversion_pixel_id?: string;\n spend_cap_micros?: number;\n },\n guard: WriteGuard,\n ): Promise<WritePlan> {\n if (payload.budget_micros && !payload.conversion_pixel_id) {\n throw new AdportError('INVALID_INPUT', 'reddit: conversion_pixel_id is required for CBO campaigns (effective July 13, 2026)');\n }\n const requested = payload.configured_status ?? 'PAUSED';\n const status = guard.forcePausedCreation ? 'PAUSED' : requested;\n const coercions = requested !== status\n ? ['configured_status coerced to PAUSED by policy (paused_creation)']\n : [];\n const data: Record<string, unknown> = {\n name: payload.name,\n configured_status: status,\n objective: payload.objective,\n funding_instrument_id: payload.funding_instrument_id,\n is_campaign_budget_optimization: payload.budget_micros !== undefined,\n ...(payload.budget_micros ? {\n goal_type: payload.budget_type ?? 'DAILY_SPEND',\n goal_value: payload.budget_micros,\n conversion_pixel_id: payload.conversion_pixel_id,\n } : {}),\n ...(payload.spend_cap_micros ? { spend_cap: payload.spend_cap_micros } : {}),\n };\n const budgetDeltas: WritePreview['budgetDeltas'] = [];\n if (payload.budget_micros) budgetDeltas.push({ target: `new campaign \"${payload.name}\" budget`, toMicros: payload.budget_micros });\n if (payload.spend_cap_micros) budgetDeltas.push({ target: `new campaign \"${payload.name}\" spend cap`, toMicros: payload.spend_cap_micros });\n return {\n summary: `Create Reddit campaign \"${payload.name}\" (${payload.objective}, ${status.toLowerCase()})`,\n changes: [`+ POST /ad_accounts/${accountId}/campaigns ${JSON.stringify({ data })}`],\n coercions,\n budgetDeltas,\n execute: async () => extractIds(await this.client.post<RedditCampaign>(`ad_accounts/${encodeURIComponent(accountId)}/campaigns`, { data })),\n };\n }\n\n private async planSetStatus(\n accountId: string,\n payload: { campaign_id: string; configured_status: 'ACTIVE' | 'PAUSED' },\n ): Promise<WritePlan> {\n const campaign = await this.getOwnedCampaign(accountId, payload.campaign_id);\n return {\n summary: `Set Reddit campaign \"${campaign.name}\" → ${payload.configured_status.toLowerCase()}`,\n changes: [`~ campaign ${campaign.id} configured_status ${campaign.configured_status} → ${payload.configured_status}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.patch<RedditCampaign>(`campaigns/${encodeURIComponent(campaign.id)}`, {\n data: { configured_status: payload.configured_status },\n })),\n };\n }\n\n private async planSetBudget(\n accountId: string,\n payload: { campaign_id: string; budget_micros: number; budget_type?: 'DAILY_SPEND' | 'LIFETIME_SPEND' },\n ): Promise<WritePlan> {\n const campaign = await this.getOwnedCampaign(accountId, payload.campaign_id);\n if (!campaign.is_campaign_budget_optimization) {\n throw new AdportError('INVALID_INPUT', `reddit: campaign \"${campaign.name}\" is not a CBO campaign; budget its ad groups instead`);\n }\n return {\n summary: `Change Reddit campaign \"${campaign.name}\" budget ${campaign.goal_value ?? '?'} → ${payload.budget_micros} micros`,\n changes: [`~ campaign ${campaign.id} ${campaign.goal_type ?? 'goal'} ${campaign.goal_value ?? '?'} → ${payload.budget_micros}`],\n coercions: [],\n budgetDeltas: [{ target: `campaign \"${campaign.name}\" budget`, fromMicros: campaign.goal_value, toMicros: payload.budget_micros }],\n execute: async () => extractIds(await this.client.patch<RedditCampaign>(`campaigns/${encodeURIComponent(campaign.id)}`, {\n data: { goal_value: payload.budget_micros, ...(payload.budget_type ? { goal_type: payload.budget_type } : {}) },\n })),\n };\n }\n\n private async planApiCreate(\n accountId: string,\n payload: { path: string; body: Record<string, unknown> },\n guard: WriteGuard,\n ): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n if (!path.startsWith(`ad_accounts/${accountId}/`)) {\n throw new AdportError('INVALID_INPUT', 'reddit: create path must be under the selected ad account');\n }\n const body = structuredClone(payload.body);\n const coercions: string[] = [];\n if (guard.forcePausedCreation) {\n coerceCreatedStatuses(body, coercions, /\\/(?:campaigns|ad_groups|ads)$/.test(path));\n }\n return {\n summary: `Create Reddit resource via /${path}`,\n changes: [`+ POST /${path} ${JSON.stringify(body)}`],\n coercions,\n budgetDeltas: collectMicros(body),\n execute: async () => extractIds(await this.client.post(path, body)),\n };\n }\n\n private async planApiUpdate(\n accountId: string,\n payload: { path: string; body: Record<string, unknown> },\n ): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n await this.assertOwnedResource(path, accountId);\n if (containsMoney(payload.body)) {\n throw new AdportError('INVALID_INPUT', 'reddit: budget, bid, goal, and spend-cap updates require a typed budget tool for policy checks');\n }\n return {\n summary: `Update Reddit resource via /${path}`,\n changes: [`~ PATCH /${path} ${JSON.stringify(payload.body)}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.patch(path, payload.body)),\n };\n }\n\n private async planApiDelete(accountId: string, payload: { path: string }): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n await this.assertOwnedResource(path, accountId);\n return {\n summary: `Permanently delete Reddit resource via /${path}`,\n changes: [`- DELETE /${path}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.delete(path)),\n };\n }\n\n private async getOwnedCampaign(accountId: string, campaignId: string): Promise<RedditCampaign> {\n const campaign = (await this.client.get<RedditCampaign>(`campaigns/${encodeURIComponent(campaignId)}`)).data;\n if (campaign.ad_account_id !== accountId) {\n throw new AdportError('INVALID_INPUT', `reddit: campaign ${campaignId} does not belong to selected account ${accountId}`);\n }\n return campaign;\n }\n\n private async assertOwnedResource(path: string, accountId: string): Promise<void> {\n const resource = (await this.client.get<Record<string, unknown>>(path)).data;\n if (String(resource.ad_account_id ?? '') !== accountId) {\n throw new AdportError('INVALID_INPUT', `reddit: /${path} does not prove ownership by selected account ${accountId}`);\n }\n }\n}\n\nfunction validateRedditPath(path: string): string {\n const normalized = path.replace(/^\\/+|\\/+$/g, '').trim();\n if (!/^[A-Za-z0-9_.~/-]{3,220}$/.test(normalized) || normalized.includes('..') || normalized.includes('//')) {\n throw new AdportError('INVALID_INPUT', `reddit: invalid Ads API v3 path \"${path}\"`);\n }\n return normalized;\n}\n\nfunction assertReadScoped(path: string, accountId: string): void {\n if (path === `ad_accounts/${accountId}` || path.startsWith(`ad_accounts/${accountId}/`)) return;\n if (/^(campaigns|ad_groups|ads|structured_posts)\\/[^/]+$/.test(path)) return;\n throw new AdportError('INVALID_INPUT', 'reddit: read path must be selected-account scoped or a single campaign/ad-group/ad/post resource');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction coerceCreatedStatuses(value: Record<string, unknown>, coercions: string[], statusBearingPath: boolean): void {\n const data = isRecord(value.data) ? value.data : value;\n const visit = (item: unknown, path = 'body'): void => {\n if (Array.isArray(item)) return item.forEach((child, index) => visit(child, `${path}[${index}]`));\n if (!isRecord(item)) return;\n for (const [key, child] of Object.entries(item)) {\n if (key === 'configured_status' && child !== 'PAUSED') {\n item[key] = 'PAUSED';\n coercions.push(`${path}.${key} coerced to PAUSED by policy (paused_creation)`);\n } else visit(child, `${path}.${key}`);\n }\n };\n visit(value);\n if (statusBearingPath && data.configured_status === undefined) {\n data.configured_status = 'PAUSED';\n coercions.push('body.data.configured_status set to PAUSED by policy (paused_creation)');\n }\n}\n\nconst MONEY_KEY = /(?:goal_value|spend_cap|bid_value|budget(?:_micros)?|daily_budget|lifetime_budget)$/i;\n\nfunction containsMoney(value: unknown): boolean {\n if (Array.isArray(value)) return value.some(containsMoney);\n if (!isRecord(value)) return false;\n return Object.entries(value).some(([key, child]) => MONEY_KEY.test(key) || containsMoney(child));\n}\n\nfunction collectMicros(value: unknown, path = 'body'): WritePreview['budgetDeltas'] {\n if (Array.isArray(value)) return value.flatMap((child, index) => collectMicros(child, `${path}[${index}]`));\n if (!isRecord(value)) return [];\n const deltas: WritePreview['budgetDeltas'] = [];\n for (const [key, child] of Object.entries(value)) {\n if (MONEY_KEY.test(key) && Number.isInteger(child) && Number(child) > 0) {\n deltas.push({ target: `${path}.${key}`, toMicros: Number(child) });\n }\n deltas.push(...collectMicros(child, `${path}.${key}`));\n }\n return deltas;\n}\n\nfunction extractIds(envelope: RedditEnvelope<unknown>): string[] {\n const ids: string[] = [];\n const visit = (value: unknown): void => {\n if (Array.isArray(value)) return value.forEach(visit);\n if (!isRecord(value)) return;\n if (typeof value.id === 'string' || typeof value.id === 'number') ids.push(String(value.id));\n for (const child of Object.values(value)) visit(child);\n };\n visit(envelope.data);\n return [...new Set(ids)];\n}\n\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","import { defineTool, guardedWriteTool, type AnyToolDefinition } from '@adport/core';\nimport { z } from 'zod';\nimport type { RedditAdsProvider } from './provider.js';\n\nconst status = z.enum(['ACTIVE', 'PAUSED']);\n\nexport function redditTools(provider: RedditAdsProvider): AnyToolDefinition[] {\n return [\n defineTool({\n name: 'reddit_api_read',\n namespace: 'reddit',\n description: 'Call a documented Reddit Ads API v3 GET or read-only reports/history/query/search POST endpoint, scoped to the selected ad account.',\n input: z.object({\n account_id: z.string(),\n path: z.string().min(3).describe('Relative v3 path, e.g. ad_accounts/{id}/ad_groups or campaigns/{campaign_id}'),\n method: z.enum(['GET', 'POST']).default('GET'),\n params: z.record(z.string(), z.unknown()).optional(),\n body: z.record(z.string(), z.unknown()).optional(),\n limit: z.number().int().positive().max(5000).optional().describe('Follow list pagination for GET responses'),\n }),\n annotations: { readOnly: true },\n handler: (input) => provider.apiRead(input),\n }),\n defineTool({\n name: 'reddit_campaigns',\n namespace: 'reddit',\n description: 'List Reddit campaigns and their native configured/effective status, CBO budget, objective, and funding instrument.',\n input: z.object({ account_id: z.string(), campaign_ids: z.array(z.string()).optional() }),\n annotations: { readOnly: true },\n async handler(input) { return { campaigns: await provider.listCampaigns(input.account_id, input.campaign_ids) }; },\n }),\n defineTool({\n name: 'reddit_report',\n namespace: 'reddit',\n description: 'Query Reddit Ads API v3 reporting directly. Spend, CPC, CPM, and applicable conversion fields are returned by Reddit in micros.',\n input: z.object({\n account_id: z.string(),\n breakdowns: z.array(z.string()).max(4).optional(),\n fields: z.array(z.string()).min(1).default(['spend', 'impressions', 'clicks']),\n starts_at: z.string().datetime({ offset: true }),\n ends_at: z.string().datetime({ offset: true }),\n time_zone_id: z.string().default('UTC'),\n filter: z.string().optional(),\n page_size: z.number().int().positive().max(1000).default(200),\n }),\n annotations: { readOnly: true },\n handler: (input) => provider.rawReport(input),\n }),\n guardedWriteTool({\n name: 'reddit_create_campaign',\n namespace: 'reddit',\n description: 'Create a Reddit campaign through API v3. Campaigns are forced PAUSED. CBO budgets and spend caps are integer micros.',\n provider: 'reddit', kind: 'create',\n payload: z.object({\n name: z.string().min(1),\n objective: z.string().min(1).default('TRAFFIC').describe('Current Reddit objective enum; Reddit has announced an enum migration for Sep 30, 2026'),\n funding_instrument_id: z.string().min(1),\n configured_status: status.optional(),\n budget_micros: z.number().int().positive().optional().describe('Sets CBO goal_value; requires conversion_pixel_id'),\n budget_type: z.enum(['DAILY_SPEND', 'LIFETIME_SPEND']).default('DAILY_SPEND'),\n conversion_pixel_id: z.string().optional(),\n spend_cap_micros: z.number().int().positive().optional(),\n }),\n }),\n guardedWriteTool({\n name: 'reddit_set_campaign_status', namespace: 'reddit',\n description: 'Activate or pause a Reddit campaign after verifying ad-account ownership.',\n provider: 'reddit', kind: 'update',\n payload: z.object({ campaign_id: z.string(), configured_status: status }),\n }),\n guardedWriteTool({\n name: 'reddit_set_budget', namespace: 'reddit',\n description: 'Change a Reddit CBO campaign goal_value in integer micros, with current-value lookup and policy checks.',\n provider: 'reddit', kind: 'update',\n payload: z.object({\n campaign_id: z.string(),\n budget_micros: z.number().int().positive(),\n budget_type: z.enum(['DAILY_SPEND', 'LIFETIME_SPEND']).optional(),\n }),\n }),\n guardedWriteTool({\n name: 'reddit_api_create', namespace: 'reddit',\n description: 'Create via an account-scoped Reddit Ads API v3 endpoint. Campaign status is forced PAUSED and native micros fields are policy-checked.',\n provider: 'reddit', kind: 'create',\n payload: z.object({ path: z.string().min(3), body: z.record(z.string(), z.unknown()) }),\n }),\n guardedWriteTool({\n name: 'reddit_api_update', namespace: 'reddit',\n description: 'PATCH non-budget fields on a Reddit Ads API v3 resource after verifying ad-account ownership.',\n provider: 'reddit', kind: 'update',\n payload: z.object({ path: z.string().min(3), body: z.record(z.string(), z.unknown()) }),\n }),\n guardedWriteTool({\n name: 'reddit_api_delete', namespace: 'reddit',\n description: 'Permanently DELETE a Reddit Ads API v3 resource after verifying ad-account ownership.',\n provider: 'reddit', kind: 'remove', destructive: true,\n payload: z.object({ path: z.string().min(3) }),\n }),\n ];\n}\n","import type { CredentialStore, ProviderModule } from '@adport/core';\nimport { RedditAdsClient, type RedditCredentials } from './client.js';\nimport { RedditAdsProvider } from './provider.js';\nimport { redditTools } from './tools.js';\n\nexport { RedditAdsClient, REDDIT_API_BASE, REDDIT_API_VERSION, REDDIT_TOKEN_URL, type RedditCredentials, type RedditEnvelope } from './client.js';\nexport { RedditAdsProvider, REDDIT_MICROS, type RedditCampaign } from './provider.js';\nexport { redditTools } from './tools.js';\n\nexport async function resolveRedditCredentials(store: CredentialStore): Promise<RedditCredentials | undefined> {\n const record = await store.get('reddit');\n const data = record?.data;\n if (data?.client_id && data.client_secret && data.refresh_token && data.user_agent) {\n const source = record?.source ?? 'byo';\n return {\n clientId: data.client_id,\n clientSecret: data.client_secret,\n refreshToken: data.refresh_token,\n userAgent: data.user_agent,\n onRefreshToken: async (refreshToken) => {\n await store.set({ provider: 'reddit', source, data: { ...data, refresh_token: refreshToken } });\n },\n };\n }\n if (process.env.REDDIT_ADS_CLIENT_ID && process.env.REDDIT_ADS_CLIENT_SECRET && process.env.REDDIT_ADS_REFRESH_TOKEN && process.env.REDDIT_ADS_USER_AGENT) {\n return {\n clientId: process.env.REDDIT_ADS_CLIENT_ID,\n clientSecret: process.env.REDDIT_ADS_CLIENT_SECRET,\n refreshToken: process.env.REDDIT_ADS_REFRESH_TOKEN,\n userAgent: process.env.REDDIT_ADS_USER_AGENT,\n };\n }\n return undefined;\n}\n\nexport async function createRedditModule(store: CredentialStore): Promise<ProviderModule | undefined> {\n const credentials = await resolveRedditCredentials(store);\n if (!credentials) return undefined;\n const provider = new RedditAdsProvider(new RedditAdsClient(credentials));\n return { provider, tools: redditTools(provider) };\n}\n"],"mappings":"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","names":["AdportError","status"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/provider.ts","../src/tools.ts","../src/index.ts"],"sourcesContent":["import { AdportError } from '@adport/core';\n\nexport interface RedditCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n /** Reddit requires an honest, uniquely identifying User-Agent on every request. */\n userAgent: string;\n onRefreshToken?: (refreshToken: string) => Promise<void>;\n}\n\nexport const REDDIT_API_VERSION = 'v3';\nexport const REDDIT_API_BASE = `https://ads-api.reddit.com/api/${REDDIT_API_VERSION}`;\nexport const REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token';\n\ninterface TokenState {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface RedditEnvelope<T> {\n data: T;\n pagination?: { next_url?: string; previous_url?: string; page_index?: number; total_count?: number };\n}\n\n/** OAuth-refreshing Reddit Ads API v3 client with exact next_url pagination. */\nexport class RedditAdsClient {\n private token?: TokenState;\n private refreshToken: string;\n\n constructor(\n private readonly credentials: RedditCredentials,\n private readonly fetchImpl: typeof fetch = fetch,\n ) {\n this.refreshToken = credentials.refreshToken;\n }\n\n async get<T>(pathOrUrl: string, params: Record<string, unknown> = {}): Promise<RedditEnvelope<T>> {\n const url = this.url(pathOrUrl, params);\n return this.send<T>(url, { method: 'GET' });\n }\n\n async post<T>(pathOrUrl: string, body: Record<string, unknown>): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'POST', body: JSON.stringify(body) });\n }\n\n async patch<T>(pathOrUrl: string, body: Record<string, unknown>): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'PATCH', body: JSON.stringify(body) });\n }\n\n async delete<T>(pathOrUrl: string): Promise<RedditEnvelope<T>> {\n return this.send<T>(this.url(pathOrUrl), { method: 'DELETE' });\n }\n\n async getPaged<T>(path: string, params: Record<string, unknown> = {}, limit = 1000): Promise<T[]> {\n const rows: T[] = [];\n let page = await this.get<T[]>(path, params);\n while (true) {\n rows.push(...page.data.slice(0, Math.max(0, limit - rows.length)));\n if (rows.length >= limit || !page.pagination?.next_url) return rows;\n page = await this.get<T[]>(page.pagination.next_url);\n }\n }\n\n private url(pathOrUrl: string, params: Record<string, unknown> = {}): string {\n const normalized = pathOrUrl.trim();\n const url = normalized.startsWith('https://')\n ? new URL(normalized)\n : new URL(`${REDDIT_API_BASE}/${normalized.replace(/^\\/+/, '')}`);\n if (url.origin !== 'https://ads-api.reddit.com' || !url.pathname.startsWith('/api/v3/')) {\n throw new AdportError('INVALID_INPUT', 'reddit: pagination or API URL must stay within https://ads-api.reddit.com/api/v3/');\n }\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));\n }\n return url.toString();\n }\n\n private async send<T>(url: string, init: { method: string; body?: string }): Promise<RedditEnvelope<T>> {\n const accessToken = await this.accessToken();\n const response = await this.fetchImpl(url, {\n method: init.method,\n headers: {\n authorization: `Bearer ${accessToken}`,\n 'user-agent': this.credentials.userAgent,\n accept: 'application/json',\n ...(init.body ? { 'content-type': 'application/json' } : {}),\n },\n body: init.body,\n });\n const raw = await response.text();\n const parsed = raw ? safeJson(raw) : { data: {} };\n if (!response.ok) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Reddit Ads API ${response.status} ${init.method} ${new URL(url).pathname}: ${redditErrorMessage(parsed)}`,\n parsed,\n );\n }\n return parsed as RedditEnvelope<T>;\n }\n\n private async accessToken(): Promise<string> {\n if (this.token && this.token.expiresAt > Date.now() + 60_000) return this.token.accessToken;\n const response = await this.fetchImpl(REDDIT_TOKEN_URL, {\n method: 'POST',\n headers: {\n authorization: `Basic ${Buffer.from(`${this.credentials.clientId}:${this.credentials.clientSecret}`).toString('base64')}`,\n 'content-type': 'application/x-www-form-urlencoded',\n 'user-agent': this.credentials.userAgent,\n },\n body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: this.refreshToken }),\n });\n const data = (await response.json()) as {\n access_token?: string;\n expires_in?: number;\n refresh_token?: string;\n error?: string;\n message?: string;\n };\n if (!response.ok || !data.access_token) {\n throw new AdportError(\n 'PROVIDER_ERROR',\n `Reddit OAuth refresh failed${data.error ? ` (${data.error})` : ''}: ${data.message ?? 'no access token returned'}`,\n data,\n );\n }\n if (data.refresh_token && data.refresh_token !== this.refreshToken) {\n this.refreshToken = data.refresh_token;\n await this.credentials.onRefreshToken?.(data.refresh_token);\n }\n this.token = {\n accessToken: data.access_token,\n expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,\n };\n return data.access_token;\n }\n}\n\nfunction safeJson(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return { message: raw };\n }\n}\n\nfunction redditErrorMessage(value: unknown): string {\n if (!value || typeof value !== 'object') return String(value);\n const record = value as Record<string, unknown>;\n return String(record.message ?? record.error ?? record.detail ?? 'unknown provider error');\n}\n","import {\n AdportError,\n resolveDateRange,\n type Account,\n type AdProvider,\n type MetricName,\n type NormalizedQuery,\n type ProviderCapabilities,\n type Report,\n type ReportRow,\n type StandardActions,\n type WriteGuard,\n type WriteOperation,\n type WritePreview,\n type WriteResult,\n} from '@adport/core';\nimport { RedditAdsClient, type RedditEnvelope } from './client.js';\n\n/** Reddit budget, bid, spend, CPC, and CPM fields are integer micros. */\nexport const REDDIT_MICROS = 1_000_000;\n\ninterface RedditBusiness { id: string; name?: string }\ninterface RedditAdAccount {\n id: string;\n name?: string;\n currency?: string;\n admin_approval?: string;\n suspension_reason?: string;\n}\nexport interface RedditCampaign {\n id: string;\n ad_account_id: string;\n name: string;\n configured_status: string;\n effective_status?: string;\n objective?: string;\n funding_instrument_id?: string;\n goal_type?: string;\n goal_value?: number;\n spend_cap?: number;\n is_campaign_budget_optimization?: boolean;\n}\n\ninterface WritePlan {\n summary: string;\n changes: string[];\n coercions: string[];\n budgetDeltas: WritePreview['budgetDeltas'];\n execute: () => Promise<string[]>;\n}\n\n// Reddit v3 request enums are uppercase even though response object keys are lowercase.\nconst BREAKDOWN = { account: undefined, campaign: 'CAMPAIGN_ID', ad_group: 'AD_GROUP_ID', ad: 'AD_ID' } as const;\n\nexport class RedditAdsProvider implements AdProvider {\n readonly id = 'reddit';\n\n constructor(private readonly client: RedditAdsClient) {}\n\n capabilities(): ProviderCapabilities {\n return { serverDryRun: false };\n }\n\n standardActions(): StandardActions {\n return {\n pauseCampaign: (accountId, campaignId) => ({\n tool: 'reddit_set_campaign_status',\n input: { account_id: accountId, campaign_id: campaignId, configured_status: 'PAUSED' },\n }),\n };\n }\n\n async listAccounts(): Promise<Account[]> {\n const businesses = await this.client.getPaged<RedditBusiness>('me/businesses', { 'page.size': 100 }, 1000);\n const byId = new Map<string, RedditAdAccount>();\n for (const business of businesses) {\n const accounts = await this.client.getPaged<RedditAdAccount>(\n `businesses/${encodeURIComponent(business.id)}/ad_accounts`,\n { 'page.size': 100 },\n 1000,\n );\n for (const account of accounts) byId.set(account.id, account);\n }\n return [...byId.values()].map((account) => ({\n provider: this.id,\n id: account.id,\n name: account.name ?? account.id,\n currency: account.currency,\n status: account.suspension_reason ? `SUSPENDED:${account.suspension_reason}` : account.admin_approval,\n }));\n }\n\n async report(query: NormalizedQuery): Promise<Report> {\n const range = resolveDateRange(query.dateRange);\n const accountIds = query.accountIds ?? (await this.listAccounts()).map((account) => account.id);\n const rows: ReportRow[] = [];\n for (const accountId of accountIds) {\n const breakdown = BREAKDOWN[query.level];\n const body = {\n data: {\n ...(breakdown ? { breakdowns: [breakdown] } : {}),\n fields: [\n 'SPEND',\n 'IMPRESSIONS',\n 'CLICKS',\n 'CONVERSION_PURCHASE_CLICKS',\n 'CONVERSION_PURCHASE_VIEWS',\n 'CONVERSION_PURCHASE_TOTAL_VALUE',\n ],\n starts_at: `${range.start}T00:00:00Z`,\n // The v3 schema accepts hourly granularity only. Midnight on the\n // following day keeps an inclusive Adport date range intact.\n ends_at: `${nextDay(range.end)}T00:00:00Z`,\n time_zone_id: 'UTC',\n },\n };\n const metrics = await this.fetchReportPages(accountId, body, query.limit ?? 1000);\n for (const metric of metrics) rows.push(this.toReportRow(metric, accountId, query, breakdown?.toLowerCase()));\n }\n return { rows };\n }\n\n async rawReport(input: {\n account_id: string;\n breakdowns?: string[];\n fields: string[];\n starts_at: string;\n ends_at: string;\n time_zone_id?: string;\n filter?: string;\n page_size?: number;\n }): Promise<{ metrics: Array<Record<string, unknown>>; metrics_updated_at?: string }> {\n const body = {\n data: {\n ...(input.breakdowns?.length ? { breakdowns: input.breakdowns } : {}),\n fields: input.fields,\n starts_at: input.starts_at,\n ends_at: input.ends_at,\n time_zone_id: input.time_zone_id ?? 'UTC',\n ...(input.filter ? { filter: input.filter } : {}),\n },\n };\n const first = await this.client.post<{ metrics?: Array<Record<string, unknown>>; metrics_updated_at?: string }>(\n `ad_accounts/${encodeURIComponent(input.account_id)}/reports?page.size=${Math.min(input.page_size ?? 200, 1000)}`,\n body,\n );\n const metrics = [...(first.data.metrics ?? [])];\n let next = first.pagination?.next_url;\n while (next && metrics.length < (input.page_size ?? 200)) {\n const page = await this.client.get<{ metrics?: Array<Record<string, unknown>> }>(next);\n metrics.push(...(page.data.metrics ?? []));\n next = page.pagination?.next_url;\n }\n return { metrics: metrics.slice(0, input.page_size ?? 200), metrics_updated_at: first.data.metrics_updated_at };\n }\n\n async apiRead(input: {\n account_id: string;\n path: string;\n method?: 'GET' | 'POST';\n params?: Record<string, unknown>;\n body?: Record<string, unknown>;\n limit?: number;\n }): Promise<unknown> {\n const path = validateRedditPath(input.path);\n assertReadScoped(path, input.account_id);\n if (input.method === 'POST') {\n if (!/(?:\\/reports|\\/history|\\/query|\\/search)$/.test(path)) {\n throw new AdportError('INVALID_INPUT', 'reddit: read-only POST is limited to documented reports, history, query, and search endpoints');\n }\n return this.client.post(path, input.body ?? {});\n }\n if (input.limit) return this.client.getPaged(path, input.params, input.limit);\n return this.client.get(path, input.params);\n }\n\n async listCampaigns(accountId: string, ids?: string[]): Promise<RedditCampaign[]> {\n return this.client.getPaged<RedditCampaign>(\n `ad_accounts/${encodeURIComponent(accountId)}/campaigns`,\n { ...(ids?.length ? { id: ids } : {}), 'page.size': Math.min(ids?.length ?? 100, 100) },\n ids?.length ?? 1000,\n );\n }\n\n async previewWrite(op: WriteOperation, guard: WriteGuard): Promise<WritePreview> {\n const plan = await this.plan(op, guard);\n return {\n summary: plan.summary,\n changes: plan.changes,\n coercions: plan.coercions,\n budgetDeltas: plan.budgetDeltas,\n serverValidated: false,\n };\n }\n\n async applyWrite(op: WriteOperation, guard: WriteGuard): Promise<WriteResult> {\n const plan = await this.plan(op, guard);\n return { applied: true, resourceIds: await plan.execute() };\n }\n\n private async fetchReportPages(\n accountId: string,\n body: Record<string, unknown>,\n limit: number,\n ): Promise<Array<Record<string, unknown>>> {\n const first = await this.client.post<{ metrics?: Array<Record<string, unknown>> }>(\n `ad_accounts/${encodeURIComponent(accountId)}/reports?page.size=${Math.min(limit, 1000)}`,\n body,\n );\n const rows = [...(first.data.metrics ?? [])];\n let next = first.pagination?.next_url;\n while (next && rows.length < limit) {\n const page = await this.client.get<{ metrics?: Array<Record<string, unknown>> }>(next);\n rows.push(...(page.data.metrics ?? []));\n next = page.pagination?.next_url;\n }\n return rows.slice(0, limit);\n }\n\n private toReportRow(\n raw: Record<string, unknown>,\n accountId: string,\n query: NormalizedQuery,\n breakdown?: string,\n ): ReportRow {\n const value = (key: string) => Number(raw[key] ?? 0);\n const spend = value('spend') / REDDIT_MICROS;\n const impressions = value('impressions');\n const clicks = value('clicks');\n const conversions = value('conversion_purchase_clicks') + value('conversion_purchase_views');\n const conversionValue = value('conversion_purchase_total_value') / 100;\n const all: Record<MetricName, number> = {\n spend: round2(spend), impressions, clicks, conversions, conversion_value: round2(conversionValue),\n ctr: impressions ? round2((clicks / impressions) * 100) : 0,\n cpc: clicks ? round2(spend / clicks) : 0,\n cpm: impressions ? round2((spend / impressions) * 1000) : 0,\n cpa: conversions ? round2(spend / conversions) : 0,\n roas: spend ? round2(conversionValue / spend) : 0,\n };\n const entityId = breakdown ? String(raw[breakdown] ?? '') : accountId;\n return {\n provider: this.id,\n accountId,\n entity: { level: query.level, id: entityId, name: entityId },\n metrics: Object.fromEntries(query.metrics.map((metric) => [metric, all[metric]])),\n };\n }\n\n private async plan(op: WriteOperation, guard: WriteGuard): Promise<WritePlan> {\n const payload = op.payload as never;\n switch (op.tool) {\n case 'reddit_create_campaign': return this.planCreateCampaign(op.accountId, payload, guard);\n case 'reddit_set_campaign_status': return this.planSetStatus(op.accountId, payload);\n case 'reddit_set_budget': return this.planSetBudget(op.accountId, payload);\n case 'reddit_api_create': return this.planApiCreate(op.accountId, payload, guard);\n case 'reddit_api_update': return this.planApiUpdate(op.accountId, payload);\n case 'reddit_api_delete': return this.planApiDelete(op.accountId, payload);\n default: throw new AdportError('PROVIDER_ERROR', `reddit: unsupported write tool ${op.tool}`);\n }\n }\n\n private async planCreateCampaign(\n accountId: string,\n payload: {\n name: string;\n objective: string;\n funding_instrument_id: string;\n configured_status?: 'ACTIVE' | 'PAUSED';\n budget_micros?: number;\n budget_type?: 'DAILY_SPEND' | 'LIFETIME_SPEND';\n conversion_pixel_id?: string;\n spend_cap_micros?: number;\n },\n guard: WriteGuard,\n ): Promise<WritePlan> {\n if (payload.budget_micros && !payload.conversion_pixel_id) {\n throw new AdportError('INVALID_INPUT', 'reddit: conversion_pixel_id is required for CBO campaigns (effective July 13, 2026)');\n }\n const requested = payload.configured_status ?? 'PAUSED';\n const status = guard.forcePausedCreation ? 'PAUSED' : requested;\n const coercions = requested !== status\n ? ['configured_status coerced to PAUSED by policy (paused_creation)']\n : [];\n const data: Record<string, unknown> = {\n name: payload.name,\n configured_status: status,\n objective: payload.objective,\n funding_instrument_id: payload.funding_instrument_id,\n is_campaign_budget_optimization: payload.budget_micros !== undefined,\n ...(payload.budget_micros ? {\n goal_type: payload.budget_type ?? 'DAILY_SPEND',\n goal_value: payload.budget_micros,\n conversion_pixel_id: payload.conversion_pixel_id,\n } : {}),\n ...(payload.spend_cap_micros ? { spend_cap: payload.spend_cap_micros } : {}),\n };\n const budgetDeltas: WritePreview['budgetDeltas'] = [];\n if (payload.budget_micros) budgetDeltas.push({ target: `new campaign \"${payload.name}\" budget`, toMicros: payload.budget_micros });\n if (payload.spend_cap_micros) budgetDeltas.push({ target: `new campaign \"${payload.name}\" spend cap`, toMicros: payload.spend_cap_micros });\n return {\n summary: `Create Reddit campaign \"${payload.name}\" (${payload.objective}, ${status.toLowerCase()})`,\n changes: [`+ POST /ad_accounts/${accountId}/campaigns ${JSON.stringify({ data })}`],\n coercions,\n budgetDeltas,\n execute: async () => extractIds(await this.client.post<RedditCampaign>(`ad_accounts/${encodeURIComponent(accountId)}/campaigns`, { data })),\n };\n }\n\n private async planSetStatus(\n accountId: string,\n payload: { campaign_id: string; configured_status: 'ACTIVE' | 'PAUSED' },\n ): Promise<WritePlan> {\n const campaign = await this.getOwnedCampaign(accountId, payload.campaign_id);\n return {\n summary: `Set Reddit campaign \"${campaign.name}\" → ${payload.configured_status.toLowerCase()}`,\n changes: [`~ campaign ${campaign.id} configured_status ${campaign.configured_status} → ${payload.configured_status}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.patch<RedditCampaign>(`campaigns/${encodeURIComponent(campaign.id)}`, {\n data: { configured_status: payload.configured_status },\n })),\n };\n }\n\n private async planSetBudget(\n accountId: string,\n payload: { campaign_id: string; budget_micros: number; budget_type?: 'DAILY_SPEND' | 'LIFETIME_SPEND' },\n ): Promise<WritePlan> {\n const campaign = await this.getOwnedCampaign(accountId, payload.campaign_id);\n if (!campaign.is_campaign_budget_optimization) {\n throw new AdportError('INVALID_INPUT', `reddit: campaign \"${campaign.name}\" is not a CBO campaign; budget its ad groups instead`);\n }\n return {\n summary: `Change Reddit campaign \"${campaign.name}\" budget ${campaign.goal_value ?? '?'} → ${payload.budget_micros} micros`,\n changes: [`~ campaign ${campaign.id} ${campaign.goal_type ?? 'goal'} ${campaign.goal_value ?? '?'} → ${payload.budget_micros}`],\n coercions: [],\n budgetDeltas: [{ target: `campaign \"${campaign.name}\" budget`, fromMicros: campaign.goal_value, toMicros: payload.budget_micros }],\n execute: async () => extractIds(await this.client.patch<RedditCampaign>(`campaigns/${encodeURIComponent(campaign.id)}`, {\n data: { goal_value: payload.budget_micros, ...(payload.budget_type ? { goal_type: payload.budget_type } : {}) },\n })),\n };\n }\n\n private async planApiCreate(\n accountId: string,\n payload: { path: string; body: Record<string, unknown> },\n guard: WriteGuard,\n ): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n if (!path.startsWith(`ad_accounts/${accountId}/`)) {\n throw new AdportError('INVALID_INPUT', 'reddit: create path must be under the selected ad account');\n }\n const body = structuredClone(payload.body);\n const coercions: string[] = [];\n if (guard.forcePausedCreation) {\n coerceCreatedStatuses(body, coercions, /\\/(?:campaigns|ad_groups|ads)$/.test(path));\n }\n return {\n summary: `Create Reddit resource via /${path}`,\n changes: [`+ POST /${path} ${JSON.stringify(body)}`],\n coercions,\n budgetDeltas: collectMicros(body),\n execute: async () => extractIds(await this.client.post(path, body)),\n };\n }\n\n private async planApiUpdate(\n accountId: string,\n payload: { path: string; body: Record<string, unknown> },\n ): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n await this.assertOwnedResource(path, accountId);\n if (containsMoney(payload.body)) {\n throw new AdportError('INVALID_INPUT', 'reddit: budget, bid, goal, and spend-cap updates require a typed budget tool for policy checks');\n }\n return {\n summary: `Update Reddit resource via /${path}`,\n changes: [`~ PATCH /${path} ${JSON.stringify(payload.body)}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.patch(path, payload.body)),\n };\n }\n\n private async planApiDelete(accountId: string, payload: { path: string }): Promise<WritePlan> {\n const path = validateRedditPath(payload.path);\n await this.assertOwnedResource(path, accountId);\n return {\n summary: `Permanently delete Reddit resource via /${path}`,\n changes: [`- DELETE /${path}`],\n coercions: [], budgetDeltas: [],\n execute: async () => extractIds(await this.client.delete(path)),\n };\n }\n\n private async getOwnedCampaign(accountId: string, campaignId: string): Promise<RedditCampaign> {\n const campaign = (await this.client.get<RedditCampaign>(`campaigns/${encodeURIComponent(campaignId)}`)).data;\n if (campaign.ad_account_id !== accountId) {\n throw new AdportError('INVALID_INPUT', `reddit: campaign ${campaignId} does not belong to selected account ${accountId}`);\n }\n return campaign;\n }\n\n private async assertOwnedResource(path: string, accountId: string): Promise<void> {\n const resource = (await this.client.get<Record<string, unknown>>(path)).data;\n if (String(resource.ad_account_id ?? '') !== accountId) {\n throw new AdportError('INVALID_INPUT', `reddit: /${path} does not prove ownership by selected account ${accountId}`);\n }\n }\n}\n\nfunction nextDay(date: string): string {\n const value = new Date(`${date}T00:00:00Z`);\n value.setUTCDate(value.getUTCDate() + 1);\n return value.toISOString().slice(0, 10);\n}\n\nfunction validateRedditPath(path: string): string {\n const normalized = path.replace(/^\\/+|\\/+$/g, '').trim();\n if (!/^[A-Za-z0-9_.~/-]{3,220}$/.test(normalized) || normalized.includes('..') || normalized.includes('//')) {\n throw new AdportError('INVALID_INPUT', `reddit: invalid Ads API v3 path \"${path}\"`);\n }\n return normalized;\n}\n\nfunction assertReadScoped(path: string, accountId: string): void {\n if (path === `ad_accounts/${accountId}` || path.startsWith(`ad_accounts/${accountId}/`)) return;\n if (/^(campaigns|ad_groups|ads|structured_posts)\\/[^/]+$/.test(path)) return;\n throw new AdportError('INVALID_INPUT', 'reddit: read path must be selected-account scoped or a single campaign/ad-group/ad/post resource');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction coerceCreatedStatuses(value: Record<string, unknown>, coercions: string[], statusBearingPath: boolean): void {\n const data = isRecord(value.data) ? value.data : value;\n const visit = (item: unknown, path = 'body'): void => {\n if (Array.isArray(item)) return item.forEach((child, index) => visit(child, `${path}[${index}]`));\n if (!isRecord(item)) return;\n for (const [key, child] of Object.entries(item)) {\n if (key === 'configured_status' && child !== 'PAUSED') {\n item[key] = 'PAUSED';\n coercions.push(`${path}.${key} coerced to PAUSED by policy (paused_creation)`);\n } else visit(child, `${path}.${key}`);\n }\n };\n visit(value);\n if (statusBearingPath && data.configured_status === undefined) {\n data.configured_status = 'PAUSED';\n coercions.push('body.data.configured_status set to PAUSED by policy (paused_creation)');\n }\n}\n\nconst MONEY_KEY = /(?:goal_value|spend_cap|bid_value|budget(?:_micros)?|daily_budget|lifetime_budget)$/i;\n\nfunction containsMoney(value: unknown): boolean {\n if (Array.isArray(value)) return value.some(containsMoney);\n if (!isRecord(value)) return false;\n return Object.entries(value).some(([key, child]) => MONEY_KEY.test(key) || containsMoney(child));\n}\n\nfunction collectMicros(value: unknown, path = 'body'): WritePreview['budgetDeltas'] {\n if (Array.isArray(value)) return value.flatMap((child, index) => collectMicros(child, `${path}[${index}]`));\n if (!isRecord(value)) return [];\n const deltas: WritePreview['budgetDeltas'] = [];\n for (const [key, child] of Object.entries(value)) {\n if (MONEY_KEY.test(key) && Number.isInteger(child) && Number(child) > 0) {\n deltas.push({ target: `${path}.${key}`, toMicros: Number(child) });\n }\n deltas.push(...collectMicros(child, `${path}.${key}`));\n }\n return deltas;\n}\n\nfunction extractIds(envelope: RedditEnvelope<unknown>): string[] {\n const ids: string[] = [];\n const visit = (value: unknown): void => {\n if (Array.isArray(value)) return value.forEach(visit);\n if (!isRecord(value)) return;\n if (typeof value.id === 'string' || typeof value.id === 'number') ids.push(String(value.id));\n for (const child of Object.values(value)) visit(child);\n };\n visit(envelope.data);\n return [...new Set(ids)];\n}\n\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","import { defineTool, guardedWriteTool, type AnyToolDefinition } from '@adport/core';\nimport { z } from 'zod';\nimport type { RedditAdsProvider } from './provider.js';\n\nconst status = z.enum(['ACTIVE', 'PAUSED']);\n\nexport function redditTools(provider: RedditAdsProvider): AnyToolDefinition[] {\n return [\n defineTool({\n name: 'reddit_api_read',\n namespace: 'reddit',\n description: 'Call a documented Reddit Ads API v3 GET or read-only reports/history/query/search POST endpoint, scoped to the selected ad account.',\n input: z.object({\n account_id: z.string(),\n path: z.string().min(3).describe('Relative v3 path, e.g. ad_accounts/{id}/ad_groups or campaigns/{campaign_id}'),\n method: z.enum(['GET', 'POST']).default('GET'),\n params: z.record(z.string(), z.unknown()).optional(),\n body: z.record(z.string(), z.unknown()).optional(),\n limit: z.number().int().positive().max(5000).optional().describe('Follow list pagination for GET responses'),\n }),\n annotations: { readOnly: true },\n handler: (input) => provider.apiRead(input),\n }),\n defineTool({\n name: 'reddit_campaigns',\n namespace: 'reddit',\n description: 'List Reddit campaigns and their native configured/effective status, CBO budget, objective, and funding instrument.',\n input: z.object({ account_id: z.string(), campaign_ids: z.array(z.string()).optional() }),\n annotations: { readOnly: true },\n async handler(input) { return { campaigns: await provider.listCampaigns(input.account_id, input.campaign_ids) }; },\n }),\n defineTool({\n name: 'reddit_report',\n namespace: 'reddit',\n description: 'Query Reddit Ads API v3 reporting directly. Spend, CPC, CPM, and applicable conversion fields are returned by Reddit in micros.',\n input: z.object({\n account_id: z.string(),\n breakdowns: z.array(z.string()).max(4).optional(),\n fields: z.array(z.string()).min(1).default(['spend', 'impressions', 'clicks']),\n starts_at: z.string().datetime({ offset: true }),\n ends_at: z.string().datetime({ offset: true }),\n time_zone_id: z.string().default('UTC'),\n filter: z.string().optional(),\n page_size: z.number().int().positive().max(1000).default(200),\n }),\n annotations: { readOnly: true },\n handler: (input) => provider.rawReport(input),\n }),\n guardedWriteTool({\n name: 'reddit_create_campaign',\n namespace: 'reddit',\n description: 'Create a Reddit campaign through API v3. Campaigns are forced PAUSED. CBO budgets and spend caps are integer micros.',\n provider: 'reddit', kind: 'create',\n payload: z.object({\n name: z.string().min(1),\n objective: z.string().min(1).default('TRAFFIC').describe('Current Reddit objective enum; Reddit has announced an enum migration for Sep 30, 2026'),\n funding_instrument_id: z.string().min(1),\n configured_status: status.optional(),\n budget_micros: z.number().int().positive().optional().describe('Sets CBO goal_value; requires conversion_pixel_id'),\n budget_type: z.enum(['DAILY_SPEND', 'LIFETIME_SPEND']).default('DAILY_SPEND'),\n conversion_pixel_id: z.string().optional(),\n spend_cap_micros: z.number().int().positive().optional(),\n }),\n }),\n guardedWriteTool({\n name: 'reddit_set_campaign_status', namespace: 'reddit',\n description: 'Activate or pause a Reddit campaign after verifying ad-account ownership.',\n provider: 'reddit', kind: 'update',\n payload: z.object({ campaign_id: z.string(), configured_status: status }),\n }),\n guardedWriteTool({\n name: 'reddit_set_budget', namespace: 'reddit',\n description: 'Change a Reddit CBO campaign goal_value in integer micros, with current-value lookup and policy checks.',\n provider: 'reddit', kind: 'update',\n payload: z.object({\n campaign_id: z.string(),\n budget_micros: z.number().int().positive(),\n budget_type: z.enum(['DAILY_SPEND', 'LIFETIME_SPEND']).optional(),\n }),\n }),\n guardedWriteTool({\n name: 'reddit_api_create', namespace: 'reddit',\n description: 'Create via an account-scoped Reddit Ads API v3 endpoint. Campaign status is forced PAUSED and native micros fields are policy-checked.',\n provider: 'reddit', kind: 'create',\n payload: z.object({ path: z.string().min(3), body: z.record(z.string(), z.unknown()) }),\n }),\n guardedWriteTool({\n name: 'reddit_api_update', namespace: 'reddit',\n description: 'PATCH non-budget fields on a Reddit Ads API v3 resource after verifying ad-account ownership.',\n provider: 'reddit', kind: 'update',\n payload: z.object({ path: z.string().min(3), body: z.record(z.string(), z.unknown()) }),\n }),\n guardedWriteTool({\n name: 'reddit_api_delete', namespace: 'reddit',\n description: 'Permanently DELETE a Reddit Ads API v3 resource after verifying ad-account ownership.',\n provider: 'reddit', kind: 'remove', destructive: true,\n payload: z.object({ path: z.string().min(3) }),\n }),\n ];\n}\n","import type { CredentialStore, ProviderModule } from '@adport/core';\nimport { RedditAdsClient, type RedditCredentials } from './client.js';\nimport { RedditAdsProvider } from './provider.js';\nimport { redditTools } from './tools.js';\n\nexport { RedditAdsClient, REDDIT_API_BASE, REDDIT_API_VERSION, REDDIT_TOKEN_URL, type RedditCredentials, type RedditEnvelope } from './client.js';\nexport { RedditAdsProvider, REDDIT_MICROS, type RedditCampaign } from './provider.js';\nexport { redditTools } from './tools.js';\n\nexport async function resolveRedditCredentials(store: CredentialStore): Promise<RedditCredentials | undefined> {\n const record = await store.get('reddit');\n const data = record?.data;\n if (data?.client_id && data.client_secret && data.refresh_token && data.user_agent) {\n const source = record?.source ?? 'byo';\n return {\n clientId: data.client_id,\n clientSecret: data.client_secret,\n refreshToken: data.refresh_token,\n userAgent: data.user_agent,\n onRefreshToken: async (refreshToken) => {\n await store.set({ provider: 'reddit', source, data: { ...data, refresh_token: refreshToken } });\n },\n };\n }\n if (process.env.REDDIT_ADS_CLIENT_ID && process.env.REDDIT_ADS_CLIENT_SECRET && process.env.REDDIT_ADS_REFRESH_TOKEN && process.env.REDDIT_ADS_USER_AGENT) {\n return {\n clientId: process.env.REDDIT_ADS_CLIENT_ID,\n clientSecret: process.env.REDDIT_ADS_CLIENT_SECRET,\n refreshToken: process.env.REDDIT_ADS_REFRESH_TOKEN,\n userAgent: process.env.REDDIT_ADS_USER_AGENT,\n };\n }\n return undefined;\n}\n\nexport async function createRedditModule(store: CredentialStore): Promise<ProviderModule | undefined> {\n const credentials = await resolveRedditCredentials(store);\n if (!credentials) return undefined;\n const provider = new RedditAdsProvider(new RedditAdsClient(credentials));\n return { provider, tools: redditTools(provider) };\n}\n"],"mappings":"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","names":["AdportError","status"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adport/provider-reddit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Reddit Ads provider for adport — Reddit Ads API v3",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"zod": "^4.4.3",
|
|
20
|
-
"@adport/core": "0.
|
|
20
|
+
"@adport/core": "0.6.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^26.1.2",
|