Skip to content

Commit dabfe09

Browse files
committed
ads: Include render context
1 parent 19ee42b commit dabfe09

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

cli/src/hooks/use-gravity-ad.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export const useGravityAd = (): GravityAdState => {
156156
body: JSON.stringify({
157157
messages: adMessages,
158158
sessionId: loggerContext.clientSessionId,
159+
device: getDeviceInfo(),
159160
}),
160161
})
161162

@@ -317,3 +318,29 @@ const convertToAdMessages = (messages: Message[]): AdMessage[] => {
317318

318319
return adMessages
319320
}
321+
322+
/** Device info sent to the ads API for targeting */
323+
type DeviceInfo = {
324+
os: 'macos' | 'windows' | 'linux'
325+
timezone: string
326+
locale: string
327+
}
328+
329+
/** Get device info for ads API */
330+
function getDeviceInfo(): DeviceInfo {
331+
// Map Node.js platform to Gravity API os values
332+
const platformToOs: Record<string, 'macos' | 'windows' | 'linux'> = {
333+
darwin: 'macos',
334+
win32: 'windows',
335+
linux: 'linux',
336+
}
337+
const os = platformToOs[process.platform] ?? 'linux'
338+
339+
// Get IANA timezone (e.g., "America/New_York")
340+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
341+
342+
// Get locale (e.g., "en-US")
343+
const locale = Intl.DateTimeFormat().resolvedOptions().locale
344+
345+
return { os, timezone, locale }
346+
}

web/src/app/api/v1/ads/_post.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,16 @@ const messageSchema = z.object({
2121
content: z.string(),
2222
})
2323

24+
const deviceSchema = z.object({
25+
os: z.enum(['macos', 'windows', 'linux']).optional(),
26+
timezone: z.string().optional(),
27+
locale: z.string().optional(),
28+
})
29+
2430
const bodySchema = z.object({
2531
messages: z.array(messageSchema),
2632
sessionId: z.string().optional(),
33+
device: deviceSchema.optional(),
2734
})
2835

2936
export type GravityEnv = {
@@ -67,9 +74,16 @@ export async function postAds(params: {
6774
return NextResponse.json({ ad: null }, { status: 200 })
6875
}
6976

77+
// Extract client IP from request headers
78+
const forwardedFor = req.headers.get('x-forwarded-for')
79+
const clientIp = forwardedFor
80+
? forwardedFor.split(',')[0].trim()
81+
: req.headers.get('x-real-ip') ?? undefined
82+
7083
// Parse and validate request body
7184
let messages: z.infer<typeof bodySchema>['messages']
7285
let sessionId: string | undefined
86+
let deviceInfo: z.infer<typeof deviceSchema> | undefined
7387
try {
7488
const json = await req.json()
7589
const parsed = bodySchema.safeParse(json)
@@ -95,6 +109,7 @@ export async function postAds(params: {
95109
return message
96110
})
97111
sessionId = parsed.data.sessionId
112+
deviceInfo = parsed.data.device
98113
} catch {
99114
logger.error(
100115
{ error: 'Invalid JSON in request body' },
@@ -115,6 +130,17 @@ export async function postAds(params: {
115130
.slice(0, lastUserMessageIndex)
116131
.findLast((message) => message.role === 'assistant')
117132
const filteredMessages = buildArray(lastAssistantMessage, lastUserMessage)
133+
134+
// Build device object for Gravity API
135+
const device = clientIp
136+
? {
137+
ip: clientIp,
138+
...(deviceInfo?.os ? { os: deviceInfo.os } : {}),
139+
...(deviceInfo?.timezone ? { timezone: deviceInfo.timezone } : {}),
140+
...(deviceInfo?.locale ? { locale: deviceInfo.locale } : {}),
141+
}
142+
: undefined
143+
118144
try {
119145
const requestBody = {
120146
messages: filteredMessages,
@@ -123,6 +149,11 @@ export async function postAds(params: {
123149
user: {
124150
email: userInfo.email,
125151
},
152+
renderContext: {
153+
placements: [{ placement: 'below_response' }],
154+
max_ad_length: 200,
155+
},
156+
...(device ? { device } : {}),
126157
testAd: serverEnv.CB_ENVIRONMENT !== 'prod',
127158
}
128159
// Call Gravity API

0 commit comments

Comments
 (0)