{"version":3,"file":"citations.mjs","sources":["../../../src/memory/citations.ts"],"sourcesContent":["/**\n * Citation decoration — Phase 2.\n *\n * Ported from upstream `extensions/memory-core/src/tools.citations.ts`.\n * Decorates memory_search hits with `[path#L{start}-L{end}]` markers so\n * the model can attribute claims back to specific memory files when it\n * uses them in its answer.\n *\n * Since our backend stores files as single rows (not line-chunked), we\n * compute line ranges from the returned content block on the fly:\n *   - `startLine` = 1 (line 1 of the file)\n *   - `endLine`   = total number of lines in the block\n * This matches upstream's output format exactly while keeping the pg\n * schema chunk-free.\n */\n\nexport type MemoryCitationsMode = 'on' | 'off' | 'auto';\n\nexport const DEFAULT_CITATIONS_MODE: MemoryCitationsMode = 'auto';\n\nexport function resolveMemoryCitationsMode(\n  raw: string | undefined | null\n): MemoryCitationsMode {\n  if (raw === 'on' || raw === 'off' || raw === 'auto') return raw;\n  return DEFAULT_CITATIONS_MODE;\n}\n\nexport interface CitationCandidate {\n  path: string;\n  content: string;\n  startLine?: number;\n  endLine?: number;\n  citation?: string;\n}\n\nfunction countLines(text: string): number {\n  if (!text) return 1;\n  return Math.max(1, text.split('\\n').length);\n}\n\nfunction formatCitation(\n  path: string,\n  startLine: number,\n  endLine: number\n): string {\n  if (startLine === endLine) return `${path}#L${startLine}`;\n  return `${path}#L${startLine}-L${endLine}`;\n}\n\n/**\n * Decorate each hit with a citation marker. Mirrors upstream's behavior:\n * appends `\\n\\nSource: <citation>` to the content and sets `citation`.\n * When `include=false`, clears any existing citation field.\n */\nexport function decorateCitations<T extends CitationCandidate>(\n  hits: T[],\n  include: boolean\n): T[] {\n  if (!include) return hits.map((h) => ({ ...h, citation: undefined }));\n  return hits.map((h) => {\n    const start = Math.max(1, Math.floor(h.startLine ?? 1));\n    const end = Math.max(start, Math.floor(h.endLine ?? countLines(h.content)));\n    const citation = formatCitation(h.path, start, end);\n    const content = `${(h.content ?? '').trimEnd()}\\n\\nSource: ${citation}`;\n    return { ...h, citation, content, startLine: start, endLine: end };\n  });\n}\n\n/**\n * Whether citations should be emitted for this call.\n *\n * Upstream keys `auto` off the session type (direct/group/channel). In\n * Phase 1 we only have direct chat, so `auto` => `on`. Callers that\n * later distinguish session types can pass `mode` explicitly.\n */\nexport function shouldIncludeCitations(mode: MemoryCitationsMode): boolean {\n  if (mode === 'on') return true;\n  if (mode === 'off') return false;\n  return true;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;AAcG;AAII,MAAM,sBAAsB,GAAwB;AAErD,SAAU,0BAA0B,CACxC,GAA8B,EAAA;IAE9B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;AAAE,QAAA,OAAO,GAAG;AAC/D,IAAA,OAAO,sBAAsB;AAC/B;AAUA,SAAS,UAAU,CAAC,IAAY,EAAA;AAC9B,IAAA,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,CAAC;AACnB,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAC7C;AAEA,SAAS,cAAc,CACrB,IAAY,EACZ,SAAiB,EACjB,OAAe,EAAA;IAEf,IAAI,SAAS,KAAK,OAAO;AAAE,QAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,SAAS,EAAE;AACzD,IAAA,OAAO,GAAG,IAAI,CAAA,EAAA,EAAK,SAAS,CAAA,EAAA,EAAK,OAAO,EAAE;AAC5C;AAEA;;;;AAIG;AACG,SAAU,iBAAiB,CAC/B,IAAS,EACT,OAAgB,EAAA;AAEhB,IAAA,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;AACrE,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAI;AACpB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,QAAA,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC;AACnD,QAAA,MAAM,OAAO,GAAG,CAAA,EAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,OAAO,EAAE,CAAA,YAAA,EAAe,QAAQ,EAAE;AACvE,QAAA,OAAO,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;AACpE,IAAA,CAAC,CAAC;AACJ;AAEA;;;;;;AAMG;AACG,SAAU,sBAAsB,CAAC,IAAyB,EAAA;IAC9D,IAAI,IAAI,KAAK,IAAI;AAAE,QAAA,OAAO,IAAI;IAC9B,IAAI,IAAI,KAAK,KAAK;AAAE,QAAA,OAAO,KAAK;AAChC,IAAA,OAAO,IAAI;AACb;;;;"}