{"version":3,"file":"search.cjs","names":["flattenHeaders","matchesPattern"],"sources":["../src/search.ts"],"sourcesContent":["/**\n * Web Search API support for LLMock.\n *\n * Handles POST /search requests (Tavily-compatible). Matches fixtures by\n * comparing the request `query` field against registered patterns. First\n * match wins; no match returns empty results.\n */\n\nimport type * as http from \"node:http\";\nimport { flattenHeaders, matchesPattern } from \"./helpers.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\n\n// ─── Search types ─────────────────────────────────────────────────────────\n\nexport interface SearchResult {\n  title: string;\n  url: string;\n  content: string;\n  score?: number;\n}\n\nexport interface SearchFixture {\n  match: string | RegExp;\n  results: SearchResult[];\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleSearch(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  raw: string,\n  fixtures: SearchFixture[],\n  journal: Journal,\n  defaults: { logger: Logger },\n  setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n  const { logger } = defaults;\n  setCorsHeaders(res);\n\n  let body: { query?: string; max_results?: number };\n  try {\n    body = JSON.parse(raw) as { query?: string; max_results?: number };\n  } catch (parseErr) {\n    const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: req.url ?? \"/search\",\n      headers: flattenHeaders(req.headers),\n      body: null,\n      service: \"search\",\n      response: { status: 400, fixture: null },\n    });\n    res.writeHead(400, { \"Content-Type\": \"application/json\" });\n    res.end(\n      JSON.stringify({\n        error: {\n          message: `Malformed JSON: ${detail}`,\n          type: \"invalid_request_error\",\n          code: \"invalid_json\",\n        },\n      }),\n    );\n    return;\n  }\n\n  const query = body.query ?? \"\";\n  const maxResults = body.max_results;\n\n  // Find first matching fixture\n  let matchedResults: SearchResult[] = [];\n  let matchedFixture: SearchFixture | null = null;\n\n  for (const fixture of fixtures) {\n    if (matchesPattern(query, fixture.match)) {\n      matchedFixture = fixture;\n      matchedResults = fixture.results;\n      break;\n    }\n  }\n\n  if (matchedFixture) {\n    logger.debug(`Search fixture matched for query \"${query.slice(0, 80)}\"`);\n  } else {\n    logger.debug(`No search fixture matched for query \"${query.slice(0, 80)}\" — returning empty`);\n  }\n\n  // Apply max_results limit\n  if (maxResults !== undefined && maxResults > 0) {\n    matchedResults = matchedResults.slice(0, maxResults);\n  }\n\n  journal.add({\n    method: req.method ?? \"POST\",\n    path: req.url ?? \"/search\",\n    headers: flattenHeaders(req.headers),\n    body: null,\n    service: \"search\",\n    response: { status: 200, fixture: null },\n  });\n\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(\n    JSON.stringify({\n      query,\n      results: matchedResults,\n      images: [],\n      response_time: 0,\n      answer: null,\n    }),\n  );\n}\n"],"mappings":";;;AA6BA,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,aAAa,KAAK;CAGxB,IAAI,iBAAiC,EAAE;CACvC,IAAI,iBAAuC;AAE3C,MAAK,MAAM,WAAW,SACpB,KAAIC,+BAAe,OAAO,QAAQ,MAAM,EAAE;AACxC,mBAAiB;AACjB,mBAAiB,QAAQ;AACzB;;AAIJ,KAAI,eACF,QAAO,MAAM,qCAAqC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;KAExE,QAAO,MAAM,wCAAwC,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB;AAI/F,KAAI,eAAe,UAAa,aAAa,EAC3C,kBAAiB,eAAe,MAAM,GAAG,WAAW;AAGtD,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASD,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,SAAS;EACT,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;AAEF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IACF,KAAK,UAAU;EACb;EACA,SAAS;EACT,QAAQ,EAAE;EACV,eAAe;EACf,QAAQ;EACT,CAAC,CACH"}