@ar-ai/brink-cli 0.0.0-stage → 1.0.6

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.
@@ -0,0 +1,485 @@
1
+ "use strict";
2
+
3
+ const https = require("https");
4
+ const { PassThrough } = require("stream");
5
+ const { StringDecoder } = require("string_decoder");
6
+ const zlib = require("zlib");
7
+
8
+ const MAX_RETRIES = 5;
9
+ // Responses preambles can echo the client's tool catalog and instructions.
10
+ // Bound both raw and decoded bytes without committing ordinary large metadata.
11
+ const MAX_PRELUDE_BYTES = 8 * 1024 * 1024;
12
+ const RETRY_OWNER_HEADER = "x-agentrouter-retry-owner";
13
+
14
+ const CONNECT_TIMEOUT_MS = 30_000;
15
+ const HEADER_TIMEOUT_MS = 120_000;
16
+ const IDLE_TIMEOUT_MS = 300_000;
17
+ const HOP_HEADERS = [
18
+ "connection",
19
+ "keep-alive",
20
+ "proxy-authenticate",
21
+ "proxy-authorization",
22
+ "proxy-connection",
23
+ "te",
24
+ "trailer",
25
+ "transfer-encoding",
26
+ "upgrade",
27
+ ];
28
+
29
+ function cleanHeaders(source) {
30
+ const headers = { ...source };
31
+ const nominated = String(headers.connection || "").split(",");
32
+ for (const key of [...HOP_HEADERS, ...nominated])
33
+ delete headers[key.trim().toLowerCase()];
34
+ delete headers[RETRY_OWNER_HEADER];
35
+ return headers;
36
+ }
37
+
38
+ function createUpstreamAgent() {
39
+ return new https.Agent({
40
+ keepAlive: true,
41
+ keepAliveMsecs: 1_000,
42
+ maxSockets: 64,
43
+ maxTotalSockets: 64,
44
+ maxFreeSockets: 8,
45
+ scheduling: "lifo",
46
+ timeout: IDLE_TIMEOUT_MS,
47
+ });
48
+ }
49
+
50
+ // Deadlines cover connection and response headers only. Once headers arrive,
51
+ // arbitrarily long inference streams are allowed while the socket stays active.
52
+ function watchUpstream(request) {
53
+ let response;
54
+ let socket;
55
+ let headerTimer;
56
+ const expire = (code) => {
57
+ const error = Object.assign(new Error("AgentRouter upstream timed out"), {
58
+ code,
59
+ status: 504,
60
+ });
61
+ if (response && !response.destroyed) response.destroy(error);
62
+ request.destroy(error);
63
+ };
64
+ const connectTimer = setTimeout(
65
+ () => expire("upstream_connect_timeout"),
66
+ CONNECT_TIMEOUT_MS,
67
+ );
68
+ connectTimer.unref();
69
+ const connected = () => {
70
+ clearTimeout(connectTimer);
71
+ headerTimer = setTimeout(
72
+ () => expire("upstream_header_timeout"),
73
+ HEADER_TIMEOUT_MS,
74
+ );
75
+ headerTimer.unref();
76
+ };
77
+ const clearDeadlines = () => {
78
+ clearTimeout(connectTimer);
79
+ clearTimeout(headerTimer);
80
+ socket?.off("secureConnect", connected);
81
+ };
82
+ const timedOut = () => expire("upstream_idle_timeout");
83
+ request.setTimeout(IDLE_TIMEOUT_MS, timedOut);
84
+ request.once("socket", (assigned) => {
85
+ socket = assigned;
86
+ if (socket.connecting || socket.secureConnecting)
87
+ socket.once("secureConnect", connected);
88
+ else connected();
89
+ });
90
+ request.once("response", (upstream) => {
91
+ response = upstream;
92
+ clearDeadlines();
93
+ });
94
+ request.on("error", (error) => {
95
+ clearDeadlines();
96
+ if (response && !response.destroyed) response.destroy(error);
97
+ });
98
+ request.once("close", () => {
99
+ clearDeadlines();
100
+ request.off("timeout", timedOut);
101
+ });
102
+ }
103
+
104
+ function errorDetails(value) {
105
+ if (!value || typeof value !== "object") return value;
106
+ return value.error || value.response?.error || value;
107
+ }
108
+ function permanentFailure(status, value, text = "") {
109
+ if ([401, 402, 403].includes(status)) return true;
110
+ const error = errorDetails(value);
111
+ const message = typeof error === "string" ? error : error?.message || text;
112
+ return /quota|billing|credit|balance|authentication|unauthori[sz]ed|invalid[_ -]?(?:api[_ -]?)?key|permission|access[_ -]?denied|account[_ -]?(?:disabled|suspended)/i.test(`${error?.code || ""} ${error?.type || ""} ${message}`);
113
+ }
114
+
115
+ function isRateLimit(status, value, text = "") {
116
+ if (permanentFailure(status, value, text)) return false;
117
+ const error = errorDetails(value);
118
+ const code = String(error?.code || "");
119
+ const type = String(error?.type || "");
120
+ const message = typeof error === "string" ? error : error?.message || text;
121
+ const structured = /^rate_limit_(?:exceeded|error)$/i.test(code) ||
122
+ /^rate_limit_(?:exceeded|error)$/i.test(type);
123
+ if (status === 400) return structured;
124
+ return status === 429 || structured || /\brate_limit_exceeded\b/i.test(message);
125
+ }
126
+
127
+ // Fixed classifications only: never retain upstream text in retry telemetry.
128
+ function retryableFailure(status, value, text = "") {
129
+ if (permanentFailure(status, value, text)) return null;
130
+ if (isRateLimit(status, value, text)) return "rate_limit_exceeded";
131
+ if (status >= 400 && status < 500) return null;
132
+ const error = errorDetails(value);
133
+ const code = String(error?.code || error?.type || "");
134
+ const message = typeof error === "string" ? error : error?.message || text;
135
+ if (/^(?:ECONNRESET|ECONNREFUSED|EPIPE|ETIMEDOUT|EAI_AGAIN|upstream_connect_timeout|upstream_header_timeout|upstream_idle_timeout|incomplete_stream|upstream_connection_error)$/.test(code) ||
136
+ /upstream connect error|disconnect\/reset before headers|reset reason:\s*connection termination/i.test(message))
137
+ return "upstream_connection_error";
138
+ if ([500, 502, 503, 504].includes(status) || /^(?:server_error|internal_server_error|service_unavailable|overloaded_error)$/.test(code) ||
139
+ /\bthe server had an error processing your request\b/i.test(message))
140
+ return "server_error";
141
+ return null;
142
+ }
143
+
144
+ function retryDelay(retry) {
145
+ return retry * 1000;
146
+ }
147
+
148
+ function cancelledError() {
149
+ return Object.assign(new Error("Client disconnected"), {
150
+ code: "client_disconnected", status: 499,
151
+ });
152
+ }
153
+
154
+ // The fixed 1–5 second retry waits remain cancellable on client disconnect.
155
+ function waitForRetry(delay, signal) {
156
+ return new Promise((resolve, reject) => {
157
+ if (signal?.aborted) return reject(cancelledError());
158
+ const abort = () => {
159
+ clearTimeout(timer);
160
+ signal.removeEventListener("abort", abort);
161
+ reject(cancelledError());
162
+ };
163
+ const timer = setTimeout(() => {
164
+ signal?.removeEventListener("abort", abort);
165
+ resolve();
166
+ }, delay);
167
+ signal?.addEventListener("abort", abort, { once: true });
168
+ });
169
+ }
170
+
171
+ function harmlessPrelude(value, event) {
172
+ if (!value || typeof value !== "object") return false;
173
+ if (Array.isArray(value.choices)) {
174
+ return value.choices.every((choice) => {
175
+ const delta = choice.delta;
176
+ return !choice.finish_reason && delta && Object.entries(delta).every(
177
+ ([key, entry]) => key === "role" || entry == null || entry === "" ||
178
+ (Array.isArray(entry) && entry.length === 0),
179
+ );
180
+ });
181
+ }
182
+ const type = value.type || event;
183
+ if (["response.created", "response.in_progress", "response.queued"].includes(type))
184
+ return !value.response?.output?.length;
185
+ if (type === "response.output_item.added")
186
+ return value.item?.type === "message" && !value.item.content?.length;
187
+ if (type === "response.content_part.added")
188
+ return value.part?.type === "output_text" && !value.part.text;
189
+ if (type === "ping") return true;
190
+ if (type === "message_start") return !value.message?.content?.length;
191
+ if (type === "content_block_start")
192
+ return value.content_block?.type === "text" && !value.content_block.text;
193
+ // Unknown events lock replay off. A new protocol must never duplicate tools.
194
+ return false;
195
+ }
196
+
197
+ // Inspect only a bounded prefix, withholding even metadata so discarded attempt
198
+ // IDs cannot leak. All accepted response bytes (including compressed bytes) are
199
+ // replayed verbatim. Reaching the limit commits the response without retrying.
200
+ function inspectPrelude(response, signal) {
201
+ const status = response.statusCode || 502;
202
+ const contentType = String(response.headers["content-type"] || "").toLowerCase();
203
+ const sse = status === 200 && contentType.includes("text/event-stream");
204
+ const inspect = sse || status >= 400 ||
205
+ (status === 200 && contentType.includes("application/json"));
206
+ if (!inspect || [401, 402, 403].includes(status))
207
+ return Promise.resolve({ chunks: [], retryReason: null, ended: false });
208
+ return new Promise((resolve, reject) => {
209
+ const chunks = [];
210
+ const decoder = new StringDecoder("utf8");
211
+ let rawBytes = 0;
212
+ let decodedBytes = 0;
213
+ let text = "";
214
+ let line = "";
215
+ let skipLF = false;
216
+ let first = true;
217
+ let data = [];
218
+ let event = "";
219
+ let settled = false;
220
+ let ended = false;
221
+ let unzip;
222
+ const encoding = String(response.headers["content-encoding"] || "identity").toLowerCase();
223
+ if (encoding === "gzip") unzip = zlib.createGunzip();
224
+ else if (encoding === "deflate") unzip = zlib.createInflate();
225
+ else if (encoding === "br") unzip = zlib.createBrotliDecompress();
226
+ else if (encoding !== "identity") {
227
+ resolve({ chunks, retryReason: null, ended });
228
+ return;
229
+ }
230
+ const cleanup = () => {
231
+ response.pause();
232
+ response.off("data", onData);
233
+ response.off("end", onEnd);
234
+ response.off("error", onError);
235
+ response.off("close", onClose);
236
+ signal?.removeEventListener("abort", onAbort);
237
+ unzip?.destroy();
238
+ };
239
+ const finish = (retryReason, limitReached = false) => {
240
+ if (settled) return;
241
+ settled = true;
242
+ cleanup();
243
+ resolve({ chunks, retryReason: retryReason || null, ended, limitReached });
244
+ };
245
+ const onError = (error) => {
246
+ if (settled) return;
247
+ // A partial/unknown event may contain tool output. Retry a reset only
248
+ // before bytes arrive or after fully parsed, harmless identity metadata.
249
+ error.retrySafe = rawBytes === 0 || (sse && !unzip && !line && !data.length);
250
+ settled = true;
251
+ cleanup();
252
+ reject(error);
253
+ };
254
+ const onAbort = () => onError(cancelledError());
255
+ const onClose = () => {
256
+ if (!response.complete) onError(Object.assign(new Error("Upstream response aborted"), {
257
+ code: "incomplete_stream",
258
+ }));
259
+ };
260
+ const consumeLine = () => {
261
+ if (!line) {
262
+ if (!data.length) { event = ""; return; }
263
+ const payload = data.join("\n");
264
+ data = [];
265
+ if (payload.trim() === "[DONE]") return finish(false);
266
+ let value;
267
+ try { value = JSON.parse(payload); } catch { return finish(false); }
268
+ const error = value?.error || value?.response?.error ||
269
+ event === "error" || value?.type === "error" || value?.type === "response.failed" ||
270
+ /^rate_limit_(?:exceeded|error)$/i.test(String(value?.code || "")) ||
271
+ /^rate_limit_(?:exceeded|error)$/i.test(String(value?.type || ""));
272
+ if (error) finish(value?.response?.output?.length ? null : retryableFailure(status, value));
273
+ else if (!harmlessPrelude(value, event)) finish(false);
274
+ event = "";
275
+ return;
276
+ }
277
+ const colon = line.indexOf(":");
278
+ const field = colon < 0 ? line : line.slice(0, colon);
279
+ let value = colon < 0 ? "" : line.slice(colon + 1);
280
+ if (value.startsWith(" ")) value = value.slice(1);
281
+ if (field === "data") data.push(value);
282
+ else if (field === "event") event = value;
283
+ };
284
+ const consume = (chunk) => {
285
+ if (settled) return;
286
+ decodedBytes += chunk.length;
287
+ if (decodedBytes > MAX_PRELUDE_BYTES) return finish(false, true);
288
+ let decoded = decoder.write(chunk);
289
+ if (!sse) { text += decoded; return; }
290
+ if (first && decoded.length) {
291
+ first = false;
292
+ if (decoded.charCodeAt(0) === 0xfeff) decoded = decoded.slice(1);
293
+ }
294
+ let start = 0;
295
+ for (let i = 0; i < decoded.length; i++) {
296
+ if (settled) return;
297
+ const char = decoded[i];
298
+ if (skipLF) {
299
+ skipLF = false;
300
+ if (char === "\n") { start = i + 1; continue; }
301
+ }
302
+ if (char === "\r" || char === "\n") {
303
+ line += decoded.slice(start, i);
304
+ consumeLine();
305
+ line = "";
306
+ start = i + 1;
307
+ skipLF = char === "\r";
308
+ }
309
+ }
310
+ if (!settled) line += decoded.slice(start);
311
+ };
312
+ const complete = () => {
313
+ if (settled) return;
314
+ if (sse) return finish(false); // Never retry truncated or unknown SSE.
315
+ text += decoder.end();
316
+ let value;
317
+ try { value = JSON.parse(text); } catch { /* Plain-text HTTP errors exist. */ }
318
+ if (status === 200 && (!errorDetails(value) || (!value?.error && !value?.response?.error))) return finish(false);
319
+ if (value?.output?.length || value?.response?.output?.length) return finish(false);
320
+ finish(retryableFailure(status, value, text));
321
+ };
322
+ const onData = (chunk) => {
323
+ chunks.push(chunk);
324
+ rawBytes += chunk.length;
325
+ if (rawBytes > MAX_PRELUDE_BYTES) return finish(false, true);
326
+ if (unzip) {
327
+ if (!unzip.write(chunk)) response.pause();
328
+ } else consume(chunk);
329
+ };
330
+ const onEnd = () => {
331
+ ended = true;
332
+ if (unzip) unzip.end();
333
+ else complete();
334
+ };
335
+ if (unzip) {
336
+ unzip.on("data", consume);
337
+ unzip.on("end", complete);
338
+ unzip.on("error", () => finish(false));
339
+ unzip.on("drain", () => { if (!settled) response.resume(); });
340
+ }
341
+ response.on("data", onData);
342
+ response.once("end", onEnd);
343
+ response.on("error", onError);
344
+ response.once("close", onClose);
345
+ signal?.addEventListener("abort", onAbort, { once: true });
346
+ if (signal?.aborted) onAbort();
347
+ if (response.errored) onError(response.errored);
348
+ else if (response.destroyed && !response.complete) onClose();
349
+ });
350
+ }
351
+
352
+ async function requestWithRetries(target, options, body, {
353
+ signal, tracker, clientOwnsRetries = false,
354
+ } = {}) {
355
+ let request;
356
+ let response;
357
+ let stream;
358
+ const abort = () => {
359
+ response?.destroy();
360
+ request?.destroy();
361
+ stream?.destroy(cancelledError());
362
+ };
363
+ const cleanup = () => signal?.removeEventListener("abort", abort);
364
+ signal?.addEventListener("abort", abort, { once: true });
365
+ try {
366
+ for (let attempt = 0; ; attempt++) {
367
+ if (signal?.aborted) throw cancelledError();
368
+ response = undefined;
369
+ let inspected;
370
+ let attemptError;
371
+ let retryReason;
372
+ try {
373
+ response = await new Promise((resolve, reject) => {
374
+ let received = false;
375
+ request = https.request(target, options, (upstream) => {
376
+ received = true;
377
+ upstream.on("error", () => {});
378
+ resolve(upstream);
379
+ });
380
+ request.on("error", reject);
381
+ request.once("close", () => {
382
+ if (!received) reject(signal?.aborted ? cancelledError() :
383
+ Object.assign(new Error("Upstream connection closed"), { code: "incomplete_stream" }));
384
+ });
385
+ watchUpstream(request);
386
+ request.end(body);
387
+ });
388
+ if (signal?.aborted) throw cancelledError();
389
+ inspected = await inspectPrelude(response, signal);
390
+ retryReason = inspected.retryReason;
391
+ } catch (error) {
392
+ attemptError = error;
393
+ if (!response || error.retrySafe)
394
+ retryReason = retryableFailure(response?.statusCode, error);
395
+ }
396
+ if (signal?.aborted) throw cancelledError();
397
+ if (retryReason && !clientOwnsRetries && attempt < MAX_RETRIES) {
398
+ const retry = attempt + 1;
399
+ const delay = retryDelay(retry);
400
+ response?.destroy();
401
+ request?.destroy();
402
+ tracker?.note("warn", `${upstreamFailure(null, { code: retryReason })}; retry ${retry}/${MAX_RETRIES} in ${delay}ms`);
403
+ await waitForRetry(delay, signal);
404
+ continue;
405
+ }
406
+ if (attemptError) throw attemptError;
407
+ if (inspected.limitReached)
408
+ tracker?.note("warn", "Retry inspection exceeded 8 MiB; forwarding without automatic replay");
409
+ stream = new PassThrough();
410
+ stream.on("error", () => {});
411
+ stream.once("end", cleanup);
412
+ stream.once("close", () => {
413
+ cleanup();
414
+ if (!response.complete) {
415
+ response.destroy();
416
+ request.destroy();
417
+ }
418
+ });
419
+ response.on("error", (error) => stream.destroy(error));
420
+ response.once("aborted", () => stream.destroy(Object.assign(
421
+ new Error("Upstream response aborted"), { code: "incomplete_stream" },
422
+ )));
423
+ response.once("close", () => {
424
+ if (!response.complete) stream.destroy(Object.assign(
425
+ new Error("Upstream response ended before completion"), { code: "incomplete_stream" },
426
+ ));
427
+ });
428
+ for (const chunk of inspected.chunks) stream.write(chunk);
429
+ if (inspected.ended) stream.end();
430
+ else response.pipe(stream);
431
+ return { request, response, stream, retryReason };
432
+ }
433
+ } catch (error) {
434
+ cleanup();
435
+ abort();
436
+ throw error;
437
+ }
438
+ }
439
+
440
+ // Only fixed classifications enter telemetry: never upstream messages, bodies,
441
+ // request headers, URLs, or arbitrary provider error codes.
442
+ function upstreamFailure(status, error) {
443
+ const code = error?.code;
444
+ if (
445
+ status === 429 ||
446
+ code === "rate_limit_exceeded" ||
447
+ code === "rate_limit_error"
448
+ )
449
+ return "AgentRouter rate limit reached";
450
+ if (
451
+ status === 401 ||
452
+ status === 403 ||
453
+ code === "authentication_error" ||
454
+ code === "invalid_api_key" ||
455
+ code === "permission_error"
456
+ )
457
+ return "AgentRouter authentication or access denied";
458
+ if (code === "upstream_connect_timeout")
459
+ return "AgentRouter connection deadline exceeded";
460
+ if (code === "upstream_header_timeout")
461
+ return "AgentRouter response-header deadline exceeded";
462
+ if (code === "upstream_idle_timeout")
463
+ return "AgentRouter idle timeout exceeded";
464
+ if (code === "upstream_connection_error" || ["ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT", "EAI_AGAIN"].includes(code))
465
+ return "AgentRouter upstream connection reset or unavailable";
466
+ if (code === "server_error") return "AgentRouter upstream server error";
467
+ if (code === "incomplete_stream")
468
+ return "AgentRouter stream ended before completion";
469
+ if (status === 400 || error?.status === 400)
470
+ return "Request rejected as invalid";
471
+ if (status >= 400 && status < 500) return "AgentRouter rejected the request";
472
+ if (status >= 500) return "AgentRouter upstream server error";
473
+ return "AgentRouter connection or response failed";
474
+ }
475
+
476
+ module.exports = {
477
+ cleanHeaders,
478
+ createUpstreamAgent,
479
+ watchUpstream,
480
+ requestWithRetries,
481
+ retryDelay,
482
+ isRateLimit,
483
+ retryableFailure,
484
+ upstreamFailure,
485
+ };
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+
3
+ const { Transform } = require("node:stream");
4
+ const { StringDecoder } = require("node:string_decoder");
5
+ const { observeActivity } = require("./activity");
6
+ const { retryableFailure, upstreamFailure } = require("./transport");
7
+ const MAX_EVENT_CHARS = 1024 * 1024;
8
+ const MAX_JSON_CHARS = 8 * 1024 * 1024;
9
+
10
+ // Observe bounded visible activity, usage and terminal metadata. Forward bytes
11
+ // unchanged; never retain prompts, tool arguments or private reasoning content.
12
+ function createUsageObserver(headers, tracker) {
13
+ const contentType = String(headers["content-type"] || "").toLowerCase();
14
+ const encoding = String(
15
+ headers["content-encoding"] || "identity",
16
+ ).toLowerCase();
17
+ const sse = contentType.includes("text/event-stream");
18
+ const json = contentType.includes("application/json");
19
+ if ((!sse && !json) || !["", "identity"].includes(encoding)) {
20
+ tracker.note("info", "Token usage unavailable for this response encoding");
21
+ return null;
22
+ }
23
+ const decoder = new StringDecoder("utf8");
24
+ let pending = "";
25
+ let event = [];
26
+ let eventSize = 0;
27
+ let discardLine = false;
28
+ let discardEvent = false;
29
+ let warned = false;
30
+ let jsonOverflow = false;
31
+ function overflow() {
32
+ if (warned) return;
33
+ warned = true;
34
+ tracker.note(
35
+ "warn",
36
+ "Oversized usage metadata skipped; response stream unchanged",
37
+ );
38
+ }
39
+ function inspect(text) {
40
+ if (
41
+ !/"usage"\s*:|"type"\s*:\s*"response\.|"object"\s*:\s*"response"/.test(
42
+ text,
43
+ )
44
+ )
45
+ return;
46
+ try {
47
+ const value = JSON.parse(text);
48
+ observeActivity(value, tracker);
49
+ const usage =
50
+ value?.response?.usage || value?.message?.usage || value?.usage;
51
+ if (usage && typeof usage === "object") tracker.usage(usage);
52
+ // Usage and output_item.done alone do not prove response completion.
53
+ const type = value?.type;
54
+ const status = value?.object === "response" ? value.status : null;
55
+ if (type === "response.completed" || status === "completed") {
56
+ tracker.complete();
57
+ } else if (type === "response.failed" || status === "failed") {
58
+ const reason = retryableFailure(null, value);
59
+ tracker.fail(
60
+ reason
61
+ ? `${upstreamFailure(null, { code: reason })} (failed response; HTTP status may still be 200)`
62
+ : "AgentRouter reported a failed response (HTTP status may still be 200)",
63
+ );
64
+ } else if (type === "response.incomplete" || status === "incomplete") {
65
+ tracker.fail(
66
+ "AgentRouter reported an incomplete response (generation limit or content restriction)",
67
+ );
68
+ }
69
+ } catch {
70
+ /* Malformed metadata must not interrupt response delivery. */
71
+ }
72
+ }
73
+ function dispatch() {
74
+ if (!discardEvent && event.length) inspect(event.join("\n"));
75
+ event = [];
76
+ eventSize = 0;
77
+ discardEvent = false;
78
+ }
79
+ function line(text) {
80
+ if (text.endsWith("\r")) text = text.slice(0, -1);
81
+ if (!text) {
82
+ dispatch();
83
+ return;
84
+ }
85
+ if (discardEvent || !text.startsWith("data:")) return;
86
+ const data = text.slice(text[5] === " " ? 6 : 5);
87
+ eventSize += data.length + 1;
88
+ if (eventSize > MAX_EVENT_CHARS) {
89
+ event = [];
90
+ discardEvent = true;
91
+ overflow();
92
+ } else event.push(data);
93
+ }
94
+ function consume(text) {
95
+ if (json) {
96
+ if (jsonOverflow) return;
97
+ if (pending.length + text.length > MAX_JSON_CHARS) {
98
+ pending = "";
99
+ jsonOverflow = true;
100
+ overflow();
101
+ } else pending += text;
102
+ return;
103
+ }
104
+ let start = 0;
105
+ for (;;) {
106
+ const end = text.indexOf("\n", start);
107
+ const part = text.slice(start, end < 0 ? undefined : end);
108
+ if (!discardLine) {
109
+ if (pending.length + part.length > MAX_EVENT_CHARS) {
110
+ pending = "";
111
+ discardLine = true;
112
+ discardEvent = true;
113
+ event = [];
114
+ overflow();
115
+ } else pending += part;
116
+ }
117
+ if (end < 0) break;
118
+ if (!discardLine) line(pending);
119
+ pending = "";
120
+ discardLine = false;
121
+ start = end + 1;
122
+ }
123
+ }
124
+ return new Transform({
125
+ transform(chunk, encoding, callback) {
126
+ consume(decoder.write(chunk));
127
+ callback(null, chunk);
128
+ },
129
+ flush(callback) {
130
+ consume(decoder.end());
131
+ if (json) {
132
+ if (!jsonOverflow) inspect(pending);
133
+ } else {
134
+ if (pending && !discardLine) line(pending);
135
+ dispatch();
136
+ }
137
+ pending = "";
138
+ event = [];
139
+ callback();
140
+ },
141
+ });
142
+ }
143
+
144
+ module.exports = { createUsageObserver };