bunnyquery 1.3.2 → 1.3.4
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.
- package/bunnyquery.js +33 -3
- package/dist/engine.cjs +34 -3
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +2 -1
- package/dist/engine.d.ts +2 -1
- package/dist/engine.mjs +34 -4
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/errors.ts +49 -0
- package/src/engine/index.ts +1 -1
- package/src/engine/session.ts +8 -4
package/package.json
CHANGED
package/src/engine/errors.ts
CHANGED
|
@@ -34,6 +34,55 @@ export function isErrorResponseBody(response: any): boolean {
|
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// A request that failed because the REQUEST ITSELF is malformed — an unknown or
|
|
38
|
+
// rejected parameter on a 400/422 — will deterministically re-fail if resent
|
|
39
|
+
// unchanged; no session refresh or retry can fix it. Detecting it lets the retry
|
|
40
|
+
// gates refuse to auto-resend (otherwise a 400 whose param name merely contains
|
|
41
|
+
// "token", e.g. `max_output_tokens`, trips the `invalid_request + token` branch
|
|
42
|
+
// of isAuthExpiredError below and loops refresh->resend forever, burning tokens).
|
|
43
|
+
// This is the `_skapi_extract` 400 class. Distinct from auth-expiry, which IS
|
|
44
|
+
// retryable after a token refresh — so 401 (auth) and 429/5xx (transient) are
|
|
45
|
+
// intentionally NOT treated as non-retryable here.
|
|
46
|
+
export function isNonRetryableRequestError(input: any): boolean {
|
|
47
|
+
if (!input || typeof input !== 'object') return false;
|
|
48
|
+
|
|
49
|
+
var status = typeof input.status_code === 'number' ? input.status_code
|
|
50
|
+
: typeof input.status === 'number' ? input.status : undefined;
|
|
51
|
+
|
|
52
|
+
// Collect the error envelope wherever it lives (response, response.body, or a
|
|
53
|
+
// thrown error), mirroring isErrorResponseBody / isAuthExpiredError.
|
|
54
|
+
var param: any = undefined;
|
|
55
|
+
var blobs: string[] = [];
|
|
56
|
+
var sources = [input.error, input.body && input.body.error, input.body, input];
|
|
57
|
+
for (var i = 0; i < sources.length; i++) {
|
|
58
|
+
var e = sources[i];
|
|
59
|
+
if (!e) continue;
|
|
60
|
+
if (typeof e === 'string') { blobs.push(e); continue; }
|
|
61
|
+
if (typeof e !== 'object') continue;
|
|
62
|
+
if (param === undefined && e.param != null) param = e.param;
|
|
63
|
+
if (typeof e.code === 'string') blobs.push(e.code);
|
|
64
|
+
if (typeof e.type === 'string') blobs.push(e.type);
|
|
65
|
+
if (typeof e.message === 'string') blobs.push(e.message);
|
|
66
|
+
}
|
|
67
|
+
var hay = blobs.join(' | ').toLowerCase();
|
|
68
|
+
|
|
69
|
+
// Explicit "unknown / unsupported parameter" is always a malformed request.
|
|
70
|
+
if (hay.indexOf('unknown_parameter') !== -1 || hay.indexOf('unknown parameter') !== -1 ||
|
|
71
|
+
hay.indexOf('unsupported_parameter') !== -1 || hay.indexOf('unsupported parameter') !== -1) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A 400/422 invalid_request that points at a specific rejected field: resending
|
|
76
|
+
// the identical body re-fails. (401 -> isAuthExpiredError; 429/5xx -> transient.)
|
|
77
|
+
var isClientReqStatus = status === 400 || status === 422;
|
|
78
|
+
if (isClientReqStatus && param != null && param !== '') return true;
|
|
79
|
+
if (isClientReqStatus && hay.indexOf('invalid_request') !== -1 &&
|
|
80
|
+
(hay.indexOf('parameter') !== -1 || hay.indexOf('param') !== -1)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
37
86
|
export function isAuthExpiredError(input: any): boolean {
|
|
38
87
|
if (!input) return false;
|
|
39
88
|
var blobs: string[] = [];
|
package/src/engine/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export * from './prompts';
|
|
|
30
30
|
|
|
31
31
|
// Pure helpers (Tier-1.5): error detection, token budgeting, link/path
|
|
32
32
|
// normalization, and history mapping — shared so both consumers stay identical.
|
|
33
|
-
export { getErrorMessage, isErrorResponseBody, isAuthExpiredError } from './errors';
|
|
33
|
+
export { getErrorMessage, isErrorResponseBody, isAuthExpiredError, isNonRetryableRequestError } from './errors';
|
|
34
34
|
export * from './budget';
|
|
35
35
|
export * from './links';
|
|
36
36
|
export {
|
package/src/engine/session.ts
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
OPENAI_RESPONSES_API_URL,
|
|
31
31
|
type BgTaskEntry,
|
|
32
32
|
} from './requests';
|
|
33
|
-
import { isErrorResponseBody, isAuthExpiredError, getErrorMessage } from './errors';
|
|
33
|
+
import { isErrorResponseBody, isAuthExpiredError, isNonRetryableRequestError, getErrorMessage } from './errors';
|
|
34
34
|
import { buildBoundedChatMessages } from './budget';
|
|
35
35
|
import { createInlineLinkRegex } from './links';
|
|
36
36
|
import { mapHistoryListToMessages, extractLastUserTextFromRequest } from './history';
|
|
@@ -116,11 +116,15 @@ export class ChatSession {
|
|
|
116
116
|
};
|
|
117
117
|
var run = sendAndPoll()
|
|
118
118
|
.catch(function (err: any) {
|
|
119
|
-
|
|
119
|
+
// Only auth-expiry is worth a refresh+resend; a malformed-request 400
|
|
120
|
+
// (e.g. the `_skapi_extract`/unknown-parameter class) re-fails identically,
|
|
121
|
+
// so never loop on it — guard against isAuthExpiredError's heuristic
|
|
122
|
+
// misfiring on a param name that merely contains "token".
|
|
123
|
+
if (isAuthExpiredError(err) && !isNonRetryableRequestError(err)) return self.host.refreshSession().then(sendAndPoll);
|
|
120
124
|
throw err;
|
|
121
125
|
})
|
|
122
126
|
.then(function (response: any) {
|
|
123
|
-
if (isErrorResponseBody(response) && isAuthExpiredError(response)) {
|
|
127
|
+
if (isErrorResponseBody(response) && isAuthExpiredError(response) && !isNonRetryableRequestError(response)) {
|
|
124
128
|
return self.host.refreshSession().then(sendAndPoll);
|
|
125
129
|
}
|
|
126
130
|
return response;
|
|
@@ -656,7 +660,7 @@ export class ChatSession {
|
|
|
656
660
|
var fetchHistory = function () { return getChatHistory({ service: serviceId, owner: owner, platform: platform }, options); };
|
|
657
661
|
|
|
658
662
|
return Promise.resolve().then(fetchHistory).catch(function (err: any) {
|
|
659
|
-
if (isAuthExpiredError(err)) return self.host.refreshSession().then(fetchHistory);
|
|
663
|
+
if (isAuthExpiredError(err) && !isNonRetryableRequestError(err)) return self.host.refreshSession().then(fetchHistory);
|
|
660
664
|
throw err;
|
|
661
665
|
}).then(function (history: any) {
|
|
662
666
|
if (token !== self.state.gateRefreshToken) return;
|