@letrquan/book 0.2.0 → 0.3.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 +2747 -40
- package/README.md +132 -1311
- package/dist/additional-roots-H7UNAEHS.js +31 -0
- package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
- package/dist/app-GE4OHJOC.js.map +1 -0
- package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
- package/dist/chunk-224FJZLH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
- package/dist/chunk-4AS7LKKQ.js.map +1 -0
- package/dist/chunk-525CAZPY.js +120 -0
- package/dist/chunk-525CAZPY.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
- package/dist/chunk-56SIEMDX.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
- package/dist/chunk-5YHTKWJN.js.map +1 -0
- package/dist/chunk-62QL5QUP.js +283 -0
- package/dist/chunk-62QL5QUP.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
- package/dist/chunk-67UZEWWF.js.map +1 -0
- package/dist/chunk-6OQQOMZR.js +785 -0
- package/dist/chunk-6OQQOMZR.js.map +1 -0
- package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
- package/dist/chunk-AQEQX4FF.js.map +1 -0
- package/dist/chunk-C7ADUNPR.js +273 -0
- package/dist/chunk-C7ADUNPR.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
- package/dist/chunk-EFJ2NH6E.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
- package/dist/chunk-HQI7NYGX.js.map +1 -0
- package/dist/chunk-IME7R2UI.js +6541 -0
- package/dist/chunk-IME7R2UI.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
- package/dist/chunk-J62VZYOB.js.map +1 -0
- package/dist/chunk-JTPOI3KU.js +826 -0
- package/dist/chunk-JTPOI3KU.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
- package/dist/chunk-KBQS35OV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
- package/dist/chunk-QYBD22H5.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
- package/dist/chunk-RFJ6Q6LE.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
- package/dist/chunk-S4JMID4G.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
- package/dist/chunk-SOW4WRQP.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
- package/dist/chunk-TLX3H2CL.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
- package/dist/chunk-VPNLCMVR.js.map +1 -0
- package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
- package/dist/chunk-W3XFHEF4.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
- package/dist/chunk-X4NJT436.js.map +1 -0
- package/dist/chunk-X66I2NQO.js +137 -0
- package/dist/chunk-X66I2NQO.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
- package/dist/chunk-Y2VHN7TC.js.map +1 -0
- package/dist/chunk-YC6W4EJH.js +947 -0
- package/dist/chunk-YC6W4EJH.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
- package/dist/context-CBOBDY5Q.js +38 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
- package/dist/index.js +996 -155
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
- package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
- package/dist/job-runner.js +159 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +112 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-S77OVTHH.js +18 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
- package/dist/memory-store-DZUT5HUH.js +75 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
- package/dist/openai-compatible-2OB54YST.js +14 -0
- package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
- package/dist/permissions-DHOKF3W2.js +45 -0
- package/dist/sandbox-WRYTZH3D.js +41 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
- package/dist/scrollback-35ME2GFX.js.map +1 -0
- package/dist/sdk.d.ts +2219 -2361
- package/dist/sdk.js +53 -38
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-JUNPGAAM.js +35 -0
- package/dist/settings-loader-JUNPGAAM.js.map +1 -0
- package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
- package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
- package/package.json +23 -22
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
|
@@ -1,289 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeInvisibleCharacters
|
|
3
|
+
} from "./chunk-2AAOF5B5.js";
|
|
1
4
|
import {
|
|
2
5
|
systemClock
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
|
|
5
|
-
// src/debug-log.ts
|
|
6
|
+
} from "./chunk-RT5SATF7.js";
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
var DEFAULT_DEBUG_LOG_BACKUPS = 3;
|
|
25
|
-
var DEFAULT_LOCAL_DATA_RETENTION_DAYS = 30;
|
|
26
|
-
function positiveInteger(value, fallback) {
|
|
27
|
-
const parsed = Number(value);
|
|
28
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
29
|
-
}
|
|
30
|
-
function debugLogMaxBytes() {
|
|
31
|
-
return Math.max(
|
|
32
|
-
MIN_DEBUG_LOG_MAX_BYTES,
|
|
33
|
-
positiveInteger(process.env.BOOK_DEBUG_MAX_BYTES, DEFAULT_DEBUG_LOG_MAX_BYTES)
|
|
34
|
-
);
|
|
8
|
+
createDebugLogger,
|
|
9
|
+
isDebugEnabled
|
|
10
|
+
} from "./chunk-62QL5QUP.js";
|
|
11
|
+
|
|
12
|
+
// src/provider/reliability.ts
|
|
13
|
+
function classifyHttpStatus(status) {
|
|
14
|
+
if (status === 413) return { code: "context_overflow", retryable: false };
|
|
15
|
+
if (status === 429) return { code: "rate_limited", retryable: true };
|
|
16
|
+
if (status === 529) return { code: "overloaded", retryable: true };
|
|
17
|
+
if (status >= 500 && status < 600) return { code: "server_error", retryable: true };
|
|
18
|
+
if (status === 408) return { code: "timeout", retryable: true };
|
|
19
|
+
if (status === 401 || status === 403) return { code: "auth", retryable: false };
|
|
20
|
+
if (status === 402) return { code: "quota", retryable: false };
|
|
21
|
+
if (status === 400) return { code: "bad_request", retryable: false };
|
|
22
|
+
if (status === 404) return { code: "not_found", retryable: false };
|
|
23
|
+
if (status === 422) return { code: "unprocessable", retryable: false };
|
|
24
|
+
return { code: "unknown", retryable: false };
|
|
35
25
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
var CAPACITY_OUTAGE_404 = /no\s+(?:endpoints|accounts)\s+found/i;
|
|
27
|
+
var RESET_AFTER = /\(reset after\s*[^)]+\)/i;
|
|
28
|
+
function quotedUpstreamStatus(body) {
|
|
29
|
+
if (!body) return void 0;
|
|
30
|
+
const text = body.length > 65536 ? body.slice(0, 65536) : body;
|
|
31
|
+
const patterns = [
|
|
32
|
+
/\[[^\]\s]+\]\s*\[(4\d\d)\]:/,
|
|
33
|
+
/"error"\s*:\s*\{[^{}]*?"code"\s*:\s*"?(4\d\d)(?![\d.])/
|
|
34
|
+
];
|
|
35
|
+
const capacityOutage = CAPACITY_OUTAGE_404.test(text) && RESET_AFTER.test(text);
|
|
36
|
+
if (capacityOutage) return void 0;
|
|
37
|
+
for (const pattern of patterns) {
|
|
38
|
+
const match = text.match(pattern);
|
|
39
|
+
if (match) return Number(match[1]);
|
|
40
40
|
}
|
|
41
|
-
const maxBytes = debugLogMaxBytes();
|
|
42
|
-
const currentBytes = debugLogSizes.get(target) ?? statSync(target).size;
|
|
43
|
-
debugLogSizes.set(target, currentBytes);
|
|
44
|
-
if (currentBytes + incomingBytes <= maxBytes) return;
|
|
45
|
-
const backups = positiveInteger(process.env.BOOK_DEBUG_BACKUPS, DEFAULT_DEBUG_LOG_BACKUPS);
|
|
46
|
-
const oldest = `${target}.${backups}`;
|
|
47
|
-
if (existsSync(oldest)) unlinkSync(oldest);
|
|
48
|
-
for (let index = backups - 1; index >= 1; index--) {
|
|
49
|
-
const source = `${target}.${index}`;
|
|
50
|
-
if (existsSync(source)) renameSync(source, `${target}.${index + 1}`);
|
|
51
|
-
}
|
|
52
|
-
renameSync(target, `${target}.1`);
|
|
53
|
-
debugLogSizes.set(target, 0);
|
|
54
|
-
}
|
|
55
|
-
function defaultDebugLogPath() {
|
|
56
|
-
return join(process.env.BOOK_WORKSPACE || process.cwd(), ".book", "debug.log");
|
|
57
|
-
}
|
|
58
|
-
function getDebugLogPath() {
|
|
59
|
-
if (process.env.BOOK_DEBUG_STDERR === "1") return void 0;
|
|
60
|
-
if (process.env.BOOK_DEBUG_FILE) return process.env.BOOK_DEBUG_FILE;
|
|
61
|
-
if (process.stderr.isTTY) return defaultDebugLogPath();
|
|
62
41
|
return void 0;
|
|
63
42
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
function wrappedUpstreamStatus(status, body) {
|
|
44
|
+
return classifyHttpStatus(status).retryable ? quotedUpstreamStatus(body) : void 0;
|
|
45
|
+
}
|
|
46
|
+
var CONTEXT_OVERFLOW_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
47
|
+
"context_length_exceeded",
|
|
48
|
+
"request_too_large",
|
|
49
|
+
// llama.cpp's server.
|
|
50
|
+
"exceed_context_size_error"
|
|
51
|
+
]);
|
|
52
|
+
function errorBodyParts(body) {
|
|
53
|
+
let parsedJson;
|
|
70
54
|
try {
|
|
71
|
-
|
|
72
|
-
if (!entry.isFile() || !entry.name.startsWith(prefix)) continue;
|
|
73
|
-
const path = join(directory, entry.name);
|
|
74
|
-
if (statSync(path).mtimeMs >= cutoff) continue;
|
|
75
|
-
unlinkSync(path);
|
|
76
|
-
debugLogSizes.delete(path);
|
|
77
|
-
removed++;
|
|
78
|
-
}
|
|
55
|
+
parsedJson = JSON.parse(body);
|
|
79
56
|
} catch {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
function writeDebugLine(line) {
|
|
84
|
-
const target = debugLogPath ?? (debugLogPath = getDebugLogPath());
|
|
85
|
-
if (!target) {
|
|
86
|
-
process.stderr.write(line);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
91
|
-
const lineBytes = Buffer.byteLength(line);
|
|
92
|
-
rotateDebugLog(target, lineBytes);
|
|
93
|
-
appendFileSync(target, line, "utf8");
|
|
94
|
-
debugLogSizes.set(target, (debugLogSizes.get(target) ?? 0) + lineBytes);
|
|
95
|
-
} catch {
|
|
96
|
-
process.stderr.write(line);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function writeSizeBoundedDebugLine(line, maxBytes) {
|
|
100
|
-
if (Buffer.byteLength(line) <= maxBytes) {
|
|
101
|
-
writeDebugLine(line);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
let chunk = "";
|
|
105
|
-
let chunkBytes = 0;
|
|
106
|
-
for (const character of line) {
|
|
107
|
-
const characterBytes = Buffer.byteLength(character);
|
|
108
|
-
if (chunk && chunkBytes + characterBytes > maxBytes) {
|
|
109
|
-
writeDebugLine(chunk);
|
|
110
|
-
chunk = "";
|
|
111
|
-
chunkBytes = 0;
|
|
112
|
-
}
|
|
113
|
-
chunk += character;
|
|
114
|
-
chunkBytes += characterBytes;
|
|
115
|
-
}
|
|
116
|
-
if (chunk) writeDebugLine(chunk);
|
|
117
|
-
}
|
|
118
|
-
var pendingRenderDebug = [];
|
|
119
|
-
var renderDebugFlush = null;
|
|
120
|
-
function writeRenderDebugLine(line) {
|
|
121
|
-
pendingRenderDebug.push(line);
|
|
122
|
-
if (renderDebugFlush !== null) return;
|
|
123
|
-
renderDebugFlush = setImmediate(() => {
|
|
124
|
-
renderDebugFlush = null;
|
|
125
|
-
const batch = pendingRenderDebug;
|
|
126
|
-
pendingRenderDebug = [];
|
|
127
|
-
const maxBytes = debugLogMaxBytes();
|
|
128
|
-
let chunk = "";
|
|
129
|
-
let chunkBytes = 0;
|
|
130
|
-
for (const pendingLine of batch) {
|
|
131
|
-
const lineBytes = Buffer.byteLength(pendingLine);
|
|
132
|
-
if (chunk && chunkBytes + lineBytes > maxBytes) {
|
|
133
|
-
writeDebugLine(chunk);
|
|
134
|
-
chunk = "";
|
|
135
|
-
chunkBytes = 0;
|
|
136
|
-
}
|
|
137
|
-
if (lineBytes > maxBytes) {
|
|
138
|
-
writeSizeBoundedDebugLine(pendingLine, maxBytes);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
chunk += pendingLine;
|
|
142
|
-
chunkBytes += lineBytes;
|
|
57
|
+
const reachedCap = new TextEncoder().encode(body).byteLength >= ERROR_BODY_MAX_BYTES - 3;
|
|
58
|
+
if (!reachedCap || !/^\s*(?:\{|\[\s*\{)/.test(body)) {
|
|
59
|
+
return { message: body, names: [], parsed: false };
|
|
143
60
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
var namespaceCounters = /* @__PURE__ */ new Map();
|
|
148
|
-
function nextEventId(namespace) {
|
|
149
|
-
const n = (namespaceCounters.get(namespace) ?? 0) + 1;
|
|
150
|
-
namespaceCounters.set(namespace, n);
|
|
151
|
-
return n;
|
|
152
|
-
}
|
|
153
|
-
function isUiDebugEnabled() {
|
|
154
|
-
return UI_DEBUG_ENABLED;
|
|
155
|
-
}
|
|
156
|
-
function isRenderDebugEnabled() {
|
|
157
|
-
return RENDER_DEBUG_ENABLED;
|
|
158
|
-
}
|
|
159
|
-
function timestamp() {
|
|
160
|
-
const now = /* @__PURE__ */ new Date();
|
|
161
|
-
const h = String(now.getHours()).padStart(2, "0");
|
|
162
|
-
const m = String(now.getMinutes()).padStart(2, "0");
|
|
163
|
-
const s = String(now.getSeconds()).padStart(2, "0");
|
|
164
|
-
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
165
|
-
return `${h}:${m}:${s}.${ms}`;
|
|
166
|
-
}
|
|
167
|
-
function truncate(value, max = 80) {
|
|
168
|
-
const single = value.replace(/\s+/g, " ").trim();
|
|
169
|
-
if (single.length <= max) return single;
|
|
170
|
-
return `${single.slice(0, max)}\u2026`;
|
|
171
|
-
}
|
|
172
|
-
function formatValue(a) {
|
|
173
|
-
if (typeof a === "string") return a;
|
|
174
|
-
if (a instanceof Error) return a.message;
|
|
175
|
-
try {
|
|
176
|
-
return JSON.stringify(a);
|
|
177
|
-
} catch {
|
|
178
|
-
return String(a);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function formatMessage(...args) {
|
|
182
|
-
return args.map(formatValue).join(" ");
|
|
183
|
-
}
|
|
184
|
-
function formatMeta(meta) {
|
|
185
|
-
const parts = [];
|
|
186
|
-
for (const [key, value] of Object.entries(meta)) {
|
|
187
|
-
let repr;
|
|
188
|
-
if (typeof value === "string") {
|
|
189
|
-
repr = truncate(value, 120);
|
|
190
|
-
} else if (value === null) {
|
|
191
|
-
repr = "null";
|
|
192
|
-
} else if (value === void 0) {
|
|
193
|
-
repr = "undefined";
|
|
194
|
-
} else {
|
|
61
|
+
const readString = (key) => {
|
|
62
|
+
const match = body.match(new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`));
|
|
63
|
+
if (!match) return "";
|
|
195
64
|
try {
|
|
196
|
-
|
|
65
|
+
return JSON.parse(`"${match[1]}"`);
|
|
197
66
|
} catch {
|
|
198
|
-
|
|
67
|
+
return "";
|
|
199
68
|
}
|
|
200
|
-
}
|
|
201
|
-
|
|
69
|
+
};
|
|
70
|
+
const message2 = readString("message") || readString("error") || readString("detail");
|
|
71
|
+
const names = [body.match(/"code"\s*:\s*"([^"]*)"/), body.match(/"type"\s*:\s*"([^"]*)"/)].filter((name) => name !== null).map((name) => name[1]);
|
|
72
|
+
return { message: message2, names, parsed: true };
|
|
202
73
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return `[${timestamp()}] [${namespace}] [${level}]`;
|
|
215
|
-
}
|
|
216
|
-
function buildLogger(namespace, write = writeDebugLine) {
|
|
74
|
+
const root = Array.isArray(parsedJson) ? parsedJson[0] : parsedJson;
|
|
75
|
+
if (typeof root !== "object" || root === null) {
|
|
76
|
+
return { message: body, names: [], parsed: false };
|
|
77
|
+
}
|
|
78
|
+
const error = root.error;
|
|
79
|
+
if (typeof error === "string") return { message: error, names: [], parsed: true };
|
|
80
|
+
const source = typeof error === "object" && error !== null ? error : root;
|
|
81
|
+
const fallback = root.detail;
|
|
82
|
+
const message = typeof source.message === "string" ? source.message : typeof fallback === "string" ? fallback : "";
|
|
83
|
+
const metadata = source.metadata;
|
|
84
|
+
const raw = metadata?.raw;
|
|
217
85
|
return {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
info: (...args) => {
|
|
223
|
-
write(`${makePrefix(namespace, "INFO")} ${formatMessage(...args)}
|
|
224
|
-
`);
|
|
225
|
-
},
|
|
226
|
-
warn: (...args) => {
|
|
227
|
-
write(`${makePrefix(namespace, "WARN")} ${formatMessage(...args)}
|
|
228
|
-
`);
|
|
229
|
-
},
|
|
230
|
-
event: (event, meta) => {
|
|
231
|
-
const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : "";
|
|
232
|
-
write(`${makePrefix(namespace, "DEBUG")} ${event}${suffix}
|
|
233
|
-
`);
|
|
234
|
-
}
|
|
86
|
+
message,
|
|
87
|
+
names: [source.code, source.type].filter((name) => typeof name === "string"),
|
|
88
|
+
raw: typeof raw === "string" ? raw : typeof raw === "object" && raw !== null ? JSON.stringify(raw) : void 0,
|
|
89
|
+
parsed: true
|
|
235
90
|
};
|
|
236
91
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
function createDebugLoggerWithCounter(namespace) {
|
|
242
|
-
if (!DEBUG_ENABLED) return NOOP_LOGGER;
|
|
243
|
-
const wrap = (level) => (...args) => {
|
|
244
|
-
const id = nextEventId(namespace);
|
|
245
|
-
writeDebugLine(
|
|
246
|
-
`[${timestamp()}] [${namespace}] [${level.toUpperCase()}#${id}] ${formatMessage(...args)}
|
|
247
|
-
`
|
|
248
|
-
);
|
|
249
|
-
};
|
|
250
|
-
return {
|
|
251
|
-
debug: wrap("debug"),
|
|
252
|
-
info: wrap("info"),
|
|
253
|
-
warn: wrap("warn"),
|
|
254
|
-
event: (event, meta) => {
|
|
255
|
-
const id = nextEventId(namespace);
|
|
256
|
-
const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : "";
|
|
257
|
-
writeDebugLine(`[${timestamp()}] [${namespace}] [DEBUG#${id}] ${event}${suffix}
|
|
258
|
-
`);
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function createUiDebugLogger(namespace) {
|
|
263
|
-
if (!UI_DEBUG_ENABLED) return NOOP_LOGGER;
|
|
264
|
-
return buildLogger(namespace);
|
|
265
|
-
}
|
|
266
|
-
function createRenderDebugLogger(namespace) {
|
|
267
|
-
if (!RENDER_DEBUG_ENABLED) return NOOP_LOGGER;
|
|
268
|
-
return buildLogger(namespace, writeRenderDebugLine);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// src/provider/reliability.ts
|
|
272
|
-
function classifyHttpStatus(status) {
|
|
273
|
-
if (status === 413) return { code: "context_overflow", retryable: false };
|
|
274
|
-
if (status === 429) return { code: "rate_limited", retryable: true };
|
|
275
|
-
if (status === 529) return { code: "overloaded", retryable: true };
|
|
276
|
-
if (status >= 500 && status < 600) return { code: "server_error", retryable: true };
|
|
277
|
-
if (status === 408) return { code: "timeout", retryable: true };
|
|
278
|
-
if (status === 401 || status === 403) return { code: "auth", retryable: false };
|
|
279
|
-
if (status === 402) return { code: "quota", retryable: false };
|
|
280
|
-
if (status === 400) return { code: "bad_request", retryable: false };
|
|
281
|
-
if (status === 404) return { code: "not_found", retryable: false };
|
|
282
|
-
return { code: "unknown", retryable: false };
|
|
92
|
+
function statesContextOverflow(body) {
|
|
93
|
+
const { message, names, raw } = errorBodyParts(body);
|
|
94
|
+
return names.some((name) => CONTEXT_OVERFLOW_ERROR_NAMES.has(name.toLowerCase())) || isContextOverflowError(message) || raw !== void 0 && statesContextOverflow(raw);
|
|
283
95
|
}
|
|
284
96
|
function classifyApiError(status, body) {
|
|
285
|
-
const code = classifyHttpStatus(status).code;
|
|
286
|
-
return code === "bad_request" &&
|
|
97
|
+
const code = classifyHttpStatus(wrappedUpstreamStatus(status, body) ?? status).code;
|
|
98
|
+
return code === "bad_request" && statesContextOverflow(body) ? "context_overflow" : code;
|
|
287
99
|
}
|
|
288
100
|
function classifyProviderError(error) {
|
|
289
101
|
if (error instanceof DOMException && error.name === "TimeoutError") return "timeout";
|
|
@@ -300,7 +112,7 @@ function formatApiError(status, body) {
|
|
|
300
112
|
case "context_overflow":
|
|
301
113
|
return `${base} ${detail || "Input exceeds the model context window."} Reduce the conversation or tool output and try again.`;
|
|
302
114
|
case "rate_limited":
|
|
303
|
-
return `${base} ${detail}. This may be a temporary capacity issue. Try again in a moment.`;
|
|
115
|
+
return isOversizedForRateLimit(detail) ? `${base} ${detail}. The request is larger than the rate limit allows at once, so it has to shrink; waiting will not help.` : `${base} ${detail}. This may be a temporary capacity issue. Try again in a moment.`;
|
|
304
116
|
case "overloaded":
|
|
305
117
|
return `${base} Repeated 529 Overloaded errors. The API is at capacity \u2014 this is usually temporary. Try again in a moment.`;
|
|
306
118
|
case "server_error":
|
|
@@ -317,10 +129,80 @@ function formatApiError(status, body) {
|
|
|
317
129
|
}
|
|
318
130
|
function isContextOverflowError(error) {
|
|
319
131
|
const normalized = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
320
|
-
return /\b(?:api
|
|
132
|
+
return /\b(?:api\s+error|http|(?:error|status)(?:\s+code)?)\s*:?\s*413\b/.test(normalized) || normalized.includes("request entity too large") || normalized.includes("payload too large") || normalized.includes("request too large") || normalized.includes("context_length_exceeded") || normalized.includes("maximum context length") || normalized.includes("context window") || normalized.includes("prompt too long") || /prompt\s+is\s+too\s+long/.test(normalized) || /input\s+(?:is\s+)?too\s+long/.test(normalized) || normalized.includes("too many tokens") || // Gemini names the count: `The input token count (1196266) exceeds the maximum …`.
|
|
133
|
+
/input\s+(?:token\s+count\s+)?(?:\(\d+\)\s+)?exceeds?.*(?:context|limit|maximum)/.test(
|
|
134
|
+
normalized
|
|
135
|
+
) || /input\s+exceeds?.*(?:context\s+window|context\s+length|token\s+limit)/.test(normalized) || /maximum\s+context|context\s+(?:length|window|size).*(?:exceed|overflow|too\s+(?:long|large)|maximum)/.test(
|
|
321
136
|
normalized
|
|
322
137
|
) || /too\s+many\s+(?:input\s+)?tokens|request\s+too\s+large.*token/.test(normalized);
|
|
323
138
|
}
|
|
139
|
+
function isOversizedForRateLimit(text) {
|
|
140
|
+
if (!/\brequest too large\b/i.test(text)) return false;
|
|
141
|
+
const limit = text.match(/\blimit:?\s*(\d+)/i);
|
|
142
|
+
const requested = text.match(/\brequested:?\s*(\d+)/i);
|
|
143
|
+
return !limit || !requested || Number(requested[1]) > Number(limit[1]);
|
|
144
|
+
}
|
|
145
|
+
function statedRateLimit(text) {
|
|
146
|
+
if (!isOversizedForRateLimit(text)) return void 0;
|
|
147
|
+
const limit = text.match(/\blimit:?\s*(\d+)/i);
|
|
148
|
+
return limit ? Number(limit[1]) : void 0;
|
|
149
|
+
}
|
|
150
|
+
var ERROR_ENVELOPE_MAX_CHARS = 2e3;
|
|
151
|
+
function isZeroUsage(usage) {
|
|
152
|
+
return usage != null && usage.promptTokens === 0 && usage.completionTokens === 0 && (usage.cacheReadInputTokens ?? 0) === 0 && (usage.cacheCreationInputTokens ?? 0) === 0;
|
|
153
|
+
}
|
|
154
|
+
function isErrorEnvelopeShape(text, usage) {
|
|
155
|
+
if (isZeroUsage(usage)) return /^\s*\[Error\]/i.test(text);
|
|
156
|
+
const trimmed = text.trim();
|
|
157
|
+
return trimmed.length <= ERROR_ENVELOPE_MAX_CHARS && /^\[Error\]/i.test(trimmed) && !/[\r\n]/.test(trimmed);
|
|
158
|
+
}
|
|
159
|
+
function isUpstreamErrorEnvelope(text, usage) {
|
|
160
|
+
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);
|
|
162
|
+
}
|
|
163
|
+
var ERROR_BODY_READ_TIMEOUT_MS = 5e3;
|
|
164
|
+
var ERROR_BODY_MAX_BYTES = 65536;
|
|
165
|
+
async function readErrorBody(response, signal) {
|
|
166
|
+
if (signal?.aborted || !response.body) return "";
|
|
167
|
+
let reader;
|
|
168
|
+
try {
|
|
169
|
+
reader = response.body.getReader();
|
|
170
|
+
} catch {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
const decoder = new TextDecoder();
|
|
174
|
+
let text = "";
|
|
175
|
+
let received = 0;
|
|
176
|
+
let finished = false;
|
|
177
|
+
const stop = () => {
|
|
178
|
+
reader.cancel().catch(() => {
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
const timer = setTimeout(stop, ERROR_BODY_READ_TIMEOUT_MS);
|
|
182
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
183
|
+
try {
|
|
184
|
+
while (received < ERROR_BODY_MAX_BYTES) {
|
|
185
|
+
const chunk = await reader.read();
|
|
186
|
+
if (chunk.done) {
|
|
187
|
+
finished = true;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
const bytes = chunk.value.subarray(0, ERROR_BODY_MAX_BYTES - received);
|
|
191
|
+
received += bytes.byteLength;
|
|
192
|
+
text += decoder.decode(bytes, { stream: true });
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
} finally {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
signal?.removeEventListener("abort", stop);
|
|
198
|
+
if (!finished) stop();
|
|
199
|
+
try {
|
|
200
|
+
reader.releaseLock();
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return text + decoder.decode();
|
|
205
|
+
}
|
|
324
206
|
async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock = systemClock) {
|
|
325
207
|
const startMs = clock.monotonicNowMs();
|
|
326
208
|
const maxAttempts = retry.watchdog ? Number.MAX_SAFE_INTEGER : retry.maxAttempts;
|
|
@@ -341,20 +223,48 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
341
223
|
delayMs: delay2,
|
|
342
224
|
error: lastError
|
|
343
225
|
});
|
|
344
|
-
onRetry?.(attempt + 1, maxAttempts
|
|
226
|
+
onRetry?.(attempt + 1, maxAttempts === Number.MAX_SAFE_INTEGER ? -1 : maxAttempts, delay2);
|
|
345
227
|
await sleep(delay2, signal);
|
|
346
228
|
continue;
|
|
347
229
|
}
|
|
348
230
|
const classification = classifyHttpStatus(response.status);
|
|
349
231
|
if (!classification.retryable) return response;
|
|
350
232
|
lastError = `API error ${response.status}`;
|
|
233
|
+
const bodyText = await readErrorBody(response, signal);
|
|
234
|
+
const quoted = quotedUpstreamStatus(bodyText);
|
|
235
|
+
if (quoted !== void 0 && !classifyHttpStatus(quoted).retryable) {
|
|
236
|
+
logger?.warn("upstream error quoted in retryable status; not retrying", {
|
|
237
|
+
status: response.status,
|
|
238
|
+
upstreamStatus: quoted
|
|
239
|
+
});
|
|
240
|
+
return new Response(bodyText, {
|
|
241
|
+
status: response.status,
|
|
242
|
+
statusText: response.statusText,
|
|
243
|
+
headers: response.headers
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if ((quoted ?? response.status) === 429 && isOversizedForRateLimit(formatApiError(response.status, bodyText))) {
|
|
247
|
+
logger?.warn("rate limit on a request too large to ever fit; not retrying", {
|
|
248
|
+
status: response.status
|
|
249
|
+
});
|
|
250
|
+
return new Response(bodyText, {
|
|
251
|
+
status: response.status,
|
|
252
|
+
statusText: response.statusText,
|
|
253
|
+
headers: response.headers
|
|
254
|
+
});
|
|
255
|
+
}
|
|
351
256
|
const watchdogRetry = retry.watchdog && (response.status === 429 || response.status === 529);
|
|
352
257
|
const effectiveMax = watchdogRetry ? Number.MAX_SAFE_INTEGER : maxAttempts;
|
|
353
|
-
if (attempt >= effectiveMax || budgetExhausted(clock, startMs, retry.totalBudgetMs))
|
|
354
|
-
return
|
|
258
|
+
if (attempt >= effectiveMax || budgetExhausted(clock, startMs, retry.totalBudgetMs)) {
|
|
259
|
+
return new Response(bodyText, {
|
|
260
|
+
status: response.status,
|
|
261
|
+
statusText: response.statusText,
|
|
262
|
+
headers: response.headers
|
|
263
|
+
});
|
|
264
|
+
}
|
|
355
265
|
const delay = boundedDelay(
|
|
356
266
|
clock,
|
|
357
|
-
backoffMs(attempt, retry, response.headers.get("retry-after")),
|
|
267
|
+
backoffMs(attempt, retry, response.headers.get("retry-after"), bodyText),
|
|
358
268
|
startMs,
|
|
359
269
|
retry.totalBudgetMs
|
|
360
270
|
);
|
|
@@ -363,11 +273,15 @@ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock =
|
|
|
363
273
|
status: response.status,
|
|
364
274
|
delayMs: delay
|
|
365
275
|
});
|
|
366
|
-
onRetry?.(attempt + 1,
|
|
276
|
+
onRetry?.(attempt + 1, effectiveMax === Number.MAX_SAFE_INTEGER ? -1 : effectiveMax, delay);
|
|
367
277
|
try {
|
|
368
278
|
await sleep(delay, signal);
|
|
369
279
|
} catch {
|
|
370
|
-
return
|
|
280
|
+
return new Response(bodyText, {
|
|
281
|
+
status: response.status,
|
|
282
|
+
statusText: response.statusText,
|
|
283
|
+
headers: response.headers
|
|
284
|
+
});
|
|
371
285
|
}
|
|
372
286
|
try {
|
|
373
287
|
await response.body?.cancel();
|
|
@@ -413,11 +327,8 @@ function abortError(signal) {
|
|
|
413
327
|
return signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
414
328
|
}
|
|
415
329
|
function safeErrorDetail(body) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (typeof parsed.error?.message === "string") return parsed.error.message.slice(0, 2e3);
|
|
419
|
-
} catch {
|
|
420
|
-
}
|
|
330
|
+
const { message, parsed } = errorBodyParts(body);
|
|
331
|
+
if (parsed && message) return message.slice(0, 2e3);
|
|
421
332
|
return body.slice(0, 200);
|
|
422
333
|
}
|
|
423
334
|
function sleep(ms, signal) {
|
|
@@ -437,8 +348,8 @@ function sleep(ms, signal) {
|
|
|
437
348
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
438
349
|
});
|
|
439
350
|
}
|
|
440
|
-
function backoffMs(attempt, retry, retryAfter) {
|
|
441
|
-
const retryAfterMs = parseRetryAfter(retryAfter);
|
|
351
|
+
function backoffMs(attempt, retry, retryAfter, body) {
|
|
352
|
+
const retryAfterMs = parseRetryAfter(retryAfter) ?? parseCooldownReset(body);
|
|
442
353
|
if (retryAfterMs !== void 0) return Math.min(retryAfterMs, retry.maxDelayMs);
|
|
443
354
|
const exponential = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);
|
|
444
355
|
return Math.round(exponential * (0.5 + Math.random()));
|
|
@@ -447,9 +358,40 @@ function parseRetryAfter(value) {
|
|
|
447
358
|
if (!value) return void 0;
|
|
448
359
|
const seconds = Number(value);
|
|
449
360
|
if (Number.isFinite(seconds) && seconds > 0) return seconds * 1e3;
|
|
450
|
-
const
|
|
451
|
-
if (!Number.isFinite(
|
|
452
|
-
return Math.max(0,
|
|
361
|
+
const timestamp = Date.parse(value);
|
|
362
|
+
if (!Number.isFinite(timestamp)) return void 0;
|
|
363
|
+
return Math.max(0, timestamp - Date.now());
|
|
364
|
+
}
|
|
365
|
+
var COOLDOWN_UNIT_MS = {
|
|
366
|
+
ms: 1,
|
|
367
|
+
s: 1e3,
|
|
368
|
+
sec: 1e3,
|
|
369
|
+
secs: 1e3,
|
|
370
|
+
second: 1e3,
|
|
371
|
+
seconds: 1e3,
|
|
372
|
+
m: 6e4,
|
|
373
|
+
min: 6e4,
|
|
374
|
+
mins: 6e4,
|
|
375
|
+
minute: 6e4,
|
|
376
|
+
minutes: 6e4,
|
|
377
|
+
h: 36e5,
|
|
378
|
+
hr: 36e5,
|
|
379
|
+
hrs: 36e5,
|
|
380
|
+
hour: 36e5,
|
|
381
|
+
hours: 36e5
|
|
382
|
+
};
|
|
383
|
+
var COOLDOWN_UNIT = /(\d+)\s*(ms|sec(?:ond)?s?|min(?:ute)?s?|h(?:ours?|rs?)?|s|m)(?![a-z])/gi;
|
|
384
|
+
function parseCooldownReset(body) {
|
|
385
|
+
if (!body) return void 0;
|
|
386
|
+
const stated = body.match(/\(reset after\s*([^)]+)\)/i);
|
|
387
|
+
if (!stated) return void 0;
|
|
388
|
+
let totalMs = 0;
|
|
389
|
+
let statedUnits = 0;
|
|
390
|
+
for (const unit of stated[1].matchAll(COOLDOWN_UNIT)) {
|
|
391
|
+
totalMs += Number(unit[1]) * COOLDOWN_UNIT_MS[unit[2].toLowerCase()];
|
|
392
|
+
statedUnits++;
|
|
393
|
+
}
|
|
394
|
+
return statedUnits > 0 && totalMs > 0 ? totalMs : void 0;
|
|
453
395
|
}
|
|
454
396
|
function budgetExhausted(clock, startMs, budgetMs) {
|
|
455
397
|
return budgetMs > 0 && clock.monotonicNowMs() - startMs >= budgetMs;
|
|
@@ -462,12 +404,17 @@ function boundedDelay(clock, delayMs, startMs, budgetMs) {
|
|
|
462
404
|
// src/provider/openai-compatible.ts
|
|
463
405
|
var log = createDebugLogger("provider");
|
|
464
406
|
function parseToolArguments(raw) {
|
|
465
|
-
if (!raw.trim()) return {};
|
|
407
|
+
if (!raw.trim()) return { arguments: {} };
|
|
466
408
|
try {
|
|
467
409
|
const parsed = JSON.parse(raw);
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
|
|
410
|
+
return {
|
|
411
|
+
arguments: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}
|
|
412
|
+
};
|
|
413
|
+
} catch (error) {
|
|
414
|
+
return {
|
|
415
|
+
arguments: {},
|
|
416
|
+
unparsedArguments: { raw, error: error instanceof Error ? error.message : String(error) }
|
|
417
|
+
};
|
|
471
418
|
}
|
|
472
419
|
}
|
|
473
420
|
function parseReasoningDelta(delta) {
|
|
@@ -502,6 +449,64 @@ function flattenMessages(messages) {
|
|
|
502
449
|
) : reasoningContent ? ["<reasoning_context>", reasoningContent, "</reasoning_context>", msg.content ?? ""].filter(Boolean).join("\n") : msg.content
|
|
503
450
|
}));
|
|
504
451
|
}
|
|
452
|
+
function parseCompatibleUsage(raw) {
|
|
453
|
+
if (!raw || typeof raw !== "object") return null;
|
|
454
|
+
const usage = raw;
|
|
455
|
+
const details = usage.prompt_tokens_details && typeof usage.prompt_tokens_details === "object" ? usage.prompt_tokens_details : {};
|
|
456
|
+
const tokens = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
457
|
+
const firstCount = (...values) => {
|
|
458
|
+
for (const value of values) {
|
|
459
|
+
const count = tokens(value);
|
|
460
|
+
if (count > 0) return count;
|
|
461
|
+
}
|
|
462
|
+
return 0;
|
|
463
|
+
};
|
|
464
|
+
const prompt = tokens(usage.prompt_tokens);
|
|
465
|
+
const base = {
|
|
466
|
+
promptTokens: prompt,
|
|
467
|
+
completionTokens: tokens(usage.completion_tokens),
|
|
468
|
+
// A provider that omits `total_tokens` would otherwise read as zero context pressure.
|
|
469
|
+
totalTokens: tokens(usage.total_tokens) || prompt + tokens(usage.completion_tokens)
|
|
470
|
+
};
|
|
471
|
+
const topRead = tokens(usage.cache_read_input_tokens);
|
|
472
|
+
const topWrite = tokens(usage.cache_creation_input_tokens);
|
|
473
|
+
const normalisedRead = firstCount(
|
|
474
|
+
details.cached_tokens,
|
|
475
|
+
usage.prompt_cache_hit_tokens,
|
|
476
|
+
usage.cached_tokens
|
|
477
|
+
);
|
|
478
|
+
const normalisedWrite = firstCount(
|
|
479
|
+
details.cache_creation_tokens,
|
|
480
|
+
details.cache_write_tokens,
|
|
481
|
+
details.cache_creation_input_tokens
|
|
482
|
+
);
|
|
483
|
+
const read = normalisedRead || topRead;
|
|
484
|
+
const write = normalisedWrite || topWrite;
|
|
485
|
+
if (read === 0 && write === 0) return base;
|
|
486
|
+
const reportsOpenAiCacheField = [
|
|
487
|
+
details.cached_tokens,
|
|
488
|
+
details.cache_creation_tokens,
|
|
489
|
+
details.cache_write_tokens,
|
|
490
|
+
details.cache_creation_input_tokens,
|
|
491
|
+
usage.prompt_cache_hit_tokens,
|
|
492
|
+
usage.cached_tokens
|
|
493
|
+
].some((value) => typeof value === "number");
|
|
494
|
+
const anthropicPassThrough = !reportsOpenAiCacheField && topRead + topWrite > 0;
|
|
495
|
+
if (!anthropicPassThrough && read + write <= prompt) {
|
|
496
|
+
return {
|
|
497
|
+
...base,
|
|
498
|
+
promptTokens: prompt - read - write,
|
|
499
|
+
cacheReadInputTokens: read,
|
|
500
|
+
cacheCreationInputTokens: write
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
...base,
|
|
505
|
+
cacheReadInputTokens: read,
|
|
506
|
+
cacheCreationInputTokens: write,
|
|
507
|
+
contextTokens: prompt + read + write
|
|
508
|
+
};
|
|
509
|
+
}
|
|
505
510
|
function convertTools(tools) {
|
|
506
511
|
return tools.map((tool) => ({
|
|
507
512
|
type: "function",
|
|
@@ -570,11 +575,12 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
570
575
|
}
|
|
571
576
|
log.debug("response received", { status: response.status, ok: response.ok });
|
|
572
577
|
if (!response.ok) {
|
|
573
|
-
const errorText = await response
|
|
578
|
+
const errorText = await readErrorBody(response, signal);
|
|
574
579
|
yield {
|
|
575
580
|
type: "error",
|
|
576
581
|
error: formatApiError(response.status, errorText),
|
|
577
|
-
errorCode: classifyApiError(response.status, errorText)
|
|
582
|
+
errorCode: classifyApiError(response.status, errorText),
|
|
583
|
+
upstreamStatus: wrappedUpstreamStatus(response.status, errorText)
|
|
578
584
|
};
|
|
579
585
|
return;
|
|
580
586
|
}
|
|
@@ -593,12 +599,24 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
593
599
|
const emitToolCalls = function* () {
|
|
594
600
|
for (const part of [...toolCallParts].sort((a, b) => a.index - b.index)) {
|
|
595
601
|
if (!part.id && !part.name) continue;
|
|
602
|
+
const { arguments: arguments_, unparsedArguments } = parseToolArguments(part.arguments);
|
|
603
|
+
if (unparsedArguments) {
|
|
604
|
+
log.warn("tool call arguments are not valid JSON", {
|
|
605
|
+
index: part.index,
|
|
606
|
+
id: part.id,
|
|
607
|
+
name: escapeInvisibleCharacters(part.name),
|
|
608
|
+
fragments: part.fragments,
|
|
609
|
+
length: part.arguments.length,
|
|
610
|
+
head: escapeInvisibleCharacters(part.arguments.slice(0, 120))
|
|
611
|
+
});
|
|
612
|
+
}
|
|
596
613
|
yield {
|
|
597
614
|
type: "tool_call",
|
|
598
615
|
toolCall: {
|
|
599
616
|
id: part.id || `tool-${part.index}`,
|
|
600
617
|
name: part.name,
|
|
601
|
-
arguments:
|
|
618
|
+
arguments: arguments_,
|
|
619
|
+
...unparsedArguments ? { unparsedArguments } : {}
|
|
602
620
|
}
|
|
603
621
|
};
|
|
604
622
|
}
|
|
@@ -609,7 +627,9 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
609
627
|
terminal,
|
|
610
628
|
promptTokens: currentUsage?.promptTokens ?? 0,
|
|
611
629
|
completionTokens: currentUsage?.completionTokens ?? 0,
|
|
612
|
-
totalTokens: currentUsage?.totalTokens ?? 0
|
|
630
|
+
totalTokens: currentUsage?.totalTokens ?? 0,
|
|
631
|
+
cacheReadInputTokens: currentUsage?.cacheReadInputTokens ?? 0,
|
|
632
|
+
cacheCreationInputTokens: currentUsage?.cacheCreationInputTokens ?? 0
|
|
613
633
|
});
|
|
614
634
|
yield {
|
|
615
635
|
type: "done",
|
|
@@ -631,13 +651,8 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
631
651
|
const parsed = JSON.parse(data);
|
|
632
652
|
if (typeof parsed.model === "string") responseModel = parsed.model;
|
|
633
653
|
if (typeof parsed.id === "string") responseId = parsed.id;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
promptTokens: parsed.usage.prompt_tokens ?? 0,
|
|
637
|
-
completionTokens: parsed.usage.completion_tokens ?? 0,
|
|
638
|
-
totalTokens: parsed.usage.total_tokens ?? 0
|
|
639
|
-
};
|
|
640
|
-
}
|
|
654
|
+
const usage = parseCompatibleUsage(parsed.usage);
|
|
655
|
+
if (usage) currentUsage = usage;
|
|
641
656
|
const choice = parsed.choices?.[0];
|
|
642
657
|
if (!choice) return false;
|
|
643
658
|
const finishReason = choice.finish_reason;
|
|
@@ -646,15 +661,19 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
646
661
|
if (!delta) return false;
|
|
647
662
|
const reasoning = parseReasoningDelta(delta);
|
|
648
663
|
if (reasoning) {
|
|
664
|
+
if (retry.thinkingStallTimeoutMs) {
|
|
665
|
+
stallTimeoutMs = Math.max(stallTimeoutMs, retry.thinkingStallTimeoutMs);
|
|
666
|
+
}
|
|
649
667
|
log.debug("stream reasoning", { len: reasoning.length });
|
|
650
668
|
yield { type: "reasoning", reasoning };
|
|
651
669
|
}
|
|
652
670
|
if (delta.content) {
|
|
671
|
+
stallTimeoutMs = startingStallTimeoutMs;
|
|
653
672
|
log.debug("stream text", { len: delta.content.length });
|
|
654
673
|
yield { type: "text", content: delta.content };
|
|
655
674
|
}
|
|
656
675
|
if (delta.tool_calls) {
|
|
657
|
-
|
|
676
|
+
stallTimeoutMs = startingStallTimeoutMs;
|
|
658
677
|
for (const tc of delta.tool_calls) {
|
|
659
678
|
const explicitIndex = Number.isInteger(tc.index) ? tc.index : void 0;
|
|
660
679
|
let index = explicitIndex;
|
|
@@ -666,19 +685,32 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
666
685
|
}
|
|
667
686
|
let part = toolCallParts.find((p) => p.index === index);
|
|
668
687
|
if (!part) {
|
|
669
|
-
part = { index, id: "", name: "", arguments: "" };
|
|
688
|
+
part = { index, id: "", name: "", arguments: "", fragments: 0 };
|
|
670
689
|
toolCallParts.push(part);
|
|
671
690
|
}
|
|
672
691
|
if (tc.id) part.id = tc.id;
|
|
673
692
|
if (tc.function?.name) part.name = tc.function.name;
|
|
674
|
-
if (tc.function?.arguments)
|
|
693
|
+
if (tc.function?.arguments) {
|
|
694
|
+
if (part.fragments === 0 && isDebugEnabled()) {
|
|
695
|
+
log.debug("stream tool_call delta", {
|
|
696
|
+
index,
|
|
697
|
+
id: tc.id,
|
|
698
|
+
name: escapeInvisibleCharacters(tc.function.name ?? ""),
|
|
699
|
+
argumentsLength: tc.function.arguments.length,
|
|
700
|
+
argumentsHead: escapeInvisibleCharacters(tc.function.arguments.slice(0, 120))
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
part.arguments += tc.function.arguments;
|
|
704
|
+
part.fragments++;
|
|
705
|
+
}
|
|
675
706
|
}
|
|
676
707
|
}
|
|
677
708
|
} catch {
|
|
678
709
|
}
|
|
679
710
|
return false;
|
|
680
711
|
};
|
|
681
|
-
const
|
|
712
|
+
const startingStallTimeoutMs = reasoningEnabled && retry.thinkingStallTimeoutMs ? Math.max(retry.streamStallTimeoutMs, retry.thinkingStallTimeoutMs) : retry.streamStallTimeoutMs;
|
|
713
|
+
let stallTimeoutMs = startingStallTimeoutMs;
|
|
682
714
|
try {
|
|
683
715
|
while (true) {
|
|
684
716
|
const result = await readStreamChunk(reader, signal, stallTimeoutMs);
|
|
@@ -743,23 +775,20 @@ async function* chatCompletionStream(config, messages, tools, options) {
|
|
|
743
775
|
}
|
|
744
776
|
|
|
745
777
|
export {
|
|
746
|
-
|
|
747
|
-
positiveInteger,
|
|
748
|
-
getDebugLogPath,
|
|
749
|
-
cleanupDebugLogs,
|
|
750
|
-
isUiDebugEnabled,
|
|
751
|
-
isRenderDebugEnabled,
|
|
752
|
-
createDebugLogger,
|
|
753
|
-
createDebugLoggerWithCounter,
|
|
754
|
-
createUiDebugLogger,
|
|
755
|
-
createRenderDebugLogger,
|
|
778
|
+
wrappedUpstreamStatus,
|
|
756
779
|
classifyApiError,
|
|
757
780
|
classifyProviderError,
|
|
758
781
|
formatApiError,
|
|
759
782
|
isContextOverflowError,
|
|
783
|
+
isOversizedForRateLimit,
|
|
784
|
+
statedRateLimit,
|
|
785
|
+
isErrorEnvelopeShape,
|
|
786
|
+
isUpstreamErrorEnvelope,
|
|
787
|
+
readErrorBody,
|
|
760
788
|
fetchWithRetry,
|
|
761
789
|
readStreamChunk,
|
|
790
|
+
parseCompatibleUsage,
|
|
762
791
|
convertTools,
|
|
763
792
|
chatCompletionStream
|
|
764
793
|
};
|
|
765
|
-
//# sourceMappingURL=chunk-
|
|
794
|
+
//# sourceMappingURL=chunk-AQEQX4FF.js.map
|