import type { ChatMessagePart } from './types'; const LINK_CARD_LINE_PATTERN = /^\s*\[\[link-card:(.+?)\]\]\s*$/i; const STANDALONE_MARKDOWN_LINK_LINE_PATTERN = /^\s*(?:[-*+]\s+|\d+\.\s+)?\[([^\]]+)\]\(([^)]+)\)\s*$/; const STANDALONE_URL_LINE_PATTERN = /^\s*(?:[-*+]\s+|\d+\.\s+)?((?:https?:\/\/|\/)[^\s<>)\]]+)\s*$/i; const RESOURCE_PATH_PREFIXES = ['/api/chat/resources/', '/public/.codex_chat/', '/.codex_chat/'] as const; function normalizeText(value: unknown) { return String(value ?? '').trim(); } function normalizeUrl(value: string) { const normalized = normalizeText(value); if (!normalized) { return ''; } if (/^(?:https?:\/\/|\/)/i.test(normalized)) { return normalized; } return ''; } function hasKnownFileExtension(url: string) { const pathname = url.split('?')[0] ?? ''; return /\.[a-z0-9]{1,8}$/i.test(pathname); } function isStructuredLinkCardCandidate(url: string) { const normalized = normalizeUrl(url); if (!normalized) { return false; } if (RESOURCE_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) { return false; } if (/^https?:\/\//i.test(normalized)) { return !hasKnownFileExtension(normalized); } return !hasKnownFileExtension(normalized); } function buildFallbackLinkTitle(url: string) { try { const parsed = new URL(url, typeof window !== 'undefined' ? window.location.origin : 'https://local.invalid'); const lastSegment = parsed.pathname.split('/').filter(Boolean).at(-1)?.trim(); return lastSegment || parsed.hostname || normalizeText(url); } catch { return normalizeText(url); } } function normalizeStandaloneTitle(value: string) { return value .replace(/^\s*(?:[-*+]\s+|\d+\.\s+)?/, '') .replace(/[`'"]+/g, '') .replace(/\s+/g, ' ') .trim(); } function resolveStandaloneLinkTitle(keptLines: string[], url: string) { for (let index = keptLines.length - 1; index >= 0; index -= 1) { const candidate = normalizeStandaloneTitle(keptLines[index] ?? ''); if (candidate) { return candidate; } } return buildFallbackLinkTitle(url); } function buildLinkCardPart(rawBody: string): ChatMessagePart | null { const segments = rawBody .split('|') .map((segment) => segment.trim()) .filter(Boolean); if (segments.length < 2) { return null; } const [rawTitle, rawUrl, rawActionLabel] = segments; const title = normalizeText(rawTitle); const url = normalizeUrl(rawUrl); const actionLabel = normalizeText(rawActionLabel) || null; if (!title || !url) { return null; } return { type: 'link_card', title, url, actionLabel, }; } export function extractChatMessageParts(text: string) { const lines = String(text ?? '').split('\n'); const keptLines: string[] = []; const parts: ChatMessagePart[] = []; const seenLinkKeys = new Set(); const pushPart = (nextPart: ChatMessagePart | null) => { if (!nextPart) { return false; } const dedupeKey = `${nextPart.type}:${nextPart.title}:${nextPart.url}:${nextPart.actionLabel ?? ''}`; if (seenLinkKeys.has(dedupeKey)) { return true; } seenLinkKeys.add(dedupeKey); parts.push(nextPart); return true; }; for (const line of lines) { const matched = line.match(LINK_CARD_LINE_PATTERN); if (!matched) { const markdownLinkMatch = line.match(STANDALONE_MARKDOWN_LINK_LINE_PATTERN); if (markdownLinkMatch) { const [, rawTitle, rawUrl] = markdownLinkMatch; if (isStructuredLinkCardCandidate(rawUrl ?? '')) { if (pushPart(buildLinkCardPart(`${rawTitle}|${rawUrl}`))) { continue; } } } const standaloneUrlMatch = line.match(STANDALONE_URL_LINE_PATTERN); if (standaloneUrlMatch) { const rawUrl = standaloneUrlMatch[1] ?? ''; if (isStructuredLinkCardCandidate(rawUrl)) { if (pushPart(buildLinkCardPart(`${resolveStandaloneLinkTitle(keptLines, rawUrl)}|${rawUrl}`))) { continue; } } } keptLines.push(line); continue; } if (!pushPart(buildLinkCardPart(matched[1] ?? ''))) { keptLines.push(line); } } return { strippedText: keptLines.join('\n').replace(/\n{3,}/g, '\n\n').trim(), parts, }; }