@ailog/cli 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -11
- package/dist/client/assets/index-BBTm7Mxn.js +160 -0
- package/dist/client/assets/index-x-B7u97h.css +1 -0
- package/dist/client/index.html +4 -4
- package/dist/index.d.ts +35 -57
- package/dist/index.js +153 -81
- package/dist/viewer/server.js +117 -64
- package/package.json +1 -1
- package/dist/client/assets/index-BMqO0rJT.js +0 -190
- package/dist/client/assets/index-CdQ1JjwT.css +0 -1
package/dist/index.js
CHANGED
|
@@ -2,10 +2,36 @@
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
var DB_DIR = path.join(process.cwd(), ".ailog");
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
5
|
+
var RUNS_DIR = () => path.join(DB_DIR, "runs");
|
|
6
|
+
var BLOBS_DIR = () => path.join(DB_DIR, "blobs");
|
|
7
|
+
var RUN_INDEX_DIR = () => path.join(DB_DIR, "index", "runs");
|
|
8
|
+
var THREAD_INDEX_DIR = () => path.join(DB_DIR, "index", "threads");
|
|
9
|
+
var PAGE_SIZE = 50;
|
|
8
10
|
var AILOG_PORT = process.env.AILOG_PORT ? parseInt(process.env.AILOG_PORT) : 4985;
|
|
11
|
+
var dirResolved = false;
|
|
12
|
+
var dirResolvePromise = null;
|
|
13
|
+
var resolveDbDir = async () => {
|
|
14
|
+
if (dirResolved) return;
|
|
15
|
+
if (!dirResolvePromise) {
|
|
16
|
+
dirResolvePromise = (async () => {
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch(
|
|
19
|
+
`http://localhost:${AILOG_PORT}/api/db-dir`
|
|
20
|
+
);
|
|
21
|
+
if (res.ok) {
|
|
22
|
+
const body = await res.json();
|
|
23
|
+
if (typeof body.dir === "string" && body.dir.length > 0) {
|
|
24
|
+
DB_DIR = body.dir;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
} finally {
|
|
29
|
+
dirResolved = true;
|
|
30
|
+
}
|
|
31
|
+
})();
|
|
32
|
+
}
|
|
33
|
+
await dirResolvePromise;
|
|
34
|
+
};
|
|
9
35
|
var BLOB_THRESHOLD = 16 * 1024;
|
|
10
36
|
var notifyServer = (event, runId) => {
|
|
11
37
|
notifyServerAsync(event, runId);
|
|
@@ -18,29 +44,18 @@ var notifyServerAsync = async (event, runId) => {
|
|
|
18
44
|
body: JSON.stringify({
|
|
19
45
|
event,
|
|
20
46
|
timestamp: Date.now(),
|
|
21
|
-
dbPath: INDEX_PATH,
|
|
22
47
|
runId: runId ?? null
|
|
23
48
|
})
|
|
24
49
|
});
|
|
25
50
|
} catch {
|
|
26
51
|
}
|
|
27
52
|
};
|
|
53
|
+
var digitTimestamp = (date) => date.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
|
|
28
54
|
var generateRunId = () => {
|
|
29
|
-
const
|
|
30
|
-
const timestamp = now.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
|
|
55
|
+
const timestamp = digitTimestamp(/* @__PURE__ */ new Date());
|
|
31
56
|
const uniqueId = crypto.randomUUID().slice(0, 8);
|
|
32
57
|
return `${timestamp}-${uniqueId}`;
|
|
33
58
|
};
|
|
34
|
-
var EMPTY_INDEX = () => ({
|
|
35
|
-
version: 1,
|
|
36
|
-
runs: [],
|
|
37
|
-
threads: []
|
|
38
|
-
});
|
|
39
|
-
var normalizeIndex = (raw) => ({
|
|
40
|
-
version: 1,
|
|
41
|
-
runs: raw.runs ?? [],
|
|
42
|
-
threads: raw.threads ?? []
|
|
43
|
-
});
|
|
44
59
|
var ensureGitignore = () => {
|
|
45
60
|
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
46
61
|
if (!fs.existsSync(gitignorePath)) return;
|
|
@@ -56,8 +71,10 @@ var ensureGitignore = () => {
|
|
|
56
71
|
};
|
|
57
72
|
var ensureLayout = () => {
|
|
58
73
|
const isFirstRun = !fs.existsSync(DB_DIR);
|
|
59
|
-
fs.mkdirSync(RUNS_DIR, { recursive: true });
|
|
60
|
-
fs.mkdirSync(BLOBS_DIR, { recursive: true });
|
|
74
|
+
fs.mkdirSync(RUNS_DIR(), { recursive: true });
|
|
75
|
+
fs.mkdirSync(BLOBS_DIR(), { recursive: true });
|
|
76
|
+
fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
|
|
77
|
+
fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
|
|
61
78
|
if (isFirstRun) ensureGitignore();
|
|
62
79
|
};
|
|
63
80
|
var atomicWrite = (filePath, data) => {
|
|
@@ -86,34 +103,86 @@ var externalizeIfLarge = (value, stepId, field) => {
|
|
|
86
103
|
writeBlob(rel, value);
|
|
87
104
|
return { $blob: rel };
|
|
88
105
|
};
|
|
89
|
-
var indexCache = null;
|
|
90
106
|
var runCache = /* @__PURE__ */ new Map();
|
|
91
|
-
var
|
|
92
|
-
var
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
var pageCache = /* @__PURE__ */ new Map();
|
|
108
|
+
var PAGE_FILE_RE = /^\d{17}\.json$/;
|
|
109
|
+
var nextPageFilePath = (dir) => {
|
|
110
|
+
let nowMs = Date.now();
|
|
111
|
+
const files = listPageFiles(dir);
|
|
112
|
+
if (files.length > 0) {
|
|
113
|
+
const latestName = path.basename(files[files.length - 1], ".json");
|
|
114
|
+
const latestMs = parseDigitTimestamp(latestName);
|
|
115
|
+
if (Number.isFinite(latestMs) && latestMs >= nowMs) nowMs = latestMs + 1;
|
|
116
|
+
}
|
|
117
|
+
return path.join(dir, digitTimestamp(new Date(nowMs)) + ".json");
|
|
118
|
+
};
|
|
119
|
+
var parseDigitTimestamp = (s) => {
|
|
120
|
+
if (!/^\d{17}$/.test(s)) return NaN;
|
|
121
|
+
const iso = s.slice(0, 4) + "-" + s.slice(4, 6) + "-" + s.slice(6, 8) + "T" + s.slice(8, 10) + ":" + s.slice(10, 12) + ":" + s.slice(12, 14) + "." + s.slice(14, 17) + "Z";
|
|
122
|
+
return new Date(iso).getTime();
|
|
123
|
+
};
|
|
124
|
+
var listPageFiles = (dir) => {
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = fs.readdirSync(dir);
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return entries.filter((n) => PAGE_FILE_RE.test(n)).sort().map((n) => path.join(dir, n));
|
|
132
|
+
};
|
|
133
|
+
var loadPage = (file) => {
|
|
134
|
+
const cached = pageCache.get(file);
|
|
135
|
+
if (cached) return cached;
|
|
136
|
+
const data = safeReadJson(file, []);
|
|
137
|
+
pageCache.set(file, data);
|
|
138
|
+
return data;
|
|
139
|
+
};
|
|
140
|
+
var writePage = (file, items) => {
|
|
141
|
+
atomicWrite(file, JSON.stringify(items));
|
|
142
|
+
pageCache.set(file, items);
|
|
143
|
+
};
|
|
144
|
+
var appendToLatestPage = (dir, item) => {
|
|
145
|
+
ensureLayout();
|
|
146
|
+
const files = listPageFiles(dir);
|
|
147
|
+
if (files.length === 0) {
|
|
148
|
+
writePage(nextPageFilePath(dir), [item]);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const latest = files[files.length - 1];
|
|
152
|
+
const page = loadPage(latest);
|
|
153
|
+
if (page.length >= PAGE_SIZE) {
|
|
154
|
+
writePage(nextPageFilePath(dir), [item]);
|
|
155
|
+
} else {
|
|
156
|
+
writePage(latest, [...page, item]);
|
|
157
|
+
}
|
|
101
158
|
};
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
var updateInPlace = (dir, predicate, mutator) => {
|
|
160
|
+
const files = listPageFiles(dir).reverse();
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const page = loadPage(file);
|
|
163
|
+
const i = page.findIndex(predicate);
|
|
164
|
+
if (i === -1) continue;
|
|
165
|
+
const next = page.slice();
|
|
166
|
+
next[i] = mutator(page[i]);
|
|
167
|
+
writePage(file, next);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
105
171
|
};
|
|
106
172
|
var loadRunFile = (runId) => {
|
|
107
173
|
const cached = runCache.get(runId);
|
|
108
174
|
if (cached) return cached;
|
|
109
|
-
const file = safeReadJson(
|
|
175
|
+
const file = safeReadJson(
|
|
176
|
+
path.join(RUNS_DIR(), `${runId}.json`),
|
|
177
|
+
null
|
|
178
|
+
);
|
|
110
179
|
if (!file) return null;
|
|
111
180
|
runCache.set(runId, file);
|
|
112
181
|
return file;
|
|
113
182
|
};
|
|
114
183
|
var persistRunFile = (runId, file) => {
|
|
115
184
|
runCache.set(runId, file);
|
|
116
|
-
atomicWrite(path.join(RUNS_DIR, `${runId}.json`), JSON.stringify(file));
|
|
185
|
+
atomicWrite(path.join(RUNS_DIR(), `${runId}.json`), JSON.stringify(file));
|
|
117
186
|
};
|
|
118
187
|
var stepIsInProgress = (s) => s.duration_ms === null && !s.error;
|
|
119
188
|
var extractFirstMessage = (rawInput) => {
|
|
@@ -155,7 +224,6 @@ var recomputeSummary = (file) => {
|
|
|
155
224
|
id: file.run.id,
|
|
156
225
|
started_at: file.run.started_at,
|
|
157
226
|
function_id: file.run.function_id,
|
|
158
|
-
metadata: file.run.metadata,
|
|
159
227
|
thread_id: file.run.thread_id,
|
|
160
228
|
step_count: file.steps.length,
|
|
161
229
|
has_error: hasError,
|
|
@@ -164,54 +232,57 @@ var recomputeSummary = (file) => {
|
|
|
164
232
|
type: firstStep?.type ?? null
|
|
165
233
|
};
|
|
166
234
|
};
|
|
167
|
-
var upsertThread = (threadId, runStartedAt) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
existing
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
235
|
+
var upsertThread = (threadId, runStartedAt, metadata) => {
|
|
236
|
+
const updated = updateInPlace(
|
|
237
|
+
THREAD_INDEX_DIR(),
|
|
238
|
+
(t) => t.id === threadId,
|
|
239
|
+
(existing) => ({
|
|
240
|
+
...existing,
|
|
241
|
+
updated_at: new Date(runStartedAt) > new Date(existing.updated_at) ? runStartedAt : existing.updated_at,
|
|
242
|
+
run_count: existing.run_count + 1
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
if (updated) return;
|
|
246
|
+
appendToLatestPage(THREAD_INDEX_DIR(), {
|
|
247
|
+
id: threadId,
|
|
248
|
+
created_at: runStartedAt,
|
|
249
|
+
updated_at: runStartedAt,
|
|
250
|
+
run_count: 1,
|
|
251
|
+
metadata: metadata && typeof metadata === "object" ? JSON.stringify(metadata) : null
|
|
252
|
+
});
|
|
183
253
|
};
|
|
184
254
|
var upsertSummary = (file) => {
|
|
185
|
-
const index = loadIndex();
|
|
186
255
|
const summary = recomputeSummary(file);
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
256
|
+
const updated = updateInPlace(
|
|
257
|
+
RUN_INDEX_DIR(),
|
|
258
|
+
(s) => s.id === summary.id,
|
|
259
|
+
() => summary
|
|
260
|
+
);
|
|
261
|
+
if (updated) return;
|
|
262
|
+
appendToLatestPage(RUN_INDEX_DIR(), summary);
|
|
193
263
|
};
|
|
194
|
-
var createRun = async (id, functionId,
|
|
264
|
+
var createRun = async (id, functionId, threadId, metadata) => {
|
|
265
|
+
await resolveDbDir();
|
|
195
266
|
ensureLayout();
|
|
196
267
|
const existing = loadRunFile(id);
|
|
197
268
|
if (existing) return existing.run;
|
|
269
|
+
const resolvedThreadId = threadId ?? `auto-${crypto.randomUUID().slice(0, 8)}`;
|
|
198
270
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
199
271
|
const run = {
|
|
200
272
|
id,
|
|
201
273
|
started_at: startedAt,
|
|
202
274
|
function_id: functionId ?? null,
|
|
203
|
-
|
|
204
|
-
thread_id: threadId ?? null
|
|
275
|
+
thread_id: resolvedThreadId
|
|
205
276
|
};
|
|
206
277
|
const file = { version: 1, run, steps: [] };
|
|
207
278
|
persistRunFile(id, file);
|
|
208
279
|
upsertSummary(file);
|
|
209
|
-
|
|
210
|
-
persistIndex();
|
|
280
|
+
upsertThread(resolvedThreadId, startedAt, metadata);
|
|
211
281
|
notifyServer("run", id);
|
|
212
282
|
return run;
|
|
213
283
|
};
|
|
214
284
|
var createStep = async (step) => {
|
|
285
|
+
await resolveDbDir();
|
|
215
286
|
ensureLayout();
|
|
216
287
|
const file = loadRunFile(step.run_id);
|
|
217
288
|
if (!file) {
|
|
@@ -232,11 +303,11 @@ var createStep = async (step) => {
|
|
|
232
303
|
file.steps.push(newStep);
|
|
233
304
|
persistRunFile(file.run.id, file);
|
|
234
305
|
upsertSummary(file);
|
|
235
|
-
persistIndex();
|
|
236
306
|
notifyServer("step", file.run.id);
|
|
237
307
|
return stepNumber;
|
|
238
308
|
};
|
|
239
309
|
var updateStepResult = async (stepId, result) => {
|
|
310
|
+
await resolveDbDir();
|
|
240
311
|
let file;
|
|
241
312
|
let step;
|
|
242
313
|
for (const cached of runCache.values()) {
|
|
@@ -248,15 +319,18 @@ var updateStepResult = async (stepId, result) => {
|
|
|
248
319
|
}
|
|
249
320
|
}
|
|
250
321
|
if (!file || !step) {
|
|
251
|
-
for (const
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
322
|
+
for (const pageFile of listPageFiles(RUN_INDEX_DIR())) {
|
|
323
|
+
for (const summary of loadPage(pageFile)) {
|
|
324
|
+
const candidate = loadRunFile(summary.id);
|
|
325
|
+
if (!candidate) continue;
|
|
326
|
+
const match = candidate.steps.find((s) => s.id === stepId);
|
|
327
|
+
if (match) {
|
|
328
|
+
file = candidate;
|
|
329
|
+
step = match;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
259
332
|
}
|
|
333
|
+
if (file) break;
|
|
260
334
|
}
|
|
261
335
|
}
|
|
262
336
|
if (!file || !step) return;
|
|
@@ -281,7 +355,6 @@ var updateStepResult = async (stepId, result) => {
|
|
|
281
355
|
);
|
|
282
356
|
persistRunFile(file.run.id, file);
|
|
283
357
|
upsertSummary(file);
|
|
284
|
-
persistIndex();
|
|
285
358
|
notifyServer("step-update", file.run.id);
|
|
286
359
|
};
|
|
287
360
|
|
|
@@ -325,15 +398,15 @@ var aiSdkMiddleware = (options = {}) => {
|
|
|
325
398
|
);
|
|
326
399
|
}
|
|
327
400
|
registerSignalHandlers();
|
|
328
|
-
const runId =
|
|
401
|
+
const runId = generateRunId();
|
|
329
402
|
let runCreated = false;
|
|
330
403
|
const ensureRunCreated = async () => {
|
|
331
404
|
if (!runCreated) {
|
|
332
405
|
await createRun(
|
|
333
406
|
runId,
|
|
334
407
|
options.functionId,
|
|
335
|
-
options.
|
|
336
|
-
options.
|
|
408
|
+
options.threadId,
|
|
409
|
+
options.metadata
|
|
337
410
|
);
|
|
338
411
|
runCreated = true;
|
|
339
412
|
}
|
|
@@ -561,12 +634,12 @@ async function createLogger(options = {}) {
|
|
|
561
634
|
"ailog should not be used in production. Remove createLogger calls from your production builds."
|
|
562
635
|
);
|
|
563
636
|
}
|
|
564
|
-
const runId =
|
|
637
|
+
const runId = generateRunId();
|
|
565
638
|
await createRun(
|
|
566
639
|
runId,
|
|
567
640
|
options.functionId,
|
|
568
|
-
options.
|
|
569
|
-
options.
|
|
641
|
+
options.threadId,
|
|
642
|
+
options.metadata
|
|
570
643
|
);
|
|
571
644
|
return {
|
|
572
645
|
id: runId,
|
|
@@ -604,6 +677,5 @@ async function createLogger(options = {}) {
|
|
|
604
677
|
}
|
|
605
678
|
export {
|
|
606
679
|
aiSdkMiddleware,
|
|
607
|
-
createLogger
|
|
608
|
-
generateRunId
|
|
680
|
+
createLogger
|
|
609
681
|
};
|
package/dist/viewer/server.js
CHANGED
|
@@ -12,10 +12,36 @@ import { fileURLToPath } from "url";
|
|
|
12
12
|
import path from "path";
|
|
13
13
|
import fs from "fs";
|
|
14
14
|
var DB_DIR = path.join(process.cwd(), ".ailog");
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
var
|
|
15
|
+
var RUNS_DIR = () => path.join(DB_DIR, "runs");
|
|
16
|
+
var BLOBS_DIR = () => path.join(DB_DIR, "blobs");
|
|
17
|
+
var RUN_INDEX_DIR = () => path.join(DB_DIR, "index", "runs");
|
|
18
|
+
var THREAD_INDEX_DIR = () => path.join(DB_DIR, "index", "threads");
|
|
19
|
+
var getDbDir = () => DB_DIR;
|
|
18
20
|
var AILOG_PORT = process.env.AILOG_PORT ? parseInt(process.env.AILOG_PORT) : 4985;
|
|
21
|
+
var dirResolved = false;
|
|
22
|
+
var dirResolvePromise = null;
|
|
23
|
+
var resolveDbDir = async () => {
|
|
24
|
+
if (dirResolved) return;
|
|
25
|
+
if (!dirResolvePromise) {
|
|
26
|
+
dirResolvePromise = (async () => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(
|
|
29
|
+
`http://localhost:${AILOG_PORT}/api/db-dir`
|
|
30
|
+
);
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
const body = await res.json();
|
|
33
|
+
if (typeof body.dir === "string" && body.dir.length > 0) {
|
|
34
|
+
DB_DIR = body.dir;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
} finally {
|
|
39
|
+
dirResolved = true;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
await dirResolvePromise;
|
|
44
|
+
};
|
|
19
45
|
var BLOB_THRESHOLD = 16 * 1024;
|
|
20
46
|
var notifyServer = (event, runId) => {
|
|
21
47
|
notifyServerAsync(event, runId);
|
|
@@ -28,23 +54,12 @@ var notifyServerAsync = async (event, runId) => {
|
|
|
28
54
|
body: JSON.stringify({
|
|
29
55
|
event,
|
|
30
56
|
timestamp: Date.now(),
|
|
31
|
-
dbPath: INDEX_PATH,
|
|
32
57
|
runId: runId ?? null
|
|
33
58
|
})
|
|
34
59
|
});
|
|
35
60
|
} catch {
|
|
36
61
|
}
|
|
37
62
|
};
|
|
38
|
-
var EMPTY_INDEX = () => ({
|
|
39
|
-
version: 1,
|
|
40
|
-
runs: [],
|
|
41
|
-
threads: []
|
|
42
|
-
});
|
|
43
|
-
var normalizeIndex = (raw) => ({
|
|
44
|
-
version: 1,
|
|
45
|
-
runs: raw.runs ?? [],
|
|
46
|
-
threads: raw.threads ?? []
|
|
47
|
-
});
|
|
48
63
|
var ensureGitignore = () => {
|
|
49
64
|
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
50
65
|
if (!fs.existsSync(gitignorePath)) return;
|
|
@@ -60,16 +75,12 @@ var ensureGitignore = () => {
|
|
|
60
75
|
};
|
|
61
76
|
var ensureLayout = () => {
|
|
62
77
|
const isFirstRun = !fs.existsSync(DB_DIR);
|
|
63
|
-
fs.mkdirSync(RUNS_DIR, { recursive: true });
|
|
64
|
-
fs.mkdirSync(BLOBS_DIR, { recursive: true });
|
|
78
|
+
fs.mkdirSync(RUNS_DIR(), { recursive: true });
|
|
79
|
+
fs.mkdirSync(BLOBS_DIR(), { recursive: true });
|
|
80
|
+
fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
|
|
81
|
+
fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
|
|
65
82
|
if (isFirstRun) ensureGitignore();
|
|
66
83
|
};
|
|
67
|
-
var atomicWrite = (filePath, data) => {
|
|
68
|
-
ensureLayout();
|
|
69
|
-
const tmp = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 6)}.tmp`;
|
|
70
|
-
fs.writeFileSync(tmp, data);
|
|
71
|
-
fs.renameSync(tmp, filePath);
|
|
72
|
-
};
|
|
73
84
|
var safeReadJson = (filePath, fallback) => {
|
|
74
85
|
try {
|
|
75
86
|
if (!fs.existsSync(filePath)) return fallback;
|
|
@@ -79,7 +90,7 @@ var safeReadJson = (filePath, fallback) => {
|
|
|
79
90
|
}
|
|
80
91
|
};
|
|
81
92
|
var readBlobIfPresent = (relPath) => {
|
|
82
|
-
const absolute = path.join(
|
|
93
|
+
const absolute = path.join(DB_DIR, relPath);
|
|
83
94
|
try {
|
|
84
95
|
if (!fs.existsSync(absolute)) return null;
|
|
85
96
|
return fs.readFileSync(absolute, "utf-8");
|
|
@@ -94,38 +105,68 @@ var hydrate = (v) => {
|
|
|
94
105
|
if (isBlobRef(v)) return readBlobIfPresent(v.$blob);
|
|
95
106
|
return null;
|
|
96
107
|
};
|
|
97
|
-
var indexCache = null;
|
|
98
108
|
var runCache = /* @__PURE__ */ new Map();
|
|
99
|
-
var
|
|
100
|
-
var
|
|
101
|
-
var
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return
|
|
109
|
+
var pageCache = /* @__PURE__ */ new Map();
|
|
110
|
+
var PAGE_FILE_RE = /^\d{17}\.json$/;
|
|
111
|
+
var listPageFiles = (dir) => {
|
|
112
|
+
let entries;
|
|
113
|
+
try {
|
|
114
|
+
entries = fs.readdirSync(dir);
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return entries.filter((n) => PAGE_FILE_RE.test(n)).sort().map((n) => path.join(dir, n));
|
|
119
|
+
};
|
|
120
|
+
var loadPage = (file) => {
|
|
121
|
+
const cached = pageCache.get(file);
|
|
122
|
+
if (cached) return cached;
|
|
123
|
+
const data = safeReadJson(file, []);
|
|
124
|
+
pageCache.set(file, data);
|
|
125
|
+
return data;
|
|
126
|
+
};
|
|
127
|
+
var loadPagedNewestFirst = (dir, offset, limit) => {
|
|
128
|
+
const files = listPageFiles(dir).reverse();
|
|
129
|
+
let total = 0;
|
|
130
|
+
for (const f of files) total += loadPage(f).length;
|
|
131
|
+
const items = [];
|
|
132
|
+
let skipped = 0;
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const page = loadPage(file);
|
|
135
|
+
for (let i = page.length - 1; i >= 0; i--) {
|
|
136
|
+
if (skipped < offset) {
|
|
137
|
+
skipped++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
items.push(page[i]);
|
|
141
|
+
if (items.length === limit) {
|
|
142
|
+
return {
|
|
143
|
+
items,
|
|
144
|
+
total,
|
|
145
|
+
hasMore: offset + items.length < total
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { items, total, hasMore: false };
|
|
109
151
|
};
|
|
110
152
|
var loadRunFile = (runId) => {
|
|
111
153
|
const cached = runCache.get(runId);
|
|
112
154
|
if (cached) return cached;
|
|
113
|
-
const file = safeReadJson(
|
|
155
|
+
const file = safeReadJson(
|
|
156
|
+
path.join(RUNS_DIR(), `${runId}.json`),
|
|
157
|
+
null
|
|
158
|
+
);
|
|
114
159
|
if (!file) return null;
|
|
115
160
|
runCache.set(runId, file);
|
|
116
161
|
return file;
|
|
117
162
|
};
|
|
118
|
-
var reloadDb = async (
|
|
119
|
-
|
|
120
|
-
remoteDbDir = path.dirname(remoteIndexPath);
|
|
121
|
-
}
|
|
122
|
-
indexCache = null;
|
|
163
|
+
var reloadDb = async (runId) => {
|
|
164
|
+
pageCache.clear();
|
|
123
165
|
if (runId) {
|
|
124
166
|
runCache.delete(runId);
|
|
125
167
|
} else {
|
|
126
168
|
runCache.clear();
|
|
127
169
|
}
|
|
128
|
-
loadIndex();
|
|
129
170
|
};
|
|
130
171
|
var hydrateStep = (s) => ({
|
|
131
172
|
...s,
|
|
@@ -134,12 +175,8 @@ var hydrateStep = (s) => ({
|
|
|
134
175
|
raw_response: hydrate(s.raw_response),
|
|
135
176
|
raw_chunks: hydrate(s.raw_chunks)
|
|
136
177
|
});
|
|
137
|
-
var
|
|
138
|
-
|
|
139
|
-
return [...index.runs].sort(
|
|
140
|
-
(a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
|
|
141
|
-
);
|
|
142
|
-
};
|
|
178
|
+
var getRunSummariesPage = async (offset, limit) => loadPagedNewestFirst(RUN_INDEX_DIR(), offset, limit);
|
|
179
|
+
var getThreadsPage = async (offset, limit) => loadPagedNewestFirst(THREAD_INDEX_DIR(), offset, limit);
|
|
143
180
|
var getRunWithSteps = async (runId) => {
|
|
144
181
|
const file = loadRunFile(runId);
|
|
145
182
|
if (!file) return null;
|
|
@@ -149,14 +186,17 @@ var getRunWithSteps = async (runId) => {
|
|
|
149
186
|
};
|
|
150
187
|
};
|
|
151
188
|
var clearDatabase = async () => {
|
|
189
|
+
await resolveDbDir();
|
|
152
190
|
ensureLayout();
|
|
153
|
-
fs.rmSync(
|
|
154
|
-
fs.rmSync(
|
|
155
|
-
fs.
|
|
156
|
-
fs.mkdirSync(
|
|
191
|
+
fs.rmSync(path.join(DB_DIR, "index"), { recursive: true, force: true });
|
|
192
|
+
fs.rmSync(RUNS_DIR(), { recursive: true, force: true });
|
|
193
|
+
fs.rmSync(BLOBS_DIR(), { recursive: true, force: true });
|
|
194
|
+
fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
|
|
195
|
+
fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
|
|
196
|
+
fs.mkdirSync(RUNS_DIR(), { recursive: true });
|
|
197
|
+
fs.mkdirSync(BLOBS_DIR(), { recursive: true });
|
|
157
198
|
runCache.clear();
|
|
158
|
-
|
|
159
|
-
atomicWrite(INDEX_PATH, JSON.stringify(indexCache, null, 2));
|
|
199
|
+
pageCache.clear();
|
|
160
200
|
notifyServer("clear");
|
|
161
201
|
};
|
|
162
202
|
|
|
@@ -180,17 +220,24 @@ var devEnv = process.env.AILOG_DEV;
|
|
|
180
220
|
var isDevMode = devEnv !== void 0 && devEnv !== "false" && devEnv !== "0";
|
|
181
221
|
var projectRoot = path2.resolve(__dirname, "../..");
|
|
182
222
|
var clientDir = path2.join(projectRoot, "dist/client");
|
|
183
|
-
var remoteDbPath;
|
|
184
223
|
var app = new Hono();
|
|
185
224
|
app.use("/*", cors());
|
|
225
|
+
var parsePagination = (c) => {
|
|
226
|
+
const offset = Math.max(0, parseInt(c.req.query("offset") ?? "0", 10) || 0);
|
|
227
|
+
const limit = Math.min(
|
|
228
|
+
200,
|
|
229
|
+
Math.max(1, parseInt(c.req.query("limit") ?? "50", 10) || 50)
|
|
230
|
+
);
|
|
231
|
+
return { offset, limit };
|
|
232
|
+
};
|
|
186
233
|
app.get("/api/runs", async (c) => {
|
|
187
|
-
await reloadDb(
|
|
188
|
-
const
|
|
189
|
-
const
|
|
234
|
+
await reloadDb();
|
|
235
|
+
const { offset, limit } = parsePagination(c);
|
|
236
|
+
const page = await getRunSummariesPage(offset, limit);
|
|
237
|
+
const items = page.items.map((r) => ({
|
|
190
238
|
id: r.id,
|
|
191
239
|
started_at: r.started_at,
|
|
192
240
|
function_id: r.function_id,
|
|
193
|
-
metadata: r.metadata,
|
|
194
241
|
thread_id: r.thread_id,
|
|
195
242
|
stepCount: r.step_count,
|
|
196
243
|
firstMessage: r.first_message ?? "No user message",
|
|
@@ -198,11 +245,11 @@ app.get("/api/runs", async (c) => {
|
|
|
198
245
|
isInProgress: r.is_in_progress,
|
|
199
246
|
type: r.type ?? void 0
|
|
200
247
|
}));
|
|
201
|
-
return c.json(
|
|
248
|
+
return c.json({ items, total: page.total, hasMore: page.hasMore });
|
|
202
249
|
});
|
|
203
250
|
app.get("/api/runs/:id", async (c) => {
|
|
204
251
|
const runId = c.req.param("id");
|
|
205
|
-
await reloadDb(
|
|
252
|
+
await reloadDb(runId);
|
|
206
253
|
const data = await getRunWithSteps(runId);
|
|
207
254
|
if (!data) {
|
|
208
255
|
return c.json({ error: "Run not found" }, 404);
|
|
@@ -213,6 +260,14 @@ app.get("/api/runs/:id", async (c) => {
|
|
|
213
260
|
steps: data.steps
|
|
214
261
|
});
|
|
215
262
|
});
|
|
263
|
+
app.get("/api/threads", async (c) => {
|
|
264
|
+
await reloadDb();
|
|
265
|
+
const { offset, limit } = parsePagination(c);
|
|
266
|
+
return c.json(await getThreadsPage(offset, limit));
|
|
267
|
+
});
|
|
268
|
+
app.get("/api/db-dir", (c) => {
|
|
269
|
+
return c.json({ dir: getDbDir() });
|
|
270
|
+
});
|
|
216
271
|
app.post("/api/clear", async (c) => {
|
|
217
272
|
await clearDatabase();
|
|
218
273
|
return c.json({ success: true });
|
|
@@ -268,10 +323,7 @@ app.get("/api/events", (c) => {
|
|
|
268
323
|
});
|
|
269
324
|
app.post("/api/notify", async (c) => {
|
|
270
325
|
const body = await c.req.json();
|
|
271
|
-
|
|
272
|
-
remoteDbPath = body.dbPath;
|
|
273
|
-
}
|
|
274
|
-
await reloadDb(remoteDbPath, body.runId ?? void 0);
|
|
326
|
+
await reloadDb(body.runId ?? void 0);
|
|
275
327
|
broadcastToClients("update", body);
|
|
276
328
|
return c.json({ success: true });
|
|
277
329
|
});
|
|
@@ -328,6 +380,7 @@ var startViewer = (port = 4985) => {
|
|
|
328
380
|
} else {
|
|
329
381
|
console.log(`\u{1F50D} ailog running at http://localhost:${port}`);
|
|
330
382
|
}
|
|
383
|
+
console.log(` Reading .ailog from ${getDbDir()}`);
|
|
331
384
|
}
|
|
332
385
|
);
|
|
333
386
|
server.on("error", (err) => {
|