{"version":3,"file":"extract-user-text.mjs","names":[],"sources":["../../../../../../../../ai/src/middleware/utils/extract-user-text.ts"],"sourcesContent":["import type { Message } from \"../../contracts/conversation-message.type\";\n\n/**\n * Pull the text a content-inspection middleware should care about\n * from the outbound message list.\n *\n * **Role.** Built-ins that inspect \"what the user just said\" — the\n * guardrail on `trip.before`, the semantic cache on `trip.before`,\n * future consumers like PII redactors — all need the same string:\n * the most recent `user`-role message's text content. This helper\n * is the single authority on how that string is resolved.\n *\n * **Behavior.**\n * - Walks `messages` from the end backwards so the LAST user turn\n *   wins (correct when the agent has history + a fresh prompt).\n * - Returns a plain string directly when `content` is a string.\n * - Joins `text` parts with `\"\\n\"` when `content` is a multipart\n *   `ContentPart[]`. Non-text parts (images, audio, pdf) are skipped —\n *   callers concerned with multimodal content inspect `request`\n *   / attachments separately.\n * - Returns `\"\"` when there is no user message at all (e.g. a trip\n *   composed entirely of tool results).\n *\n * **Coverage limit (D3).** Because only `text` parts are extracted, any\n * guardrail / PII detector built on this helper inspects **text only** —\n * image / PDF / audio attachment content is NOT scanned. A guardrail is\n * therefore not a multimodal safety control: for non-text inputs add an\n * attachment-level policy (e.g. an OCR / moderation pass before the call)\n * rather than relying on input detectors.\n *\n * @example\n * const prompt = extractUserText(context.messages);\n * if (!prompt) return;\n * const verdict = await inputCheck(prompt);\n */\nexport function extractUserText(messages: ReadonlyArray<Message>): string {\n  for (let index = messages.length - 1; index >= 0; index--) {\n    const message = messages[index];\n\n    if (message.role !== \"user\") {\n      continue;\n    }\n\n    if (typeof message.content === \"string\") {\n      return message.content;\n    }\n\n    if (Array.isArray(message.content)) {\n      return message.content\n        .filter((part) => part.type === \"text\")\n        .map((part) => (part as { text: string }).text)\n        .join(\"\\n\");\n    }\n  }\n\n  return \"\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,gBAAgB,UAA0C;CACxE,KAAK,IAAI,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS;EACzD,MAAM,UAAU,SAAS;EAEzB,IAAI,QAAQ,SAAS,QACnB;EAGF,IAAI,OAAO,QAAQ,YAAY,UAC7B,OAAO,QAAQ;EAGjB,IAAI,MAAM,QAAQ,QAAQ,OAAO,GAC/B,OAAO,QAAQ,QACZ,QAAQ,SAAS,KAAK,SAAS,MAAM,CAAC,CACtC,KAAK,SAAU,KAA0B,IAAI,CAAC,CAC9C,KAAK,IAAI;CAEhB;CAEA,OAAO;AACT"}