@byfriends/vis-server 0.3.6 → 0.4.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/dist/index.d.mts +1 -1
- package/dist/index.mjs +21 -20
- package/dist/index.mjs.map +10 -0
- package/dist/public/assets/{index-CE9cqWg9.js → index-BicyXOcO.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/server-enpsx6np.mjs +1010 -0
- package/dist/server-enpsx6np.mjs.map +25 -0
- package/dist/server.d.mts +5 -8
- package/dist/server.mjs +13 -2
- package/dist/server.mjs.map +9 -0
- package/package.json +12 -12
- package/LICENSE +0 -21
- package/dist/server-CqbNDUBH.mjs +0 -1044
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
// src/app.ts
|
|
2
|
+
import { timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
4
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
5
|
+
import { Hono as Hono6 } from "hono";
|
|
6
|
+
|
|
7
|
+
// src/routes/context.ts
|
|
8
|
+
import { join as join3 } from "node:path";
|
|
9
|
+
import { Hono } from "hono";
|
|
10
|
+
|
|
11
|
+
// src/config.ts
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
function resolveByfHome() {
|
|
15
|
+
const envHome = process.env["BYF_HOME"];
|
|
16
|
+
if (envHome !== undefined && envHome.length > 0) {
|
|
17
|
+
return envHome;
|
|
18
|
+
}
|
|
19
|
+
return join(homedir(), ".byf");
|
|
20
|
+
}
|
|
21
|
+
function resolvePort() {
|
|
22
|
+
const raw = process.env["PORT"];
|
|
23
|
+
if (raw !== undefined && raw.length > 0) {
|
|
24
|
+
const n = Number.parseInt(raw, 10);
|
|
25
|
+
if (Number.isFinite(n) && n > 0 && n < 65536) {
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return 3001;
|
|
30
|
+
}
|
|
31
|
+
function resolveHost() {
|
|
32
|
+
const raw = process.env["VIS_HOST"] ?? process.env["HOST"];
|
|
33
|
+
const host = raw?.trim();
|
|
34
|
+
return host !== undefined && host.length > 0 ? host : "127.0.0.1";
|
|
35
|
+
}
|
|
36
|
+
function isLoopbackHost(host) {
|
|
37
|
+
const normalized = host.trim().toLowerCase().replaceAll("[", "").replaceAll("]", "");
|
|
38
|
+
return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || normalized.startsWith("127.");
|
|
39
|
+
}
|
|
40
|
+
function resolveVisAuthToken(host = resolveHost()) {
|
|
41
|
+
const raw = process.env["VIS_AUTH_TOKEN"];
|
|
42
|
+
const token = raw?.trim();
|
|
43
|
+
if (token !== undefined && token.length > 0)
|
|
44
|
+
return token;
|
|
45
|
+
if (!isLoopbackHost(host)) {
|
|
46
|
+
throw new Error(`VIS_AUTH_TOKEN is required when binding vis-server outside loopback (host=${host})`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/lib/context-projector.ts
|
|
52
|
+
var ZERO = { inputOther: 0, output: 0, inputCacheRead: 0, inputCacheCreation: 0 };
|
|
53
|
+
function projectContext(entries) {
|
|
54
|
+
let messages = [];
|
|
55
|
+
const usage = {
|
|
56
|
+
byScope: { session: { ...ZERO }, turn: { ...ZERO } },
|
|
57
|
+
byModel: {}
|
|
58
|
+
};
|
|
59
|
+
const config = {};
|
|
60
|
+
let permissionMode = null;
|
|
61
|
+
let openSteps = new Map;
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const rec = entry.data;
|
|
64
|
+
switch (rec.type) {
|
|
65
|
+
case "context.append_message":
|
|
66
|
+
messages.push({
|
|
67
|
+
lineNo: entry.lineNo,
|
|
68
|
+
time: rec.time,
|
|
69
|
+
source: "append_message",
|
|
70
|
+
message: rec.message,
|
|
71
|
+
toolStepUuids: []
|
|
72
|
+
});
|
|
73
|
+
break;
|
|
74
|
+
case "context.append_loop_event": {
|
|
75
|
+
const ev = rec.event;
|
|
76
|
+
if (ev.type === "step.begin") {
|
|
77
|
+
const message = {
|
|
78
|
+
role: "assistant",
|
|
79
|
+
content: [],
|
|
80
|
+
toolCalls: []
|
|
81
|
+
};
|
|
82
|
+
const projected = {
|
|
83
|
+
lineNo: entry.lineNo,
|
|
84
|
+
time: rec.time,
|
|
85
|
+
source: "append_message",
|
|
86
|
+
message,
|
|
87
|
+
toolStepUuids: [ev.uuid]
|
|
88
|
+
};
|
|
89
|
+
messages.push(projected);
|
|
90
|
+
openSteps.set(ev.uuid, projected);
|
|
91
|
+
} else if (ev.type === "content.part") {
|
|
92
|
+
const projected = openSteps.get(ev.stepUuid);
|
|
93
|
+
if (projected !== undefined) {
|
|
94
|
+
projected.message.content.push(ev.part);
|
|
95
|
+
}
|
|
96
|
+
} else if (ev.type === "tool.call") {
|
|
97
|
+
const projected = openSteps.get(ev.stepUuid);
|
|
98
|
+
if (projected !== undefined) {
|
|
99
|
+
const args = typeof ev.args === "string" ? ev.args : ev.args === undefined ? null : JSON.stringify(ev.args);
|
|
100
|
+
projected.message.toolCalls.push({
|
|
101
|
+
type: "function",
|
|
102
|
+
id: ev.toolCallId,
|
|
103
|
+
name: ev.name,
|
|
104
|
+
arguments: args
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} else if (ev.type === "step.end") {
|
|
108
|
+
openSteps.delete(ev.uuid);
|
|
109
|
+
} else if (ev.type === "tool.result") {
|
|
110
|
+
const output = ev.result.output;
|
|
111
|
+
const content = typeof output === "string" ? [{ type: "text", text: output }] : output;
|
|
112
|
+
const toolMsg = {
|
|
113
|
+
role: "tool",
|
|
114
|
+
content,
|
|
115
|
+
toolCalls: [],
|
|
116
|
+
toolCallId: ev.toolCallId,
|
|
117
|
+
...ev.result.isError === true ? { isError: true } : {}
|
|
118
|
+
};
|
|
119
|
+
messages.push({
|
|
120
|
+
lineNo: entry.lineNo,
|
|
121
|
+
time: rec.time,
|
|
122
|
+
source: "append_message",
|
|
123
|
+
message: toolMsg,
|
|
124
|
+
toolStepUuids: []
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "context.clear":
|
|
130
|
+
messages = [];
|
|
131
|
+
openSteps = new Map;
|
|
132
|
+
break;
|
|
133
|
+
case "context.apply_compaction":
|
|
134
|
+
openSteps = new Map;
|
|
135
|
+
messages = [
|
|
136
|
+
{
|
|
137
|
+
lineNo: entry.lineNo,
|
|
138
|
+
time: rec.time,
|
|
139
|
+
source: "compaction_summary",
|
|
140
|
+
message: {
|
|
141
|
+
role: "assistant",
|
|
142
|
+
content: [{ type: "text", text: rec.summary }],
|
|
143
|
+
toolCalls: [],
|
|
144
|
+
origin: { kind: "compaction_summary" }
|
|
145
|
+
},
|
|
146
|
+
toolStepUuids: []
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
break;
|
|
150
|
+
case "usage.record": {
|
|
151
|
+
const scope = rec.usageScope === "turn" ? "turn" : "session";
|
|
152
|
+
addUsage(usage.byScope[scope], rec.usage);
|
|
153
|
+
usage.byModel[rec.model] ??= { ...ZERO };
|
|
154
|
+
addUsage(usage.byModel[rec.model], rec.usage);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "config.update": {
|
|
158
|
+
const typeChecked = rec;
|
|
159
|
+
if (typeChecked.cwd !== undefined)
|
|
160
|
+
config.cwd = typeChecked.cwd;
|
|
161
|
+
if (typeChecked.modelAlias !== undefined)
|
|
162
|
+
config.modelAlias = typeChecked.modelAlias;
|
|
163
|
+
if (typeChecked.profileName !== undefined)
|
|
164
|
+
config.profileName = typeChecked.profileName;
|
|
165
|
+
if (typeChecked.thinkingLevel !== undefined)
|
|
166
|
+
config.thinkingLevel = typeChecked.thinkingLevel;
|
|
167
|
+
if (typeChecked.systemPrompt !== undefined)
|
|
168
|
+
config.systemPrompt = typeChecked.systemPrompt;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "permission.set_mode":
|
|
172
|
+
permissionMode = rec.mode;
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
messages,
|
|
180
|
+
usage,
|
|
181
|
+
config,
|
|
182
|
+
permission: { mode: permissionMode }
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function addUsage(into, src) {
|
|
186
|
+
into.inputOther += src.inputOther;
|
|
187
|
+
into.output += src.output;
|
|
188
|
+
into.inputCacheRead += src.inputCacheRead;
|
|
189
|
+
into.inputCacheCreation += src.inputCacheCreation;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/lib/session-store.ts
|
|
193
|
+
import { createReadStream } from "node:fs";
|
|
194
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
195
|
+
import { join as join2, resolve, sep } from "node:path";
|
|
196
|
+
import { createInterface } from "node:readline";
|
|
197
|
+
var SESSION_ID_RE = /^session_[A-Za-z0-9._-]+$/;
|
|
198
|
+
var AGENT_ID_RE = /^[A-Za-z0-9._-]+$/;
|
|
199
|
+
function isSafeAgentId(id) {
|
|
200
|
+
return AGENT_ID_RE.test(id) && id !== "." && id !== "..";
|
|
201
|
+
}
|
|
202
|
+
async function listSessions(home) {
|
|
203
|
+
const sessionsDir = join2(home, "sessions");
|
|
204
|
+
const buckets = await readdir(sessionsDir, { withFileTypes: true }).catch(() => []);
|
|
205
|
+
const index = await readSessionIndex(home);
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const bucket of buckets) {
|
|
208
|
+
if (!bucket.isDirectory())
|
|
209
|
+
continue;
|
|
210
|
+
const bucketDir = join2(sessionsDir, bucket.name);
|
|
211
|
+
const sessionDirs = await readdir(bucketDir, { withFileTypes: true }).catch(() => []);
|
|
212
|
+
for (const entry of sessionDirs) {
|
|
213
|
+
if (!entry.isDirectory() || !SESSION_ID_RE.test(entry.name))
|
|
214
|
+
continue;
|
|
215
|
+
const sessionDir = join2(bucketDir, entry.name);
|
|
216
|
+
const workDir = index.get(entry.name)?.workDir ?? "";
|
|
217
|
+
const summary = await tryReadSummary(sessionDir, entry.name, workDir);
|
|
218
|
+
if (summary !== null)
|
|
219
|
+
out.push(summary);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
out.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
async function readSessionDetail(home, sessionId) {
|
|
226
|
+
const sessionDir = await findSessionDir(home, sessionId);
|
|
227
|
+
if (sessionDir === null)
|
|
228
|
+
return null;
|
|
229
|
+
const index = await readSessionIndex(home);
|
|
230
|
+
const workDir = index.get(sessionId)?.workDir ?? "";
|
|
231
|
+
const state = await readState(sessionDir);
|
|
232
|
+
if (state === null) {
|
|
233
|
+
const agents2 = await discoverAgentsFromDisk(sessionDir);
|
|
234
|
+
return { sessionId, sessionDir, workDir, state: null, agents: agents2 };
|
|
235
|
+
}
|
|
236
|
+
if (state.custom?.["imported_from_byf_cli"] === true)
|
|
237
|
+
return null;
|
|
238
|
+
const agents = await inventoryAgents(sessionDir, state);
|
|
239
|
+
return { sessionId, sessionDir, workDir, state, agents };
|
|
240
|
+
}
|
|
241
|
+
async function discoverAgentsFromDisk(sessionDir) {
|
|
242
|
+
const agentsDir = join2(sessionDir, "agents");
|
|
243
|
+
let entries;
|
|
244
|
+
try {
|
|
245
|
+
entries = await readdir(agentsDir, { withFileTypes: true });
|
|
246
|
+
} catch {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
const out = [];
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
if (!entry.isDirectory())
|
|
252
|
+
continue;
|
|
253
|
+
const id = entry.name;
|
|
254
|
+
if (!isSafeAgentId(id))
|
|
255
|
+
continue;
|
|
256
|
+
const wirePath = join2(agentsDir, id, "wire.jsonl");
|
|
257
|
+
const exists = await pathExists(wirePath);
|
|
258
|
+
let readable = exists;
|
|
259
|
+
let info = {
|
|
260
|
+
count: 0,
|
|
261
|
+
protocolVersion: null
|
|
262
|
+
};
|
|
263
|
+
if (exists) {
|
|
264
|
+
try {
|
|
265
|
+
info = await scanWire(wirePath);
|
|
266
|
+
} catch {
|
|
267
|
+
readable = false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
out.push({
|
|
271
|
+
agentId: id,
|
|
272
|
+
type: id === "main" ? "main" : "independent",
|
|
273
|
+
parentAgentId: null,
|
|
274
|
+
homedir: join2(agentsDir, id),
|
|
275
|
+
wireExists: readable,
|
|
276
|
+
wireRecordCount: info.count,
|
|
277
|
+
wireProtocolVersion: info.protocolVersion
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return out.toSorted((a, b) => {
|
|
281
|
+
if (a.agentId === "main")
|
|
282
|
+
return -1;
|
|
283
|
+
if (b.agentId === "main")
|
|
284
|
+
return 1;
|
|
285
|
+
return a.agentId.localeCompare(b.agentId);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function tryReadSummary(sessionDir, sessionId, workDir) {
|
|
289
|
+
const state = await readState(sessionDir);
|
|
290
|
+
if (state === null) {
|
|
291
|
+
return brokenStateSummary(sessionDir, sessionId, workDir);
|
|
292
|
+
}
|
|
293
|
+
if (state.custom?.["imported_from_byf_cli"] === true)
|
|
294
|
+
return null;
|
|
295
|
+
const mainWirePath = join2(sessionDir, "agents", "main", "wire.jsonl");
|
|
296
|
+
const mainExists = await pathExists(mainWirePath);
|
|
297
|
+
let mainCount = 0;
|
|
298
|
+
let protocolVersion = null;
|
|
299
|
+
let health = "ok";
|
|
300
|
+
if (!mainExists) {
|
|
301
|
+
health = "missing_main_wire";
|
|
302
|
+
} else {
|
|
303
|
+
try {
|
|
304
|
+
const info = await scanWire(mainWirePath);
|
|
305
|
+
mainCount = info.count;
|
|
306
|
+
protocolVersion = info.protocolVersion;
|
|
307
|
+
} catch {
|
|
308
|
+
health = "broken_main_wire";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
sessionId,
|
|
313
|
+
sessionDir,
|
|
314
|
+
workDir,
|
|
315
|
+
title: state.title ?? null,
|
|
316
|
+
lastPrompt: state.lastPrompt ?? null,
|
|
317
|
+
isCustomTitle: state.isCustomTitle ?? false,
|
|
318
|
+
createdAt: parseTs(state.createdAt),
|
|
319
|
+
updatedAt: parseTs(state.updatedAt),
|
|
320
|
+
agentCount: Object.keys(state.agents ?? {}).length,
|
|
321
|
+
mainAgentExists: mainExists,
|
|
322
|
+
mainWireRecordCount: mainCount,
|
|
323
|
+
wireProtocolVersion: protocolVersion,
|
|
324
|
+
health
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function brokenStateSummary(sessionDir, sessionId, workDir) {
|
|
328
|
+
return {
|
|
329
|
+
sessionId,
|
|
330
|
+
sessionDir,
|
|
331
|
+
workDir,
|
|
332
|
+
title: null,
|
|
333
|
+
lastPrompt: null,
|
|
334
|
+
isCustomTitle: false,
|
|
335
|
+
createdAt: 0,
|
|
336
|
+
updatedAt: 0,
|
|
337
|
+
agentCount: 0,
|
|
338
|
+
mainAgentExists: false,
|
|
339
|
+
mainWireRecordCount: 0,
|
|
340
|
+
wireProtocolVersion: null,
|
|
341
|
+
health: "broken_state"
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
async function readSessionIndex(home) {
|
|
345
|
+
const out = new Map;
|
|
346
|
+
let raw;
|
|
347
|
+
try {
|
|
348
|
+
raw = await readFile(join2(home, "session_index.jsonl"), "utf8");
|
|
349
|
+
} catch {
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
353
|
+
if (!line.trim())
|
|
354
|
+
continue;
|
|
355
|
+
try {
|
|
356
|
+
const entry = JSON.parse(line);
|
|
357
|
+
if (typeof entry.sessionId === "string" && typeof entry.sessionDir === "string") {
|
|
358
|
+
out.set(entry.sessionId, {
|
|
359
|
+
sessionDir: entry.sessionDir,
|
|
360
|
+
workDir: typeof entry.workDir === "string" ? entry.workDir : ""
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
} catch {}
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
async function inventoryAgents(sessionDir, state) {
|
|
368
|
+
const result = [];
|
|
369
|
+
for (const [id, meta] of Object.entries(state.agents ?? {})) {
|
|
370
|
+
if (!isSafeAgentId(id))
|
|
371
|
+
continue;
|
|
372
|
+
const wirePath = join2(sessionDir, "agents", id, "wire.jsonl");
|
|
373
|
+
const exists = await pathExists(wirePath);
|
|
374
|
+
let readable = exists;
|
|
375
|
+
let info = {
|
|
376
|
+
count: 0,
|
|
377
|
+
protocolVersion: null
|
|
378
|
+
};
|
|
379
|
+
if (exists) {
|
|
380
|
+
try {
|
|
381
|
+
info = await scanWire(wirePath);
|
|
382
|
+
} catch {
|
|
383
|
+
readable = false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
result.push({
|
|
387
|
+
agentId: id,
|
|
388
|
+
type: meta.type,
|
|
389
|
+
parentAgentId: meta.parentAgentId,
|
|
390
|
+
homedir: meta.homedir,
|
|
391
|
+
wireExists: readable,
|
|
392
|
+
wireRecordCount: info.count,
|
|
393
|
+
wireProtocolVersion: info.protocolVersion
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return result.toSorted((a, b) => {
|
|
397
|
+
if (a.agentId === "main")
|
|
398
|
+
return -1;
|
|
399
|
+
if (b.agentId === "main")
|
|
400
|
+
return 1;
|
|
401
|
+
return a.agentId.localeCompare(b.agentId);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
async function readState(sessionDir) {
|
|
405
|
+
try {
|
|
406
|
+
return JSON.parse(await readFile(join2(sessionDir, "state.json"), "utf8"));
|
|
407
|
+
} catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function findSessionDir(home, sessionId) {
|
|
412
|
+
if (!SESSION_ID_RE.test(sessionId))
|
|
413
|
+
return null;
|
|
414
|
+
const sessionsRoot = resolve(join2(home, "sessions"));
|
|
415
|
+
const sessionsRootPrefix = sessionsRoot + sep;
|
|
416
|
+
try {
|
|
417
|
+
const indexLines = (await readFile(join2(home, "session_index.jsonl"), "utf8")).split(/\r?\n/);
|
|
418
|
+
for (const line of indexLines) {
|
|
419
|
+
if (!line.trim())
|
|
420
|
+
continue;
|
|
421
|
+
const entry = JSON.parse(line);
|
|
422
|
+
if (entry.sessionId !== sessionId || typeof entry.sessionDir !== "string")
|
|
423
|
+
continue;
|
|
424
|
+
const candidate = resolve(entry.sessionDir);
|
|
425
|
+
if (!candidate.startsWith(sessionsRootPrefix))
|
|
426
|
+
continue;
|
|
427
|
+
if (candidate.split(sep).pop() !== sessionId)
|
|
428
|
+
continue;
|
|
429
|
+
if (await pathExists(candidate))
|
|
430
|
+
return candidate;
|
|
431
|
+
}
|
|
432
|
+
} catch {}
|
|
433
|
+
const buckets = await readdir(sessionsRoot, { withFileTypes: true }).catch(() => []);
|
|
434
|
+
for (const bucket of buckets) {
|
|
435
|
+
if (!bucket.isDirectory())
|
|
436
|
+
continue;
|
|
437
|
+
const candidate = join2(sessionsRoot, bucket.name, sessionId);
|
|
438
|
+
if (await pathExists(candidate))
|
|
439
|
+
return candidate;
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
async function scanWire(path) {
|
|
444
|
+
const stream = createReadStream(path, { encoding: "utf8" });
|
|
445
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
446
|
+
let count = 0;
|
|
447
|
+
let protocolVersion = null;
|
|
448
|
+
for await (const line of rl) {
|
|
449
|
+
if (line.length === 0)
|
|
450
|
+
continue;
|
|
451
|
+
if (protocolVersion === null) {
|
|
452
|
+
let parsed;
|
|
453
|
+
try {
|
|
454
|
+
parsed = JSON.parse(line);
|
|
455
|
+
} catch {
|
|
456
|
+
throw new Error(`wire metadata is not valid JSON at line 1`);
|
|
457
|
+
}
|
|
458
|
+
if (parsed.type !== "metadata" || typeof parsed.protocol_version !== "string") {
|
|
459
|
+
throw new Error(`wire is missing a metadata header on line 1`);
|
|
460
|
+
}
|
|
461
|
+
protocolVersion = parsed.protocol_version;
|
|
462
|
+
}
|
|
463
|
+
count += 1;
|
|
464
|
+
}
|
|
465
|
+
if (protocolVersion === null) {
|
|
466
|
+
throw new Error("wire file is empty");
|
|
467
|
+
}
|
|
468
|
+
return { count, protocolVersion };
|
|
469
|
+
}
|
|
470
|
+
function parseTs(input) {
|
|
471
|
+
if (!input)
|
|
472
|
+
return 0;
|
|
473
|
+
const n = Date.parse(input);
|
|
474
|
+
return Number.isFinite(n) ? n : 0;
|
|
475
|
+
}
|
|
476
|
+
async function pathExists(p) {
|
|
477
|
+
try {
|
|
478
|
+
await stat(p);
|
|
479
|
+
return true;
|
|
480
|
+
} catch {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/lib/wire-reader.ts
|
|
486
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
487
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
488
|
+
|
|
489
|
+
// ../../../packages/agent-core/src/agent/records/migration/v1.1.ts
|
|
490
|
+
function isLegacyToolCall(v) {
|
|
491
|
+
if (!isRecord(v))
|
|
492
|
+
return false;
|
|
493
|
+
return v["type"] === "function" && typeof v["id"] === "string" && isRecord(v["function"]);
|
|
494
|
+
}
|
|
495
|
+
function migrateToolCall(v) {
|
|
496
|
+
const { function: fn, ...rest } = v;
|
|
497
|
+
return {
|
|
498
|
+
...rest,
|
|
499
|
+
name: fn.name,
|
|
500
|
+
arguments: fn.arguments
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function isRecord(value) {
|
|
504
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
505
|
+
}
|
|
506
|
+
var migrateV1_0ToV1_1 = {
|
|
507
|
+
sourceVersion: "1.0",
|
|
508
|
+
targetVersion: "1.1",
|
|
509
|
+
migrateRecord(record) {
|
|
510
|
+
if (record.type !== "context.append_message")
|
|
511
|
+
return record;
|
|
512
|
+
const message = record["message"];
|
|
513
|
+
let changed = false;
|
|
514
|
+
const toolCalls = message.toolCalls.map((toolCall) => {
|
|
515
|
+
if (!isLegacyToolCall(toolCall))
|
|
516
|
+
return toolCall;
|
|
517
|
+
changed = true;
|
|
518
|
+
return migrateToolCall(toolCall);
|
|
519
|
+
});
|
|
520
|
+
if (!changed)
|
|
521
|
+
return record;
|
|
522
|
+
return {
|
|
523
|
+
...record,
|
|
524
|
+
message: {
|
|
525
|
+
...message,
|
|
526
|
+
toolCalls
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// ../../../packages/agent-core/src/agent/records/migration/index.ts
|
|
533
|
+
var AGENT_WIRE_PROTOCOL_VERSION = "1.1";
|
|
534
|
+
var MIGRATIONS = [migrateV1_0ToV1_1];
|
|
535
|
+
function isNewerWireVersion(readVersion) {
|
|
536
|
+
return compareWireVersions(readVersion, AGENT_WIRE_PROTOCOL_VERSION) > 0;
|
|
537
|
+
}
|
|
538
|
+
function resolveWireMigrations(readVersion) {
|
|
539
|
+
if (compareWireVersions(readVersion, AGENT_WIRE_PROTOCOL_VERSION) >= 0) {
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
const migrations = [];
|
|
543
|
+
let version = readVersion;
|
|
544
|
+
while (compareWireVersions(version, AGENT_WIRE_PROTOCOL_VERSION) < 0) {
|
|
545
|
+
const migration = findMigration(version);
|
|
546
|
+
if (migration === undefined) {
|
|
547
|
+
throw new Error(`Missing wire migration for version ${version}`);
|
|
548
|
+
}
|
|
549
|
+
migrations.push(migration);
|
|
550
|
+
version = migration.targetVersion;
|
|
551
|
+
}
|
|
552
|
+
return migrations;
|
|
553
|
+
}
|
|
554
|
+
function migrateWireRecord(record, migrations) {
|
|
555
|
+
return migrations.reduce((current, migration) => migration.migrateRecord(current), record);
|
|
556
|
+
}
|
|
557
|
+
function findMigration(sourceVersion) {
|
|
558
|
+
for (const migration of MIGRATIONS) {
|
|
559
|
+
if (migration.sourceVersion === sourceVersion)
|
|
560
|
+
return migration;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function compareWireVersions(a, b) {
|
|
564
|
+
const partsA = a.split(".");
|
|
565
|
+
const partsB = b.split(".");
|
|
566
|
+
const maxLength = Math.max(partsA.length, partsB.length);
|
|
567
|
+
for (let i = 0;i < maxLength; i++) {
|
|
568
|
+
const diff = Number(partsA[i] ?? "0") - Number(partsB[i] ?? "0");
|
|
569
|
+
if (diff !== 0)
|
|
570
|
+
return diff;
|
|
571
|
+
}
|
|
572
|
+
return 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/lib/wire-reader.ts
|
|
576
|
+
function bestEffortMigrations() {
|
|
577
|
+
try {
|
|
578
|
+
return resolveWireMigrations("1.0");
|
|
579
|
+
} catch {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function readAgentWire(path) {
|
|
584
|
+
const stream = createReadStream2(path, { encoding: "utf8" });
|
|
585
|
+
const rl = createInterface2({ input: stream, crlfDelay: Infinity });
|
|
586
|
+
let lineNo = 0;
|
|
587
|
+
let metadata = null;
|
|
588
|
+
let migrations = [];
|
|
589
|
+
const records = [];
|
|
590
|
+
const warnings = [];
|
|
591
|
+
for await (const line of rl) {
|
|
592
|
+
lineNo += 1;
|
|
593
|
+
if (line.length === 0)
|
|
594
|
+
continue;
|
|
595
|
+
let parsed;
|
|
596
|
+
try {
|
|
597
|
+
parsed = JSON.parse(line);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
warnings.push(`line ${lineNo}: invalid JSON (${error.message})`);
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (!isObject(parsed) || typeof parsed["type"] !== "string") {
|
|
603
|
+
warnings.push(`line ${lineNo}: missing 'type' field`);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (metadata === null) {
|
|
607
|
+
if (parsed["type"] !== "metadata") {
|
|
608
|
+
throw new Error(`Wire file missing metadata header at line ${lineNo}`);
|
|
609
|
+
}
|
|
610
|
+
const pv = parsed["protocol_version"];
|
|
611
|
+
const ca = parsed["created_at"];
|
|
612
|
+
if (typeof pv !== "string" || typeof ca !== "number") {
|
|
613
|
+
throw new TypeError(`Wire metadata malformed at line ${lineNo}`);
|
|
614
|
+
}
|
|
615
|
+
if (isNewerWireVersion(pv)) {
|
|
616
|
+
warnings.push(`unrecognised protocol_version "${pv}" — parsing as best-effort (newer than supported)`);
|
|
617
|
+
migrations = bestEffortMigrations();
|
|
618
|
+
} else {
|
|
619
|
+
try {
|
|
620
|
+
migrations = resolveWireMigrations(pv);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
warnings.push(`unrecognised protocol_version "${pv}" — parsing as best-effort (${error.message})`);
|
|
623
|
+
migrations = bestEffortMigrations();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
metadata = { protocolVersion: pv, createdAt: ca };
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const raw = parsed;
|
|
630
|
+
let migrated;
|
|
631
|
+
try {
|
|
632
|
+
migrated = migrations.length === 0 ? raw : migrateWireRecord(raw, migrations);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
warnings.push(`line ${lineNo}: migration failed (${error.message}); using raw record`);
|
|
635
|
+
migrated = raw;
|
|
636
|
+
}
|
|
637
|
+
records.push({ lineNo, data: migrated, raw });
|
|
638
|
+
}
|
|
639
|
+
if (metadata === null) {
|
|
640
|
+
throw new Error("Wire file is empty (no metadata)");
|
|
641
|
+
}
|
|
642
|
+
return { metadata, records, warnings };
|
|
643
|
+
}
|
|
644
|
+
function isObject(v) {
|
|
645
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/routes/context.ts
|
|
649
|
+
function contextRoute() {
|
|
650
|
+
const r = new Hono;
|
|
651
|
+
r.get("/:id/context", async (c) => {
|
|
652
|
+
const id = c.req.param("id");
|
|
653
|
+
const agentId = c.req.query("agent") ?? "main";
|
|
654
|
+
if (!isSafeAgentId(agentId)) {
|
|
655
|
+
return c.json({ error: "invalid agent id", code: "BAD_REQUEST" }, 400);
|
|
656
|
+
}
|
|
657
|
+
const detail = await readSessionDetail(resolveByfHome(), id);
|
|
658
|
+
if (!detail) {
|
|
659
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
660
|
+
}
|
|
661
|
+
const agent = detail.agents.find((a) => a.agentId === agentId);
|
|
662
|
+
if (!agent || !agent.wireExists) {
|
|
663
|
+
return c.json({ error: "agent wire not found", code: "NOT_FOUND" }, 404);
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const wire = await readAgentWire(join3(detail.sessionDir, "agents", agentId, "wire.jsonl"));
|
|
667
|
+
const proj = projectContext(wire.records);
|
|
668
|
+
return c.json({
|
|
669
|
+
sessionId: id,
|
|
670
|
+
agentId,
|
|
671
|
+
messages: proj.messages,
|
|
672
|
+
usage: proj.usage,
|
|
673
|
+
config: proj.config,
|
|
674
|
+
permission: proj.permission
|
|
675
|
+
});
|
|
676
|
+
} catch (error) {
|
|
677
|
+
const msg = error.message;
|
|
678
|
+
if (msg.toLowerCase().includes("unsupported protocol")) {
|
|
679
|
+
return c.json({ error: msg, code: "UNSUPPORTED_PROTOCOL" }, 400);
|
|
680
|
+
}
|
|
681
|
+
return c.json({ error: msg, code: "READ_ERROR" }, 500);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
return r;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/routes/session-detail.ts
|
|
688
|
+
import { Hono as Hono2 } from "hono";
|
|
689
|
+
function sessionDetailRoute() {
|
|
690
|
+
const r = new Hono2;
|
|
691
|
+
r.get("/:id", async (c) => {
|
|
692
|
+
const id = c.req.param("id");
|
|
693
|
+
const detail = await readSessionDetail(resolveByfHome(), id);
|
|
694
|
+
if (!detail)
|
|
695
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
696
|
+
return c.json(detail);
|
|
697
|
+
});
|
|
698
|
+
return r;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/routes/sessions.ts
|
|
702
|
+
import { rm } from "node:fs/promises";
|
|
703
|
+
import { Hono as Hono3 } from "hono";
|
|
704
|
+
|
|
705
|
+
// src/lib/reveal.ts
|
|
706
|
+
import { spawn } from "node:child_process";
|
|
707
|
+
function revealCommandFor(path, platform = process.platform) {
|
|
708
|
+
switch (platform) {
|
|
709
|
+
case "darwin":
|
|
710
|
+
return { command: "open", args: [path] };
|
|
711
|
+
case "win32":
|
|
712
|
+
return { command: "cmd", args: ["/c", "start", '""', path] };
|
|
713
|
+
default:
|
|
714
|
+
return { command: "xdg-open", args: [path] };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function revealInOs(path) {
|
|
718
|
+
const { command, args } = revealCommandFor(path);
|
|
719
|
+
return new Promise((resolve2, reject) => {
|
|
720
|
+
let settled = false;
|
|
721
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
722
|
+
child.once("error", (err) => {
|
|
723
|
+
if (settled)
|
|
724
|
+
return;
|
|
725
|
+
settled = true;
|
|
726
|
+
reject(err);
|
|
727
|
+
});
|
|
728
|
+
child.once("spawn", () => {
|
|
729
|
+
if (settled)
|
|
730
|
+
return;
|
|
731
|
+
settled = true;
|
|
732
|
+
child.unref();
|
|
733
|
+
resolve2();
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/routes/sessions.ts
|
|
739
|
+
function sessionsRoute() {
|
|
740
|
+
const r = new Hono3;
|
|
741
|
+
r.get("/", async (c) => {
|
|
742
|
+
const sessions = await listSessions(resolveByfHome());
|
|
743
|
+
return c.json({ sessions });
|
|
744
|
+
});
|
|
745
|
+
r.delete("/:id", async (c) => {
|
|
746
|
+
const id = c.req.param("id");
|
|
747
|
+
const all = await listSessions(resolveByfHome());
|
|
748
|
+
const target = all.find((s) => s.sessionId === id);
|
|
749
|
+
if (!target)
|
|
750
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
751
|
+
await rm(target.sessionDir, { recursive: true, force: true });
|
|
752
|
+
return c.json({ sessionId: id, deleted: true });
|
|
753
|
+
});
|
|
754
|
+
r.post("/:id/reveal", async (c) => {
|
|
755
|
+
const id = c.req.param("id");
|
|
756
|
+
const detail = await readSessionDetail(resolveByfHome(), id);
|
|
757
|
+
if (!detail)
|
|
758
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
759
|
+
try {
|
|
760
|
+
await revealInOs(detail.sessionDir);
|
|
761
|
+
return c.json({ sessionId: id, opened: detail.sessionDir });
|
|
762
|
+
} catch (error) {
|
|
763
|
+
return c.json({ error: `failed to open: ${error.message}`, code: "READ_ERROR" }, 500);
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
return r;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/routes/subagents.ts
|
|
770
|
+
import { Hono as Hono4 } from "hono";
|
|
771
|
+
|
|
772
|
+
// src/lib/agent-tree.ts
|
|
773
|
+
function buildAgentTree(agents) {
|
|
774
|
+
const byId = new Map;
|
|
775
|
+
for (const a of agents)
|
|
776
|
+
byId.set(a.agentId, { ...a, children: [] });
|
|
777
|
+
const roots = [];
|
|
778
|
+
for (const node of byId.values()) {
|
|
779
|
+
if (node.parentAgentId !== null && byId.has(node.parentAgentId)) {
|
|
780
|
+
byId.get(node.parentAgentId).children.push(node);
|
|
781
|
+
} else {
|
|
782
|
+
roots.push(node);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return roots.toSorted(sortAgents);
|
|
786
|
+
}
|
|
787
|
+
function sortAgents(a, b) {
|
|
788
|
+
if (a.agentId === "main")
|
|
789
|
+
return -1;
|
|
790
|
+
if (b.agentId === "main")
|
|
791
|
+
return 1;
|
|
792
|
+
return a.agentId.localeCompare(b.agentId);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/routes/subagents.ts
|
|
796
|
+
function subagentsRoute() {
|
|
797
|
+
const r = new Hono4;
|
|
798
|
+
r.get("/:id/agents", async (c) => {
|
|
799
|
+
const id = c.req.param("id");
|
|
800
|
+
const detail = await readSessionDetail(resolveByfHome(), id);
|
|
801
|
+
if (!detail) {
|
|
802
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
803
|
+
}
|
|
804
|
+
return c.json({ sessionId: id, tree: buildAgentTree(detail.agents) });
|
|
805
|
+
});
|
|
806
|
+
return r;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/routes/wire.ts
|
|
810
|
+
import { join as join4 } from "node:path";
|
|
811
|
+
import { Hono as Hono5 } from "hono";
|
|
812
|
+
function wireRoute() {
|
|
813
|
+
const r = new Hono5;
|
|
814
|
+
r.get("/:id/wire", async (c) => {
|
|
815
|
+
const id = c.req.param("id");
|
|
816
|
+
const agentId = c.req.query("agent") ?? "main";
|
|
817
|
+
if (!isSafeAgentId(agentId)) {
|
|
818
|
+
return c.json({ error: "invalid agent id", code: "BAD_REQUEST" }, 400);
|
|
819
|
+
}
|
|
820
|
+
const detail = await readSessionDetail(resolveByfHome(), id);
|
|
821
|
+
if (!detail) {
|
|
822
|
+
return c.json({ error: "session not found", code: "NOT_FOUND" }, 404);
|
|
823
|
+
}
|
|
824
|
+
const agent = detail.agents.find((a) => a.agentId === agentId);
|
|
825
|
+
if (!agent) {
|
|
826
|
+
return c.json({ error: `agent "${agentId}" not found`, code: "NOT_FOUND" }, 404);
|
|
827
|
+
}
|
|
828
|
+
if (!agent.wireExists) {
|
|
829
|
+
return c.json({ error: "wire missing", code: "NOT_FOUND" }, 404);
|
|
830
|
+
}
|
|
831
|
+
try {
|
|
832
|
+
const result = await readAgentWire(join4(detail.sessionDir, "agents", agentId, "wire.jsonl"));
|
|
833
|
+
return c.json({
|
|
834
|
+
sessionId: id,
|
|
835
|
+
agentId,
|
|
836
|
+
protocolVersion: result.metadata.protocolVersion,
|
|
837
|
+
metadata: result.metadata,
|
|
838
|
+
records: result.records,
|
|
839
|
+
warnings: result.warnings
|
|
840
|
+
});
|
|
841
|
+
} catch (error) {
|
|
842
|
+
const msg = error.message;
|
|
843
|
+
if (msg.toLowerCase().includes("unsupported protocol")) {
|
|
844
|
+
return c.json({ error: msg, code: "UNSUPPORTED_PROTOCOL" }, 400);
|
|
845
|
+
}
|
|
846
|
+
return c.json({ error: msg, code: "READ_ERROR" }, 500);
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
return r;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/app.ts
|
|
853
|
+
async function resolvePublicDir() {
|
|
854
|
+
try {
|
|
855
|
+
const here = import.meta.dirname;
|
|
856
|
+
const candidate = resolve2(here, "public");
|
|
857
|
+
const s = await stat2(candidate);
|
|
858
|
+
if (s.isDirectory())
|
|
859
|
+
return candidate;
|
|
860
|
+
} catch {}
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
var STATIC_EXT_MIME = {
|
|
864
|
+
".html": "text/html; charset=utf-8",
|
|
865
|
+
".js": "application/javascript; charset=utf-8",
|
|
866
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
867
|
+
".css": "text/css; charset=utf-8",
|
|
868
|
+
".json": "application/json; charset=utf-8",
|
|
869
|
+
".svg": "image/svg+xml",
|
|
870
|
+
".png": "image/png",
|
|
871
|
+
".jpg": "image/jpeg",
|
|
872
|
+
".jpeg": "image/jpeg",
|
|
873
|
+
".gif": "image/gif",
|
|
874
|
+
".ico": "image/x-icon",
|
|
875
|
+
".woff": "font/woff",
|
|
876
|
+
".woff2": "font/woff2",
|
|
877
|
+
".ttf": "font/ttf",
|
|
878
|
+
".map": "application/json; charset=utf-8"
|
|
879
|
+
};
|
|
880
|
+
function mimeFor(path) {
|
|
881
|
+
const i = path.lastIndexOf(".");
|
|
882
|
+
if (i < 0)
|
|
883
|
+
return "application/octet-stream";
|
|
884
|
+
const ext = path.slice(i).toLowerCase();
|
|
885
|
+
return STATIC_EXT_MIME[ext] ?? "application/octet-stream";
|
|
886
|
+
}
|
|
887
|
+
function bearerToken(value) {
|
|
888
|
+
if (value === undefined)
|
|
889
|
+
return null;
|
|
890
|
+
const match = /^Bearer\s+(.+)$/i.exec(value);
|
|
891
|
+
return match?.[1]?.trim() ?? null;
|
|
892
|
+
}
|
|
893
|
+
function tokenMatches(actual, expected) {
|
|
894
|
+
const actualBytes = Buffer.from(actual);
|
|
895
|
+
const expectedBytes = Buffer.from(expected);
|
|
896
|
+
return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
|
|
897
|
+
}
|
|
898
|
+
async function createApp(options = {}) {
|
|
899
|
+
const app = new Hono6;
|
|
900
|
+
const api = new Hono6;
|
|
901
|
+
const authToken = options.authToken;
|
|
902
|
+
if (authToken !== undefined && authToken.length > 0) {
|
|
903
|
+
api.use("*", async (c, next) => {
|
|
904
|
+
const token = bearerToken(c.req.header("authorization"));
|
|
905
|
+
if (token !== null && tokenMatches(token, authToken)) {
|
|
906
|
+
await next();
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
c.header("www-authenticate", 'Bearer realm="byf-vis"');
|
|
910
|
+
return c.json({ error: "unauthorized", code: "UNAUTHORIZED" }, 401);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
api.route("/sessions", sessionsRoute());
|
|
914
|
+
api.route("/sessions", sessionDetailRoute());
|
|
915
|
+
api.route("/sessions", wireRoute());
|
|
916
|
+
api.route("/sessions", subagentsRoute());
|
|
917
|
+
api.route("/sessions", contextRoute());
|
|
918
|
+
app.route("/api", api);
|
|
919
|
+
const publicDir = options.publicDir ?? await resolvePublicDir();
|
|
920
|
+
if (publicDir !== null) {
|
|
921
|
+
app.get("*", async (c) => {
|
|
922
|
+
const url = new URL(c.req.url);
|
|
923
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
924
|
+
if (pathname.startsWith("/api")) {
|
|
925
|
+
return c.json({ error: `api route not found: ${pathname}`, code: "NOT_FOUND" }, 404);
|
|
926
|
+
}
|
|
927
|
+
if (pathname === "/" || pathname === "")
|
|
928
|
+
pathname = "/index.html";
|
|
929
|
+
const resolved = resolve2(publicDir, `.${pathname}`);
|
|
930
|
+
if (!resolved.startsWith(publicDir)) {
|
|
931
|
+
return c.text("forbidden", 403);
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
const s = await stat2(resolved);
|
|
935
|
+
if (s.isFile()) {
|
|
936
|
+
const buf = await readFile2(resolved);
|
|
937
|
+
const body = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
938
|
+
return new Response(body, {
|
|
939
|
+
headers: { "content-type": mimeFor(resolved) }
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
} catch {}
|
|
943
|
+
try {
|
|
944
|
+
const indexHtml = await readFile2(join5(publicDir, "index.html"));
|
|
945
|
+
const body = new Uint8Array(indexHtml.buffer, indexHtml.byteOffset, indexHtml.byteLength);
|
|
946
|
+
return new Response(body, {
|
|
947
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
948
|
+
});
|
|
949
|
+
} catch {
|
|
950
|
+
return c.text("not found", 404);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
return app;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/startup-banner.ts
|
|
958
|
+
function formatStartupBanner(options) {
|
|
959
|
+
const authStatus = options.authToken === undefined ? "auth=disabled" : "auth=required";
|
|
960
|
+
return `[vis-server] listening on http://${hostForUrl(options.host)}:${String(options.port)} ` + `(${authStatus}, BYF_HOME=${options.byfCodeHome})
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
function hostForUrl(host) {
|
|
964
|
+
if (host.includes(":") && !host.startsWith("["))
|
|
965
|
+
return `[${host}]`;
|
|
966
|
+
return host;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/server.ts
|
|
970
|
+
function hostForUrl2(host) {
|
|
971
|
+
if (host.includes(":") && !host.startsWith("["))
|
|
972
|
+
return `[${host}]`;
|
|
973
|
+
return host;
|
|
974
|
+
}
|
|
975
|
+
async function startVisServer(options = {}) {
|
|
976
|
+
const host = options.host ?? resolveHost();
|
|
977
|
+
const port = options.port ?? resolvePort();
|
|
978
|
+
const authToken = options.authToken ?? resolveVisAuthToken(host);
|
|
979
|
+
const app = await createApp({ authToken, publicDir: options.publicDir });
|
|
980
|
+
const server = Bun.serve({
|
|
981
|
+
hostname: host,
|
|
982
|
+
port,
|
|
983
|
+
fetch: app.fetch
|
|
984
|
+
});
|
|
985
|
+
const actualPort = server.port ?? port;
|
|
986
|
+
return {
|
|
987
|
+
host,
|
|
988
|
+
port: actualPort,
|
|
989
|
+
url: `http://${hostForUrl2(host)}:${actualPort}`,
|
|
990
|
+
close: () => {
|
|
991
|
+
server.stop(true);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function resolveVisByfHome() {
|
|
996
|
+
return resolveByfHome();
|
|
997
|
+
}
|
|
998
|
+
function formatVisStartupBanner(input) {
|
|
999
|
+
return formatStartupBanner({
|
|
1000
|
+
authToken: input.authToken,
|
|
1001
|
+
host: input.host,
|
|
1002
|
+
byfCodeHome: resolveByfHome(),
|
|
1003
|
+
port: input.port
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
export { resolvePort, resolveHost, resolveVisAuthToken, startVisServer, resolveVisByfHome, formatVisStartupBanner };
|
|
1008
|
+
|
|
1009
|
+
//# debugId=8EBCC0393347A59C64756E2164756E21
|
|
1010
|
+
//# sourceMappingURL=server-enpsx6np.mjs.map
|