@letrquan/book 0.3.0 → 0.4.0-beta.0
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/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
escapeInvisibleCharacters
|
|
2
|
+
escapeInvisibleCharacters,
|
|
3
|
+
foldControlCharacters
|
|
3
4
|
} from "./chunk-2AAOF5B5.js";
|
|
4
5
|
import {
|
|
5
6
|
systemClock
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
import {
|
|
8
9
|
createDebugLogger,
|
|
9
10
|
isDebugEnabled
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-OJBMDM5H.js";
|
|
11
12
|
|
|
12
13
|
// src/provider/reliability.ts
|
|
13
14
|
function classifyHttpStatus(status) {
|
|
@@ -49,6 +50,98 @@ var CONTEXT_OVERFLOW_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
|
49
50
|
// llama.cpp's server.
|
|
50
51
|
"exceed_context_size_error"
|
|
51
52
|
]);
|
|
53
|
+
var MODEL_UNAVAILABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
54
|
+
"model_not_found",
|
|
55
|
+
"model_not_available",
|
|
56
|
+
"model_unavailable"
|
|
57
|
+
]);
|
|
58
|
+
var MODEL_GONE_PHRASES = [
|
|
59
|
+
"no longer available",
|
|
60
|
+
"no longer supported",
|
|
61
|
+
"not found",
|
|
62
|
+
"does not exist",
|
|
63
|
+
"is not supported",
|
|
64
|
+
"not available",
|
|
65
|
+
"has been retired",
|
|
66
|
+
"has been removed",
|
|
67
|
+
"has been deprecated"
|
|
68
|
+
];
|
|
69
|
+
var CHOOSE_ANOTHER_MODEL = /(?:select|choose)\s+(?:another|a\s+different)\s+model\b(?!\s+provider\b)/i;
|
|
70
|
+
var MODEL_GONE_PROXIMITY_CHARS = 60;
|
|
71
|
+
var CREDENTIAL_WORD = /\b(?:api\s+key|key|token|credential)s?\b/i;
|
|
72
|
+
function splitSentences(text) {
|
|
73
|
+
return text.split(/[.!?:;]+(?:\s+|$)|[\r\n]+/);
|
|
74
|
+
}
|
|
75
|
+
var INSUFFICIENT_QUOTA_ERROR_NAMES = /* @__PURE__ */ new Set(["insufficient_quota"]);
|
|
76
|
+
function hasInsufficientQuotaName(body) {
|
|
77
|
+
if (!body) return false;
|
|
78
|
+
const { names, raw } = errorBodyParts(body);
|
|
79
|
+
if (names.some((name) => INSUFFICIENT_QUOTA_ERROR_NAMES.has(name.toLowerCase()))) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return raw !== void 0 && hasInsufficientQuotaName(raw);
|
|
83
|
+
}
|
|
84
|
+
var SPENT_LIMIT_VERBS = /\b(?:reached|exceeded|exhausted|hit|used\s+up|spent|r(?:un|an)\s+out|depleted)\b/i;
|
|
85
|
+
var SPENT_USAGE_LIMIT_PHRASES = /\b(?:usage\s+limits?|plan\s+limits?|quotas?|subscription\s+limits?|limits?\s+for\s+your\s+plan)\b/i;
|
|
86
|
+
var LONG_WINDOW_PHRASES = /\b(?:weekly|daily|monthly|\d+(?:\.\d+)?(?:-|\s+)hours?|per\s+(?:day|week|month))\b/i;
|
|
87
|
+
var SHORT_WINDOW_PHRASES = /\b(?:per\s+min(?:ute)?|per\s+sec(?:ond)?|rpm|tpm|rps)\b/i;
|
|
88
|
+
function statesSpentUsageLimit(body) {
|
|
89
|
+
if (!body) return false;
|
|
90
|
+
const { message, raw } = errorBodyParts(body);
|
|
91
|
+
const saysIt = (text) => {
|
|
92
|
+
if (!text || SHORT_WINDOW_PHRASES.test(text)) return false;
|
|
93
|
+
for (const sentence of splitSentences(text)) {
|
|
94
|
+
if (SPENT_LIMIT_VERBS.test(sentence) && SPENT_USAGE_LIMIT_PHRASES.test(sentence) && LONG_WINDOW_PHRASES.test(sentence)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
};
|
|
100
|
+
return saysIt(message) || raw !== void 0 && statesSpentUsageLimit(raw);
|
|
101
|
+
}
|
|
102
|
+
var ABSOLUTE_RESET_PATTERN = /\b(?:resets?)\s+(?:at|on)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2}))\b/i;
|
|
103
|
+
function parseAbsoluteReset(body) {
|
|
104
|
+
if (!body) return void 0;
|
|
105
|
+
const { message, raw } = errorBodyParts(body);
|
|
106
|
+
const match = message.match(ABSOLUTE_RESET_PATTERN);
|
|
107
|
+
if (match) {
|
|
108
|
+
const timestamp = Date.parse(match[1]);
|
|
109
|
+
if (Number.isFinite(timestamp)) {
|
|
110
|
+
return { epochMs: timestamp, iso: match[1] };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (raw !== void 0) {
|
|
114
|
+
return parseAbsoluteReset(raw);
|
|
115
|
+
}
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
function statesModelUnavailable(body, effectiveStatus) {
|
|
119
|
+
const { message, names, raw } = errorBodyParts(body);
|
|
120
|
+
if (names.some((name) => MODEL_UNAVAILABLE_ERROR_NAMES.has(name.toLowerCase()))) return true;
|
|
121
|
+
if (effectiveStatus === 404 && names.some((name) => name.toLowerCase() === "not_found_error") && message.trim().toLowerCase().startsWith("model:")) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const saysIt = (text) => {
|
|
125
|
+
for (const sentence of splitSentences(text)) {
|
|
126
|
+
if (CREDENTIAL_WORD.test(sentence)) continue;
|
|
127
|
+
if (CHOOSE_ANOTHER_MODEL.test(sentence)) return true;
|
|
128
|
+
const lower = sentence.toLowerCase();
|
|
129
|
+
const modelWords = [...lower.matchAll(/\bmodels?\b/g)].map((m) => m.index);
|
|
130
|
+
if (modelWords.length === 0) continue;
|
|
131
|
+
for (const phrase of MODEL_GONE_PHRASES) {
|
|
132
|
+
let gone = lower.indexOf(phrase);
|
|
133
|
+
while (gone >= 0) {
|
|
134
|
+
if (modelWords.some((at) => at < gone && gone - at <= MODEL_GONE_PROXIMITY_CHARS)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
gone = lower.indexOf(phrase, gone + 1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
};
|
|
143
|
+
return saysIt(message) || raw !== void 0 && statesModelUnavailable(raw, effectiveStatus);
|
|
144
|
+
}
|
|
52
145
|
function errorBodyParts(body) {
|
|
53
146
|
let parsedJson;
|
|
54
147
|
try {
|
|
@@ -93,38 +186,129 @@ function statesContextOverflow(body) {
|
|
|
93
186
|
const { message, names, raw } = errorBodyParts(body);
|
|
94
187
|
return names.some((name) => CONTEXT_OVERFLOW_ERROR_NAMES.has(name.toLowerCase())) || isContextOverflowError(message) || raw !== void 0 && statesContextOverflow(raw);
|
|
95
188
|
}
|
|
96
|
-
function classifyApiError(status, body) {
|
|
97
|
-
const
|
|
189
|
+
function classifyApiError(status, body, quotedStatus) {
|
|
190
|
+
const effective = quotedStatus !== void 0 ? classifyHttpStatus(status).retryable ? quotedStatus : status : wrappedUpstreamStatus(status, body) ?? status;
|
|
191
|
+
if (effective === 429) {
|
|
192
|
+
if (hasInsufficientQuotaName(body)) return "quota";
|
|
193
|
+
if (statesSpentUsageLimit(body)) return "usage_limit";
|
|
194
|
+
}
|
|
195
|
+
const code = classifyHttpStatus(effective).code;
|
|
196
|
+
if ((effective === 403 || effective === 404) && statesModelUnavailable(body, effective)) {
|
|
197
|
+
return "model_unavailable";
|
|
198
|
+
}
|
|
98
199
|
return code === "bad_request" && statesContextOverflow(body) ? "context_overflow" : code;
|
|
99
200
|
}
|
|
100
201
|
function classifyProviderError(error) {
|
|
202
|
+
if (error instanceof ProviderRedirectError) return "redirect";
|
|
101
203
|
if (error instanceof DOMException && error.name === "TimeoutError") return "timeout";
|
|
102
204
|
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
103
205
|
if (message.includes("timed out") || message.includes("timeout")) return "timeout";
|
|
104
206
|
if (isContextOverflowError(message)) return "context_overflow";
|
|
105
207
|
return "network";
|
|
106
208
|
}
|
|
107
|
-
function
|
|
108
|
-
const
|
|
209
|
+
function safeModelUnavailableDetail(body) {
|
|
210
|
+
const { raw } = errorBodyParts(body);
|
|
211
|
+
const main = safeErrorDetail(body);
|
|
212
|
+
if (raw) {
|
|
213
|
+
const rawDetail = safeErrorDetail(raw);
|
|
214
|
+
if (rawDetail && rawDetail !== main) {
|
|
215
|
+
return main ? `${main}: ${rawDetail}` : rawDetail;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return main || "The provider did not describe why";
|
|
219
|
+
}
|
|
220
|
+
function requestOrigin(request) {
|
|
221
|
+
let host;
|
|
222
|
+
try {
|
|
223
|
+
host = request?.url ? new URL(request.url).host : void 0;
|
|
224
|
+
} catch {
|
|
225
|
+
host = void 0;
|
|
226
|
+
}
|
|
227
|
+
return [host && `from ${host}`, request?.model && `for model ${request.model}`].filter(Boolean).join(" ");
|
|
228
|
+
}
|
|
229
|
+
function isUninformativeDetail(text) {
|
|
230
|
+
if (/^(?:\d{3}\s+)?page not found\.?$/i.test(text)) return true;
|
|
231
|
+
if (isHtmlDetail(text)) return true;
|
|
232
|
+
if (!/^[[{]/.test(text)) return false;
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(text);
|
|
235
|
+
if (Array.isArray(parsed)) return parsed.length === 0;
|
|
236
|
+
return typeof parsed === "object" && parsed !== null && Object.keys(parsed).length === 0;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function isHtmlDetail(text) {
|
|
242
|
+
return /^<(?:!doctype\s+html|html|head|body)\b/i.test(text);
|
|
243
|
+
}
|
|
244
|
+
var MAX_SHOWN_TITLE_CHARS = 100;
|
|
245
|
+
function describeHtmlPage(body) {
|
|
246
|
+
const title = body.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1];
|
|
247
|
+
const shown = title ? foldControlCharacters(title).replace(/\s+/g, " ").trim() : "";
|
|
248
|
+
if (!shown) return "an HTML page";
|
|
249
|
+
const clipped = shown.length > MAX_SHOWN_TITLE_CHARS ? `${shown.slice(0, MAX_SHOWN_TITLE_CHARS)}\u2026` : shown;
|
|
250
|
+
return `an HTML page titled "${clipped}"`;
|
|
251
|
+
}
|
|
252
|
+
function detailSentence(detail, request, body = detail) {
|
|
253
|
+
const text = detail.trim();
|
|
254
|
+
const origin = requestOrigin(request);
|
|
255
|
+
if (!text) {
|
|
256
|
+
const absent = request?.bodyReceived === false ? "no response body received" : "empty response body";
|
|
257
|
+
return { sentence: `${origin ? `${origin} ` : ""}(${absent}).`, rewritten: false };
|
|
258
|
+
}
|
|
259
|
+
const uninformative = isUninformativeDetail(text);
|
|
260
|
+
const shown = isHtmlDetail(text) ? describeHtmlPage(body) : text;
|
|
261
|
+
const sentence = /[.!?]$/.test(shown) ? shown : `${shown}.`;
|
|
262
|
+
const hinted = uninformative && origin !== "";
|
|
263
|
+
return {
|
|
264
|
+
sentence: hinted ? `${sentence} The reply came ${origin}.` : sentence,
|
|
265
|
+
rewritten: hinted || shown !== text
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function resetStatement(said, resetIso, clause) {
|
|
269
|
+
return said.includes(resetIso) ? "" : clause;
|
|
270
|
+
}
|
|
271
|
+
function formatApiError(status, body, quotedStatus, request) {
|
|
272
|
+
const code = classifyApiError(status, body, quotedStatus);
|
|
109
273
|
const detail = safeErrorDetail(body);
|
|
274
|
+
const { sentence: said, rewritten } = detailSentence(detail, request, body);
|
|
110
275
|
const base = `API Error: ${status}`;
|
|
111
276
|
switch (code) {
|
|
112
277
|
case "context_overflow":
|
|
113
|
-
return `${base} ${detail
|
|
114
|
-
case "rate_limited":
|
|
115
|
-
|
|
278
|
+
return `${base} ${detail.trim() ? said : "Input exceeds the model context window."} Reduce the conversation or tool output and try again.`;
|
|
279
|
+
case "rate_limited": {
|
|
280
|
+
if (isOversizedForRateLimit(detail)) {
|
|
281
|
+
return `${base} ${said} The request is larger than the rate limit allows at once, so it has to shrink; waiting will not help.`;
|
|
282
|
+
}
|
|
283
|
+
const reset = parseAbsoluteReset(body);
|
|
284
|
+
if (reset !== void 0 && reset.epochMs > Date.now()) {
|
|
285
|
+
const resetClause = resetStatement(said, reset.iso, ` It resets at ${reset.iso}.`);
|
|
286
|
+
return `${base} ${said}${resetClause} Wait for the reset, then try again.`;
|
|
287
|
+
}
|
|
288
|
+
return `${base} ${said} This may be a temporary capacity issue. Try again in a moment.`;
|
|
289
|
+
}
|
|
116
290
|
case "overloaded":
|
|
117
291
|
return `${base} Repeated 529 Overloaded errors. The API is at capacity \u2014 this is usually temporary. Try again in a moment.`;
|
|
118
292
|
case "server_error":
|
|
119
|
-
return `${base} ${
|
|
293
|
+
return `${base} ${said} This is a server-side issue, usually temporary \u2014 try again in a moment.`;
|
|
120
294
|
case "timeout":
|
|
121
295
|
return "Request timed out. Check your network connection and try again.";
|
|
122
296
|
case "auth":
|
|
123
|
-
return `${base} ${
|
|
297
|
+
return `${base} ${said} Check BOOK_API_KEY, or provider.<id>.apiKey in settings.`;
|
|
298
|
+
case "model_unavailable": {
|
|
299
|
+
const modelDetail = detailSentence(safeModelUnavailableDetail(body)).sentence;
|
|
300
|
+
return `${base} ${modelDetail} This model is not available on this provider or route \u2014 choose another with --model or /model.`;
|
|
301
|
+
}
|
|
302
|
+
case "usage_limit": {
|
|
303
|
+
const reset = parseAbsoluteReset(body);
|
|
304
|
+
const resetClause = reset !== void 0 ? resetStatement(said, reset.iso, `; it resets at ${reset.iso}`) : "";
|
|
305
|
+
return `${base} ${said} The usage limit for this plan is spent${resetClause}. Wait for the reset, or choose another model with --model or /model.`;
|
|
306
|
+
}
|
|
124
307
|
case "quota":
|
|
125
|
-
return `${base} ${
|
|
308
|
+
return `${base} ${said} Check your usage/credits.`;
|
|
126
309
|
default:
|
|
127
|
-
return `${base} ${detail}`;
|
|
310
|
+
if (detail.trim() && !rewritten) return `${base} ${detail}`;
|
|
311
|
+
return code === "not_found" ? `${base} ${said} Check the base URL and model id.` : `${base} ${said}`;
|
|
128
312
|
}
|
|
129
313
|
}
|
|
130
314
|
function isContextOverflowError(error) {
|
|
@@ -156,25 +340,31 @@ function isErrorEnvelopeShape(text, usage) {
|
|
|
156
340
|
const trimmed = text.trim();
|
|
157
341
|
return trimmed.length <= ERROR_ENVELOPE_MAX_CHARS && /^\[Error\]/i.test(trimmed) && !/[\r\n]/.test(trimmed);
|
|
158
342
|
}
|
|
343
|
+
var ROUTER_FAILED_RESPONSE = /^\s*\[Error\]\s*The model failed to generate a response\.?\s*$/i;
|
|
159
344
|
function isUpstreamErrorEnvelope(text, usage) {
|
|
160
345
|
if (!isErrorEnvelopeShape(text, usage)) return false;
|
|
161
|
-
return isZeroUsage(usage) || /\brequest id\b/i.test(text) || /help\.openai\.com/i.test(text) || /error occurred while processing/i.test(text);
|
|
346
|
+
return isZeroUsage(usage) || /\brequest id\b/i.test(text) || /help\.openai\.com/i.test(text) || /error occurred while processing/i.test(text) || ROUTER_FAILED_RESPONSE.test(text);
|
|
162
347
|
}
|
|
163
348
|
var ERROR_BODY_READ_TIMEOUT_MS = 5e3;
|
|
164
349
|
var ERROR_BODY_MAX_BYTES = 65536;
|
|
165
350
|
async function readErrorBody(response, signal) {
|
|
166
|
-
if (
|
|
351
|
+
if (response instanceof BufferedErrorResponse) {
|
|
352
|
+
return { text: response.bufferedText, received: response.bodyReceived };
|
|
353
|
+
}
|
|
354
|
+
if (signal?.aborted) return { text: "", received: false };
|
|
355
|
+
if (!response.body) return { text: "", received: true };
|
|
167
356
|
let reader;
|
|
168
357
|
try {
|
|
169
358
|
reader = response.body.getReader();
|
|
170
359
|
} catch {
|
|
171
|
-
return "";
|
|
360
|
+
return { text: "", received: false };
|
|
172
361
|
}
|
|
173
362
|
const decoder = new TextDecoder();
|
|
174
363
|
let text = "";
|
|
175
364
|
let received = 0;
|
|
176
|
-
let
|
|
365
|
+
let interrupted = false;
|
|
177
366
|
const stop = () => {
|
|
367
|
+
interrupted = true;
|
|
178
368
|
reader.cancel().catch(() => {
|
|
179
369
|
});
|
|
180
370
|
};
|
|
@@ -183,25 +373,72 @@ async function readErrorBody(response, signal) {
|
|
|
183
373
|
try {
|
|
184
374
|
while (received < ERROR_BODY_MAX_BYTES) {
|
|
185
375
|
const chunk = await reader.read();
|
|
186
|
-
if (chunk.done)
|
|
187
|
-
finished = true;
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
376
|
+
if (chunk.done) break;
|
|
190
377
|
const bytes = chunk.value.subarray(0, ERROR_BODY_MAX_BYTES - received);
|
|
191
378
|
received += bytes.byteLength;
|
|
192
379
|
text += decoder.decode(bytes, { stream: true });
|
|
193
380
|
}
|
|
194
381
|
} catch {
|
|
382
|
+
interrupted = true;
|
|
195
383
|
} finally {
|
|
196
384
|
clearTimeout(timer);
|
|
197
385
|
signal?.removeEventListener("abort", stop);
|
|
198
|
-
|
|
386
|
+
reader.cancel().catch(() => {
|
|
387
|
+
});
|
|
199
388
|
try {
|
|
200
389
|
reader.releaseLock();
|
|
201
390
|
} catch {
|
|
202
391
|
}
|
|
203
392
|
}
|
|
204
|
-
return text + decoder.decode();
|
|
393
|
+
return { text: text + decoder.decode(), received: received > 0 || !interrupted };
|
|
394
|
+
}
|
|
395
|
+
var BufferedErrorResponse = class extends Response {
|
|
396
|
+
bufferedText;
|
|
397
|
+
bodyReceived;
|
|
398
|
+
constructor(body, init, bodyReceived) {
|
|
399
|
+
super(body, init);
|
|
400
|
+
this.bufferedText = body;
|
|
401
|
+
this.bodyReceived = bodyReceived;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
var ProviderRedirectError = class extends Error {
|
|
405
|
+
constructor(status, target) {
|
|
406
|
+
const answered = status === 0 ? "answered with a redirect" : `answered HTTP ${status}`;
|
|
407
|
+
super(
|
|
408
|
+
target === void 0 ? `Provider endpoint ${answered}${status === 0 ? " that has" : " with"} no redirect target Book could read, and Book does not follow provider redirects. Check the provider's base URL.` : `Provider endpoint ${answered}${status === 0 ? "" : " with a redirect"} to ${target}, and Book does not follow provider redirects. If that host is the endpoint you meant, set the provider's base URL to that address without the endpoint path Book adds (https://gw.example/v1/chat/completions becomes https://gw.example/v1).`
|
|
409
|
+
);
|
|
410
|
+
this.status = status;
|
|
411
|
+
this.target = target;
|
|
412
|
+
this.name = "ProviderRedirectError";
|
|
413
|
+
}
|
|
414
|
+
status;
|
|
415
|
+
target;
|
|
416
|
+
};
|
|
417
|
+
function sanitizeRedirectTarget(location, requestUrl) {
|
|
418
|
+
if (!location) return void 0;
|
|
419
|
+
try {
|
|
420
|
+
const target = new URL(location, requestUrl);
|
|
421
|
+
if (!target.host) return void 0;
|
|
422
|
+
return `${target.protocol}//${target.host}${target.pathname}`;
|
|
423
|
+
} catch {
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function isRedirectResponse(response) {
|
|
428
|
+
return response.type === "opaqueredirect" || response.status >= 300 && response.status <= 399;
|
|
429
|
+
}
|
|
430
|
+
async function cancelBody(response) {
|
|
431
|
+
try {
|
|
432
|
+
await response.body?.cancel();
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function refuseRedirect(response, requestUrl) {
|
|
437
|
+
await cancelBody(response);
|
|
438
|
+
return new ProviderRedirectError(
|
|
439
|
+
response.status,
|
|
440
|
+
sanitizeRedirectTarget(response.headers.get("location"), requestUrl)
|
|
441
|
+
);
|
|
205
442
|
}
|
|
206
443
|
async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock = systemClock) {
|
|
207
444
|
const startMs = clock.monotonicNowMs();
|
|
@@ -212,7 +449,7 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
212
449
|
const fetchSignal = requestSignal(signal, retry.requestTimeoutMs);
|
|
213
450
|
let response;
|
|
214
451
|
try {
|
|
215
|
-
response = await fetch(url, { ...init, signal: fetchSignal });
|
|
452
|
+
response = await fetch(url, { ...init, redirect: "manual", signal: fetchSignal });
|
|
216
453
|
} catch (error) {
|
|
217
454
|
if (signal?.aborted) throw error;
|
|
218
455
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -227,36 +464,73 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
227
464
|
await sleep(delay2, signal);
|
|
228
465
|
continue;
|
|
229
466
|
}
|
|
467
|
+
if (isRedirectResponse(response)) throw await refuseRedirect(response, url);
|
|
230
468
|
const classification = classifyHttpStatus(response.status);
|
|
231
469
|
if (!classification.retryable) return response;
|
|
232
470
|
lastError = `API error ${response.status}`;
|
|
233
|
-
const
|
|
471
|
+
const errorBody = await readErrorBody(response, signal);
|
|
472
|
+
const bodyText = errorBody.text;
|
|
473
|
+
const buffered = (init2) => new BufferedErrorResponse(bodyText, init2, errorBody.received);
|
|
234
474
|
const quoted = quotedUpstreamStatus(bodyText);
|
|
235
475
|
if (quoted !== void 0 && !classifyHttpStatus(quoted).retryable) {
|
|
236
476
|
logger?.warn("upstream error quoted in retryable status; not retrying", {
|
|
237
477
|
status: response.status,
|
|
238
478
|
upstreamStatus: quoted
|
|
239
479
|
});
|
|
240
|
-
return
|
|
480
|
+
return buffered({
|
|
241
481
|
status: response.status,
|
|
242
482
|
statusText: response.statusText,
|
|
243
483
|
headers: response.headers
|
|
244
484
|
});
|
|
245
485
|
}
|
|
246
|
-
|
|
486
|
+
const errorCode = classifyApiError(response.status, bodyText, quoted);
|
|
487
|
+
if ((errorCode === "quota" || errorCode === "usage_limit") && !retry.watchdog) {
|
|
488
|
+
logger?.warn("usage limit reached; not retrying", {
|
|
489
|
+
status: response.status
|
|
490
|
+
});
|
|
491
|
+
return buffered({
|
|
492
|
+
status: response.status,
|
|
493
|
+
statusText: response.statusText,
|
|
494
|
+
headers: response.headers
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
if ((quoted ?? response.status) === 429 && isOversizedForRateLimit(formatApiError(response.status, bodyText, quoted))) {
|
|
247
498
|
logger?.warn("rate limit on a request too large to ever fit; not retrying", {
|
|
248
499
|
status: response.status
|
|
249
500
|
});
|
|
250
|
-
return
|
|
501
|
+
return buffered({
|
|
251
502
|
status: response.status,
|
|
252
503
|
statusText: response.statusText,
|
|
253
504
|
headers: response.headers
|
|
254
505
|
});
|
|
255
506
|
}
|
|
507
|
+
const stated = statedWait(response.headers.get("retry-after"), bodyText);
|
|
508
|
+
if ((quoted ?? response.status) === 429 && !retry.watchdog) {
|
|
509
|
+
if (stated !== void 0) {
|
|
510
|
+
const attemptsWaitMs = (maxAttempts - attempt) * retry.maxDelayMs;
|
|
511
|
+
const remainingBudgetMs = retry.totalBudgetMs > 0 ? Math.max(0, retry.totalBudgetMs - (clock.monotonicNowMs() - startMs)) : void 0;
|
|
512
|
+
const remainingWaitMs = remainingBudgetMs !== void 0 ? Math.min(remainingBudgetMs, attemptsWaitMs) : attemptsWaitMs;
|
|
513
|
+
const stopsCall = stated.waitMs > remainingWaitMs && (stated.source === "reset-time" || stated.waitMs >= FAR_STATED_WAIT_MS);
|
|
514
|
+
if (stopsCall) {
|
|
515
|
+
logger?.warn("stated wait beyond retry budget; not retrying", {
|
|
516
|
+
status: response.status,
|
|
517
|
+
source: stated.source
|
|
518
|
+
});
|
|
519
|
+
const init2 = {
|
|
520
|
+
status: response.status,
|
|
521
|
+
statusText: response.statusText,
|
|
522
|
+
headers: response.headers
|
|
523
|
+
};
|
|
524
|
+
const wholeCallMs = maxAttempts * retry.maxDelayMs;
|
|
525
|
+
const farWait = stated.waitMs >= FAR_STATED_WAIT_MS && stated.waitMs > (retry.totalBudgetMs > 0 ? Math.min(retry.totalBudgetMs, wholeCallMs) : wholeCallMs);
|
|
526
|
+
return farWait ? new WaitStoppedResponse(bodyText, init2, stated, errorBody.received) : buffered(init2);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
256
530
|
const watchdogRetry = retry.watchdog && (response.status === 429 || response.status === 529);
|
|
257
531
|
const effectiveMax = watchdogRetry ? Number.MAX_SAFE_INTEGER : maxAttempts;
|
|
258
532
|
if (attempt >= effectiveMax || budgetExhausted(clock, startMs, retry.totalBudgetMs)) {
|
|
259
|
-
return
|
|
533
|
+
return buffered({
|
|
260
534
|
status: response.status,
|
|
261
535
|
statusText: response.statusText,
|
|
262
536
|
headers: response.headers
|
|
@@ -264,7 +538,7 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
264
538
|
}
|
|
265
539
|
const delay = boundedDelay(
|
|
266
540
|
clock,
|
|
267
|
-
backoffMs(attempt, retry,
|
|
541
|
+
backoffMs(attempt, retry, stated),
|
|
268
542
|
startMs,
|
|
269
543
|
retry.totalBudgetMs
|
|
270
544
|
);
|
|
@@ -277,16 +551,13 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
277
551
|
try {
|
|
278
552
|
await sleep(delay, signal);
|
|
279
553
|
} catch {
|
|
280
|
-
return
|
|
554
|
+
return buffered({
|
|
281
555
|
status: response.status,
|
|
282
556
|
statusText: response.statusText,
|
|
283
557
|
headers: response.headers
|
|
284
558
|
});
|
|
285
559
|
}
|
|
286
|
-
|
|
287
|
-
await response.body?.cancel();
|
|
288
|
-
} catch {
|
|
289
|
-
}
|
|
560
|
+
await cancelBody(response);
|
|
290
561
|
}
|
|
291
562
|
throw new Error(lastError ?? "request failed after retries");
|
|
292
563
|
}
|
|
@@ -348,9 +619,54 @@ function sleep(ms, signal) {
|
|
|
348
619
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
349
620
|
});
|
|
350
621
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
622
|
+
var FAR_STATED_WAIT_MS = 15 * 6e4;
|
|
623
|
+
function statedWait(retryAfter, body) {
|
|
624
|
+
const header = parseRetryAfter(retryAfter);
|
|
625
|
+
const cooldown = header === void 0 ? parseCooldownReset(body) : void 0;
|
|
626
|
+
const relative = header !== void 0 ? { waitMs: header, source: "retry-after" } : cooldown !== void 0 ? { waitMs: cooldown, source: "cooldown" } : void 0;
|
|
627
|
+
const reset = parseAbsoluteReset(body);
|
|
628
|
+
const resetWaitMs = reset !== void 0 ? reset.epochMs - Date.now() : 0;
|
|
629
|
+
if (reset !== void 0 && resetWaitMs > 0 && (relative === void 0 || resetWaitMs > relative.waitMs)) {
|
|
630
|
+
return { waitMs: resetWaitMs, source: "reset-time", resetIso: reset.iso };
|
|
631
|
+
}
|
|
632
|
+
return relative;
|
|
633
|
+
}
|
|
634
|
+
var WaitStoppedResponse = class extends BufferedErrorResponse {
|
|
635
|
+
statedWait;
|
|
636
|
+
constructor(body, init, wait, bodyReceived) {
|
|
637
|
+
super(body, init, bodyReceived);
|
|
638
|
+
this.statedWait = wait;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
function statedWaitStop(response) {
|
|
642
|
+
return response instanceof WaitStoppedResponse ? response.statedWait : void 0;
|
|
643
|
+
}
|
|
644
|
+
function describeWait(waitMs) {
|
|
645
|
+
const count = (value, unit) => `${value} ${unit}${value === 1 ? "" : "s"}`;
|
|
646
|
+
const seconds = Math.max(1, Math.round(waitMs / 1e3));
|
|
647
|
+
if (seconds < 90) return count(seconds, "second");
|
|
648
|
+
const minutes = Math.round(seconds / 60);
|
|
649
|
+
if (minutes < 90) return count(minutes, "minute");
|
|
650
|
+
const hours = Math.round(minutes / 60);
|
|
651
|
+
if (hours < 48) return count(hours, "hour");
|
|
652
|
+
return count(Math.round(hours / 24), "day");
|
|
653
|
+
}
|
|
654
|
+
function formatStatedWaitError(status, body, stop, request) {
|
|
655
|
+
const said = detailSentence(safeErrorDetail(body), request, body).sentence;
|
|
656
|
+
const when = stop.resetIso !== void 0 ? `The limit resets${resetStatement(said, stop.resetIso, ` at ${stop.resetIso},`)} in about ${describeWait(stop.waitMs)}` : `The provider asks for a wait of about ${describeWait(stop.waitMs)}`;
|
|
657
|
+
return `API Error: ${status} ${said} ${when}, longer than this request will wait. Wait for the reset, or choose another model with --model or /model.`;
|
|
658
|
+
}
|
|
659
|
+
function statedWaitErrorEvent(response, errorText, request) {
|
|
660
|
+
const waitStop = statedWaitStop(response);
|
|
661
|
+
return {
|
|
662
|
+
type: "error",
|
|
663
|
+
error: waitStop ? formatStatedWaitError(response.status, errorText, waitStop, request) : formatApiError(response.status, errorText, void 0, request),
|
|
664
|
+
errorCode: waitStop ? "rate_limit_wait" : classifyApiError(response.status, errorText),
|
|
665
|
+
upstreamStatus: wrappedUpstreamStatus(response.status, errorText)
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function backoffMs(attempt, retry, stated) {
|
|
669
|
+
if (stated !== void 0) return Math.min(stated.waitMs, retry.maxDelayMs);
|
|
354
670
|
const exponential = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);
|
|
355
671
|
return Math.round(exponential * (0.5 + Math.random()));
|
|
356
672
|
}
|
|
@@ -383,7 +699,13 @@ var COOLDOWN_UNIT_MS = {
|
|
|
383
699
|
var COOLDOWN_UNIT = /(\d+)\s*(ms|sec(?:ond)?s?|min(?:ute)?s?|h(?:ours?|rs?)?|s|m)(?![a-z])/gi;
|
|
384
700
|
function parseCooldownReset(body) {
|
|
385
701
|
if (!body) return void 0;
|
|
386
|
-
const
|
|
702
|
+
const { message, raw } = errorBodyParts(body);
|
|
703
|
+
const own = cooldownIn(message);
|
|
704
|
+
if (own !== void 0) return own;
|
|
705
|
+
return raw !== void 0 ? parseCooldownReset(raw) : void 0;
|
|
706
|
+
}
|
|
707
|
+
function cooldownIn(text) {
|
|
708
|
+
const stated = text.match(/\(reset after\s*([^)]+)\)/i);
|
|
387
709
|
if (!stated) return void 0;
|
|
388
710
|
let totalMs = 0;
|
|
389
711
|
let statedUnits = 0;
|
|
@@ -575,13 +897,12 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
575
897
|
}
|
|
576
898
|
log.debug("response received", { status: response.status, ok: response.ok });
|
|
577
899
|
if (!response.ok) {
|
|
578
|
-
const
|
|
579
|
-
yield {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
};
|
|
900
|
+
const errorBody = await readErrorBody(response, signal);
|
|
901
|
+
yield statedWaitErrorEvent(response, errorBody.text, {
|
|
902
|
+
url,
|
|
903
|
+
model: config.model,
|
|
904
|
+
bodyReceived: errorBody.received
|
|
905
|
+
});
|
|
585
906
|
return;
|
|
586
907
|
}
|
|
587
908
|
const reader = response.body?.getReader();
|
|
@@ -600,6 +921,15 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
600
921
|
for (const part of [...toolCallParts].sort((a, b) => a.index - b.index)) {
|
|
601
922
|
if (!part.id && !part.name) continue;
|
|
602
923
|
const { arguments: arguments_, unparsedArguments } = parseToolArguments(part.arguments);
|
|
924
|
+
const emptyOnWire = !part.arguments.trim();
|
|
925
|
+
if (emptyOnWire) {
|
|
926
|
+
log.warn("tool call arrived with empty arguments", {
|
|
927
|
+
index: part.index,
|
|
928
|
+
id: part.id,
|
|
929
|
+
name: escapeInvisibleCharacters(part.name),
|
|
930
|
+
fragments: part.fragments
|
|
931
|
+
});
|
|
932
|
+
}
|
|
603
933
|
if (unparsedArguments) {
|
|
604
934
|
log.warn("tool call arguments are not valid JSON", {
|
|
605
935
|
index: part.index,
|
|
@@ -616,7 +946,8 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
616
946
|
id: part.id || `tool-${part.index}`,
|
|
617
947
|
name: part.name,
|
|
618
948
|
arguments: arguments_,
|
|
619
|
-
...unparsedArguments ? { unparsedArguments } : {}
|
|
949
|
+
...unparsedArguments ? { unparsedArguments } : {},
|
|
950
|
+
...emptyOnWire ? { emptyOnWire: true } : {}
|
|
620
951
|
}
|
|
621
952
|
};
|
|
622
953
|
}
|
|
@@ -661,9 +992,7 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
661
992
|
if (!delta) return false;
|
|
662
993
|
const reasoning = parseReasoningDelta(delta);
|
|
663
994
|
if (reasoning) {
|
|
664
|
-
|
|
665
|
-
stallTimeoutMs = Math.max(stallTimeoutMs, retry.thinkingStallTimeoutMs);
|
|
666
|
-
}
|
|
995
|
+
stallTimeoutMs = retry.thinkingStallTimeoutMs ? Math.max(startingStallTimeoutMs, retry.thinkingStallTimeoutMs) : startingStallTimeoutMs;
|
|
667
996
|
log.debug("stream reasoning", { len: reasoning.length });
|
|
668
997
|
yield { type: "reasoning", reasoning };
|
|
669
998
|
}
|
|
@@ -710,7 +1039,8 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
710
1039
|
return false;
|
|
711
1040
|
};
|
|
712
1041
|
const startingStallTimeoutMs = reasoningEnabled && retry.thinkingStallTimeoutMs ? Math.max(retry.streamStallTimeoutMs, retry.thinkingStallTimeoutMs) : retry.streamStallTimeoutMs;
|
|
713
|
-
|
|
1042
|
+
const firstDeltaApplies = !reasoningEnabled && config.modelInfo?.effort !== false;
|
|
1043
|
+
let stallTimeoutMs = firstDeltaApplies ? Math.max(startingStallTimeoutMs, retry.firstDeltaStallTimeoutMs ?? 0) : startingStallTimeoutMs;
|
|
714
1044
|
try {
|
|
715
1045
|
while (true) {
|
|
716
1046
|
const result = await readStreamChunk(reader, signal, stallTimeoutMs);
|
|
@@ -775,20 +1105,20 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
775
1105
|
}
|
|
776
1106
|
|
|
777
1107
|
export {
|
|
778
|
-
wrappedUpstreamStatus,
|
|
779
|
-
classifyApiError,
|
|
780
1108
|
classifyProviderError,
|
|
781
|
-
formatApiError,
|
|
782
1109
|
isContextOverflowError,
|
|
783
1110
|
isOversizedForRateLimit,
|
|
784
1111
|
statedRateLimit,
|
|
785
1112
|
isErrorEnvelopeShape,
|
|
786
1113
|
isUpstreamErrorEnvelope,
|
|
787
1114
|
readErrorBody,
|
|
1115
|
+
isRedirectResponse,
|
|
1116
|
+
refuseRedirect,
|
|
788
1117
|
fetchWithRetry,
|
|
789
1118
|
readStreamChunk,
|
|
1119
|
+
statedWaitErrorEvent,
|
|
790
1120
|
parseCompatibleUsage,
|
|
791
1121
|
convertTools,
|
|
792
1122
|
chatCompletionStream
|
|
793
1123
|
};
|
|
794
|
-
//# sourceMappingURL=chunk-
|
|
1124
|
+
//# sourceMappingURL=chunk-AHC7OU75.js.map
|