()
| 262 | |
| 263 | // Fetch an ad via web API |
| 264 | const fetchAd = async (): Promise<FetchAdResult> => { |
| 265 | // Don't fetch ads when they should be hidden |
| 266 | if (shouldHideAdsRef.current) return null |
| 267 | if (!getAdsEnabled()) return null |
| 268 | |
| 269 | const authToken = getAuthToken() |
| 270 | if (!authToken) { |
| 271 | logger.warn('[ads] No auth token available') |
| 272 | return null |
| 273 | } |
| 274 | |
| 275 | // Get message history from runState (populated after LLM responds) |
| 276 | const currentRunState = useChatStore.getState().runState |
| 277 | const messageHistory = |
| 278 | currentRunState?.sessionState?.mainAgentState?.messageHistory ?? [] |
| 279 | const adMessages = convertToAdMessages(messageHistory) |
| 280 | |
| 281 | // Also check UI messages for the latest user message |
| 282 | // (UI messages update immediately, runState.messageHistory updates after LLM responds) |
| 283 | const uiMessages = useChatStore.getState().messages |
| 284 | const lastUIMessage = [...uiMessages] |
| 285 | .reverse() |
| 286 | .find((msg) => msg.variant === 'user') |
| 287 | |
| 288 | // If the latest UI user message isn't in our converted history, append it |
| 289 | // This ensures we always include the most recent user message even before LLM responds |
| 290 | if (lastUIMessage?.content) { |
| 291 | const lastAdUserMessage = [...adMessages] |
| 292 | .reverse() |
| 293 | .find((m) => m.role === 'user') |
| 294 | if ( |
| 295 | !lastAdUserMessage || |
| 296 | !lastAdUserMessage.content.includes(lastUIMessage.content) |
| 297 | ) { |
| 298 | adMessages.push({ |
| 299 | role: 'user', |
| 300 | content: `<user_message>${lastUIMessage.content}</user_message>`, |
| 301 | }) |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | try { |
| 306 | const response = await fetch(`${WEBSITE_URL}/api/v1/ads`, { |
| 307 | method: 'POST', |
| 308 | headers: { |
| 309 | 'Content-Type': 'application/json', |
| 310 | Authorization: `Bearer ${authToken}`, |
| 311 | 'User-Agent': getCliAdRequestUserAgent(), |
| 312 | }, |
| 313 | body: JSON.stringify({ |
| 314 | provider, |
| 315 | messages: adMessages, |
| 316 | sessionId: useChatStore.getState().chatSessionId, |
| 317 | device: getDeviceInfo(), |
| 318 | ...(surface ? { surface } : {}), |
| 319 | // Carbon requires a real browser-ish useragent for targeting/fraud |
| 320 | // detection. Gravity ignores it. We source one centrally so every |
| 321 | // provider that needs it sees the same value. |
no test coverage detected