@ar-ai/brink-cli 0.0.0-stage → 1.0.4
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/README.md +71 -2
- package/package.json +18 -4
- package/support/activity.js +102 -0
- package/support/brand.js +110 -0
- package/support/bridge-stream.js +109 -0
- package/support/brink.js +275 -0
- package/support/chat-bridge.js +1093 -0
- package/support/client-setup.js +620 -0
- package/support/messages-bridge.js +317 -0
- package/support/messages-request.js +442 -0
- package/support/model-catalog.js +200 -0
- package/support/monitor.js +443 -0
- package/support/proxy.js +480 -0
- package/support/setup-ui.js +333 -0
- package/support/terminal-ui.js +630 -0
- package/support/transport.js +485 -0
- package/support/usage.js +144 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const LOG_LIMIT = 400;
|
|
4
|
+
const HISTORY_LIMIT = 100;
|
|
5
|
+
const MODEL_LIMIT = 64;
|
|
6
|
+
const { stripVTControlCharacters } = require("node:util");
|
|
7
|
+
|
|
8
|
+
function safeText(value, limit = 400) {
|
|
9
|
+
return stripVTControlCharacters(String(value ?? ""))
|
|
10
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
11
|
+
.replace(/\bBearer\s+\S+/gi, "Bearer [redacted]")
|
|
12
|
+
.replace(/\bsk-[A-Za-z0-9_-]+/g, "[redacted]")
|
|
13
|
+
.replace(
|
|
14
|
+
/((?:api[_-]?key|token|authorization|secret)\s*[=:]\s*)[^\s,;]+/gi,
|
|
15
|
+
"$1[redacted]",
|
|
16
|
+
)
|
|
17
|
+
.replace(/https?:\/\/[^\s]+/g, (url) => {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(url);
|
|
20
|
+
return parsed.origin + parsed.pathname;
|
|
21
|
+
} catch {
|
|
22
|
+
return "[url]";
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.replace(/[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g, " ")
|
|
26
|
+
.slice(0, limit);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createMonitor({ port }) {
|
|
30
|
+
const startedAt = Date.now();
|
|
31
|
+
const active = new Map();
|
|
32
|
+
const recent = [];
|
|
33
|
+
const models = new Map();
|
|
34
|
+
const logs = [];
|
|
35
|
+
const progress = new Map();
|
|
36
|
+
const listeners = new Set();
|
|
37
|
+
const totals = {
|
|
38
|
+
requests: 0,
|
|
39
|
+
completed: 0,
|
|
40
|
+
failed: 0,
|
|
41
|
+
cancelled: 0,
|
|
42
|
+
bytes: 0,
|
|
43
|
+
inputTokens: 0,
|
|
44
|
+
outputTokens: 0,
|
|
45
|
+
cachedTokens: 0,
|
|
46
|
+
reasoningTokens: 0,
|
|
47
|
+
usageRequests: 0,
|
|
48
|
+
};
|
|
49
|
+
let nextRequest = 0;
|
|
50
|
+
let nextLog = 0;
|
|
51
|
+
let timer = null;
|
|
52
|
+
let closed = false;
|
|
53
|
+
|
|
54
|
+
function modelStats(model) {
|
|
55
|
+
if (models.has(model)) return models.get(model);
|
|
56
|
+
if (models.size >= MODEL_LIMIT) {
|
|
57
|
+
const idle = [...models.values()]
|
|
58
|
+
.filter((entry) => entry.active === 0)
|
|
59
|
+
.sort((a, b) => a.lastSeen - b.lastSeen)[0];
|
|
60
|
+
if (idle) models.delete(idle.model);
|
|
61
|
+
else return null;
|
|
62
|
+
}
|
|
63
|
+
const entry = {
|
|
64
|
+
model,
|
|
65
|
+
requests: 0,
|
|
66
|
+
completed: 0,
|
|
67
|
+
failed: 0,
|
|
68
|
+
cancelled: 0,
|
|
69
|
+
bytes: 0,
|
|
70
|
+
inputTokens: 0,
|
|
71
|
+
outputTokens: 0,
|
|
72
|
+
cachedTokens: 0,
|
|
73
|
+
reasoningTokens: 0,
|
|
74
|
+
usageRequests: 0,
|
|
75
|
+
active: 0,
|
|
76
|
+
lastSeen: Date.now(),
|
|
77
|
+
};
|
|
78
|
+
models.set(model, entry);
|
|
79
|
+
return entry;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function snapshot() {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const running = [...active.values()].map((item) => ({
|
|
85
|
+
...item,
|
|
86
|
+
activity: {
|
|
87
|
+
...item.activity,
|
|
88
|
+
tools: item.activity.tools.map((tool) => ({ ...tool })),
|
|
89
|
+
},
|
|
90
|
+
durationMs: now - item.startedAt,
|
|
91
|
+
}));
|
|
92
|
+
const insights = [];
|
|
93
|
+
for (const item of running) {
|
|
94
|
+
if (item.firstByteMs === null && now - item.startedAt > 30000) {
|
|
95
|
+
insights.push({
|
|
96
|
+
level: "warn",
|
|
97
|
+
message: `${item.model}: waiting ${Math.floor((now - item.startedAt) / 1000)}s for output; long reasoning can be silent. No duplicate request sent.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const failure = recent.find(
|
|
102
|
+
(item) => item.state === "failed" && now - item.endedAt < 120000,
|
|
103
|
+
);
|
|
104
|
+
if (failure) {
|
|
105
|
+
const advice =
|
|
106
|
+
failure.status === 429
|
|
107
|
+
? "Rate limited upstream. Wait before sending another request."
|
|
108
|
+
: [401, 403].includes(failure.status)
|
|
109
|
+
? "Check the provider key and model access permissions."
|
|
110
|
+
: failure.status >= 500
|
|
111
|
+
? "Upstream/channel error. No model substitution was made."
|
|
112
|
+
: "Request failed; inspect the correlated log entry.";
|
|
113
|
+
insights.push({ level: "warn", message: `${failure.model}: ${advice}` });
|
|
114
|
+
}
|
|
115
|
+
if (running.length > 1)
|
|
116
|
+
insights.push({
|
|
117
|
+
level: "info",
|
|
118
|
+
message: `${running.length} concurrent requests; each retains its selected model and route.`,
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
startedAt,
|
|
122
|
+
now,
|
|
123
|
+
port,
|
|
124
|
+
totals: { ...totals },
|
|
125
|
+
active: running,
|
|
126
|
+
recent: recent.map((item) => ({
|
|
127
|
+
...item,
|
|
128
|
+
activity: {
|
|
129
|
+
...item.activity,
|
|
130
|
+
tools: item.activity.tools.map((tool) => ({ ...tool })),
|
|
131
|
+
},
|
|
132
|
+
})),
|
|
133
|
+
models: [...models.values()]
|
|
134
|
+
.map((item) => ({ ...item }))
|
|
135
|
+
.sort((a, b) => b.lastSeen - a.lastSeen),
|
|
136
|
+
logs: [...logs],
|
|
137
|
+
insights: insights.slice(0, 6),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function publish() {
|
|
142
|
+
timer = null;
|
|
143
|
+
if (!listeners.size || closed) return;
|
|
144
|
+
const state = snapshot();
|
|
145
|
+
for (const listener of listeners) listener(state);
|
|
146
|
+
}
|
|
147
|
+
function schedule() {
|
|
148
|
+
if (closed || timer || !listeners.size) return;
|
|
149
|
+
timer = setTimeout(publish, 250);
|
|
150
|
+
timer.unref();
|
|
151
|
+
}
|
|
152
|
+
// Only active requests need periodic elapsed/waiting-state updates.
|
|
153
|
+
const tick = setInterval(() => {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
for (const item of active.values()) {
|
|
156
|
+
const previous = progress.get(item.id);
|
|
157
|
+
if (!previous || now - previous.time < 10000) continue;
|
|
158
|
+
const received = item.bytes - previous.bytes;
|
|
159
|
+
const status = item.completionObserved
|
|
160
|
+
? "response completed; waiting for connection close"
|
|
161
|
+
: item.firstByteMs === null
|
|
162
|
+
? "waiting for first response byte"
|
|
163
|
+
: received > 0
|
|
164
|
+
? "streaming"
|
|
165
|
+
: "stream open; no new bytes in this interval";
|
|
166
|
+
log(
|
|
167
|
+
"info",
|
|
168
|
+
`${status} · ${Math.floor((now - item.startedAt) / 1000)}s elapsed · +${received} bytes · ${item.inputTokens ?? "?"} in / ${item.outputTokens ?? "?"} out tokens`,
|
|
169
|
+
item,
|
|
170
|
+
);
|
|
171
|
+
previous.time = now;
|
|
172
|
+
previous.bytes = item.bytes;
|
|
173
|
+
}
|
|
174
|
+
if (active.size) schedule();
|
|
175
|
+
}, 1000);
|
|
176
|
+
tick.unref();
|
|
177
|
+
|
|
178
|
+
function log(level, message, request) {
|
|
179
|
+
const entry = {
|
|
180
|
+
id: ++nextLog,
|
|
181
|
+
time: Date.now(),
|
|
182
|
+
level: ["info", "warn", "error"].includes(level) ? level : "info",
|
|
183
|
+
message: safeText(message),
|
|
184
|
+
};
|
|
185
|
+
if (request) {
|
|
186
|
+
entry.requestId = request.id;
|
|
187
|
+
entry.model = request.model;
|
|
188
|
+
}
|
|
189
|
+
logs.push(entry);
|
|
190
|
+
if (logs.length > LOG_LIMIT) logs.shift();
|
|
191
|
+
// A terminal subscriber owns stdout while the alternate screen is active.
|
|
192
|
+
if (!listeners.size) {
|
|
193
|
+
const method =
|
|
194
|
+
entry.level === "error"
|
|
195
|
+
? "error"
|
|
196
|
+
: entry.level === "warn"
|
|
197
|
+
? "warn"
|
|
198
|
+
: "log";
|
|
199
|
+
console[method](
|
|
200
|
+
`${new Date(entry.time).toLocaleTimeString()} ${entry.level.toUpperCase()}${request ? ` [${request.id}] ${request.model}` : ""} ${entry.message}`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
schedule();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function begin(req, res, { model, route, path: requestPath }) {
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
const routePath = String(requestPath || "").split("?")[0];
|
|
209
|
+
const displayPath = [
|
|
210
|
+
"/v1/responses",
|
|
211
|
+
"/v1/chat/completions",
|
|
212
|
+
"/v1/messages",
|
|
213
|
+
"/v1/messages/count_tokens",
|
|
214
|
+
"/responses",
|
|
215
|
+
"/messages",
|
|
216
|
+
].includes(routePath)
|
|
217
|
+
? routePath
|
|
218
|
+
: "other";
|
|
219
|
+
const item = {
|
|
220
|
+
id: `r${++nextRequest}`,
|
|
221
|
+
model: safeText(model || "unknown", 128),
|
|
222
|
+
route: ["claude-bridge", "messages-bridge", "native-messages"].includes(route) ? route : "native",
|
|
223
|
+
path: displayPath,
|
|
224
|
+
startedAt: now,
|
|
225
|
+
endedAt: null,
|
|
226
|
+
state: "waiting",
|
|
227
|
+
status: null,
|
|
228
|
+
bytes: 0,
|
|
229
|
+
firstByteMs: null,
|
|
230
|
+
durationMs: 0,
|
|
231
|
+
error: null,
|
|
232
|
+
inputTokens: null,
|
|
233
|
+
outputTokens: null,
|
|
234
|
+
cachedTokens: null,
|
|
235
|
+
reasoningTokens: null,
|
|
236
|
+
completionObserved: false,
|
|
237
|
+
clientDisconnected: false,
|
|
238
|
+
activity: {
|
|
239
|
+
phase: "waiting",
|
|
240
|
+
assistant: "",
|
|
241
|
+
summary: "",
|
|
242
|
+
tools: [],
|
|
243
|
+
updatedAt: now,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
const stats = modelStats(item.model);
|
|
247
|
+
active.set(item.id, item);
|
|
248
|
+
progress.set(item.id, { time: now, bytes: 0 });
|
|
249
|
+
totals.requests++;
|
|
250
|
+
if (stats) {
|
|
251
|
+
stats.requests++;
|
|
252
|
+
stats.active++;
|
|
253
|
+
stats.lastSeen = now;
|
|
254
|
+
}
|
|
255
|
+
let settled = false;
|
|
256
|
+
let failed = false;
|
|
257
|
+
const originalWrite = res.write;
|
|
258
|
+
const originalEnd = res.end;
|
|
259
|
+
const previewBuffers = { assistant: "", summary: "" };
|
|
260
|
+
const previewIds = { assistant: null, summary: null };
|
|
261
|
+
function activity(update) {
|
|
262
|
+
if (settled || !update || typeof update !== "object") return;
|
|
263
|
+
const preview = item.activity;
|
|
264
|
+
if (update.kind === "assistant" || update.kind === "summary") {
|
|
265
|
+
if (typeof update.text !== "string") return;
|
|
266
|
+
const kind = update.kind;
|
|
267
|
+
const limit = kind === "assistant" ? 1600 : 800;
|
|
268
|
+
const id = safeText(update.id, 160);
|
|
269
|
+
if (previewIds[kind] !== id || update.replace)
|
|
270
|
+
previewBuffers[kind] = "";
|
|
271
|
+
previewIds[kind] = id;
|
|
272
|
+
// Redact after joining deltas so split credential/escape sequences
|
|
273
|
+
// are not retained in the final visible preview. No preview enters logs.
|
|
274
|
+
previewBuffers[kind] = (previewBuffers[kind] + update.text).slice(
|
|
275
|
+
-(limit + 512),
|
|
276
|
+
);
|
|
277
|
+
preview[kind] = safeText(previewBuffers[kind], limit + 512).slice(
|
|
278
|
+
-limit,
|
|
279
|
+
);
|
|
280
|
+
preview.phase = kind === "assistant" ? "speaking" : "summary";
|
|
281
|
+
} else if (update.kind === "tool") {
|
|
282
|
+
const id = safeText(update.id, 160);
|
|
283
|
+
let entry = preview.tools.find((tool) => tool.id === id);
|
|
284
|
+
if (!entry) {
|
|
285
|
+
entry = { id, name: "tool", status: "preparing" };
|
|
286
|
+
preview.tools.push(entry);
|
|
287
|
+
if (preview.tools.length > 8) preview.tools.shift();
|
|
288
|
+
}
|
|
289
|
+
entry.name = safeText(update.name || "tool", 120);
|
|
290
|
+
entry.status =
|
|
291
|
+
update.status === "requested" ? "requested" : "preparing";
|
|
292
|
+
preview.phase = "tool";
|
|
293
|
+
} else if (update.kind === "reasoning") {
|
|
294
|
+
preview.phase = "reasoning";
|
|
295
|
+
} else return;
|
|
296
|
+
preview.updatedAt = Date.now();
|
|
297
|
+
schedule();
|
|
298
|
+
}
|
|
299
|
+
function count(chunk, encoding) {
|
|
300
|
+
if (chunk == null || typeof chunk === "function") return;
|
|
301
|
+
const bytes =
|
|
302
|
+
typeof chunk === "string"
|
|
303
|
+
? Buffer.byteLength(
|
|
304
|
+
chunk,
|
|
305
|
+
typeof encoding === "string" ? encoding : "utf8",
|
|
306
|
+
)
|
|
307
|
+
: chunk.byteLength || 0;
|
|
308
|
+
if (!bytes) return;
|
|
309
|
+
item.bytes += bytes;
|
|
310
|
+
totals.bytes += bytes;
|
|
311
|
+
if (stats) stats.bytes += bytes;
|
|
312
|
+
if (item.firstByteMs === null) {
|
|
313
|
+
item.firstByteMs = Date.now() - item.startedAt;
|
|
314
|
+
if (!failed) item.state = "streaming";
|
|
315
|
+
}
|
|
316
|
+
schedule();
|
|
317
|
+
}
|
|
318
|
+
res.write = function (chunk, encoding, callback) {
|
|
319
|
+
count(chunk, encoding);
|
|
320
|
+
return originalWrite.call(this, chunk, encoding, callback);
|
|
321
|
+
};
|
|
322
|
+
res.end = function (chunk, encoding, callback) {
|
|
323
|
+
count(chunk, encoding);
|
|
324
|
+
return originalEnd.call(this, chunk, encoding, callback);
|
|
325
|
+
};
|
|
326
|
+
function usage(report) {
|
|
327
|
+
if (settled || !report || typeof report !== "object") return;
|
|
328
|
+
const values = {
|
|
329
|
+
inputTokens: report.prompt_tokens ?? report.input_tokens,
|
|
330
|
+
outputTokens: report.completion_tokens ?? report.output_tokens,
|
|
331
|
+
cachedTokens:
|
|
332
|
+
report.prompt_tokens_details?.cached_tokens ??
|
|
333
|
+
report.input_tokens_details?.cached_tokens ??
|
|
334
|
+
report.cache_read_input_tokens,
|
|
335
|
+
reasoningTokens:
|
|
336
|
+
report.completion_tokens_details?.reasoning_tokens ??
|
|
337
|
+
report.output_tokens_details?.reasoning_tokens,
|
|
338
|
+
};
|
|
339
|
+
const first = item.inputTokens === null && item.outputTokens === null;
|
|
340
|
+
for (const [field, value] of Object.entries(values)) {
|
|
341
|
+
if (!Number.isSafeInteger(value) || value < 0) continue;
|
|
342
|
+
// Providers report cumulative request usage, not additive deltas.
|
|
343
|
+
const delta = value - (item[field] ?? 0);
|
|
344
|
+
item[field] = value;
|
|
345
|
+
totals[field] += delta;
|
|
346
|
+
if (stats) stats[field] += delta;
|
|
347
|
+
}
|
|
348
|
+
if (first && (item.inputTokens !== null || item.outputTokens !== null)) {
|
|
349
|
+
totals.usageRequests++;
|
|
350
|
+
if (stats) stats.usageRequests++;
|
|
351
|
+
}
|
|
352
|
+
schedule();
|
|
353
|
+
}
|
|
354
|
+
function fail(message) {
|
|
355
|
+
if (settled || failed) return;
|
|
356
|
+
failed = true;
|
|
357
|
+
item.error = safeText(message);
|
|
358
|
+
item.state = "failed";
|
|
359
|
+
log("error", item.error, item);
|
|
360
|
+
}
|
|
361
|
+
function finish(cancelled) {
|
|
362
|
+
if (settled) return;
|
|
363
|
+
settled = true;
|
|
364
|
+
res.write = originalWrite;
|
|
365
|
+
res.end = originalEnd;
|
|
366
|
+
item.endedAt = Date.now();
|
|
367
|
+
item.durationMs = item.endedAt - item.startedAt;
|
|
368
|
+
item.status = res.headersSent ? res.statusCode : item.status;
|
|
369
|
+
item.clientDisconnected = cancelled;
|
|
370
|
+
item.state =
|
|
371
|
+
failed || item.status >= 400
|
|
372
|
+
? "failed"
|
|
373
|
+
: cancelled && !item.completionObserved
|
|
374
|
+
? "cancelled"
|
|
375
|
+
: "completed";
|
|
376
|
+
totals[item.state]++;
|
|
377
|
+
if (stats) {
|
|
378
|
+
stats[item.state]++;
|
|
379
|
+
stats.active--;
|
|
380
|
+
stats.lastSeen = item.endedAt;
|
|
381
|
+
}
|
|
382
|
+
active.delete(item.id);
|
|
383
|
+
progress.delete(item.id);
|
|
384
|
+
recent.unshift(item);
|
|
385
|
+
if (recent.length > HISTORY_LIMIT) recent.pop();
|
|
386
|
+
const closure = cancelled
|
|
387
|
+
? item.completionObserved
|
|
388
|
+
? " · client disconnected after response completion; delivery not confirmed"
|
|
389
|
+
: " · client disconnected before response completion; reason unknown"
|
|
390
|
+
: "";
|
|
391
|
+
log(
|
|
392
|
+
item.state === "failed"
|
|
393
|
+
? "error"
|
|
394
|
+
: item.state === "cancelled"
|
|
395
|
+
? "warn"
|
|
396
|
+
: "info",
|
|
397
|
+
`${item.state}${item.status ? ` · HTTP ${item.status}` : ""} · ${item.durationMs}ms · ${item.inputTokens ?? "?"} in / ${item.outputTokens ?? "?"} out tokens · ${item.bytes} bytes${closure}`,
|
|
398
|
+
item,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
res.once("finish", () => finish(false));
|
|
402
|
+
res.once("close", () => finish(!res.writableFinished));
|
|
403
|
+
res.once("error", () => fail("Client response stream failed"));
|
|
404
|
+
log(
|
|
405
|
+
"info",
|
|
406
|
+
`Request started · ${item.route === "claude-bridge" ? "Responses -> Chat bridge" : item.route === "messages-bridge" ? "Messages -> Responses bridge" : item.route === "native-messages" ? "native Messages" : "native Responses"}`,
|
|
407
|
+
item,
|
|
408
|
+
);
|
|
409
|
+
return {
|
|
410
|
+
activity,
|
|
411
|
+
usage,
|
|
412
|
+
complete() {
|
|
413
|
+
if (settled || failed) return;
|
|
414
|
+
item.completionObserved = true;
|
|
415
|
+
schedule();
|
|
416
|
+
},
|
|
417
|
+
upstream(status) {
|
|
418
|
+
if (settled) return;
|
|
419
|
+
item.status = status;
|
|
420
|
+
log(status >= 400 ? "warn" : "info", `Upstream HTTP ${status}`, item);
|
|
421
|
+
},
|
|
422
|
+
fail,
|
|
423
|
+
note(level, message) {
|
|
424
|
+
if (!settled) log(level, message, item);
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function subscribe(listener) {
|
|
430
|
+
listeners.add(listener);
|
|
431
|
+
listener(snapshot());
|
|
432
|
+
return () => listeners.delete(listener);
|
|
433
|
+
}
|
|
434
|
+
function close() {
|
|
435
|
+
closed = true;
|
|
436
|
+
clearInterval(tick);
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
listeners.clear();
|
|
439
|
+
}
|
|
440
|
+
return { snapshot, subscribe, begin, log, close };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = { createMonitor };
|