/** * Config-driven in-stream error pattern matching. * See docs/2026-08-04-stream-error-patterns-design.md. * * Settings shape: streamErrorPatterns: { [provider]: string[] } * Pattern syntax per entry: * - "plain text" → case-insensitive substring match * - "/regex/flags" → RegExp match (flags after the last "/") */ export function parsePatterns(patterns) { if (!Array.isArray(patterns)) return []; const out = []; for (const entry of patterns) { if (typeof entry !== "string" || !entry.trim()) continue; const raw = entry.trim(); const m = raw.match(/^\/(.*)\/([a-z]*)$/s); if (m) { try { out.push({ regex: new RegExp(m[1], m[2]), raw }); } catch { // invalid regex → skip; config errors must never break requests } continue; } out.push({ text: raw.toLowerCase(), raw }); } return out; } export function matchStreamErrorPatterns(patterns, text) { if (!Array.isArray(patterns) || patterns.length === 0) return null; if (typeof text !== "string" || !text) return null; const lower = text.toLowerCase(); for (const p of parsePatterns(patterns)) { if (p.text && lower.includes(p.text)) return p.raw; if (p.regex) { p.regex.lastIndex = 0; // stateless even with /g if (p.regex.test(text)) return p.raw; } } return null; } export function streamStatusForContent(patterns, content) { return matchStreamErrorPatterns(patterns, content) ? "error" : "success"; }