bunnyquery 1.0.6 → 1.0.7
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 +93 -8
- package/package.json +1 -1
package/bunnyquery.js
CHANGED
|
@@ -492,6 +492,50 @@
|
|
|
492
492
|
return 'Request failed. Please try again.';
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
// Detect MCP / upstream "your access token is no longer valid" failures
|
|
496
|
+
// so we can transparently re-mint the MCP token from the live skapi
|
|
497
|
+
// session and retry the call once. Patterns observed in the wild:
|
|
498
|
+
// - skapi-js inside the MCP server throws "Token has expired"
|
|
499
|
+
// (surfaces here as part of err.message or response body text)
|
|
500
|
+
// - MCP returns OAuth-style error codes: invalid_token / expired_token
|
|
501
|
+
// - Proxy wraps it with INVALID_REQUEST + 401
|
|
502
|
+
function _isAuthExpiredError(input) {
|
|
503
|
+
if (!input) return false;
|
|
504
|
+
const blobs = [];
|
|
505
|
+
const push = (v) => { if (typeof v === 'string' && v) blobs.push(v); };
|
|
506
|
+
if (typeof input === 'string') push(input);
|
|
507
|
+
else {
|
|
508
|
+
push(input.message);
|
|
509
|
+
push(input.code);
|
|
510
|
+
if (input.error) {
|
|
511
|
+
push(input.error.message);
|
|
512
|
+
push(input.error.code);
|
|
513
|
+
push(input.error.type);
|
|
514
|
+
}
|
|
515
|
+
if (input.body) {
|
|
516
|
+
push(input.body.message);
|
|
517
|
+
if (input.body.error) {
|
|
518
|
+
push(input.body.error.message);
|
|
519
|
+
push(input.body.error.code);
|
|
520
|
+
push(input.body.error.type);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (typeof input.status === 'number' && input.status === 401) return true;
|
|
524
|
+
if (typeof input.status_code === 'number' && input.status_code === 401) return true;
|
|
525
|
+
}
|
|
526
|
+
const hay = blobs.join(' | ').toLowerCase();
|
|
527
|
+
if (!hay) return false;
|
|
528
|
+
return (
|
|
529
|
+
hay.includes('token has expired') ||
|
|
530
|
+
hay.includes('token is expired') ||
|
|
531
|
+
hay.includes('expired_token') ||
|
|
532
|
+
hay.includes('invalid_token') ||
|
|
533
|
+
hay.includes('unauthorized') ||
|
|
534
|
+
hay.includes('not authorized') ||
|
|
535
|
+
(hay.includes('invalid_request') && hay.includes('token'))
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
495
539
|
// Mirror of agent.vue's isErrorResponseBody — detects provider/proxy
|
|
496
540
|
// error payloads that should render as a red error bubble even when
|
|
497
541
|
// no exception was thrown.
|
|
@@ -1090,12 +1134,24 @@
|
|
|
1090
1134
|
|
|
1091
1135
|
// ---- history ------------------------------------------------------
|
|
1092
1136
|
async _loadFirstHistoryPage() {
|
|
1137
|
+
const fetchPage = () => getChatHistory(
|
|
1138
|
+
this.skapi,
|
|
1139
|
+
{ service: this.serviceId, owner: this.ownerId, platform: this.platform },
|
|
1140
|
+
{ ascending: false }
|
|
1141
|
+
);
|
|
1093
1142
|
try {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1143
|
+
let res;
|
|
1144
|
+
try {
|
|
1145
|
+
res = await fetchPage();
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
if (_isAuthExpiredError(err)) {
|
|
1148
|
+
this.oauth.clearToken();
|
|
1149
|
+
await this.oauth.exchangeSession(this.skapi.session);
|
|
1150
|
+
res = await fetchPage();
|
|
1151
|
+
} else {
|
|
1152
|
+
throw err;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1099
1155
|
this.startKeyHistory = res && res.startKeyHistory;
|
|
1100
1156
|
const list = this._filterByClearHorizon((res && res.list) || []);
|
|
1101
1157
|
// If the clear horizon has filtered the entire first page,
|
|
@@ -1374,13 +1430,42 @@ The same pattern applies to other formats: \`\`\`my-data.json, \`\`\`index.html,
|
|
|
1374
1430
|
model: this.model || undefined,
|
|
1375
1431
|
};
|
|
1376
1432
|
|
|
1377
|
-
const
|
|
1378
|
-
?
|
|
1379
|
-
:
|
|
1433
|
+
const argsBuilder = () => (this.platform === 'claude'
|
|
1434
|
+
? buildClaudeRequest(this.skapi, args)
|
|
1435
|
+
: buildOpenAIRequest(this.skapi, args));
|
|
1436
|
+
|
|
1437
|
+
let result;
|
|
1438
|
+
try {
|
|
1439
|
+
result = await argsBuilder();
|
|
1440
|
+
} catch (err) {
|
|
1441
|
+
if (_isAuthExpiredError(err)) {
|
|
1442
|
+
// The MCP-side skapi session expired. Re-mint a fresh
|
|
1443
|
+
// MCP token bundle from the live host skapi session
|
|
1444
|
+
// (skapi-js auto-refreshes its own tokens), then retry
|
|
1445
|
+
// the call once.
|
|
1446
|
+
try {
|
|
1447
|
+
this.oauth.clearToken();
|
|
1448
|
+
await this.oauth.exchangeSession(this.skapi.session);
|
|
1449
|
+
} catch (reauthErr) {
|
|
1450
|
+
throw reauthErr;
|
|
1451
|
+
}
|
|
1452
|
+
result = await argsBuilder();
|
|
1453
|
+
} else {
|
|
1454
|
+
throw err;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1380
1457
|
|
|
1381
1458
|
// The proxy may resolve with an error-shaped body instead of
|
|
1382
1459
|
// throwing; surface that as an error bubble with the
|
|
1383
1460
|
// upstream message rather than dropping it.
|
|
1461
|
+
if (isErrorResponseBody(result) && _isAuthExpiredError(result)) {
|
|
1462
|
+
try {
|
|
1463
|
+
this.oauth.clearToken();
|
|
1464
|
+
await this.oauth.exchangeSession(this.skapi.session);
|
|
1465
|
+
result = await argsBuilder();
|
|
1466
|
+
} catch (_reauthErr) { /* fall through to normal error rendering */ }
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1384
1469
|
if (isErrorResponseBody(result)) {
|
|
1385
1470
|
const errMsg = getErrorMessage(result);
|
|
1386
1471
|
const last = this.messages[this.messages.length - 1];
|