@letrquan/book 0.2.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +2504 -0
  2. package/LICENSE +123 -0
  3. package/README.md +1383 -0
  4. package/dist/app-MOXXWEOY.js +18945 -0
  5. package/dist/app-MOXXWEOY.js.map +1 -0
  6. package/dist/chunk-24AXE6SP.js +12 -0
  7. package/dist/chunk-24AXE6SP.js.map +1 -0
  8. package/dist/chunk-3U5IBM24.js +213 -0
  9. package/dist/chunk-3U5IBM24.js.map +1 -0
  10. package/dist/chunk-574C6MQR.js +743 -0
  11. package/dist/chunk-574C6MQR.js.map +1 -0
  12. package/dist/chunk-5GDZ22YP.js +449 -0
  13. package/dist/chunk-5GDZ22YP.js.map +1 -0
  14. package/dist/chunk-5KLVH3PY.js +167 -0
  15. package/dist/chunk-5KLVH3PY.js.map +1 -0
  16. package/dist/chunk-5RDRAO4B.js +92 -0
  17. package/dist/chunk-5RDRAO4B.js.map +1 -0
  18. package/dist/chunk-7N4J557C.js +10 -0
  19. package/dist/chunk-7N4J557C.js.map +1 -0
  20. package/dist/chunk-A7JLBK2Y.js +3506 -0
  21. package/dist/chunk-A7JLBK2Y.js.map +1 -0
  22. package/dist/chunk-ANSJPYGI.js +84 -0
  23. package/dist/chunk-ANSJPYGI.js.map +1 -0
  24. package/dist/chunk-BTZZUCLI.js +264 -0
  25. package/dist/chunk-BTZZUCLI.js.map +1 -0
  26. package/dist/chunk-C7D4NJ2D.js +217 -0
  27. package/dist/chunk-C7D4NJ2D.js.map +1 -0
  28. package/dist/chunk-CAGOB2N4.js +208 -0
  29. package/dist/chunk-CAGOB2N4.js.map +1 -0
  30. package/dist/chunk-DGMQEI6Y.js +98 -0
  31. package/dist/chunk-DGMQEI6Y.js.map +1 -0
  32. package/dist/chunk-E75IPST5.js +289 -0
  33. package/dist/chunk-E75IPST5.js.map +1 -0
  34. package/dist/chunk-EYGD6X2T.js +180 -0
  35. package/dist/chunk-EYGD6X2T.js.map +1 -0
  36. package/dist/chunk-GTVB4HEM.js +765 -0
  37. package/dist/chunk-GTVB4HEM.js.map +1 -0
  38. package/dist/chunk-J7MIZ5CA.js +1907 -0
  39. package/dist/chunk-J7MIZ5CA.js.map +1 -0
  40. package/dist/chunk-KWHQ2XXL.js +626 -0
  41. package/dist/chunk-KWHQ2XXL.js.map +1 -0
  42. package/dist/chunk-L6LK2QIQ.js +657 -0
  43. package/dist/chunk-L6LK2QIQ.js.map +1 -0
  44. package/dist/chunk-MAQXCUR4.js +1007 -0
  45. package/dist/chunk-MAQXCUR4.js.map +1 -0
  46. package/dist/chunk-S4XL7HOM.js +402 -0
  47. package/dist/chunk-S4XL7HOM.js.map +1 -0
  48. package/dist/chunk-URLQAXTR.js +60 -0
  49. package/dist/chunk-URLQAXTR.js.map +1 -0
  50. package/dist/chunk-V4BOYR52.js +3504 -0
  51. package/dist/chunk-V4BOYR52.js.map +1 -0
  52. package/dist/chunk-VZQXX3WW.js +210 -0
  53. package/dist/chunk-VZQXX3WW.js.map +1 -0
  54. package/dist/chunk-WKBO5R4O.js +34 -0
  55. package/dist/chunk-WKBO5R4O.js.map +1 -0
  56. package/dist/chunk-XIFFTATU.js +15753 -0
  57. package/dist/chunk-XIFFTATU.js.map +1 -0
  58. package/dist/command-approvals-6Y57VCWZ.js +28 -0
  59. package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
  60. package/dist/context-ZC5IFEAX.js +25 -0
  61. package/dist/context-ZC5IFEAX.js.map +1 -0
  62. package/dist/hook-approvals-CTUFKEJT.js +22 -0
  63. package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +2582 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/interactive-assets-CCAAWVKH.js +31 -0
  68. package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
  69. package/dist/job-runner.d.ts +2 -0
  70. package/dist/job-runner.js +235 -0
  71. package/dist/job-runner.js.map +1 -0
  72. package/dist/loader-4IDS2RIE.js +15 -0
  73. package/dist/loader-4IDS2RIE.js.map +1 -0
  74. package/dist/mcp-approvals-WNPH65BK.js +18 -0
  75. package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
  76. package/dist/mcp-config-YIJIES2C.js +16 -0
  77. package/dist/mcp-config-YIJIES2C.js.map +1 -0
  78. package/dist/model-window-store-YFVNXCIP.js +27 -0
  79. package/dist/model-window-store-YFVNXCIP.js.map +1 -0
  80. package/dist/openai-compatible-RHZUBHV6.js +10 -0
  81. package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
  82. package/dist/permission-approvals-76PVREFD.js +14 -0
  83. package/dist/permission-approvals-76PVREFD.js.map +1 -0
  84. package/dist/permissions-CHCKTFQQ.js +28 -0
  85. package/dist/permissions-CHCKTFQQ.js.map +1 -0
  86. package/dist/sandbox-CNVAQDA7.js +23 -0
  87. package/dist/sandbox-CNVAQDA7.js.map +1 -0
  88. package/dist/scrollback-LUKLIBVI.js +136 -0
  89. package/dist/scrollback-LUKLIBVI.js.map +1 -0
  90. package/dist/sdk.d.ts +4574 -0
  91. package/dist/sdk.js +665 -0
  92. package/dist/sdk.js.map +1 -0
  93. package/dist/settings-loader-M2QP5TX2.js +21 -0
  94. package/dist/settings-loader-M2QP5TX2.js.map +1 -0
  95. package/dist/settings-removed-JWWPT4WA.js +19 -0
  96. package/dist/settings-removed-JWWPT4WA.js.map +1 -0
  97. package/dist/shell-selection-V6QAYXPX.js +17 -0
  98. package/dist/shell-selection-V6QAYXPX.js.map +1 -0
  99. package/package.json +143 -0
  100. package/patches/ink+6.8.0.patch +13 -0
  101. package/scripts/apply-ink-patch.mjs +29 -0
  102. package/scripts/ink-patch.mjs +59 -0
  103. package/scripts/verify-ink-patch.mjs +11 -0
@@ -0,0 +1,765 @@
1
+ import {
2
+ systemClock
3
+ } from "./chunk-7N4J557C.js";
4
+
5
+ // src/debug-log.ts
6
+ import {
7
+ appendFileSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ renameSync,
12
+ statSync,
13
+ unlinkSync
14
+ } from "fs";
15
+ import { dirname, join } from "path";
16
+ var DEBUG_ENABLED = process.env.BOOK_DEBUG === "1";
17
+ var UI_DEBUG_ENABLED = process.env.BOOK_DEBUG_UI === "1" || DEBUG_ENABLED;
18
+ var RENDER_DEBUG_ENABLED = process.env.BOOK_DEBUG_RENDER === "1";
19
+ var FLOW_DEBUG_ENABLED = process.env.BOOK_DEBUG_FLOW === "1";
20
+ var debugLogPath;
21
+ var debugLogSizes = /* @__PURE__ */ new Map();
22
+ var DEFAULT_DEBUG_LOG_MAX_BYTES = 5 * 1024 * 1024;
23
+ var MIN_DEBUG_LOG_MAX_BYTES = 4;
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
+ );
35
+ }
36
+ function rotateDebugLog(target, incomingBytes) {
37
+ if (!existsSync(target)) {
38
+ debugLogSizes.set(target, 0);
39
+ return;
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
+ return void 0;
63
+ }
64
+ function cleanupDebugLogs(days = DEFAULT_LOCAL_DATA_RETENTION_DAYS, target = getDebugLogPath(), now = Date.now()) {
65
+ if (!target) return 0;
66
+ const cutoff = now - Math.max(1, days) * 864e5;
67
+ const directory = dirname(target);
68
+ const prefix = `${target.split(/[\\/]/).pop()}.`;
69
+ let removed = 0;
70
+ try {
71
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
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
+ }
79
+ } catch {
80
+ }
81
+ return removed;
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;
143
+ }
144
+ if (chunk) writeDebugLine(chunk);
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 {
195
+ try {
196
+ repr = JSON.stringify(value);
197
+ } catch {
198
+ repr = String(value);
199
+ }
200
+ }
201
+ parts.push(`${key}=${repr}`);
202
+ }
203
+ return parts.join(" ");
204
+ }
205
+ function noop() {
206
+ }
207
+ var NOOP_LOGGER = {
208
+ debug: noop,
209
+ info: noop,
210
+ warn: noop,
211
+ event: noop
212
+ };
213
+ function makePrefix(namespace, level) {
214
+ return `[${timestamp()}] [${namespace}] [${level}]`;
215
+ }
216
+ function buildLogger(namespace, write = writeDebugLine) {
217
+ return {
218
+ debug: (...args) => {
219
+ write(`${makePrefix(namespace, "DEBUG")} ${formatMessage(...args)}
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
+ }
235
+ };
236
+ }
237
+ function createDebugLogger(namespace) {
238
+ if (!DEBUG_ENABLED) return NOOP_LOGGER;
239
+ return buildLogger(namespace);
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 };
283
+ }
284
+ function classifyApiError(status, body) {
285
+ const code = classifyHttpStatus(status).code;
286
+ return code === "bad_request" && isContextOverflowError(body) ? "context_overflow" : code;
287
+ }
288
+ function classifyProviderError(error) {
289
+ if (error instanceof DOMException && error.name === "TimeoutError") return "timeout";
290
+ const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
291
+ if (message.includes("timed out") || message.includes("timeout")) return "timeout";
292
+ if (isContextOverflowError(message)) return "context_overflow";
293
+ return "network";
294
+ }
295
+ function formatApiError(status, body) {
296
+ const code = classifyApiError(status, body);
297
+ const detail = safeErrorDetail(body);
298
+ const base = `API Error: ${status}`;
299
+ switch (code) {
300
+ case "context_overflow":
301
+ return `${base} ${detail || "Input exceeds the model context window."} Reduce the conversation or tool output and try again.`;
302
+ case "rate_limited":
303
+ return `${base} ${detail}. This may be a temporary capacity issue. Try again in a moment.`;
304
+ case "overloaded":
305
+ return `${base} Repeated 529 Overloaded errors. The API is at capacity \u2014 this is usually temporary. Try again in a moment.`;
306
+ case "server_error":
307
+ return `${base} ${detail}. This is a server-side issue, usually temporary \u2014 try again in a moment.`;
308
+ case "timeout":
309
+ return "Request timed out. Check your network connection and try again.";
310
+ case "auth":
311
+ return `${base} ${detail}. Check BOOK_API_KEY, or provider.<id>.apiKey in settings.`;
312
+ case "quota":
313
+ return `${base} ${detail}. Check your usage/credits.`;
314
+ default:
315
+ return `${base} ${detail}`;
316
+ }
317
+ }
318
+ function isContextOverflowError(error) {
319
+ const normalized = (error instanceof Error ? error.message : String(error)).toLowerCase();
320
+ return /\b(?:api error:\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") || /input\s+(?:token\s+count\s+)?exceeds?.*(?:context|limit|maximum)/.test(normalized) || /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
+ normalized
322
+ ) || /too\s+many\s+(?:input\s+)?tokens|request\s+too\s+large.*token/.test(normalized);
323
+ }
324
+ async function fetchWithRetry(url, init, retry, signal, onRetry, logger, clock = systemClock) {
325
+ const startMs = clock.monotonicNowMs();
326
+ const maxAttempts = retry.watchdog ? Number.MAX_SAFE_INTEGER : retry.maxAttempts;
327
+ let lastError = null;
328
+ for (let attempt = 0; attempt <= maxAttempts; attempt++) {
329
+ if (attempt > 0 && signal?.aborted) throw abortError(signal);
330
+ const fetchSignal = requestSignal(signal, retry.requestTimeoutMs);
331
+ let response;
332
+ try {
333
+ response = await fetch(url, { ...init, signal: fetchSignal });
334
+ } catch (error) {
335
+ if (signal?.aborted) throw error;
336
+ lastError = error instanceof Error ? error.message : String(error);
337
+ if (attempt >= maxAttempts || budgetExhausted(clock, startMs, retry.totalBudgetMs)) break;
338
+ const delay2 = boundedDelay(clock, backoffMs(attempt, retry), startMs, retry.totalBudgetMs);
339
+ logger?.warn("retry network error", {
340
+ attempt: attempt + 1,
341
+ delayMs: delay2,
342
+ error: lastError
343
+ });
344
+ onRetry?.(attempt + 1, maxAttempts > 100 ? -1 : maxAttempts, delay2);
345
+ await sleep(delay2, signal);
346
+ continue;
347
+ }
348
+ const classification = classifyHttpStatus(response.status);
349
+ if (!classification.retryable) return response;
350
+ lastError = `API error ${response.status}`;
351
+ const watchdogRetry = retry.watchdog && (response.status === 429 || response.status === 529);
352
+ const effectiveMax = watchdogRetry ? Number.MAX_SAFE_INTEGER : maxAttempts;
353
+ if (attempt >= effectiveMax || budgetExhausted(clock, startMs, retry.totalBudgetMs))
354
+ return response;
355
+ const delay = boundedDelay(
356
+ clock,
357
+ backoffMs(attempt, retry, response.headers.get("retry-after")),
358
+ startMs,
359
+ retry.totalBudgetMs
360
+ );
361
+ logger?.warn("retry http status", {
362
+ attempt: attempt + 1,
363
+ status: response.status,
364
+ delayMs: delay
365
+ });
366
+ onRetry?.(attempt + 1, watchdogRetry ? -1 : maxAttempts, delay);
367
+ try {
368
+ await sleep(delay, signal);
369
+ } catch {
370
+ return response;
371
+ }
372
+ try {
373
+ await response.body?.cancel();
374
+ } catch {
375
+ }
376
+ }
377
+ throw new Error(lastError ?? "request failed after retries");
378
+ }
379
+ async function readStreamChunk(reader, signal, stallTimeoutMs) {
380
+ if (signal?.aborted) return { tag: "abort" };
381
+ let timeout;
382
+ let onAbort;
383
+ try {
384
+ return await Promise.race([
385
+ reader.read().then((result) => ({
386
+ tag: "read",
387
+ done: result.done,
388
+ value: result.value
389
+ })),
390
+ ...stallTimeoutMs > 0 ? [
391
+ new Promise((resolve) => {
392
+ timeout = setTimeout(() => resolve({ tag: "stall" }), stallTimeoutMs);
393
+ })
394
+ ] : [],
395
+ ...signal ? [
396
+ new Promise((resolve) => {
397
+ onAbort = () => resolve({ tag: "abort" });
398
+ signal.addEventListener("abort", onAbort, { once: true });
399
+ })
400
+ ] : []
401
+ ]);
402
+ } finally {
403
+ if (timeout) clearTimeout(timeout);
404
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
405
+ }
406
+ }
407
+ function requestSignal(signal, timeoutMs) {
408
+ if (timeoutMs <= 0) return signal;
409
+ const timeout = AbortSignal.timeout(timeoutMs);
410
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
411
+ }
412
+ function abortError(signal) {
413
+ return signal.reason instanceof Error ? signal.reason : new Error("Aborted");
414
+ }
415
+ function safeErrorDetail(body) {
416
+ try {
417
+ const parsed = JSON.parse(body);
418
+ if (typeof parsed.error?.message === "string") return parsed.error.message.slice(0, 2e3);
419
+ } catch {
420
+ }
421
+ return body.slice(0, 200);
422
+ }
423
+ function sleep(ms, signal) {
424
+ return new Promise((resolve, reject) => {
425
+ if (signal?.aborted) {
426
+ reject(abortError(signal));
427
+ return;
428
+ }
429
+ const timer = setTimeout(() => {
430
+ signal?.removeEventListener("abort", onAbort);
431
+ resolve();
432
+ }, ms);
433
+ const onAbort = () => {
434
+ clearTimeout(timer);
435
+ reject(signal ? abortError(signal) : new Error("Aborted"));
436
+ };
437
+ signal?.addEventListener("abort", onAbort, { once: true });
438
+ });
439
+ }
440
+ function backoffMs(attempt, retry, retryAfter) {
441
+ const retryAfterMs = parseRetryAfter(retryAfter);
442
+ if (retryAfterMs !== void 0) return Math.min(retryAfterMs, retry.maxDelayMs);
443
+ const exponential = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);
444
+ return Math.round(exponential * (0.5 + Math.random()));
445
+ }
446
+ function parseRetryAfter(value) {
447
+ if (!value) return void 0;
448
+ const seconds = Number(value);
449
+ if (Number.isFinite(seconds) && seconds > 0) return seconds * 1e3;
450
+ const timestamp2 = Date.parse(value);
451
+ if (!Number.isFinite(timestamp2)) return void 0;
452
+ return Math.max(0, timestamp2 - Date.now());
453
+ }
454
+ function budgetExhausted(clock, startMs, budgetMs) {
455
+ return budgetMs > 0 && clock.monotonicNowMs() - startMs >= budgetMs;
456
+ }
457
+ function boundedDelay(clock, delayMs, startMs, budgetMs) {
458
+ if (budgetMs <= 0) return delayMs;
459
+ return Math.min(delayMs, Math.max(0, budgetMs - (clock.monotonicNowMs() - startMs)));
460
+ }
461
+
462
+ // src/provider/openai-compatible.ts
463
+ var log = createDebugLogger("provider");
464
+ function parseToolArguments(raw) {
465
+ if (!raw.trim()) return {};
466
+ try {
467
+ const parsed = JSON.parse(raw);
468
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
469
+ } catch {
470
+ return { __raw: raw };
471
+ }
472
+ }
473
+ function parseReasoningDelta(delta) {
474
+ for (const key of ["reasoning_content", "reasoning", "thinking", "analysis"]) {
475
+ const value = delta[key];
476
+ if (typeof value === "string" && value) return value;
477
+ }
478
+ const details = delta.reasoning_details;
479
+ if (!Array.isArray(details)) return void 0;
480
+ const text = details.map((detail) => {
481
+ if (typeof detail === "string") return detail;
482
+ if (!detail || typeof detail !== "object") return "";
483
+ const item = detail;
484
+ for (const key of ["text", "reasoning", "content"]) {
485
+ if (typeof item[key] === "string") return item[key];
486
+ }
487
+ return "";
488
+ }).filter(Boolean).join("");
489
+ return text || void 0;
490
+ }
491
+ function isSystemPromptZones(content) {
492
+ return !!content && typeof content === "object" && "cachedPrefix" in content && "dynamicSuffix" in content;
493
+ }
494
+ function flattenMessages(messages) {
495
+ return messages.map(({ providerMetadata: _providerMetadata, reasoningContent, ...msg }) => ({
496
+ ...msg,
497
+ content: isSystemPromptZones(msg.content) ? [msg.content.cachedPrefix, msg.content.dynamicSuffix].filter(Boolean).join("\n\n") : Array.isArray(msg.content) ? msg.content.map(
498
+ (part) => part.type === "text" ? part : {
499
+ type: "image_url",
500
+ image_url: { url: `data:${part.mediaType};base64,${part.data}` }
501
+ }
502
+ ) : reasoningContent ? ["<reasoning_context>", reasoningContent, "</reasoning_context>", msg.content ?? ""].filter(Boolean).join("\n") : msg.content
503
+ }));
504
+ }
505
+ function convertTools(tools) {
506
+ return tools.map((tool) => ({
507
+ type: "function",
508
+ function: {
509
+ name: tool.name,
510
+ description: tool.description,
511
+ parameters: tool.inputSchema ?? tool.parameters
512
+ }
513
+ }));
514
+ }
515
+ async function* chatCompletionStream(config, messages, tools, options) {
516
+ const retry = config.retry;
517
+ const signal = options?.signal;
518
+ const url = `${config.baseUrl}/chat/completions`;
519
+ const body = {
520
+ model: config.model,
521
+ messages: flattenMessages(messages),
522
+ stream: true,
523
+ // Request token usage in the final SSE chunk so we can track cost.
524
+ stream_options: { include_usage: true }
525
+ };
526
+ if (options?.maxOutputTokens || config.maxTokensExplicit || config.modelInfo?.maxOutputTokens) {
527
+ body.max_tokens = options?.maxOutputTokens ?? config.maxTokens;
528
+ }
529
+ if (config.effortExplicit && config.effort) body.reasoning_effort = config.effort;
530
+ const reasoningEnabled = body.reasoning_effort !== void 0 || typeof config.modelInfo?.effort === "object";
531
+ if (tools.length > 0) {
532
+ body.tools = convertTools(tools);
533
+ }
534
+ log.debug("chatCompletionStream request", {
535
+ model: config.model,
536
+ messageCount: messages.length,
537
+ toolCount: tools.length,
538
+ maxTokens: config.maxTokens
539
+ });
540
+ let response;
541
+ try {
542
+ response = await fetchWithRetry(
543
+ url,
544
+ {
545
+ method: "POST",
546
+ headers: {
547
+ "Content-Type": "application/json",
548
+ Authorization: `Bearer ${config.apiKey}`
549
+ },
550
+ body: JSON.stringify(body)
551
+ },
552
+ retry,
553
+ signal,
554
+ options?.onRetry,
555
+ log
556
+ );
557
+ } catch (e) {
558
+ if (signal?.aborted) return;
559
+ log.warn("fetchWithRetry failed", e instanceof Error ? e.message : String(e));
560
+ yield {
561
+ type: "error",
562
+ error: e instanceof Error ? e.message : String(e),
563
+ errorCode: classifyProviderError(e)
564
+ };
565
+ return;
566
+ }
567
+ if (signal?.aborted) {
568
+ await response.body?.cancel();
569
+ return;
570
+ }
571
+ log.debug("response received", { status: response.status, ok: response.ok });
572
+ if (!response.ok) {
573
+ const errorText = await response.text();
574
+ yield {
575
+ type: "error",
576
+ error: formatApiError(response.status, errorText),
577
+ errorCode: classifyApiError(response.status, errorText)
578
+ };
579
+ return;
580
+ }
581
+ const reader = response.body?.getReader();
582
+ if (!reader) {
583
+ yield { type: "error", error: "No response body", errorCode: "protocol_error" };
584
+ return;
585
+ }
586
+ const decoder = new TextDecoder();
587
+ let buffer = "";
588
+ const toolCallParts = [];
589
+ let currentUsage = null;
590
+ let responseModel;
591
+ let responseId;
592
+ const finishReasons = /* @__PURE__ */ new Set();
593
+ const emitToolCalls = function* () {
594
+ for (const part of [...toolCallParts].sort((a, b) => a.index - b.index)) {
595
+ if (!part.id && !part.name) continue;
596
+ yield {
597
+ type: "tool_call",
598
+ toolCall: {
599
+ id: part.id || `tool-${part.index}`,
600
+ name: part.name,
601
+ arguments: parseToolArguments(part.arguments)
602
+ }
603
+ };
604
+ }
605
+ };
606
+ const emitDone = function* (terminal) {
607
+ yield* emitToolCalls();
608
+ log.info("stream done", {
609
+ terminal,
610
+ promptTokens: currentUsage?.promptTokens ?? 0,
611
+ completionTokens: currentUsage?.completionTokens ?? 0,
612
+ totalTokens: currentUsage?.totalTokens ?? 0
613
+ });
614
+ yield {
615
+ type: "done",
616
+ usage: currentUsage ?? void 0,
617
+ ...responseModel ? { responseModel } : {},
618
+ ...responseId ? { responseId } : {},
619
+ ...finishReasons.size > 0 ? { finishReasons: [...finishReasons] } : {}
620
+ };
621
+ };
622
+ const processSseLine = function* (line) {
623
+ const trimmed = line.trim();
624
+ if (!trimmed || !trimmed.startsWith("data:")) return false;
625
+ const data = trimmed.slice(5).replace(/^ /, "");
626
+ if (data === "[DONE]") {
627
+ yield* emitDone("[DONE]");
628
+ return true;
629
+ }
630
+ try {
631
+ const parsed = JSON.parse(data);
632
+ if (typeof parsed.model === "string") responseModel = parsed.model;
633
+ if (typeof parsed.id === "string") responseId = parsed.id;
634
+ if (parsed.usage) {
635
+ currentUsage = {
636
+ promptTokens: parsed.usage.prompt_tokens ?? 0,
637
+ completionTokens: parsed.usage.completion_tokens ?? 0,
638
+ totalTokens: parsed.usage.total_tokens ?? 0
639
+ };
640
+ }
641
+ const choice = parsed.choices?.[0];
642
+ if (!choice) return false;
643
+ const finishReason = choice.finish_reason;
644
+ if (typeof finishReason === "string" && finishReason) finishReasons.add(finishReason);
645
+ const delta = choice.delta;
646
+ if (!delta) return false;
647
+ const reasoning = parseReasoningDelta(delta);
648
+ if (reasoning) {
649
+ log.debug("stream reasoning", { len: reasoning.length });
650
+ yield { type: "reasoning", reasoning };
651
+ }
652
+ if (delta.content) {
653
+ log.debug("stream text", { len: delta.content.length });
654
+ yield { type: "text", content: delta.content };
655
+ }
656
+ if (delta.tool_calls) {
657
+ log.debug("stream tool_call delta", { count: delta.tool_calls.length });
658
+ for (const tc of delta.tool_calls) {
659
+ const explicitIndex = Number.isInteger(tc.index) ? tc.index : void 0;
660
+ let index = explicitIndex;
661
+ if (index === void 0 && tc.id) {
662
+ index = toolCallParts.find((part2) => part2.id === tc.id)?.index;
663
+ }
664
+ if (index === void 0) {
665
+ index = toolCallParts.length;
666
+ }
667
+ let part = toolCallParts.find((p) => p.index === index);
668
+ if (!part) {
669
+ part = { index, id: "", name: "", arguments: "" };
670
+ toolCallParts.push(part);
671
+ }
672
+ if (tc.id) part.id = tc.id;
673
+ if (tc.function?.name) part.name = tc.function.name;
674
+ if (tc.function?.arguments) part.arguments += tc.function.arguments;
675
+ }
676
+ }
677
+ } catch {
678
+ }
679
+ return false;
680
+ };
681
+ const stallTimeoutMs = reasoningEnabled && retry.thinkingStallTimeoutMs ? Math.max(retry.streamStallTimeoutMs, retry.thinkingStallTimeoutMs) : retry.streamStallTimeoutMs;
682
+ try {
683
+ while (true) {
684
+ const result = await readStreamChunk(reader, signal, stallTimeoutMs);
685
+ if (result.tag === "abort") {
686
+ try {
687
+ await reader.cancel();
688
+ } catch {
689
+ }
690
+ return;
691
+ }
692
+ if (result.tag === "stall") {
693
+ log.warn("stream stalled", { timeoutMs: stallTimeoutMs });
694
+ options?.onStreamStall?.(stallTimeoutMs);
695
+ try {
696
+ await reader.cancel();
697
+ } catch {
698
+ }
699
+ yield {
700
+ type: "error",
701
+ error: "Stream stalled: no data received for " + stallTimeoutMs + "ms",
702
+ errorCode: "stream_stall"
703
+ };
704
+ return;
705
+ }
706
+ if (result.done) break;
707
+ const value = result.value;
708
+ if (!value) continue;
709
+ buffer += decoder.decode(value, { stream: true });
710
+ const lines = buffer.split("\n");
711
+ buffer = lines.pop() || "";
712
+ for (const line of lines) {
713
+ if (yield* processSseLine(line)) return;
714
+ }
715
+ }
716
+ buffer += decoder.decode();
717
+ for (const line of buffer.split("\n")) {
718
+ if (yield* processSseLine(line)) return;
719
+ }
720
+ if (finishReasons.size > 0) {
721
+ yield* emitDone("finish_reason");
722
+ return;
723
+ }
724
+ } catch (e) {
725
+ if (signal?.aborted) return;
726
+ yield {
727
+ type: "error",
728
+ error: e instanceof Error ? e.message : String(e),
729
+ errorCode: classifyProviderError(e)
730
+ };
731
+ return;
732
+ } finally {
733
+ try {
734
+ reader.releaseLock();
735
+ } catch {
736
+ }
737
+ }
738
+ yield {
739
+ type: "error",
740
+ error: "Provider stream ended before its terminal event.",
741
+ errorCode: "transport_interrupted"
742
+ };
743
+ }
744
+
745
+ export {
746
+ DEFAULT_LOCAL_DATA_RETENTION_DAYS,
747
+ positiveInteger,
748
+ getDebugLogPath,
749
+ cleanupDebugLogs,
750
+ isUiDebugEnabled,
751
+ isRenderDebugEnabled,
752
+ createDebugLogger,
753
+ createDebugLoggerWithCounter,
754
+ createUiDebugLogger,
755
+ createRenderDebugLogger,
756
+ classifyApiError,
757
+ classifyProviderError,
758
+ formatApiError,
759
+ isContextOverflowError,
760
+ fetchWithRetry,
761
+ readStreamChunk,
762
+ convertTools,
763
+ chatCompletionStream
764
+ };
765
+ //# sourceMappingURL=chunk-GTVB4HEM.js.map