/** * Pass 2 public thinking is a complete product sentence, never a CoT fragment. * This gate only accepts or rejects. It must not rewrite, strip, or splice. */ const CJK_RE = /[\u4e00-\u9fff]/; const SENTENCE_RE = /[。!?]/; export const THINK_STEP_TEXT_MIN = 8; export const THINK_STEP_TEXT_MAX = 400; export function acceptThinkStepText(text: string): string | null { const trimmed = text.replace(/\s+/g, " ").trim(); if (trimmed.length < THINK_STEP_TEXT_MIN || trimmed.length > THINK_STEP_TEXT_MAX) return null; if (!CJK_RE.test(trimmed)) return null; if (!SENTENCE_RE.test(trimmed)) return null; return trimmed; } const TOOLISH_FRAGMENT_RE = /(?:rectification|run-jyotish)-[a-z0-9-]+|skill_read|proposedKind|validationErrors/i; /** * Rectification still publishes reasoning as fragments. This gate only accepts * or rejects a chunk. It does not rewrite, strip English, or require 8 chars. */ export function acceptThinkingFragment(text: string): string | null { const trimmed = text.replace(/\s+/g, " ").trim(); if (!trimmed) return null; if (!CJK_RE.test(trimmed)) return null; if (TOOLISH_FRAGMENT_RE.test(trimmed)) return null; return trimmed; } export function createThinkingFragmentAssembler() { let buffer = ""; return { push(chunk: string): string | null { const accepted = acceptThinkingFragment(chunk); if (!accepted) return null; buffer += accepted; if (!SENTENCE_RE.test(buffer)) return null; const released = buffer; buffer = ""; return released; }, flush(): string | null { const leftover = buffer; buffer = ""; return leftover ? acceptThinkingFragment(leftover) : null; }, }; }