@aexol/spectral 0.9.177 → 0.9.178
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This module converts any caught error into a short, safe, user-facing
|
|
9
9
|
* string. Raw errors are still logged server-side at the catch sites; this
|
|
10
|
-
* helper only controls the user-facing message.
|
|
10
|
+
* helper only controls the user-facing message. Unknown, non-transient errors
|
|
11
|
+
* keep a sanitized version of their underlying cause so the UI can show what
|
|
12
|
+
* actually happened instead of only a generic fallback.
|
|
11
13
|
*/
|
|
12
14
|
/**
|
|
13
15
|
* Classification regex reused from agent-loop.ts (retry classification).
|
|
@@ -22,10 +24,13 @@ export declare const PROVIDER_ERROR_PATTERNS: RegExp;
|
|
|
22
24
|
* 1. Rate limit / 429 / overloaded / 529 / "too many requests" /
|
|
23
25
|
* "temporarily rate-limited" → "Rate limit reached. Please try again in a moment."
|
|
24
26
|
* 2. timeout / econnreset / "fetch failed" / network → "Network error. Please try again."
|
|
25
|
-
* 3. Any other Error
|
|
27
|
+
* 3. Any other Error / string → a sanitized, concise version of the underlying
|
|
28
|
+
* cause. Non-string/non-Error values (or values whose cause is empty after
|
|
29
|
+
* sanitization) → "Something went wrong. Please try again."
|
|
26
30
|
*
|
|
27
|
-
*
|
|
28
|
-
* user_id, provider_name, is_byok,
|
|
31
|
+
* The sanitizer still strips JSON blobs, stack frames, URLs, absolute paths,
|
|
32
|
+
* and internal identifiers (org_..., user_id, provider_name, is_byok,
|
|
33
|
+
* upstream model names).
|
|
29
34
|
*/
|
|
30
35
|
export declare function humanizeProviderError(err: unknown): string;
|
|
31
36
|
//# sourceMappingURL=error-humanizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-humanizer.d.ts","sourceRoot":"","sources":["../../src/server/error-humanizer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"error-humanizer.d.ts","sourceRoot":"","sources":["../../src/server/error-humanizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QACsN,CAAC;AAqC3P;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAa1D"}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This module converts any caught error into a short, safe, user-facing
|
|
9
9
|
* string. Raw errors are still logged server-side at the catch sites; this
|
|
10
|
-
* helper only controls the user-facing message.
|
|
10
|
+
* helper only controls the user-facing message. Unknown, non-transient errors
|
|
11
|
+
* keep a sanitized version of their underlying cause so the UI can show what
|
|
12
|
+
* actually happened instead of only a generic fallback.
|
|
11
13
|
*/
|
|
12
14
|
/**
|
|
13
15
|
* Classification regex reused from agent-loop.ts (retry classification).
|
|
@@ -18,6 +20,7 @@ export const PROVIDER_ERROR_PATTERNS = /rate.?limit|429|overloaded|529|timeout|n
|
|
|
18
20
|
const RATE_LIMIT_MESSAGE = "Rate limit reached. Please try again in a moment.";
|
|
19
21
|
const NETWORK_ERROR_MESSAGE = "Network error. Please try again.";
|
|
20
22
|
const GENERIC_MESSAGE = "Something went wrong. Please try again.";
|
|
23
|
+
const MAX_USER_ERROR_LENGTH = 240;
|
|
21
24
|
/**
|
|
22
25
|
* Coerce any caught value into a single string suitable for regex matching,
|
|
23
26
|
* without ever returning it to the user. This is only used internally for
|
|
@@ -57,10 +60,13 @@ function toErrorString(err) {
|
|
|
57
60
|
* 1. Rate limit / 429 / overloaded / 529 / "too many requests" /
|
|
58
61
|
* "temporarily rate-limited" → "Rate limit reached. Please try again in a moment."
|
|
59
62
|
* 2. timeout / econnreset / "fetch failed" / network → "Network error. Please try again."
|
|
60
|
-
* 3. Any other Error
|
|
63
|
+
* 3. Any other Error / string → a sanitized, concise version of the underlying
|
|
64
|
+
* cause. Non-string/non-Error values (or values whose cause is empty after
|
|
65
|
+
* sanitization) → "Something went wrong. Please try again."
|
|
61
66
|
*
|
|
62
|
-
*
|
|
63
|
-
* user_id, provider_name, is_byok,
|
|
67
|
+
* The sanitizer still strips JSON blobs, stack frames, URLs, absolute paths,
|
|
68
|
+
* and internal identifiers (org_..., user_id, provider_name, is_byok,
|
|
69
|
+
* upstream model names).
|
|
64
70
|
*/
|
|
65
71
|
export function humanizeProviderError(err) {
|
|
66
72
|
const text = toErrorString(err);
|
|
@@ -73,5 +79,69 @@ export function humanizeProviderError(err) {
|
|
|
73
79
|
if (PROVIDER_ERROR_PATTERNS.test(text)) {
|
|
74
80
|
return NETWORK_ERROR_MESSAGE;
|
|
75
81
|
}
|
|
76
|
-
return GENERIC_MESSAGE;
|
|
82
|
+
return extractSafeErrorCause(err) || GENERIC_MESSAGE;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract the deepest, non-empty, human-readable cause from an Error/string
|
|
86
|
+
* and remove anything that could leak internal identifiers, credentials,
|
|
87
|
+
* JSON payloads, stack frames, URLs, or absolute filesystem paths.
|
|
88
|
+
*
|
|
89
|
+
* Non-Error, non-string values intentionally have no safe textual cause and
|
|
90
|
+
* fall back to the generic message.
|
|
91
|
+
*/
|
|
92
|
+
function extractSafeErrorCause(err) {
|
|
93
|
+
if (err instanceof Error) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
let current = err;
|
|
96
|
+
while (current instanceof Error) {
|
|
97
|
+
const sanitized = sanitizeErrorCause(current.message);
|
|
98
|
+
if (sanitized)
|
|
99
|
+
parts.push(sanitized);
|
|
100
|
+
current = current.cause;
|
|
101
|
+
}
|
|
102
|
+
// Some SDKs attach a non-Error cause (usually a string or parsed JSON).
|
|
103
|
+
if (typeof current === "string") {
|
|
104
|
+
const sanitized = sanitizeErrorCause(current);
|
|
105
|
+
if (sanitized)
|
|
106
|
+
parts.push(sanitized);
|
|
107
|
+
}
|
|
108
|
+
return parts.length > 0 ? parts[parts.length - 1] : "";
|
|
109
|
+
}
|
|
110
|
+
if (typeof err === "string")
|
|
111
|
+
return sanitizeErrorCause(err);
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
function sanitizeErrorCause(raw) {
|
|
115
|
+
if (!raw)
|
|
116
|
+
return "";
|
|
117
|
+
let text = raw;
|
|
118
|
+
// Remove whole JSON payloads. Raw provider errors often wrap the cause in a
|
|
119
|
+
// JSON blob carrying org ids, provider names, and model names.
|
|
120
|
+
text = text.replace(/\{[\s\S]*\}/g, " ");
|
|
121
|
+
text = text.replace(/\[[\s\S]*\]/g, " ");
|
|
122
|
+
// Remove URLs (may embed signed tokens).
|
|
123
|
+
text = text.replace(/\bhttps?:\/\/\S+/gi, " ");
|
|
124
|
+
// Remove absolute filesystem paths (POSIX and Windows).
|
|
125
|
+
text = text.replace(/(?:^|[\s("'])\/[\w.@-]+(?:\/[\w.@-]+)*/g, " ");
|
|
126
|
+
text = text.replace(/(?:^|[\s("'])[A-Za-z]:[\\/][^\s]*/g, " ");
|
|
127
|
+
// Remove internal identifiers and sensitive key/value metadata. Key/value
|
|
128
|
+
// metadata is stripped first so an identifier inside a value is consumed
|
|
129
|
+
// with the key rather than leaving a dangling key that then eats the next
|
|
130
|
+
// word in the message.
|
|
131
|
+
text = text.replace(/\b(?:provider_name|user_id|is_byok|api[_-]?key|authorization|secret|token)\s*[:=]\s*[^\s,;]+/gi, " ");
|
|
132
|
+
text = text.replace(/\bmodel\s*[:=]\s*[^\s,;]+/gi, " ");
|
|
133
|
+
text = text.replace(/\borg_[A-Za-z0-9_-]+\b/gi, " ");
|
|
134
|
+
// Remove stack-trace frames (multi-line Error messages).
|
|
135
|
+
text = text.replace(/^\s+at\s+[^\n]*/gm, " ");
|
|
136
|
+
// Remove ANSI escape sequences and normalize whitespace/punctuation noise.
|
|
137
|
+
text = text
|
|
138
|
+
.replace(/\u001b\[[0-9;]*m/g, " ")
|
|
139
|
+
.replace(/\s+/g, " ")
|
|
140
|
+
.replace(/\s*([,;:])\s*(?=[,;:.])/g, "")
|
|
141
|
+
.trim();
|
|
142
|
+
if (!text)
|
|
143
|
+
return "";
|
|
144
|
+
if (text.length <= MAX_USER_ERROR_LENGTH)
|
|
145
|
+
return text;
|
|
146
|
+
return `${text.slice(0, MAX_USER_ERROR_LENGTH - 1).trimEnd()}…`;
|
|
77
147
|
}
|