| export type ResponseShellSections = { |
| result: string; |
| why: string; |
| nextAction: string; |
| rest: string; |
| hasShell: boolean; |
| }; |
|
|
| type HeadingKind = "result" | "why" | "nextAction"; |
|
|
| const SHELL_HEADING_RE = /^\s*\*\*(Result|Why|Next action|Action)\*\*\s*$/gim; |
|
|
| function headingKind(raw: string): HeadingKind | null { |
| const low = String(raw || "").trim().toLowerCase(); |
| if (low === "result") return "result"; |
| if (low === "why") return "why"; |
| if (low === "next action" || low === "action") return "nextAction"; |
| return null; |
| } |
|
|
| export function splitResponseShellMarkdown(content: string): ResponseShellSections { |
| const text = String(content || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); |
| const matches: Array<{ kind: HeadingKind; start: number; bodyStart: number }> = []; |
| let match: RegExpExecArray | null = null; |
| while ((match = SHELL_HEADING_RE.exec(text)) !== null) { |
| const kind = headingKind(match[1]); |
| if (!kind) continue; |
| matches.push({ kind, start: match.index, bodyStart: SHELL_HEADING_RE.lastIndex }); |
| } |
| if (!matches.length) { |
| return { |
| result: text.trim(), |
| why: "", |
| nextAction: "", |
| rest: "", |
| hasShell: false, |
| }; |
| } |
|
|
| const buckets: Record<HeadingKind, string> = { |
| result: "", |
| why: "", |
| nextAction: "", |
| }; |
| const consumed: Array<[number, number]> = []; |
| for (let i = 0; i < matches.length; i += 1) { |
| const curr = matches[i]; |
| const next = matches[i + 1]; |
| const end = next ? next.start : text.length; |
| const body = text.slice(curr.bodyStart, end).trim(); |
| if (body && !buckets[curr.kind]) buckets[curr.kind] = body; |
| consumed.push([curr.start, end]); |
| } |
|
|
| let rest = ""; |
| let cursor = 0; |
| for (const [start, end] of consumed.sort((a, b) => a[0] - b[0])) { |
| if (start > cursor) rest += text.slice(cursor, start); |
| cursor = Math.max(cursor, end); |
| } |
| if (cursor < text.length) rest += text.slice(cursor); |
|
|
| return { |
| result: buckets.result.trim(), |
| why: buckets.why.trim(), |
| nextAction: buckets.nextAction.trim(), |
| rest: rest.trim(), |
| hasShell: Boolean(buckets.result || buckets.why || buckets.nextAction), |
| }; |
| } |
|
|
|
|