@agentshouse/mdruntime 0.1.0-alpha.1 → 0.1.0-alpha.11
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/AGENTS.md +78 -0
- package/dist/adapter.d.ts +142 -29
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/bootstrap.d.ts +14 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +55 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/change.d.ts +65 -0
- package/dist/change.d.ts.map +1 -0
- package/dist/change.js +12 -0
- package/dist/change.js.map +1 -0
- package/dist/client.d.ts +63 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +227 -0
- package/dist/client.js.map +1 -0
- package/dist/collection.d.ts +33 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +115 -0
- package/dist/collection.js.map +1 -0
- package/dist/connection.d.ts +85 -7
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1573 -46
- package/dist/connection.js.map +1 -1
- package/dist/conversation.d.ts +57 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +404 -0
- package/dist/conversation.js.map +1 -0
- package/dist/durability.d.ts +9 -0
- package/dist/durability.d.ts.map +1 -0
- package/dist/durability.js +11 -0
- package/dist/durability.js.map +1 -0
- package/dist/file.d.ts +20 -0
- package/dist/file.d.ts.map +1 -0
- package/dist/file.js +149 -0
- package/dist/file.js.map +1 -0
- package/dist/host.d.ts +9 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +200 -0
- package/dist/host.js.map +1 -0
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +16 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +5 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +15 -0
- package/dist/log.js.map +1 -0
- package/dist/memory.d.ts +24 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +300 -37
- package/dist/memory.js.map +1 -1
- package/dist/model.d.ts +70 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +28 -0
- package/dist/model.js.map +1 -0
- package/dist/outcome.d.ts +37 -0
- package/dist/outcome.d.ts.map +1 -0
- package/dist/outcome.js +23 -0
- package/dist/outcome.js.map +1 -0
- package/dist/overlay.d.ts +8 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +139 -0
- package/dist/overlay.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +47 -0
- package/dist/paths.js.map +1 -0
- package/dist/query.d.ts +13 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +18 -0
- package/dist/query.js.map +1 -0
- package/dist/refresh.d.ts +14 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +65 -0
- package/dist/refresh.js.map +1 -0
- package/dist/replica.d.ts +23 -0
- package/dist/replica.d.ts.map +1 -0
- package/dist/replica.js +78 -0
- package/dist/replica.js.map +1 -0
- package/dist/save.d.ts +31 -0
- package/dist/save.d.ts.map +1 -0
- package/dist/save.js +537 -0
- package/dist/save.js.map +1 -0
- package/dist/status.d.ts +25 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +32 -0
- package/dist/status.js.map +1 -0
- package/dist/store.d.ts +76 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +191 -0
- package/dist/store.js.map +1 -0
- package/dist/transfer.d.ts +5 -0
- package/dist/transfer.d.ts.map +1 -0
- package/dist/transfer.js +5 -0
- package/dist/transfer.js.map +1 -0
- package/dist/wire.d.ts +63 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +139 -0
- package/dist/wire.js.map +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { declaredFile } from './file.js';
|
|
2
|
+
const NO_TURNS = new Set();
|
|
3
|
+
function carriedMessage(value) {
|
|
4
|
+
if (typeof value !== 'object' || value === null) return null;
|
|
5
|
+
const record = value;
|
|
6
|
+
if (typeof record.turn !== 'string') return null;
|
|
7
|
+
return structuredClone(value);
|
|
8
|
+
}
|
|
9
|
+
function historyPage(result) {
|
|
10
|
+
if (result.kind !== 'action') return null;
|
|
11
|
+
const answer = result.result;
|
|
12
|
+
if (typeof answer !== 'object' || answer === null) return null;
|
|
13
|
+
const record = answer;
|
|
14
|
+
if (!Array.isArray(record.messages)) return null;
|
|
15
|
+
const messages = [];
|
|
16
|
+
for (const entry of record.messages){
|
|
17
|
+
const carried = carriedMessage(entry);
|
|
18
|
+
if (carried !== null) messages.push(carried);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
messages,
|
|
22
|
+
cursor: typeof record.cursor === 'string' ? record.cursor : null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function turnOf(result) {
|
|
26
|
+
if (result.kind !== 'action') return null;
|
|
27
|
+
const answer = result.result;
|
|
28
|
+
if (typeof answer !== 'object' || answer === null) return null;
|
|
29
|
+
const record = answer;
|
|
30
|
+
return typeof record.turn === 'string' ? record.turn : null;
|
|
31
|
+
}
|
|
32
|
+
function newest(held, page, arrived) {
|
|
33
|
+
const arriving = new Set(page.messages.map((entry)=>entry.turn));
|
|
34
|
+
const overlap = held.messages.findIndex((entry)=>arriving.has(entry.turn));
|
|
35
|
+
const carried = overlap === -1 ? [] : held.messages.slice(0, overlap);
|
|
36
|
+
const placed = new Set([
|
|
37
|
+
...carried,
|
|
38
|
+
...page.messages
|
|
39
|
+
].map((entry)=>entry.turn));
|
|
40
|
+
const live = held.messages.filter((entry)=>arrived.has(entry.turn) && !placed.has(entry.turn));
|
|
41
|
+
return {
|
|
42
|
+
...held,
|
|
43
|
+
messages: [
|
|
44
|
+
...carried,
|
|
45
|
+
...page.messages,
|
|
46
|
+
...live
|
|
47
|
+
],
|
|
48
|
+
cursor: overlap === -1 ? page.cursor : held.cursor
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function older(held, page) {
|
|
52
|
+
const present = new Set(held.messages.map((entry)=>entry.turn));
|
|
53
|
+
return {
|
|
54
|
+
...held,
|
|
55
|
+
messages: [
|
|
56
|
+
...page.messages.filter((entry)=>!present.has(entry.turn)),
|
|
57
|
+
...held.messages
|
|
58
|
+
],
|
|
59
|
+
cursor: page.cursor
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function sealed(held, arriving) {
|
|
63
|
+
const at = held.messages.findIndex((entry)=>entry.turn === arriving.turn);
|
|
64
|
+
if (at === -1) return {
|
|
65
|
+
...held,
|
|
66
|
+
messages: [
|
|
67
|
+
...held.messages,
|
|
68
|
+
arriving
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
const messages = [
|
|
72
|
+
...held.messages
|
|
73
|
+
];
|
|
74
|
+
messages[at] = arriving;
|
|
75
|
+
return {
|
|
76
|
+
...held,
|
|
77
|
+
messages
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function createConversationStore(deps) {
|
|
81
|
+
const held = new Map();
|
|
82
|
+
const composed = new Map();
|
|
83
|
+
const growing = new Map();
|
|
84
|
+
const viewers = new Set();
|
|
85
|
+
const issued = new Map();
|
|
86
|
+
const live = new Map();
|
|
87
|
+
let handed;
|
|
88
|
+
function heldOf(conversation) {
|
|
89
|
+
return held.get(conversation) ?? {
|
|
90
|
+
messages: [],
|
|
91
|
+
cursor: null,
|
|
92
|
+
summary: null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function instructionOf(conversation) {
|
|
96
|
+
return composed.get(conversation) ?? {
|
|
97
|
+
text: '',
|
|
98
|
+
files: [],
|
|
99
|
+
turn: null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function viewOf(conversation) {
|
|
103
|
+
const carried = heldOf(conversation);
|
|
104
|
+
const unsent = composed.get(conversation);
|
|
105
|
+
return {
|
|
106
|
+
conversation,
|
|
107
|
+
messages: structuredClone(carried.messages),
|
|
108
|
+
olderPages: carried.cursor !== null,
|
|
109
|
+
growingText: growing.get(conversation) ?? null,
|
|
110
|
+
summary: structuredClone(carried.summary),
|
|
111
|
+
instruction: unsent === undefined || unsent.turn !== null ? null : {
|
|
112
|
+
text: unsent.text,
|
|
113
|
+
files: [
|
|
114
|
+
...unsent.files
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function emit(conversation) {
|
|
120
|
+
if (deps.closed()) return;
|
|
121
|
+
for (const viewer of viewers){
|
|
122
|
+
if (viewer.conversation === conversation && viewer.ready) viewer.notify(viewOf(conversation));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function viewed() {
|
|
126
|
+
return [
|
|
127
|
+
...new Set([
|
|
128
|
+
...viewers
|
|
129
|
+
].map((viewer)=>viewer.conversation))
|
|
130
|
+
].sort();
|
|
131
|
+
}
|
|
132
|
+
function hand() {
|
|
133
|
+
const path = deps.path();
|
|
134
|
+
if (path === undefined) return;
|
|
135
|
+
const open = viewed();
|
|
136
|
+
const named = JSON.stringify(open);
|
|
137
|
+
if (named === handed) return;
|
|
138
|
+
handed = named;
|
|
139
|
+
path.view(open);
|
|
140
|
+
}
|
|
141
|
+
function keptRecord(conversation) {
|
|
142
|
+
const carried = heldOf(conversation);
|
|
143
|
+
return {
|
|
144
|
+
conversation,
|
|
145
|
+
messages: carried.messages,
|
|
146
|
+
cursor: carried.cursor,
|
|
147
|
+
summary: carried.summary
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function settle(local, conversation) {
|
|
151
|
+
const sent = instructionOf(conversation).turn;
|
|
152
|
+
if (sent === null) return;
|
|
153
|
+
if (!heldOf(conversation).messages.some((entry)=>entry.turn === sent)) return;
|
|
154
|
+
composed.delete(conversation);
|
|
155
|
+
await local.dropInstruction(conversation);
|
|
156
|
+
}
|
|
157
|
+
async function keep(conversation, next) {
|
|
158
|
+
const kept = await deps.durably(async (local)=>{
|
|
159
|
+
const following = next(heldOf(conversation));
|
|
160
|
+
if (following === null) return false;
|
|
161
|
+
held.set(conversation, following);
|
|
162
|
+
await local.keepConversation(keptRecord(conversation));
|
|
163
|
+
await settle(local, conversation);
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
if (kept) emit(conversation);
|
|
167
|
+
return kept;
|
|
168
|
+
}
|
|
169
|
+
async function keepInstruction(conversation, next) {
|
|
170
|
+
await deps.durably(async (local)=>{
|
|
171
|
+
const stored = next(instructionOf(conversation));
|
|
172
|
+
composed.set(conversation, stored);
|
|
173
|
+
await local.keepInstruction({
|
|
174
|
+
conversation,
|
|
175
|
+
...stored
|
|
176
|
+
});
|
|
177
|
+
await settle(local, conversation);
|
|
178
|
+
});
|
|
179
|
+
emit(conversation);
|
|
180
|
+
}
|
|
181
|
+
async function dropInstruction(conversation) {
|
|
182
|
+
await deps.durably(async (local)=>{
|
|
183
|
+
composed.delete(conversation);
|
|
184
|
+
await local.dropInstruction(conversation);
|
|
185
|
+
});
|
|
186
|
+
emit(conversation);
|
|
187
|
+
}
|
|
188
|
+
function supersede(conversation) {
|
|
189
|
+
issued.set(conversation, (issued.get(conversation) ?? 0) + 1);
|
|
190
|
+
}
|
|
191
|
+
async function fill(conversation, cursor) {
|
|
192
|
+
const generation = issued.get(conversation);
|
|
193
|
+
const result = await deps.call('conversation.history', {
|
|
194
|
+
conversation,
|
|
195
|
+
...cursor === null ? {} : {
|
|
196
|
+
cursor
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const page = historyPage(result);
|
|
200
|
+
if (page === null) return result;
|
|
201
|
+
const landed = await keep(conversation, (carried)=>{
|
|
202
|
+
if (issued.get(conversation) !== generation) return null;
|
|
203
|
+
if (cursor !== null) return carried.cursor === cursor ? older(carried, page) : null;
|
|
204
|
+
return newest(carried, page, live.get(conversation) ?? NO_TURNS);
|
|
205
|
+
});
|
|
206
|
+
if (landed && cursor === null) live.delete(conversation);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
function invoke(name, conversation, extra = {}) {
|
|
210
|
+
return deps.call(name, {
|
|
211
|
+
conversation,
|
|
212
|
+
...extra
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const surface = {
|
|
216
|
+
list () {
|
|
217
|
+
const declaration = deps.cards();
|
|
218
|
+
if (declaration === undefined) return Promise.resolve([]);
|
|
219
|
+
const documents = deps.documents().documents.filter((document)=>document.room === declaration.locator);
|
|
220
|
+
const entries = [];
|
|
221
|
+
for (const card of documents){
|
|
222
|
+
if (card.type !== declaration.type) continue;
|
|
223
|
+
const identity = card.fields[declaration.identity];
|
|
224
|
+
if (typeof identity !== 'string') continue;
|
|
225
|
+
const agent = documents.find((document)=>document.backlinks.some((backlink)=>backlink.path === card.path && backlink.field === declaration.agent));
|
|
226
|
+
entries.push({
|
|
227
|
+
conversation: identity,
|
|
228
|
+
card,
|
|
229
|
+
agent: agent ?? null
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
entries.sort((left, right)=>left.conversation < right.conversation ? -1 : 1);
|
|
233
|
+
return Promise.resolve(entries);
|
|
234
|
+
},
|
|
235
|
+
read (conversation) {
|
|
236
|
+
return deps.durably(()=>Promise.resolve(viewOf(conversation)));
|
|
237
|
+
},
|
|
238
|
+
older (conversation) {
|
|
239
|
+
const cursor = heldOf(conversation).cursor;
|
|
240
|
+
if (cursor === null) return Promise.resolve(null);
|
|
241
|
+
return fill(conversation, cursor);
|
|
242
|
+
},
|
|
243
|
+
compose (conversation, supplied) {
|
|
244
|
+
return keepInstruction(conversation, ()=>({
|
|
245
|
+
text: supplied.text,
|
|
246
|
+
files: [
|
|
247
|
+
...supplied.files
|
|
248
|
+
],
|
|
249
|
+
turn: null
|
|
250
|
+
}));
|
|
251
|
+
},
|
|
252
|
+
discard (conversation) {
|
|
253
|
+
return dropInstruction(conversation);
|
|
254
|
+
},
|
|
255
|
+
create (input) {
|
|
256
|
+
return deps.call('conversation.create', input);
|
|
257
|
+
},
|
|
258
|
+
async send (conversation) {
|
|
259
|
+
const unsent = instructionOf(conversation);
|
|
260
|
+
const files = await Promise.all(unsent.files.map(async (file)=>({
|
|
261
|
+
...await declaredFile(file),
|
|
262
|
+
blob: file.blob
|
|
263
|
+
})));
|
|
264
|
+
const result = await invoke('conversation.send', conversation, {
|
|
265
|
+
text: unsent.text,
|
|
266
|
+
files
|
|
267
|
+
});
|
|
268
|
+
const turn = turnOf(result);
|
|
269
|
+
if (turn === null) return result;
|
|
270
|
+
await keepInstruction(conversation, (stored)=>({
|
|
271
|
+
...stored,
|
|
272
|
+
turn
|
|
273
|
+
}));
|
|
274
|
+
return result;
|
|
275
|
+
},
|
|
276
|
+
answer (conversation, answer) {
|
|
277
|
+
return invoke('conversation.answer', conversation, {
|
|
278
|
+
answer
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
interrupt (conversation) {
|
|
282
|
+
return invoke('conversation.interrupt', conversation);
|
|
283
|
+
},
|
|
284
|
+
kill (conversation) {
|
|
285
|
+
return invoke('conversation.kill', conversation);
|
|
286
|
+
},
|
|
287
|
+
async cancel (conversation, turn) {
|
|
288
|
+
const result = await invoke('conversation.cancel', conversation, {
|
|
289
|
+
turn
|
|
290
|
+
});
|
|
291
|
+
if (result.kind !== 'action') return result;
|
|
292
|
+
if (instructionOf(conversation).turn === turn) {
|
|
293
|
+
await keepInstruction(conversation, (stored)=>({
|
|
294
|
+
...stored,
|
|
295
|
+
turn: null
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
},
|
|
300
|
+
async delete (conversation) {
|
|
301
|
+
const result = await invoke('conversation.delete', conversation);
|
|
302
|
+
if (result.kind !== 'action') return result;
|
|
303
|
+
growing.delete(conversation);
|
|
304
|
+
live.delete(conversation);
|
|
305
|
+
supersede(conversation);
|
|
306
|
+
await deps.durably(async (local)=>{
|
|
307
|
+
held.delete(conversation);
|
|
308
|
+
composed.delete(conversation);
|
|
309
|
+
await local.dropConversation(conversation);
|
|
310
|
+
await local.dropInstruction(conversation);
|
|
311
|
+
});
|
|
312
|
+
emit(conversation);
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
surface,
|
|
318
|
+
hydrate (conversations, instructions) {
|
|
319
|
+
held.clear();
|
|
320
|
+
for (const stored of conversations){
|
|
321
|
+
held.set(stored.conversation, {
|
|
322
|
+
messages: stored.messages,
|
|
323
|
+
cursor: stored.cursor,
|
|
324
|
+
summary: stored.summary
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
composed.clear();
|
|
328
|
+
for (const stored of instructions){
|
|
329
|
+
composed.set(stored.conversation, {
|
|
330
|
+
text: stored.text,
|
|
331
|
+
files: stored.files,
|
|
332
|
+
turn: stored.turn
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
observe (conversation, notify) {
|
|
337
|
+
const first = !viewed().includes(conversation);
|
|
338
|
+
const viewer = {
|
|
339
|
+
conversation,
|
|
340
|
+
notify,
|
|
341
|
+
ready: false
|
|
342
|
+
};
|
|
343
|
+
viewers.add(viewer);
|
|
344
|
+
if (first) {
|
|
345
|
+
live.set(conversation, new Set());
|
|
346
|
+
supersede(conversation);
|
|
347
|
+
}
|
|
348
|
+
hand();
|
|
349
|
+
void deps.durably(()=>Promise.resolve()).then(async ()=>{
|
|
350
|
+
if (deps.closed() || !viewers.has(viewer)) return;
|
|
351
|
+
viewer.ready = true;
|
|
352
|
+
viewer.notify(viewOf(conversation));
|
|
353
|
+
if (first) await fill(conversation, null);
|
|
354
|
+
}).catch(()=>{});
|
|
355
|
+
return ()=>{
|
|
356
|
+
if (!viewers.delete(viewer)) return;
|
|
357
|
+
if (!viewed().includes(conversation)) {
|
|
358
|
+
growing.delete(conversation);
|
|
359
|
+
live.delete(conversation);
|
|
360
|
+
}
|
|
361
|
+
hand();
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
receive (event) {
|
|
365
|
+
if (deps.closed() || !viewed().includes(event.conversation)) return;
|
|
366
|
+
if (event.kind === 'growing-text') {
|
|
367
|
+
growing.set(event.conversation, {
|
|
368
|
+
turn: event.turn,
|
|
369
|
+
text: event.text
|
|
370
|
+
});
|
|
371
|
+
emit(event.conversation);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (event.kind === 'session-summary') {
|
|
375
|
+
const summary = structuredClone(event.summary);
|
|
376
|
+
void keep(event.conversation, (carried)=>({
|
|
377
|
+
...carried,
|
|
378
|
+
summary
|
|
379
|
+
})).catch(()=>{});
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const arriving = carriedMessage(event.message);
|
|
383
|
+
if (arriving === null) return;
|
|
384
|
+
live.get(event.conversation)?.add(arriving.turn);
|
|
385
|
+
if (growing.get(event.conversation)?.turn === arriving.turn) {
|
|
386
|
+
growing.delete(event.conversation);
|
|
387
|
+
}
|
|
388
|
+
void deps.durably(async (local)=>{
|
|
389
|
+
held.set(event.conversation, sealed(heldOf(event.conversation), arriving));
|
|
390
|
+
await local.keepConversation(keptRecord(event.conversation));
|
|
391
|
+
await settle(local, event.conversation);
|
|
392
|
+
}).then(()=>{
|
|
393
|
+
emit(event.conversation);
|
|
394
|
+
}).catch(()=>{});
|
|
395
|
+
},
|
|
396
|
+
close () {
|
|
397
|
+
viewers.clear();
|
|
398
|
+
growing.clear();
|
|
399
|
+
live.clear();
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
//# sourceMappingURL=conversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/workspace/packages/mdruntime/src/conversation.ts"],"sourcesContent":["import type { QueryDocument, QueryResult } from '@agentshouse/query';\nimport type {\n ConversationAction,\n ConversationCards,\n ConversationEvent,\n ConversationMessage,\n ViewedConversations,\n} from './adapter.js';\nimport { declaredFile, type InstructionFile } from './file.js';\nimport type { CallResult } from './outcome.js';\nimport type { ConversationRecord, InstructionRecord, RuntimeStore } from './store.js';\n\nexport type ConversationEntry = {\n conversation: string;\n card: QueryDocument;\n agent: QueryDocument | null;\n};\n\nexport type GrowingText = { turn: string; text: string };\n\nexport type UnsentInstruction = {\n text: string;\n files: readonly InstructionFile[];\n};\n\nexport type ConversationView = {\n conversation: string;\n messages: readonly ConversationMessage[];\n olderPages: boolean;\n growingText: GrowingText | null;\n summary: unknown;\n instruction: UnsentInstruction | null;\n};\n\nexport type ConversationSurface = {\n list(): Promise<readonly ConversationEntry[]>;\n read(conversation: string): Promise<ConversationView>;\n older(conversation: string): Promise<CallResult | null>;\n compose(conversation: string, instruction: UnsentInstruction): Promise<void>;\n discard(conversation: string): Promise<void>;\n create(input: unknown): Promise<CallResult>;\n send(conversation: string): Promise<CallResult>;\n answer(conversation: string, answer: unknown): Promise<CallResult>;\n interrupt(conversation: string): Promise<CallResult>;\n kill(conversation: string): Promise<CallResult>;\n cancel(conversation: string, turn: string): Promise<CallResult>;\n delete(conversation: string): Promise<CallResult>;\n};\n\nexport type ConversationDependencies = {\n cards(): ConversationCards | undefined;\n documents(): QueryResult;\n call(name: ConversationAction, input: unknown): Promise<CallResult>;\n durably<T>(work: (local: RuntimeStore) => Promise<T>): Promise<T>;\n path(): ViewedConversations | undefined;\n closed(): boolean;\n};\n\nexport type ConversationStore = {\n surface: ConversationSurface;\n hydrate(\n conversations: readonly ConversationRecord[],\n instructions: readonly InstructionRecord[],\n ): void;\n observe(conversation: string, notify: (view: ConversationView) => void): () => void;\n receive(event: ConversationEvent): void;\n close(): void;\n};\n\ntype Held = {\n messages: readonly ConversationMessage[];\n cursor: string | null;\n summary: unknown;\n};\n\ntype Composed = {\n text: string;\n files: readonly InstructionFile[];\n turn: string | null;\n};\n\ntype Viewing = {\n conversation: string;\n notify: (view: ConversationView) => void;\n ready: boolean;\n};\n\ntype HistoryPage = {\n messages: readonly ConversationMessage[];\n cursor: string | null;\n};\n\nconst NO_TURNS: ReadonlySet<string> = new Set();\n\nfunction carriedMessage(value: unknown): ConversationMessage | null {\n if (typeof value !== 'object' || value === null) return null;\n const record = value as { turn?: unknown };\n if (typeof record.turn !== 'string') return null;\n return structuredClone(value) as ConversationMessage;\n}\n\nfunction historyPage(result: CallResult): HistoryPage | null {\n if (result.kind !== 'action') return null;\n const answer = result.result;\n if (typeof answer !== 'object' || answer === null) return null;\n const record = answer as { messages?: unknown; cursor?: unknown };\n if (!Array.isArray(record.messages)) return null;\n const messages: ConversationMessage[] = [];\n for (const entry of record.messages) {\n const carried = carriedMessage(entry);\n if (carried !== null) messages.push(carried);\n }\n return { messages, cursor: typeof record.cursor === 'string' ? record.cursor : null };\n}\n\nfunction turnOf(result: CallResult): string | null {\n if (result.kind !== 'action') return null;\n const answer = result.result;\n if (typeof answer !== 'object' || answer === null) return null;\n const record = answer as { turn?: unknown };\n return typeof record.turn === 'string' ? record.turn : null;\n}\n\nfunction newest(held: Held, page: HistoryPage, arrived: ReadonlySet<string>): Held {\n const arriving = new Set(page.messages.map((entry) => entry.turn));\n const overlap = held.messages.findIndex((entry) => arriving.has(entry.turn));\n const carried = overlap === -1 ? [] : held.messages.slice(0, overlap);\n const placed = new Set([...carried, ...page.messages].map((entry) => entry.turn));\n const live = held.messages.filter((entry) => arrived.has(entry.turn) && !placed.has(entry.turn));\n return {\n ...held,\n messages: [...carried, ...page.messages, ...live],\n cursor: overlap === -1 ? page.cursor : held.cursor,\n };\n}\n\nfunction older(held: Held, page: HistoryPage): Held {\n const present = new Set(held.messages.map((entry) => entry.turn));\n return {\n ...held,\n messages: [...page.messages.filter((entry) => !present.has(entry.turn)), ...held.messages],\n cursor: page.cursor,\n };\n}\n\nfunction sealed(held: Held, arriving: ConversationMessage): Held {\n const at = held.messages.findIndex((entry) => entry.turn === arriving.turn);\n if (at === -1) return { ...held, messages: [...held.messages, arriving] };\n const messages = [...held.messages];\n messages[at] = arriving;\n return { ...held, messages };\n}\n\nexport function createConversationStore(deps: ConversationDependencies): ConversationStore {\n const held = new Map<string, Held>();\n const composed = new Map<string, Composed>();\n const growing = new Map<string, GrowingText>();\n const viewers = new Set<Viewing>();\n const issued = new Map<string, number>();\n const live = new Map<string, Set<string>>();\n let handed: string | undefined;\n\n function heldOf(conversation: string): Held {\n return held.get(conversation) ?? { messages: [], cursor: null, summary: null };\n }\n\n function instructionOf(conversation: string): Composed {\n return composed.get(conversation) ?? { text: '', files: [], turn: null };\n }\n\n function viewOf(conversation: string): ConversationView {\n const carried = heldOf(conversation);\n const unsent = composed.get(conversation);\n return {\n conversation,\n messages: structuredClone(carried.messages),\n olderPages: carried.cursor !== null,\n growingText: growing.get(conversation) ?? null,\n summary: structuredClone(carried.summary),\n instruction:\n unsent === undefined || unsent.turn !== null\n ? null\n : { text: unsent.text, files: [...unsent.files] },\n };\n }\n\n function emit(conversation: string) {\n if (deps.closed()) return;\n for (const viewer of viewers) {\n if (viewer.conversation === conversation && viewer.ready) viewer.notify(viewOf(conversation));\n }\n }\n\n function viewed(): readonly string[] {\n return [...new Set([...viewers].map((viewer) => viewer.conversation))].sort();\n }\n\n function hand() {\n const path = deps.path();\n if (path === undefined) return;\n const open = viewed();\n const named = JSON.stringify(open);\n if (named === handed) return;\n handed = named;\n path.view(open);\n }\n\n function keptRecord(conversation: string): ConversationRecord {\n const carried = heldOf(conversation);\n return {\n conversation,\n messages: carried.messages,\n cursor: carried.cursor,\n summary: carried.summary,\n };\n }\n\n async function settle(local: RuntimeStore, conversation: string) {\n const sent = instructionOf(conversation).turn;\n if (sent === null) return;\n if (!heldOf(conversation).messages.some((entry) => entry.turn === sent)) return;\n composed.delete(conversation);\n await local.dropInstruction(conversation);\n }\n\n async function keep(\n conversation: string,\n next: (carried: Held) => Held | null,\n ): Promise<boolean> {\n const kept = await deps.durably(async (local) => {\n const following = next(heldOf(conversation));\n if (following === null) return false;\n held.set(conversation, following);\n await local.keepConversation(keptRecord(conversation));\n await settle(local, conversation);\n return true;\n });\n if (kept) emit(conversation);\n return kept;\n }\n\n async function keepInstruction(conversation: string, next: (unsent: Composed) => Composed) {\n await deps.durably(async (local) => {\n const stored = next(instructionOf(conversation));\n composed.set(conversation, stored);\n await local.keepInstruction({ conversation, ...stored });\n await settle(local, conversation);\n });\n emit(conversation);\n }\n\n async function dropInstruction(conversation: string) {\n await deps.durably(async (local) => {\n composed.delete(conversation);\n await local.dropInstruction(conversation);\n });\n emit(conversation);\n }\n\n function supersede(conversation: string) {\n issued.set(conversation, (issued.get(conversation) ?? 0) + 1);\n }\n\n async function fill(conversation: string, cursor: string | null): Promise<CallResult> {\n const generation = issued.get(conversation);\n const result = await deps.call('conversation.history', {\n conversation,\n ...(cursor === null ? {} : { cursor }),\n });\n const page = historyPage(result);\n if (page === null) return result;\n const landed = await keep(conversation, (carried) => {\n if (issued.get(conversation) !== generation) return null;\n if (cursor !== null) return carried.cursor === cursor ? older(carried, page) : null;\n return newest(carried, page, live.get(conversation) ?? NO_TURNS);\n });\n if (landed && cursor === null) live.delete(conversation);\n return result;\n }\n\n function invoke(\n name: ConversationAction,\n conversation: string,\n extra: Readonly<Record<string, unknown>> = {},\n ): Promise<CallResult> {\n return deps.call(name, { conversation, ...extra });\n }\n\n const surface: ConversationSurface = {\n list() {\n const declaration = deps.cards();\n if (declaration === undefined) return Promise.resolve([]);\n const documents = deps\n .documents()\n .documents.filter((document) => document.room === declaration.locator);\n const entries: ConversationEntry[] = [];\n for (const card of documents) {\n if (card.type !== declaration.type) continue;\n const identity = card.fields[declaration.identity];\n if (typeof identity !== 'string') continue;\n const agent = documents.find((document) =>\n document.backlinks.some(\n (backlink) => backlink.path === card.path && backlink.field === declaration.agent,\n ),\n );\n entries.push({ conversation: identity, card, agent: agent ?? null });\n }\n entries.sort((left, right) => (left.conversation < right.conversation ? -1 : 1));\n return Promise.resolve(entries);\n },\n read(conversation: string) {\n return deps.durably(() => Promise.resolve(viewOf(conversation)));\n },\n older(conversation: string) {\n const cursor = heldOf(conversation).cursor;\n if (cursor === null) return Promise.resolve(null);\n return fill(conversation, cursor);\n },\n compose(conversation: string, supplied: UnsentInstruction) {\n return keepInstruction(conversation, () => ({\n text: supplied.text,\n files: [...supplied.files],\n turn: null,\n }));\n },\n discard(conversation: string) {\n return dropInstruction(conversation);\n },\n create(input: unknown) {\n return deps.call('conversation.create', input);\n },\n async send(conversation: string) {\n const unsent = instructionOf(conversation);\n const files = await Promise.all(\n unsent.files.map(async (file) => ({ ...(await declaredFile(file)), blob: file.blob })),\n );\n const result = await invoke('conversation.send', conversation, { text: unsent.text, files });\n const turn = turnOf(result);\n if (turn === null) return result;\n await keepInstruction(conversation, (stored) => ({ ...stored, turn }));\n return result;\n },\n answer(conversation: string, answer: unknown) {\n return invoke('conversation.answer', conversation, { answer });\n },\n interrupt(conversation: string) {\n return invoke('conversation.interrupt', conversation);\n },\n kill(conversation: string) {\n return invoke('conversation.kill', conversation);\n },\n async cancel(conversation: string, turn: string) {\n const result = await invoke('conversation.cancel', conversation, { turn });\n if (result.kind !== 'action') return result;\n if (instructionOf(conversation).turn === turn) {\n await keepInstruction(conversation, (stored) => ({ ...stored, turn: null }));\n }\n return result;\n },\n async delete(conversation: string) {\n const result = await invoke('conversation.delete', conversation);\n if (result.kind !== 'action') return result;\n growing.delete(conversation);\n live.delete(conversation);\n supersede(conversation);\n await deps.durably(async (local) => {\n held.delete(conversation);\n composed.delete(conversation);\n await local.dropConversation(conversation);\n await local.dropInstruction(conversation);\n });\n emit(conversation);\n return result;\n },\n };\n\n return {\n surface,\n hydrate(conversations, instructions) {\n held.clear();\n for (const stored of conversations) {\n held.set(stored.conversation, {\n messages: stored.messages,\n cursor: stored.cursor,\n summary: stored.summary,\n });\n }\n composed.clear();\n for (const stored of instructions) {\n composed.set(stored.conversation, {\n text: stored.text,\n files: stored.files,\n turn: stored.turn,\n });\n }\n },\n observe(conversation: string, notify: (view: ConversationView) => void) {\n const first = !viewed().includes(conversation);\n const viewer: Viewing = { conversation, notify, ready: false };\n viewers.add(viewer);\n if (first) {\n live.set(conversation, new Set());\n supersede(conversation);\n }\n hand();\n void deps\n .durably(() => Promise.resolve())\n .then(async () => {\n if (deps.closed() || !viewers.has(viewer)) return;\n viewer.ready = true;\n viewer.notify(viewOf(conversation));\n if (first) await fill(conversation, null);\n })\n .catch(() => {});\n return () => {\n if (!viewers.delete(viewer)) return;\n if (!viewed().includes(conversation)) {\n growing.delete(conversation);\n live.delete(conversation);\n }\n hand();\n };\n },\n receive(event: ConversationEvent) {\n if (deps.closed() || !viewed().includes(event.conversation)) return;\n if (event.kind === 'growing-text') {\n growing.set(event.conversation, { turn: event.turn, text: event.text });\n emit(event.conversation);\n return;\n }\n if (event.kind === 'session-summary') {\n const summary = structuredClone(event.summary);\n void keep(event.conversation, (carried) => ({ ...carried, summary })).catch(() => {});\n return;\n }\n const arriving = carriedMessage(event.message);\n if (arriving === null) return;\n live.get(event.conversation)?.add(arriving.turn);\n if (growing.get(event.conversation)?.turn === arriving.turn) {\n growing.delete(event.conversation);\n }\n void deps\n .durably(async (local) => {\n held.set(event.conversation, sealed(heldOf(event.conversation), arriving));\n await local.keepConversation(keptRecord(event.conversation));\n await settle(local, event.conversation);\n })\n .then(() => {\n emit(event.conversation);\n })\n .catch(() => {});\n },\n close() {\n viewers.clear();\n growing.clear();\n live.clear();\n },\n };\n}\n"],"names":["declaredFile","NO_TURNS","Set","carriedMessage","value","record","turn","structuredClone","historyPage","result","kind","answer","Array","isArray","messages","entry","carried","push","cursor","turnOf","newest","held","page","arrived","arriving","map","overlap","findIndex","has","slice","placed","live","filter","older","present","sealed","at","createConversationStore","deps","Map","composed","growing","viewers","issued","handed","heldOf","conversation","get","summary","instructionOf","text","files","viewOf","unsent","olderPages","growingText","instruction","undefined","emit","closed","viewer","ready","notify","viewed","sort","hand","path","open","named","JSON","stringify","view","keptRecord","settle","local","sent","some","delete","dropInstruction","keep","next","kept","durably","following","set","keepConversation","keepInstruction","stored","supersede","fill","generation","call","landed","invoke","name","extra","surface","list","declaration","cards","Promise","resolve","documents","document","room","locator","entries","card","type","identity","fields","agent","find","backlinks","backlink","field","left","right","read","compose","supplied","discard","create","input","send","all","file","blob","interrupt","kill","cancel","dropConversation","hydrate","conversations","instructions","clear","observe","first","includes","add","then","catch","receive","event","message","close"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type RuntimeShell = {
|
|
2
|
+
persistentStorage: boolean;
|
|
3
|
+
generation: string | null;
|
|
4
|
+
keepGeneration(value: string): void | Promise<void>;
|
|
5
|
+
dropGeneration(): void | Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
export declare function detectStorageLoss(stored: string | null, reported: string | null): string | null;
|
|
8
|
+
export declare function nextGeneration(stored: string | null, reported: string | null): string;
|
|
9
|
+
//# sourceMappingURL=durability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,cAAc,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAG/F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGrF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const STORAGE_LOSS_STATEMENT = 'Unsynchronized Drafts and Pending Changes were lost.';
|
|
2
|
+
export function detectStorageLoss(stored, reported) {
|
|
3
|
+
if (reported === null || reported === stored) return null;
|
|
4
|
+
return STORAGE_LOSS_STATEMENT;
|
|
5
|
+
}
|
|
6
|
+
export function nextGeneration(stored, reported) {
|
|
7
|
+
if (stored !== null && (reported === null || reported === stored)) return stored;
|
|
8
|
+
return crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=durability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/workspace/packages/mdruntime/src/durability.ts"],"sourcesContent":["const STORAGE_LOSS_STATEMENT = 'Unsynchronized Drafts and Pending Changes were lost.';\n\nexport type RuntimeShell = {\n persistentStorage: boolean;\n generation: string | null;\n keepGeneration(value: string): void | Promise<void>;\n dropGeneration(): void | Promise<void>;\n};\n\nexport function detectStorageLoss(stored: string | null, reported: string | null): string | null {\n if (reported === null || reported === stored) return null;\n return STORAGE_LOSS_STATEMENT;\n}\n\nexport function nextGeneration(stored: string | null, reported: string | null): string {\n if (stored !== null && (reported === null || reported === stored)) return stored;\n return crypto.randomUUID();\n}\n"],"names":["STORAGE_LOSS_STATEMENT","detectStorageLoss","stored","reported","nextGeneration","crypto","randomUUID"],"mappings":"AAAA,MAAMA,yBAAyB;AAS/B,OAAO,SAASC,kBAAkBC,MAAqB,EAAEC,QAAuB;IAC9E,IAAIA,aAAa,QAAQA,aAAaD,QAAQ,OAAO;IACrD,OAAOF;AACT;AAEA,OAAO,SAASI,eAAeF,MAAqB,EAAEC,QAAuB;IAC3E,IAAID,WAAW,QAASC,CAAAA,aAAa,QAAQA,aAAaD,MAAK,GAAI,OAAOA;IAC1E,OAAOG,OAAOC,UAAU;AAC1B"}
|
package/dist/file.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type InstructionFile = {
|
|
2
|
+
name: string;
|
|
3
|
+
blob: Blob;
|
|
4
|
+
};
|
|
5
|
+
export type FileDeclaration = {
|
|
6
|
+
name: string;
|
|
7
|
+
mediaType: string;
|
|
8
|
+
byteLength: number;
|
|
9
|
+
contentHash: string;
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
};
|
|
13
|
+
export type Extent = {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function extentOf(bytes: Uint8Array): Extent | null;
|
|
18
|
+
export declare function hexDigest(digest: ArrayBuffer): string;
|
|
19
|
+
export declare function declaredFile(file: InstructionFile): Promise<FileDeclaration>;
|
|
20
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA2GvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAQzD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAErD;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAUlF"}
|
package/dist/file.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const PNG_SIGNATURE = [
|
|
2
|
+
0x89,
|
|
3
|
+
0x50,
|
|
4
|
+
0x4e,
|
|
5
|
+
0x47,
|
|
6
|
+
0x0d,
|
|
7
|
+
0x0a,
|
|
8
|
+
0x1a,
|
|
9
|
+
0x0a
|
|
10
|
+
];
|
|
11
|
+
const WEBP_SYNC = [
|
|
12
|
+
0x9d,
|
|
13
|
+
0x01,
|
|
14
|
+
0x2a
|
|
15
|
+
];
|
|
16
|
+
const SHAPELESS_JPEG_MARKERS = new Set([
|
|
17
|
+
0xc4,
|
|
18
|
+
0xc8,
|
|
19
|
+
0xcc
|
|
20
|
+
]);
|
|
21
|
+
const BMFF_CONTAINERS = new Set([
|
|
22
|
+
'meta',
|
|
23
|
+
'iprp',
|
|
24
|
+
'ipco'
|
|
25
|
+
]);
|
|
26
|
+
function starts(bytes, signature, at = 0) {
|
|
27
|
+
return signature.every((value, index)=>bytes[at + index] === value);
|
|
28
|
+
}
|
|
29
|
+
function text(bytes, at, length) {
|
|
30
|
+
return String.fromCharCode(...bytes.subarray(at, at + length));
|
|
31
|
+
}
|
|
32
|
+
function bigEndian(bytes, at, length) {
|
|
33
|
+
let value = 0;
|
|
34
|
+
for(let index = 0; index < length; index += 1)value = value * 256 + (bytes[at + index] ?? 0);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function littleEndian(bytes, at, length) {
|
|
38
|
+
let value = 0;
|
|
39
|
+
for(let index = length - 1; index >= 0; index -= 1)value = value * 256 + (bytes[at + index] ?? 0);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function pngExtent(bytes) {
|
|
43
|
+
if (bytes.length < 24 || !starts(bytes, PNG_SIGNATURE)) return null;
|
|
44
|
+
if (text(bytes, 12, 4) !== 'IHDR') return null;
|
|
45
|
+
return {
|
|
46
|
+
width: bigEndian(bytes, 16, 4),
|
|
47
|
+
height: bigEndian(bytes, 20, 4)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function gifExtent(bytes) {
|
|
51
|
+
if (bytes.length < 10 || text(bytes, 0, 3) !== 'GIF') return null;
|
|
52
|
+
return {
|
|
53
|
+
width: littleEndian(bytes, 6, 2),
|
|
54
|
+
height: littleEndian(bytes, 8, 2)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function jpegExtent(bytes) {
|
|
58
|
+
if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return null;
|
|
59
|
+
let at = 2;
|
|
60
|
+
while(at + 9 < bytes.length){
|
|
61
|
+
if (bytes[at] !== 0xff) return null;
|
|
62
|
+
const marker = bytes[at + 1] ?? 0;
|
|
63
|
+
if (marker === 0xd8 || marker === 0x01 || marker >= 0xd0 && marker <= 0xd7) {
|
|
64
|
+
at += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const length = bigEndian(bytes, at + 2, 2);
|
|
68
|
+
if (marker >= 0xc0 && marker <= 0xcf && !SHAPELESS_JPEG_MARKERS.has(marker)) {
|
|
69
|
+
return {
|
|
70
|
+
width: bigEndian(bytes, at + 7, 2),
|
|
71
|
+
height: bigEndian(bytes, at + 5, 2)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (length < 2) return null;
|
|
75
|
+
at += 2 + length;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function webpExtent(bytes) {
|
|
80
|
+
if (bytes.length < 30 || text(bytes, 0, 4) !== 'RIFF' || text(bytes, 8, 4) !== 'WEBP') return null;
|
|
81
|
+
const chunk = text(bytes, 12, 4);
|
|
82
|
+
if (chunk === 'VP8X') {
|
|
83
|
+
return {
|
|
84
|
+
width: littleEndian(bytes, 24, 3) + 1,
|
|
85
|
+
height: littleEndian(bytes, 27, 3) + 1
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (chunk === 'VP8 ' && starts(bytes, WEBP_SYNC, 23)) {
|
|
89
|
+
return {
|
|
90
|
+
width: littleEndian(bytes, 26, 2) & 0x3fff,
|
|
91
|
+
height: littleEndian(bytes, 28, 2) & 0x3fff
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (chunk === 'VP8L' && bytes[20] === 0x2f) {
|
|
95
|
+
const packed = littleEndian(bytes, 21, 4);
|
|
96
|
+
return {
|
|
97
|
+
width: (packed & 0x3fff) + 1,
|
|
98
|
+
height: (packed >>> 14 & 0x3fff) + 1
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function spatialExtent(bytes, from, to) {
|
|
104
|
+
let at = from;
|
|
105
|
+
while(at + 8 <= to){
|
|
106
|
+
const size = bigEndian(bytes, at, 4);
|
|
107
|
+
const name = text(bytes, at + 4, 4);
|
|
108
|
+
const end = size === 0 ? to : at + size;
|
|
109
|
+
if (size === 1 || end <= at || end > to) return null;
|
|
110
|
+
if (name === 'ispe') {
|
|
111
|
+
if (end < at + 20) return null;
|
|
112
|
+
return {
|
|
113
|
+
width: bigEndian(bytes, at + 12, 4),
|
|
114
|
+
height: bigEndian(bytes, at + 16, 4)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (BMFF_CONTAINERS.has(name)) {
|
|
118
|
+
const inner = spatialExtent(bytes, at + (name === 'meta' ? 12 : 8), end);
|
|
119
|
+
if (inner !== null) return inner;
|
|
120
|
+
}
|
|
121
|
+
at = end;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function bmffExtent(bytes) {
|
|
126
|
+
if (bytes.length < 16 || text(bytes, 4, 4) !== 'ftyp') return null;
|
|
127
|
+
return spatialExtent(bytes, 0, bytes.length);
|
|
128
|
+
}
|
|
129
|
+
export function extentOf(bytes) {
|
|
130
|
+
return pngExtent(bytes) ?? jpegExtent(bytes) ?? gifExtent(bytes) ?? webpExtent(bytes) ?? bmffExtent(bytes);
|
|
131
|
+
}
|
|
132
|
+
export function hexDigest(digest) {
|
|
133
|
+
return [
|
|
134
|
+
...new Uint8Array(digest)
|
|
135
|
+
].map((value)=>value.toString(16).padStart(2, '0')).join('');
|
|
136
|
+
}
|
|
137
|
+
export async function declaredFile(file) {
|
|
138
|
+
const bytes = new Uint8Array(await file.blob.arrayBuffer());
|
|
139
|
+
const extent = file.blob.type.startsWith('image/') ? extentOf(bytes) : null;
|
|
140
|
+
return {
|
|
141
|
+
name: file.name,
|
|
142
|
+
mediaType: file.blob.type,
|
|
143
|
+
byteLength: bytes.byteLength,
|
|
144
|
+
contentHash: hexDigest(await crypto.subtle.digest('SHA-256', bytes)),
|
|
145
|
+
...extent === null ? {} : extent
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=file.js.map
|