48 lines
1.5 KiB
JavaScript
48 lines
1.5 KiB
JavaScript
/**
|
|
* 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";
|
|
}
|