@faithfulalabi/agent-lens 0.5.0 → 0.7.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/src/content/resolve.js +37 -7
- package/dist/src/db/read.js +3 -2
- package/dist/src/db/schema.js +12 -2
- package/dist/src/db/write.js +5 -4
- package/dist/src/project/pipeline.js +8 -5
- package/dist/src/project/tools.js +17 -0
- package/dist/src/server/api.js +30 -0
- package/dist/src/server/start.js +5 -2
- package/dist/src/transcript/blocks.js +35 -1
- package/dist/src/transcript/version.js +1 -1
- package/dist/src/transcript/wire.js +39 -0
- package/package.json +1 -1
- package/ui/dist/assets/index-BGFMl-Rd.js +254 -0
- package/ui/dist/assets/index-CNZgKRsc.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-D9RVohDs.css +0 -1
- package/ui/dist/assets/index-khXVDRM8.js +0 -254
|
@@ -2,16 +2,13 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
3
|
import { isUnderAnyRoot } from '../archive/paths.js';
|
|
4
4
|
import { toolResultsDirOf } from '../corpus/paths.js';
|
|
5
|
-
import { contentBlocks } from '../transcript/blocks.js';
|
|
5
|
+
import { contentBlocks, imageSourceAt, resultText, SERVABLE_IMAGE_TYPES, } from '../transcript/blocks.js';
|
|
6
6
|
import { DriftCounter } from '../transcript/drift.js';
|
|
7
7
|
import { classifyLine } from '../transcript/line.js';
|
|
8
8
|
function byteLength(text) {
|
|
9
9
|
return Buffer.byteLength(text, 'utf8');
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
return children.flatMap((child) => (child.kind === 'text' ? [child.text] : [])).join('\n');
|
|
13
|
-
}
|
|
14
|
-
function blockAt(env, archivePath, byteOffset, len, blockIndex) {
|
|
11
|
+
function lineAt(env, archivePath, byteOffset, len) {
|
|
15
12
|
let bytes;
|
|
16
13
|
try {
|
|
17
14
|
bytes = env.reader.read(archivePath, byteOffset, len);
|
|
@@ -22,17 +19,20 @@ function blockAt(env, archivePath, byteOffset, len, blockIndex) {
|
|
|
22
19
|
if (bytes.length === 0)
|
|
23
20
|
return undefined;
|
|
24
21
|
try {
|
|
25
|
-
|
|
22
|
+
return classifyLine(JSON.parse(bytes.toString('utf8')), {
|
|
26
23
|
byteOffset,
|
|
27
24
|
byteLength: len,
|
|
28
25
|
drift: new DriftCounter(),
|
|
29
26
|
});
|
|
30
|
-
return contentBlocks(line)[blockIndex];
|
|
31
27
|
}
|
|
32
28
|
catch {
|
|
33
29
|
return undefined;
|
|
34
30
|
}
|
|
35
31
|
}
|
|
32
|
+
function blockAt(env, archivePath, byteOffset, len, blockIndex) {
|
|
33
|
+
const line = lineAt(env, archivePath, byteOffset, len);
|
|
34
|
+
return line === undefined ? undefined : contentBlocks(line)[blockIndex];
|
|
35
|
+
}
|
|
36
36
|
function stored(row, field) {
|
|
37
37
|
const text = (field === 'text' ? row.text : row.input) ?? '';
|
|
38
38
|
return { text, bytes: field === 'text' ? row.text_bytes : row.input_bytes };
|
|
@@ -148,6 +148,33 @@ function resolveText(row, archivePath, env) {
|
|
|
148
148
|
return fromColumn(row, 'text', row.output_storage);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
+
export const IMAGE_LINE_MAX = 16 * 1024 * 1024;
|
|
152
|
+
function imageLineOf(row) {
|
|
153
|
+
const { result_offset, result_len, result_block } = row;
|
|
154
|
+
if (result_offset !== null && result_len !== null && result_block !== null) {
|
|
155
|
+
return { offset: result_offset, len: result_len, block: result_block };
|
|
156
|
+
}
|
|
157
|
+
return row.block_index === null
|
|
158
|
+
? undefined
|
|
159
|
+
: { offset: row.src_offset, len: row.src_len, block: row.block_index };
|
|
160
|
+
}
|
|
161
|
+
export function resolveImage(row, ordinal, archivePath, env) {
|
|
162
|
+
const at = imageLineOf(row);
|
|
163
|
+
if (at === undefined)
|
|
164
|
+
return { ok: false, reason: 'gone' };
|
|
165
|
+
if (at.len > IMAGE_LINE_MAX)
|
|
166
|
+
return { ok: false, reason: 'too_large' };
|
|
167
|
+
const line = archivePath === undefined ? undefined : lineAt(env, archivePath, at.offset, at.len);
|
|
168
|
+
const source = line === undefined ? undefined : imageSourceAt(line, at.block, ordinal);
|
|
169
|
+
if (source === undefined)
|
|
170
|
+
return { ok: false, reason: 'gone' };
|
|
171
|
+
if (!SERVABLE_IMAGE_TYPES.has(source.media_type))
|
|
172
|
+
return { ok: false, reason: 'unsupported' };
|
|
173
|
+
const bytes = Buffer.from(source.data, 'base64');
|
|
174
|
+
if (bytes.length === 0)
|
|
175
|
+
return { ok: false, reason: 'gone' };
|
|
176
|
+
return { ok: true, media_type: source.media_type, bytes };
|
|
177
|
+
}
|
|
151
178
|
export function createContentEnv(reader, roots) {
|
|
152
179
|
return {
|
|
153
180
|
reader,
|
|
@@ -161,3 +188,6 @@ export function createContentResolver(archivePathOf, env) {
|
|
|
161
188
|
export function createSpillLocator(archivePathOf, env) {
|
|
162
189
|
return (row) => spillSource(row, archivePathOf(row.session_id), env);
|
|
163
190
|
}
|
|
191
|
+
export function createImageResolver(archivePathOf, env) {
|
|
192
|
+
return (row, ordinal) => resolveImage(row, ordinal, archivePathOf(row.session_id), env);
|
|
193
|
+
}
|
package/dist/src/db/read.js
CHANGED
|
@@ -128,9 +128,10 @@ export function readTurns(db, session_id) {
|
|
|
128
128
|
}
|
|
129
129
|
const EVENT_COLUMNS = `id, turn_id, seq, kind, ts, request_id, block_index, name, status,
|
|
130
130
|
duration_ms, duration_source, input, input_bytes, input_storage,
|
|
131
|
+
wire_input, wire_input_bytes, wire_input_storage,
|
|
131
132
|
text, text_bytes, output_storage, spill_path, spill_bytes,
|
|
132
133
|
model, tokens_in, tokens_out, tokens_cache_read, tokens_cache_write, est_cost,
|
|
133
|
-
child_session_id, agent_type, agent_status, raw_type, raw_subtype`;
|
|
134
|
+
child_session_id, agent_type, agent_status, raw_type, raw_subtype, image_count`;
|
|
134
135
|
export function buildEventPageSql(session_id, query) {
|
|
135
136
|
return {
|
|
136
137
|
sql: `SELECT ${EVENT_COLUMNS} FROM events WHERE session_id = ? AND seq >= ?` +
|
|
@@ -165,7 +166,7 @@ export function readEventsByIds(db, session_id, ids) {
|
|
|
165
166
|
}
|
|
166
167
|
export const EVENT_CONTENT_COLUMNS = `id, session_id, block_index, input, input_bytes, input_storage,
|
|
167
168
|
text, text_bytes, output_storage, spill_path, spill_bytes,
|
|
168
|
-
src_offset, src_len, result_offset, result_len, result_block`;
|
|
169
|
+
src_offset, src_len, result_offset, result_len, result_block, image_count`;
|
|
169
170
|
export function readEventContentRow(db, event_id) {
|
|
170
171
|
return db
|
|
171
172
|
.prepare(`SELECT ${EVENT_CONTENT_COLUMNS} FROM events WHERE id = ?`)
|
package/dist/src/db/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SCHEMA_VERSION =
|
|
1
|
+
export const SCHEMA_VERSION = 5;
|
|
2
2
|
export const SCHEMA_DDL = `-- ~/.agent-lens/cache.db
|
|
3
3
|
--
|
|
4
4
|
-- THIS FILE IS A CACHE. Every byte is a pure function of ~/.agent-lens/archive/** --
|
|
@@ -21,7 +21,7 @@ export const SCHEMA_DDL = `-- ~/.agent-lens/cache.db
|
|
|
21
21
|
PRAGMA journal_mode = WAL;
|
|
22
22
|
PRAGMA synchronous = NORMAL;
|
|
23
23
|
PRAGMA foreign_keys = OFF; -- projections are dropped wholesale, per session, by hand
|
|
24
|
-
PRAGMA user_version =
|
|
24
|
+
PRAGMA user_version = 5; -- SCHEMA_VERSION; mismatch => unlink + recreate
|
|
25
25
|
|
|
26
26
|
-- ===================================================================== sessions
|
|
27
27
|
-- The file index AND the session-list row AND the projection freshness header,
|
|
@@ -227,6 +227,15 @@ CREATE TABLE events (
|
|
|
227
227
|
-- tools.ts:118, unwitnessed by its own comment.
|
|
228
228
|
-- 'line_ref' is live at exactly 1 row. Keep the
|
|
229
229
|
-- arms; see task 0.12.
|
|
230
|
+
|
|
231
|
+
-- wireToolInputs[tool_use.id]: the form the harness SENT, which is not always the
|
|
232
|
+
-- form the model asked for. NULL means THE TWO AGREED -- never that nothing was
|
|
233
|
+
-- recorded. Storage words and the cap are input_storage's, written by the same
|
|
234
|
+
-- tools.ts:toolInput.
|
|
235
|
+
wire_input TEXT,
|
|
236
|
+
wire_input_bytes INTEGER,
|
|
237
|
+
wire_input_storage TEXT,
|
|
238
|
+
|
|
230
239
|
text TEXT, -- prose / thinking / prompt / tool output
|
|
231
240
|
text_bytes INTEGER,
|
|
232
241
|
output_storage TEXT, -- inline | line_ref | spill | missing | absent
|
|
@@ -245,6 +254,7 @@ CREATE TABLE events (
|
|
|
245
254
|
result_offset INTEGER, -- byte offset of the tool_result line
|
|
246
255
|
result_len INTEGER,
|
|
247
256
|
result_block INTEGER,
|
|
257
|
+
image_count INTEGER NOT NULL DEFAULT 0, -- images servable by ordinal; never their bytes
|
|
248
258
|
|
|
249
259
|
model TEXT, -- \\ stamped on the FIRST event of each requestId
|
|
250
260
|
tokens_in INTEGER, -- } group ONLY. Naive summing is 1.51x HIGH,
|
package/dist/src/db/write.js
CHANGED
|
@@ -63,12 +63,13 @@ const INSERT_TURN_SQL = `INSERT INTO turns
|
|
|
63
63
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
|
|
64
64
|
const INSERT_EVENT_SQL = `INSERT INTO events
|
|
65
65
|
(id, session_id, turn_id, seq, kind, ts, request_id, block_index, name, status,
|
|
66
|
-
duration_ms, duration_source, input, input_bytes, input_storage,
|
|
66
|
+
duration_ms, duration_source, input, input_bytes, input_storage,
|
|
67
|
+
wire_input, wire_input_bytes, wire_input_storage, text, text_bytes,
|
|
67
68
|
output_storage, spill_path, spill_bytes, src_offset, src_len, result_offset,
|
|
68
|
-
result_len, result_block, model, tokens_in, tokens_out, tokens_cache_read,
|
|
69
|
+
result_len, result_block, image_count, model, tokens_in, tokens_out, tokens_cache_read,
|
|
69
70
|
tokens_cache_write, est_cost, child_session_id, agent_type, agent_status, raw_type,
|
|
70
71
|
raw_subtype, attrs)
|
|
71
|
-
VALUES (
|
|
72
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
|
|
72
73
|
const POPULATE_FTS_SQL = `INSERT INTO events_fts(rowid, text, input)
|
|
73
74
|
SELECT rowid, text, input FROM events WHERE session_id = ?`;
|
|
74
75
|
const WRITE_HEADER_SQL = `UPDATE sessions SET
|
|
@@ -178,7 +179,7 @@ function insertEvents(db, events, spills) {
|
|
|
178
179
|
cache_read: event.tokens_cache_read,
|
|
179
180
|
cache_write: event.tokens_cache_write,
|
|
180
181
|
});
|
|
181
|
-
insert.run(event.id, event.session_id, event.turn_id, event.seq, event.kind, event.ts, event.request_id ?? null, event.block_index, event.name ?? null, event.status ?? null, event.duration_ms ?? null, event.duration_source ?? null, event.input ?? null, event.input_bytes ?? null, event.input_storage ?? null, event.text ?? null, event.text_bytes ?? null, output.output_storage, output.spill_path, output.spill_bytes, event.src_offset, event.src_len, event.result_offset ?? null, event.result_len ?? null, event.result_block ?? null, event.model ?? null, event.tokens_in ?? null, event.tokens_out ?? null, event.tokens_cache_read ?? null, event.tokens_cache_write ?? null, est_cost, event.child_session_id ?? null, event.agent_type ?? null, event.agent_status ?? null, event.raw_type, event.raw_subtype ?? null, event.attrs);
|
|
182
|
+
insert.run(event.id, event.session_id, event.turn_id, event.seq, event.kind, event.ts, event.request_id ?? null, event.block_index, event.name ?? null, event.status ?? null, event.duration_ms ?? null, event.duration_source ?? null, event.input ?? null, event.input_bytes ?? null, event.input_storage ?? null, event.wire_input ?? null, event.wire_input_bytes ?? null, event.wire_input_storage ?? null, event.text ?? null, event.text_bytes ?? null, output.output_storage, output.spill_path, output.spill_bytes, event.src_offset, event.src_len, event.result_offset ?? null, event.result_len ?? null, event.result_block ?? null, event.image_count, event.model ?? null, event.tokens_in ?? null, event.tokens_out ?? null, event.tokens_cache_read ?? null, event.tokens_cache_write ?? null, est_cost, event.child_session_id ?? null, event.agent_type ?? null, event.agent_status ?? null, event.raw_type, event.raw_subtype ?? null, event.attrs);
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
function spillColumns(event, spills) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { asyncAgentLaunch, taskNotification } from '../transcript/agents.js';
|
|
2
2
|
import { attachmentRecord } from '../transcript/attachments.js';
|
|
3
|
-
import { contentBlocks } from '../transcript/blocks.js';
|
|
3
|
+
import { contentBlocks, imageCount, resultText } from '../transcript/blocks.js';
|
|
4
4
|
import { isHumanPrompt, MACHINERY_TAGS } from '../transcript/human.js';
|
|
5
5
|
import { foldControlLines, foldSessionEnvelope, promptGroupId, toolDenialKind, turnDurationMs, } from '../transcript/line.js';
|
|
6
6
|
import { foldRequestGroup, groupByRequestId, modelOfRequestGroup, requestIdOf, } from '../transcript/usage.js';
|
|
7
|
-
import { joinToolCalls, NO_INPUT, toolInput, } from './tools.js';
|
|
7
|
+
import { joinToolCalls, NO_INPUT, NO_WIRE_INPUT, toolInput, wireInput, } from './tools.js';
|
|
8
|
+
import { wireInputOf } from '../transcript/wire.js';
|
|
8
9
|
const MAX_TITLE_CHARS = 200;
|
|
9
10
|
const NO_BLOCKS = Object.freeze([]);
|
|
10
11
|
const MS_PER_DAY = 86_400_000;
|
|
@@ -35,9 +36,6 @@ function prose(blocks) {
|
|
|
35
36
|
.join('\n')
|
|
36
37
|
.trim();
|
|
37
38
|
}
|
|
38
|
-
function resultText(children) {
|
|
39
|
-
return children.flatMap((child) => (child.kind === 'text' ? [child.text] : [])).join('\n');
|
|
40
|
-
}
|
|
41
39
|
function blockText(block) {
|
|
42
40
|
switch (block?.kind) {
|
|
43
41
|
case 'text':
|
|
@@ -145,6 +143,7 @@ export function runPipeline(lines, ctx) {
|
|
|
145
143
|
result_offset: line.byte_offset,
|
|
146
144
|
result_len: line.byte_length,
|
|
147
145
|
result_block: blockIndex,
|
|
146
|
+
image_count: imageCount(block),
|
|
148
147
|
});
|
|
149
148
|
}
|
|
150
149
|
const text = prose(blocks);
|
|
@@ -205,6 +204,9 @@ export function runPipeline(lines, ctx) {
|
|
|
205
204
|
child_session_id: undefined,
|
|
206
205
|
agent_type: undefined,
|
|
207
206
|
...(block?.kind === 'tool_use' ? toolInput(block.input) : NO_INPUT),
|
|
207
|
+
...(block?.kind === 'tool_use'
|
|
208
|
+
? wireInput(wireInputOf(line.raw, block.id), block.input)
|
|
209
|
+
: NO_WIRE_INPUT),
|
|
208
210
|
text: kind === 'tool_call' ? undefined : (blockText(block) ?? lineText),
|
|
209
211
|
text_bytes: undefined,
|
|
210
212
|
output_storage: kind === 'tool_call' ? 'absent' : undefined,
|
|
@@ -215,6 +217,7 @@ export function runPipeline(lines, ctx) {
|
|
|
215
217
|
result_offset: undefined,
|
|
216
218
|
result_len: undefined,
|
|
217
219
|
result_block: undefined,
|
|
220
|
+
image_count: block === undefined ? 0 : imageCount(block),
|
|
218
221
|
model,
|
|
219
222
|
tokens_in: tokens?.input_tokens,
|
|
220
223
|
tokens_out: tokens?.output_tokens,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { claimsPersistedOutput, } from '../transcript/agents.js';
|
|
2
|
+
import { sameValue } from '../transcript/wire.js';
|
|
2
3
|
import { epochMs } from './pipeline.js';
|
|
3
4
|
export const INLINE_MAX = 65536;
|
|
4
5
|
export const PREVIEW_MAX = 8192;
|
|
@@ -7,6 +8,11 @@ export const NO_INPUT = Object.freeze({
|
|
|
7
8
|
input_bytes: undefined,
|
|
8
9
|
input_storage: undefined,
|
|
9
10
|
});
|
|
11
|
+
export const NO_WIRE_INPUT = Object.freeze({
|
|
12
|
+
wire_input: undefined,
|
|
13
|
+
wire_input_bytes: undefined,
|
|
14
|
+
wire_input_storage: undefined,
|
|
15
|
+
});
|
|
10
16
|
function byteLength(text) {
|
|
11
17
|
return Buffer.byteLength(text, 'utf8');
|
|
12
18
|
}
|
|
@@ -29,6 +35,16 @@ export function toolInput(input) {
|
|
|
29
35
|
? { input: headPreview(text), input_bytes: bytes, input_storage: 'line_ref' }
|
|
30
36
|
: { input: text, input_bytes: bytes, input_storage: 'inline' };
|
|
31
37
|
}
|
|
38
|
+
export function wireInput(wire, request) {
|
|
39
|
+
if (wire === undefined || sameValue(wire, request))
|
|
40
|
+
return NO_WIRE_INPUT;
|
|
41
|
+
const stored = toolInput(wire);
|
|
42
|
+
return {
|
|
43
|
+
wire_input: stored.input,
|
|
44
|
+
wire_input_bytes: stored.input_bytes,
|
|
45
|
+
wire_input_storage: stored.input_storage === 'line_ref' ? 'line_ref' : 'inline',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
32
48
|
function elapsedMs(call, result) {
|
|
33
49
|
const ms = epochMs(result) - epochMs(call);
|
|
34
50
|
return ms < 0 ? 0 : ms;
|
|
@@ -82,6 +98,7 @@ export function joinToolCalls(events, results, notifications, drift) {
|
|
|
82
98
|
event.result_offset = result.result_offset;
|
|
83
99
|
event.result_len = result.result_len;
|
|
84
100
|
event.result_block = result.result_block;
|
|
101
|
+
event.image_count = result.image_count;
|
|
85
102
|
event.duration_ms = elapsedMs(event.ts, result.ts);
|
|
86
103
|
event.duration_source = 'elapsed';
|
|
87
104
|
event.status = statusOf(result);
|
package/dist/src/server/api.js
CHANGED
|
@@ -49,6 +49,12 @@ export function parseSort(raw) {
|
|
|
49
49
|
return sort === undefined ? { ok: false, error: 'invalid sort' } : { ok: true, value: sort };
|
|
50
50
|
}
|
|
51
51
|
const FIELDS = ['text', 'input'];
|
|
52
|
+
export function parseImageOrdinal(raw) {
|
|
53
|
+
const value = Number(raw);
|
|
54
|
+
return NON_NEGATIVE_INT.test(raw) && Number.isSafeInteger(value)
|
|
55
|
+
? { ok: true, value }
|
|
56
|
+
: { ok: false, error: 'invalid image index' };
|
|
57
|
+
}
|
|
52
58
|
export function parseField(raw) {
|
|
53
59
|
if (raw === undefined || raw === '')
|
|
54
60
|
return { ok: true, value: 'text' };
|
|
@@ -105,6 +111,11 @@ export function toDetailProjection(header, outcome) {
|
|
|
105
111
|
}
|
|
106
112
|
return PROJECTION_STATES.includes(stored.state) ? stored : { ...stored, state: 'none' };
|
|
107
113
|
}
|
|
114
|
+
const IMAGE_FAILURES = {
|
|
115
|
+
gone: { status: 410, error: 'image no longer on disk' },
|
|
116
|
+
too_large: { status: 413, error: 'image too large to serve' },
|
|
117
|
+
unsupported: { status: 415, error: 'image type not servable' },
|
|
118
|
+
};
|
|
108
119
|
function storedContent(row, field) {
|
|
109
120
|
const isText = field === 'text';
|
|
110
121
|
const resolved = {
|
|
@@ -200,6 +211,25 @@ export function registerApi(app, deps) {
|
|
|
200
211
|
body.spill_path = resolved.spill_path;
|
|
201
212
|
return c.json(body);
|
|
202
213
|
});
|
|
214
|
+
app.get('/api/events/:id/images/:n', (c) => {
|
|
215
|
+
const ordinal = parseImageOrdinal(c.req.param('n'));
|
|
216
|
+
if (!ordinal.ok)
|
|
217
|
+
return c.json({ error: ordinal.error }, 400);
|
|
218
|
+
const row = readEventContentRow(db, c.req.param('id'));
|
|
219
|
+
if (row === undefined || ordinal.value >= row.image_count)
|
|
220
|
+
return jsonNotFound(c);
|
|
221
|
+
const image = deps.resolveImage?.(row, ordinal.value) ?? { ok: false, reason: 'gone' };
|
|
222
|
+
if (!image.ok) {
|
|
223
|
+
const failure = IMAGE_FAILURES[image.reason];
|
|
224
|
+
return c.json({ error: failure.error }, failure.status);
|
|
225
|
+
}
|
|
226
|
+
return c.body(new Uint8Array(image.bytes), 200, {
|
|
227
|
+
'Content-Type': image.media_type,
|
|
228
|
+
'Content-Length': String(image.bytes.length),
|
|
229
|
+
'X-Content-Type-Options': 'nosniff',
|
|
230
|
+
'Cache-Control': 'no-store',
|
|
231
|
+
});
|
|
232
|
+
});
|
|
203
233
|
app.get('/api/search', (c) => {
|
|
204
234
|
const parsed = parseSearchQuery(c.req.query());
|
|
205
235
|
if (!parsed.ok)
|
package/dist/src/server/start.js
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
5
5
|
import { readOrCreateToken } from '../shared/index.js';
|
|
6
6
|
import { resolveArchiveRoot, resolveTranscriptRoot } from '../archive/index.js';
|
|
7
7
|
import { createArchiveReader } from '../archive/read.js';
|
|
8
|
-
import { createContentResolver,
|
|
8
|
+
import { createContentEnv, createContentResolver, createImageResolver, } from '../content/resolve.js';
|
|
9
9
|
import { createCorpusSweep } from '../corpus/watch.js';
|
|
10
10
|
import { createProjectionEnv } from '../corpus/env.js';
|
|
11
11
|
import { openDb } from '../db/open.js';
|
|
@@ -64,6 +64,8 @@ export async function startServer(options = {}) {
|
|
|
64
64
|
});
|
|
65
65
|
const hub = createStreamHub();
|
|
66
66
|
const warm = createWarmQueue({ db, env: createProjectionEnv(reader, roots), hub });
|
|
67
|
+
const archivePathOf = (id) => readEventArchivePath(db, id)?.archive_path;
|
|
68
|
+
const contentEnv = createContentEnv(reader, [roots.archiveRoot, roots.transcriptRoot]);
|
|
67
69
|
const app = buildApiApp({
|
|
68
70
|
db,
|
|
69
71
|
token,
|
|
@@ -73,7 +75,8 @@ export async function startServer(options = {}) {
|
|
|
73
75
|
sweep,
|
|
74
76
|
hub,
|
|
75
77
|
warm,
|
|
76
|
-
resolveContent: createContentResolver(
|
|
78
|
+
resolveContent: createContentResolver(archivePathOf, contentEnv),
|
|
79
|
+
resolveImage: createImageResolver(archivePathOf, contentEnv),
|
|
77
80
|
});
|
|
78
81
|
const fetch = app.fetch;
|
|
79
82
|
const explicit = options.port !== undefined;
|
|
@@ -70,6 +70,40 @@ export function classifyBlock(raw, position) {
|
|
|
70
70
|
export function classifyContent(content) {
|
|
71
71
|
return normalizeContent(content).map((block) => classifyBlock(block, 'top'));
|
|
72
72
|
}
|
|
73
|
+
export function resultText(children) {
|
|
74
|
+
const texts = children.flatMap((child) => (child.kind === 'text' ? [child.text] : []));
|
|
75
|
+
const images = children.flatMap((child) => (child.kind === 'image' ? [child.placeholder] : []));
|
|
76
|
+
return [...texts, ...images].join('\n');
|
|
77
|
+
}
|
|
73
78
|
export function contentBlocks(line) {
|
|
74
|
-
return classifyContent(
|
|
79
|
+
return classifyContent(rawContent(line));
|
|
80
|
+
}
|
|
81
|
+
function rawContent(line) {
|
|
82
|
+
return obj(line.raw.message, undefined)?.content;
|
|
83
|
+
}
|
|
84
|
+
export const SERVABLE_IMAGE_TYPES = new Set([
|
|
85
|
+
'image/png',
|
|
86
|
+
'image/jpeg',
|
|
87
|
+
'image/gif',
|
|
88
|
+
'image/webp',
|
|
89
|
+
]);
|
|
90
|
+
export function imageCount(block) {
|
|
91
|
+
if (block.kind === 'image')
|
|
92
|
+
return 1;
|
|
93
|
+
if (block.kind !== 'tool_result')
|
|
94
|
+
return 0;
|
|
95
|
+
return block.children.filter((child) => child.kind === 'image').length;
|
|
96
|
+
}
|
|
97
|
+
export function imageSourceAt(line, blockIndex, ordinal) {
|
|
98
|
+
const block = obj(normalizeContent(rawContent(line))[blockIndex], EMPTY_RECORD);
|
|
99
|
+
const images = block.type === 'image'
|
|
100
|
+
? [block]
|
|
101
|
+
: block.type === 'tool_result'
|
|
102
|
+
? normalizeContent(block.content).filter((child) => obj(child, EMPTY_RECORD).type === 'image')
|
|
103
|
+
: [];
|
|
104
|
+
if (ordinal >= images.length)
|
|
105
|
+
return undefined;
|
|
106
|
+
const image = obj(images[ordinal], EMPTY_RECORD);
|
|
107
|
+
const source = obj(image.source, EMPTY_RECORD);
|
|
108
|
+
return { media_type: str(source.media_type, ''), data: str(source.data, '') };
|
|
75
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PROJECTOR_VERSION =
|
|
1
|
+
export const PROJECTOR_VERSION = 15;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { arr, obj } from './accessors.js';
|
|
2
|
+
export function wireInputOf(raw, toolUseId) {
|
|
3
|
+
try {
|
|
4
|
+
const field = obj(obj(raw, undefined)?.wireToolInputs, undefined);
|
|
5
|
+
if (field === undefined || !Object.hasOwn(field, toolUseId))
|
|
6
|
+
return undefined;
|
|
7
|
+
return obj(field[toolUseId], undefined);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function sameValue(a, b) {
|
|
14
|
+
try {
|
|
15
|
+
return same(a, b);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function same(a, b) {
|
|
22
|
+
if (a === b)
|
|
23
|
+
return true;
|
|
24
|
+
const aList = arr(a, undefined);
|
|
25
|
+
const bList = arr(b, undefined);
|
|
26
|
+
if (aList !== undefined || bList !== undefined) {
|
|
27
|
+
if (aList === undefined || bList === undefined || aList.length !== bList.length)
|
|
28
|
+
return false;
|
|
29
|
+
return aList.every((item, index) => same(item, bList[index]));
|
|
30
|
+
}
|
|
31
|
+
const aObj = obj(a, undefined);
|
|
32
|
+
const bObj = obj(b, undefined);
|
|
33
|
+
if (aObj === undefined || bObj === undefined)
|
|
34
|
+
return false;
|
|
35
|
+
const aKeys = Object.keys(aObj);
|
|
36
|
+
if (aKeys.length !== Object.keys(bObj).length)
|
|
37
|
+
return false;
|
|
38
|
+
return aKeys.every((key) => Object.hasOwn(bObj, key) && same(aObj[key], bObj[key]));
|
|
39
|
+
}
|