@@ -13,13 +13,18 @@ import {
1313 mergeScrapedContent ,
1414 calculateScrapeCredits ,
1515} from "./scrape" ;
16- import { applyIndexedSearchHighlights , highlightsEnvReady } from "./highlights" ;
17- import { runSearchHighlightsShadow } from "./highlights-shadow" ;
16+ import {
17+ highlightsEnvReady ,
18+ runIndexedSearchHighlights ,
19+ searchHighlightsMode ,
20+ } from "./highlights" ;
1821import { trackSearchResults , trackSearchRequest } from "../lib/tracking" ;
1922import type { BillingMetadata } from "../services/billing/types" ;
2023import type { ThreatProtectionPolicy } from "../lib/threat-protection/types" ;
2124import { checkUrlsAgainstThreatPolicy } from "../lib/threat-protection/request" ;
2225import { calculateThreatScanCredits } from "../lib/scrape-billing" ;
26+ import { config } from "../config" ;
27+ import { logger as rootLogger } from "../lib/logger" ;
2328
2429interface SearchOptions {
2530 query : string ;
@@ -42,6 +47,7 @@ interface SearchOptions {
4247interface SearchContext {
4348 teamId : string ;
4449 origin : string ;
50+ integration ?: string | null ;
4551 apiKeyId : number | null ;
4652 flags : TeamFlags ;
4753 requestId : string ;
@@ -241,32 +247,47 @@ export async function executeSearch(
241247 }
242248 }
243249
244- // Experimental highlights beta: replace provider snippets with index-backed
245- // highlights. Gated on (1) the request opting in, (2) the team's highlightsBeta
246- // flag, and (3) all required envs being present (index DB, GCS, model). Any
247- // gate failing => silently keep the provider snippets.
250+ // Every eligible request runs the same index-backed highlight path. MCP/CLI,
251+ // explicit opt-ins, and rollout-selected cohorts await and apply the result;
252+ // everyone else runs it in shadow without delaying or mutating the response.
248253 // Runs after scraping (mergeScrapedContent rebuilds the result objects, so
249254 // highlight mutations must come last to survive). Uses the user's original
250255 // query, not the domain-filtered upstream query.
251- const shouldApplyHighlights =
252- options . highlights &&
253- flags ?. highlightsBeta === true &&
254- highlightsEnvReady ( ) ;
255- if ( shouldApplyHighlights ) {
256- await applyIndexedSearchHighlights (
256+ if ( zeroDataRetention !== true && ! isZDR && highlightsEnvReady ( ) ) {
257+ const mode = searchHighlightsMode ( {
258+ requested : options . highlights ,
259+ origin : context . origin ,
260+ integration : context . integration ,
261+ cohortKey :
262+ context . apiKeyId !== null
263+ ? `api-key:${ context . apiKeyId } `
264+ : `team:${ context . teamId } ` ,
265+ rolloutPercent : config . HIGHLIGHT_ROLLOUT_PERCENT ,
266+ } ) ;
267+ const highlightRun = runIndexedSearchHighlights (
257268 searchResponse ,
258269 query ,
259270 logger ,
260- context . requestId ,
271+ {
272+ mode,
273+ requestId : context . requestId ,
274+ teamId : context . teamId ,
275+ } ,
261276 ) ;
262- } else {
263- runSearchHighlightsShadow ( {
264- response : searchResponse ,
265- query,
266- requestId : context . requestId ,
267- teamId,
268- zeroDataRetention : zeroDataRetention === true || isZDR === true ,
269- } ) ;
277+
278+ if ( mode === "apply" ) {
279+ await highlightRun ;
280+ } else {
281+ void highlightRun . catch ( error => {
282+ rootLogger . warn ( "Search highlights shadow failed" , {
283+ canonicalLog : "search/highlights" ,
284+ mode,
285+ requestId : context . requestId ,
286+ teamId : context . teamId ,
287+ errorType : error instanceof Error ? error . name : "unknown" ,
288+ } ) ;
289+ } ) ;
290+ }
270291 }
271292
272293 const scrapeFormats = scrapeOptions ?. formats
0 commit comments