@ellipsis-dev/sdk 0.1.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/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +53 -0
- package/dist/store/index.d.ts +145 -0
- package/dist/store/index.js +643 -0
- package/dist/stream/index.d.ts +64 -0
- package/dist/stream/index.js +207 -0
- package/dist/types-BRE4NMnS.d.ts +1192 -0
- package/package.json +29 -0
- package/schema/frames.schema.json +1427 -0
- package/schema/openapi.v1.json +1857 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
// src/store/lifecycle.ts
|
|
2
|
+
function setupOutputHook(payload) {
|
|
3
|
+
return typeof payload.hook === "string" ? payload.hook : "setup";
|
|
4
|
+
}
|
|
5
|
+
function setupOutputLine(payload) {
|
|
6
|
+
const lines = Array.isArray(payload.lines) ? payload.lines.filter(
|
|
7
|
+
(l) => typeof l === "string" && l.trim().length > 0
|
|
8
|
+
) : [];
|
|
9
|
+
return lines.length ? lines[lines.length - 1].trim() : null;
|
|
10
|
+
}
|
|
11
|
+
function cacheTierLabel(tier) {
|
|
12
|
+
switch (tier) {
|
|
13
|
+
case "exact":
|
|
14
|
+
return "cached image";
|
|
15
|
+
case "incremental":
|
|
16
|
+
return "incremental build";
|
|
17
|
+
case "full":
|
|
18
|
+
return "full build";
|
|
19
|
+
default:
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function lifecycleText(recordType, payload) {
|
|
24
|
+
switch (recordType) {
|
|
25
|
+
case "sandbox_starting":
|
|
26
|
+
return "Starting sandbox\u2026";
|
|
27
|
+
case "sandbox_setup_output": {
|
|
28
|
+
const last = setupOutputLine(payload);
|
|
29
|
+
return last ? `${setupOutputHook(payload)} \xB7 ${last}` : null;
|
|
30
|
+
}
|
|
31
|
+
case "sandbox_ready": {
|
|
32
|
+
const repos = Array.isArray(payload.repositories) ? payload.repositories.filter(
|
|
33
|
+
(r) => typeof r === "string"
|
|
34
|
+
) : [];
|
|
35
|
+
const parts = ["Sandbox ready"];
|
|
36
|
+
if (repos.length) parts.push(repos.join(", "));
|
|
37
|
+
const tier = cacheTierLabel(payload.cache_tier);
|
|
38
|
+
if (tier) parts.push(tier);
|
|
39
|
+
return parts.join(" \xB7 ");
|
|
40
|
+
}
|
|
41
|
+
case "session_resumed":
|
|
42
|
+
return "Resumed the conversation";
|
|
43
|
+
case "session_paused":
|
|
44
|
+
return "Sleeping \u2014 your next message wakes it";
|
|
45
|
+
case "session_closed":
|
|
46
|
+
return "Conversation closed";
|
|
47
|
+
case "session_cancelled": {
|
|
48
|
+
const reason = typeof payload.reason === "string" ? payload.reason : null;
|
|
49
|
+
return reason ? `Session cancelled \xB7 ${reason}` : "Session cancelled";
|
|
50
|
+
}
|
|
51
|
+
default:
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/store/transcript.ts
|
|
57
|
+
var LineBuffer = class {
|
|
58
|
+
buf = "";
|
|
59
|
+
push(chunk) {
|
|
60
|
+
this.buf += chunk;
|
|
61
|
+
const parts = this.buf.split("\n");
|
|
62
|
+
this.buf = parts.pop() ?? "";
|
|
63
|
+
return parts;
|
|
64
|
+
}
|
|
65
|
+
flush() {
|
|
66
|
+
const rest = this.buf.trim();
|
|
67
|
+
this.buf = "";
|
|
68
|
+
return rest ? [rest] : [];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function parseEventLine(line) {
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
if (!trimmed) return null;
|
|
74
|
+
try {
|
|
75
|
+
const value = JSON.parse(trimmed);
|
|
76
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function oneLine(text, max) {
|
|
84
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
85
|
+
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 3)}...`;
|
|
86
|
+
}
|
|
87
|
+
function summarizeToolInput(name, input) {
|
|
88
|
+
const args = input && typeof input === "object" ? input : {};
|
|
89
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
90
|
+
const tool = name.toLowerCase();
|
|
91
|
+
const path = str(args.file_path) ?? str(args.path) ?? str(args.notebook_path);
|
|
92
|
+
if (["read", "write", "edit", "multiedit", "notebookedit"].includes(tool) && path) {
|
|
93
|
+
return oneLine(path, 100);
|
|
94
|
+
}
|
|
95
|
+
if (tool === "bash" && str(args.command))
|
|
96
|
+
return oneLine(str(args.command), 100);
|
|
97
|
+
if ((tool === "grep" || tool === "glob") && str(args.pattern)) {
|
|
98
|
+
const where = str(args.path) ?? str(args.glob);
|
|
99
|
+
return oneLine(str(args.pattern) + (where ? ` in ${where}` : ""), 100);
|
|
100
|
+
}
|
|
101
|
+
if ((tool === "task" || tool === "agent") && str(args.description)) {
|
|
102
|
+
return oneLine(str(args.description), 100);
|
|
103
|
+
}
|
|
104
|
+
if (tool === "webfetch" && str(args.url)) return oneLine(str(args.url), 100);
|
|
105
|
+
if (tool === "websearch" && str(args.query))
|
|
106
|
+
return oneLine(str(args.query), 100);
|
|
107
|
+
const keys = Object.keys(args);
|
|
108
|
+
if (keys.length === 0) return "";
|
|
109
|
+
return oneLine(JSON.stringify(args), 100);
|
|
110
|
+
}
|
|
111
|
+
function formatDuration(seconds) {
|
|
112
|
+
const s = Math.round(seconds);
|
|
113
|
+
return s >= 60 ? `${Math.floor(s / 60)}m ${s % 60}s` : `${s}s`;
|
|
114
|
+
}
|
|
115
|
+
function blocksOf(content) {
|
|
116
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
117
|
+
if (Array.isArray(content)) return content;
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function toolResultText(block) {
|
|
121
|
+
const c = block.content;
|
|
122
|
+
if (typeof c === "string") return c.trim();
|
|
123
|
+
if (Array.isArray(c)) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
for (const inner of c) {
|
|
126
|
+
if (typeof inner.text === "string") parts.push(inner.text);
|
|
127
|
+
else parts.push(JSON.stringify(inner));
|
|
128
|
+
}
|
|
129
|
+
return parts.join("\n").trim();
|
|
130
|
+
}
|
|
131
|
+
if (c === void 0 || c === null) return "";
|
|
132
|
+
return JSON.stringify(c);
|
|
133
|
+
}
|
|
134
|
+
function eventToItems(event, keyBase, options = {}) {
|
|
135
|
+
const items = [];
|
|
136
|
+
const push = (item) => {
|
|
137
|
+
items.push({ ...item, key: `${keyBase}:${items.length}` });
|
|
138
|
+
};
|
|
139
|
+
const type = event.type;
|
|
140
|
+
if (type === "result") {
|
|
141
|
+
const bits = [];
|
|
142
|
+
if (typeof event.duration_ms === "number")
|
|
143
|
+
bits.push(formatDuration(event.duration_ms / 1e3));
|
|
144
|
+
if (typeof event.total_cost_usd === "number")
|
|
145
|
+
bits.push(`$${event.total_cost_usd.toFixed(2)}`);
|
|
146
|
+
const label = event.is_error ? "turn ended with an error" : "turn complete";
|
|
147
|
+
push({
|
|
148
|
+
kind: "summary",
|
|
149
|
+
text: bits.length ? `${label} \xB7 ${bits.join(" \xB7 ")}` : label,
|
|
150
|
+
spaceBefore: true,
|
|
151
|
+
isError: event.is_error
|
|
152
|
+
});
|
|
153
|
+
return items;
|
|
154
|
+
}
|
|
155
|
+
if (type === "system") {
|
|
156
|
+
if (!options.systemInitLine || event.subtype !== "init") return items;
|
|
157
|
+
const bits = [];
|
|
158
|
+
if (typeof event.model === "string") bits.push(event.model);
|
|
159
|
+
if (typeof event.cwd === "string") bits.push(event.cwd);
|
|
160
|
+
push({
|
|
161
|
+
kind: "system",
|
|
162
|
+
text: bits.length ? `session started \xB7 ${bits.join(" \xB7 ")}` : "session started",
|
|
163
|
+
spaceBefore: true
|
|
164
|
+
});
|
|
165
|
+
return items;
|
|
166
|
+
}
|
|
167
|
+
const blocks = blocksOf(event.message?.content);
|
|
168
|
+
if (type === "user") {
|
|
169
|
+
for (const block of blocks) {
|
|
170
|
+
if (block.type === "tool_result") {
|
|
171
|
+
const body = toolResultText(block);
|
|
172
|
+
push({
|
|
173
|
+
kind: "tool_result",
|
|
174
|
+
gutter: "\u23BF",
|
|
175
|
+
text: body || "(no output)",
|
|
176
|
+
spaceBefore: false,
|
|
177
|
+
isError: block.is_error
|
|
178
|
+
});
|
|
179
|
+
} else if (typeof block.text === "string" && block.text.trim()) {
|
|
180
|
+
push({
|
|
181
|
+
kind: "user",
|
|
182
|
+
gutter: "\u203A",
|
|
183
|
+
text: block.text.trim(),
|
|
184
|
+
spaceBefore: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return items;
|
|
189
|
+
}
|
|
190
|
+
for (const block of blocks) {
|
|
191
|
+
if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim()) {
|
|
192
|
+
push({
|
|
193
|
+
kind: "thinking",
|
|
194
|
+
gutter: "\u273B",
|
|
195
|
+
text: block.thinking.trim(),
|
|
196
|
+
spaceBefore: true
|
|
197
|
+
});
|
|
198
|
+
} else if (block.type === "tool_use") {
|
|
199
|
+
const name = block.name ?? "tool";
|
|
200
|
+
const summary = summarizeToolInput(name, block.input);
|
|
201
|
+
push({
|
|
202
|
+
kind: "tool",
|
|
203
|
+
gutter: "\u25CF",
|
|
204
|
+
text: name,
|
|
205
|
+
detail: summary ? `(${summary})` : void 0,
|
|
206
|
+
spaceBefore: true,
|
|
207
|
+
tool: { name, input: block.input }
|
|
208
|
+
});
|
|
209
|
+
} else if (typeof block.text === "string" && block.text.trim()) {
|
|
210
|
+
push({ kind: "assistant", text: block.text.trim(), spaceBefore: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return items;
|
|
214
|
+
}
|
|
215
|
+
function recordToItems(record, keyBase, options = {}) {
|
|
216
|
+
if (record.source === "lifecycle") {
|
|
217
|
+
const text = lifecycleText(record.record_type, record.payload);
|
|
218
|
+
return text ? [{ key: keyBase, kind: "notice", text, spaceBefore: true }] : [];
|
|
219
|
+
}
|
|
220
|
+
if (record.source !== "claude_code") return [];
|
|
221
|
+
return eventToItems(record.payload, keyBase, options);
|
|
222
|
+
}
|
|
223
|
+
function isConnectVisibleRecord(record) {
|
|
224
|
+
return record.source !== "lifecycle" || record.record_type === "sandbox_ready";
|
|
225
|
+
}
|
|
226
|
+
function pendingToolCalls(items) {
|
|
227
|
+
let pending = [];
|
|
228
|
+
for (const item of items) {
|
|
229
|
+
if (item.kind === "tool") pending.push(item);
|
|
230
|
+
else if (item.kind === "tool_result") pending.shift();
|
|
231
|
+
else pending = [];
|
|
232
|
+
}
|
|
233
|
+
return pending;
|
|
234
|
+
}
|
|
235
|
+
function collapseToolRuns(items) {
|
|
236
|
+
const out = [];
|
|
237
|
+
let group = [];
|
|
238
|
+
const flush = () => {
|
|
239
|
+
if (group.length === 0) return;
|
|
240
|
+
const names = [
|
|
241
|
+
...new Set(group.filter((i) => i.kind === "tool").map((i) => i.text))
|
|
242
|
+
];
|
|
243
|
+
const n = group.filter((i) => i.kind === "tool").length || group.length;
|
|
244
|
+
const plural = n === 1 ? "" : "s";
|
|
245
|
+
let label;
|
|
246
|
+
if (names.length === 1 && names[0] === "Bash")
|
|
247
|
+
label = `Ran ${n} shell command${plural}`;
|
|
248
|
+
else if (names.length === 1 && names[0] === "Read")
|
|
249
|
+
label = `Read ${n} file${plural}`;
|
|
250
|
+
else if (names.length === 0) label = `Ran ${n} tool call${plural}`;
|
|
251
|
+
else {
|
|
252
|
+
const shown = names.slice(0, 3).join(", ") + (names.length > 3 ? ", \u2026" : "");
|
|
253
|
+
label = `Ran ${n} tool call${plural} (${shown})`;
|
|
254
|
+
}
|
|
255
|
+
out.push({
|
|
256
|
+
key: `grp:${group[0].key}`,
|
|
257
|
+
kind: "notice",
|
|
258
|
+
text: label,
|
|
259
|
+
spaceBefore: true
|
|
260
|
+
});
|
|
261
|
+
group = [];
|
|
262
|
+
};
|
|
263
|
+
for (const item of items) {
|
|
264
|
+
if (item.kind === "tool" || item.kind === "tool_result") group.push(item);
|
|
265
|
+
else {
|
|
266
|
+
flush();
|
|
267
|
+
out.push(item);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
flush();
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function clampLines(text, maxLines) {
|
|
274
|
+
const lines = text.split("\n");
|
|
275
|
+
if (lines.length <= maxLines) return { body: text, more: 0 };
|
|
276
|
+
return {
|
|
277
|
+
body: lines.slice(0, maxLines).join("\n"),
|
|
278
|
+
more: lines.length - maxLines
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function resultCostUsd(event) {
|
|
282
|
+
if (event.type !== "result") return null;
|
|
283
|
+
return typeof event.total_cost_usd === "number" ? event.total_cost_usd : null;
|
|
284
|
+
}
|
|
285
|
+
function foldCosts(events) {
|
|
286
|
+
let prev = null;
|
|
287
|
+
let total = null;
|
|
288
|
+
for (const event of events) {
|
|
289
|
+
const cost = resultCostUsd(event);
|
|
290
|
+
if (cost == null) continue;
|
|
291
|
+
prev = total;
|
|
292
|
+
total = cost;
|
|
293
|
+
}
|
|
294
|
+
const lastStep = total != null && prev != null ? Math.max(0, total - prev) : total;
|
|
295
|
+
return { total, lastStep };
|
|
296
|
+
}
|
|
297
|
+
function statusActivityText(status) {
|
|
298
|
+
switch (status) {
|
|
299
|
+
case "scheduled":
|
|
300
|
+
return "Waiting for a worker";
|
|
301
|
+
case "starting":
|
|
302
|
+
return "Starting sandbox";
|
|
303
|
+
case "retrying":
|
|
304
|
+
return "Retrying after a transient error";
|
|
305
|
+
default:
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/store/chatTurns.ts
|
|
311
|
+
var isInitEvent = (data) => data.type === "system" && data.subtype === "init";
|
|
312
|
+
var TOOL_USE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
313
|
+
"tool_use",
|
|
314
|
+
"server_tool_use",
|
|
315
|
+
"mcp_tool_use"
|
|
316
|
+
]);
|
|
317
|
+
function toolResultText2(block) {
|
|
318
|
+
const c = block.content;
|
|
319
|
+
if (typeof c === "string") return c.trim();
|
|
320
|
+
if (Array.isArray(c)) {
|
|
321
|
+
return c.map(
|
|
322
|
+
(inner) => typeof inner.text === "string" ? inner.text : JSON.stringify(inner)
|
|
323
|
+
).join("\n").trim();
|
|
324
|
+
}
|
|
325
|
+
if (c === void 0 || c === null) return "";
|
|
326
|
+
return JSON.stringify(c);
|
|
327
|
+
}
|
|
328
|
+
function groupRecordsToChatTurns(records) {
|
|
329
|
+
const turns = [];
|
|
330
|
+
const toolNodeByUseId = /* @__PURE__ */ new Map();
|
|
331
|
+
let seq = 0;
|
|
332
|
+
const nextKey = (base) => `${base}:${seq++}`;
|
|
333
|
+
let openIdx = -1;
|
|
334
|
+
let initCount = 0;
|
|
335
|
+
let pendingResume = false;
|
|
336
|
+
const openAgentTurn = (record) => {
|
|
337
|
+
if (openIdx < 0) {
|
|
338
|
+
turns.push({
|
|
339
|
+
key: nextKey(record.id),
|
|
340
|
+
role: "assistant",
|
|
341
|
+
nodes: [],
|
|
342
|
+
startedAt: record.created_at,
|
|
343
|
+
completedAt: null,
|
|
344
|
+
durationMs: null,
|
|
345
|
+
costUsd: null,
|
|
346
|
+
tokens: null,
|
|
347
|
+
resumed: pendingResume
|
|
348
|
+
});
|
|
349
|
+
pendingResume = false;
|
|
350
|
+
openIdx = turns.length - 1;
|
|
351
|
+
}
|
|
352
|
+
return turns[openIdx];
|
|
353
|
+
};
|
|
354
|
+
for (const record of records) {
|
|
355
|
+
if (record.source === "lifecycle") {
|
|
356
|
+
const text = lifecycleText(record.record_type, record.payload);
|
|
357
|
+
if (text) {
|
|
358
|
+
const isSetupOutput = record.record_type === "sandbox_setup_output";
|
|
359
|
+
const hook = isSetupOutput ? setupOutputHook(record.payload) : void 0;
|
|
360
|
+
const prev = turns.length > 0 ? turns[turns.length - 1] : null;
|
|
361
|
+
const prevNode = prev?.nodes[0];
|
|
362
|
+
if (isSetupOutput && prev && prevNode && prevNode.kind === "lifecycle" && prevNode.hook === hook) {
|
|
363
|
+
prevNode.text = text;
|
|
364
|
+
prev.completedAt = record.created_at;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
turns.push({
|
|
368
|
+
key: nextKey(record.id),
|
|
369
|
+
role: "lifecycle",
|
|
370
|
+
nodes: [
|
|
371
|
+
{
|
|
372
|
+
key: nextKey(record.id),
|
|
373
|
+
kind: "lifecycle",
|
|
374
|
+
text,
|
|
375
|
+
recordType: record.record_type,
|
|
376
|
+
hook
|
|
377
|
+
}
|
|
378
|
+
],
|
|
379
|
+
startedAt: record.created_at,
|
|
380
|
+
completedAt: record.created_at,
|
|
381
|
+
durationMs: null,
|
|
382
|
+
costUsd: null,
|
|
383
|
+
tokens: null,
|
|
384
|
+
resumed: false
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (record.source !== "claude_code") continue;
|
|
390
|
+
const data = record.payload;
|
|
391
|
+
if (data.type === "system") {
|
|
392
|
+
if (isInitEvent(data)) {
|
|
393
|
+
initCount += 1;
|
|
394
|
+
if (initCount > 1) pendingResume = true;
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (data.type === "result") {
|
|
399
|
+
if (openIdx >= 0) {
|
|
400
|
+
turns[openIdx].completedAt = record.created_at;
|
|
401
|
+
turns[openIdx].durationMs = typeof data.duration_ms === "number" ? data.duration_ms : null;
|
|
402
|
+
if (typeof data.total_cost_usd === "number")
|
|
403
|
+
turns[openIdx].costUsd = data.total_cost_usd;
|
|
404
|
+
openIdx = -1;
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const message = data.message;
|
|
409
|
+
if (!message) continue;
|
|
410
|
+
if (data.type === "user") {
|
|
411
|
+
const content2 = message.content;
|
|
412
|
+
if (typeof content2 === "string") {
|
|
413
|
+
if (content2.trim()) {
|
|
414
|
+
openIdx = -1;
|
|
415
|
+
turns.push({
|
|
416
|
+
resumed: false,
|
|
417
|
+
key: nextKey(record.id),
|
|
418
|
+
role: "user",
|
|
419
|
+
nodes: [
|
|
420
|
+
{ key: nextKey(record.id), kind: "user", text: content2.trim() }
|
|
421
|
+
],
|
|
422
|
+
startedAt: record.created_at,
|
|
423
|
+
completedAt: record.created_at,
|
|
424
|
+
durationMs: null,
|
|
425
|
+
costUsd: null,
|
|
426
|
+
tokens: null
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (!Array.isArray(content2)) continue;
|
|
432
|
+
for (const block of content2) {
|
|
433
|
+
if (block.type !== "tool_result") continue;
|
|
434
|
+
const body = toolResultText2(block) || "(no output)";
|
|
435
|
+
const useId = block.tool_use_id ?? void 0;
|
|
436
|
+
const node = useId != null ? toolNodeByUseId.get(useId) : void 0;
|
|
437
|
+
if (node) {
|
|
438
|
+
node.result = body;
|
|
439
|
+
node.isError = !!block.is_error;
|
|
440
|
+
} else {
|
|
441
|
+
const orphan = {
|
|
442
|
+
key: nextKey(record.id),
|
|
443
|
+
kind: "tool",
|
|
444
|
+
name: "tool",
|
|
445
|
+
input: null,
|
|
446
|
+
summary: "",
|
|
447
|
+
result: body,
|
|
448
|
+
isError: !!block.is_error
|
|
449
|
+
};
|
|
450
|
+
openAgentTurn(record).nodes.push(orphan);
|
|
451
|
+
if (useId != null) toolNodeByUseId.set(useId, orphan);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const content = message.content;
|
|
457
|
+
if (!Array.isArray(content)) continue;
|
|
458
|
+
const turn = openAgentTurn(record);
|
|
459
|
+
const usage = message.usage;
|
|
460
|
+
if (usage && typeof usage.output_tokens === "number") {
|
|
461
|
+
turn.tokens = (turn.tokens ?? 0) + usage.output_tokens;
|
|
462
|
+
}
|
|
463
|
+
for (const block of content) {
|
|
464
|
+
if ((block.type === "thinking" || block.type === "redacted_thinking") && typeof block.thinking === "string" && block.thinking.trim()) {
|
|
465
|
+
turn.nodes.push({
|
|
466
|
+
key: nextKey(record.id),
|
|
467
|
+
kind: "thinking",
|
|
468
|
+
text: block.thinking.trim()
|
|
469
|
+
});
|
|
470
|
+
} else if (block.type != null && TOOL_USE_BLOCK_TYPES.has(block.type)) {
|
|
471
|
+
const name = block.name ?? "tool";
|
|
472
|
+
const node = {
|
|
473
|
+
key: nextKey(record.id),
|
|
474
|
+
kind: "tool",
|
|
475
|
+
name,
|
|
476
|
+
input: block.input ?? null,
|
|
477
|
+
summary: summarizeToolInput(name, block.input ?? void 0),
|
|
478
|
+
result: null,
|
|
479
|
+
isError: false
|
|
480
|
+
};
|
|
481
|
+
turn.nodes.push(node);
|
|
482
|
+
if (block.id) toolNodeByUseId.set(block.id, node);
|
|
483
|
+
} else if (block.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
484
|
+
turn.nodes.push({
|
|
485
|
+
key: nextKey(record.id),
|
|
486
|
+
kind: "assistant",
|
|
487
|
+
text: block.text.trim()
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return turns;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/store/index.ts
|
|
496
|
+
var TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set([
|
|
497
|
+
"completed",
|
|
498
|
+
"error",
|
|
499
|
+
"cancelled",
|
|
500
|
+
"stopped"
|
|
501
|
+
]);
|
|
502
|
+
function isConversationOver(session) {
|
|
503
|
+
if (session.session_state != null) {
|
|
504
|
+
return session.session_state === "closed";
|
|
505
|
+
}
|
|
506
|
+
return TERMINAL_SESSION_STATUSES.has(session.status);
|
|
507
|
+
}
|
|
508
|
+
var EMPTY_SNAPSHOT = {
|
|
509
|
+
session: null,
|
|
510
|
+
records: [],
|
|
511
|
+
messages: [],
|
|
512
|
+
acknowledgedMessageIds: /* @__PURE__ */ new Set(),
|
|
513
|
+
historyTruncated: false,
|
|
514
|
+
liveText: "",
|
|
515
|
+
liveOutputTokens: null,
|
|
516
|
+
lastEventAt: null,
|
|
517
|
+
conversationOver: false
|
|
518
|
+
};
|
|
519
|
+
function emptySessionTranscriptSnapshot() {
|
|
520
|
+
return EMPTY_SNAPSHOT;
|
|
521
|
+
}
|
|
522
|
+
var SessionTranscriptStore = class {
|
|
523
|
+
snapshot = EMPTY_SNAPSHOT;
|
|
524
|
+
listeners = /* @__PURE__ */ new Set();
|
|
525
|
+
acknowledged = /* @__PURE__ */ new Set();
|
|
526
|
+
// The resume cursor: the highest feed_seq received via records_append. Only
|
|
527
|
+
// records advance it (§3.4) — never messages/session frames. streamSession
|
|
528
|
+
// tracks its own copy; this one seeds a NEW streamSession call (e.g. a
|
|
529
|
+
// page-level reconnect after unmount) from already-held state.
|
|
530
|
+
cursorSeq = 0;
|
|
531
|
+
// Derived-turn cache, keyed on the records array identity.
|
|
532
|
+
turnsFor = null;
|
|
533
|
+
turnsCache = [];
|
|
534
|
+
get cursor() {
|
|
535
|
+
return this.cursorSeq;
|
|
536
|
+
}
|
|
537
|
+
subscribe = (listener) => {
|
|
538
|
+
this.listeners.add(listener);
|
|
539
|
+
return () => {
|
|
540
|
+
this.listeners.delete(listener);
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
getSnapshot = () => this.snapshot;
|
|
544
|
+
// The chat-turn grouping of the current record log (memoized per
|
|
545
|
+
// records_append batch) — what a chat-style transcript renders.
|
|
546
|
+
chatTurns = () => {
|
|
547
|
+
const records = this.snapshot.records;
|
|
548
|
+
if (this.turnsFor !== records) {
|
|
549
|
+
this.turnsCache = groupRecordsToChatTurns(records);
|
|
550
|
+
this.turnsFor = records;
|
|
551
|
+
}
|
|
552
|
+
return this.turnsCache;
|
|
553
|
+
};
|
|
554
|
+
// Ingest one frame from the stream client. Unknown frame types stamp
|
|
555
|
+
// liveness and change nothing else (§3.6).
|
|
556
|
+
ingest = (rawFrame) => {
|
|
557
|
+
const prev = this.snapshot;
|
|
558
|
+
const next = {
|
|
559
|
+
...prev,
|
|
560
|
+
lastEventAt: Date.now()
|
|
561
|
+
};
|
|
562
|
+
const frame = rawFrame;
|
|
563
|
+
switch (frame.type) {
|
|
564
|
+
case "snapshot": {
|
|
565
|
+
next.session = frame.session;
|
|
566
|
+
next.messages = frame.messages;
|
|
567
|
+
for (const message of frame.messages) this.acknowledged.add(message.id);
|
|
568
|
+
next.acknowledgedMessageIds = new Set(this.acknowledged);
|
|
569
|
+
next.conversationOver = isConversationOver(frame.session);
|
|
570
|
+
next.historyTruncated = frame.earliest_feed_seq != null && this.cursorSeq < frame.earliest_feed_seq - 1;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case "records_append": {
|
|
574
|
+
const fresh = frame.records.filter((r) => r.feed_seq > this.cursorSeq);
|
|
575
|
+
if (fresh.length === 0) break;
|
|
576
|
+
this.cursorSeq = fresh[fresh.length - 1].feed_seq;
|
|
577
|
+
next.records = [...prev.records, ...fresh];
|
|
578
|
+
let acknowledgedNew = false;
|
|
579
|
+
for (const record of fresh) {
|
|
580
|
+
if (record.session_message_id != null) {
|
|
581
|
+
this.acknowledged.add(record.session_message_id);
|
|
582
|
+
acknowledgedNew = true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (acknowledgedNew) {
|
|
586
|
+
next.acknowledgedMessageIds = new Set(this.acknowledged);
|
|
587
|
+
}
|
|
588
|
+
next.liveText = "";
|
|
589
|
+
next.liveOutputTokens = null;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
case "messages": {
|
|
593
|
+
next.messages = frame.messages;
|
|
594
|
+
for (const message of frame.messages) this.acknowledged.add(message.id);
|
|
595
|
+
next.acknowledgedMessageIds = new Set(this.acknowledged);
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
case "session": {
|
|
599
|
+
next.session = frame.session;
|
|
600
|
+
next.conversationOver = isConversationOver(frame.session);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
case "delta": {
|
|
604
|
+
if (frame.kind !== "text") break;
|
|
605
|
+
if (frame.text != null) next.liveText = prev.liveText + frame.text;
|
|
606
|
+
if (frame.output_tokens != null)
|
|
607
|
+
next.liveOutputTokens = frame.output_tokens;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
case "done": {
|
|
611
|
+
next.conversationOver = true;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
default:
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
this.snapshot = next;
|
|
618
|
+
for (const listener of this.listeners) listener();
|
|
619
|
+
};
|
|
620
|
+
};
|
|
621
|
+
export {
|
|
622
|
+
LineBuffer,
|
|
623
|
+
SessionTranscriptStore,
|
|
624
|
+
clampLines,
|
|
625
|
+
collapseToolRuns,
|
|
626
|
+
emptySessionTranscriptSnapshot,
|
|
627
|
+
eventToItems,
|
|
628
|
+
foldCosts,
|
|
629
|
+
formatDuration,
|
|
630
|
+
groupRecordsToChatTurns,
|
|
631
|
+
isConnectVisibleRecord,
|
|
632
|
+
isConversationOver,
|
|
633
|
+
lifecycleText,
|
|
634
|
+
oneLine,
|
|
635
|
+
parseEventLine,
|
|
636
|
+
pendingToolCalls,
|
|
637
|
+
recordToItems,
|
|
638
|
+
resultCostUsd,
|
|
639
|
+
setupOutputHook,
|
|
640
|
+
setupOutputLine,
|
|
641
|
+
statusActivityText,
|
|
642
|
+
summarizeToolInput
|
|
643
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { w as StreamFrame, A as AgentSessionWire } from '../types-BRE4NMnS.js';
|
|
2
|
+
|
|
3
|
+
declare const SESSION_STREAM_PROTOCOL_VERSION = 2;
|
|
4
|
+
declare const WS_CLOSE_NORMAL = 1000;
|
|
5
|
+
declare const WS_CLOSE_GOING_AWAY = 1001;
|
|
6
|
+
declare const WS_CLOSE_UNSUPPORTED_PROTOCOL_VERSION = 1002;
|
|
7
|
+
declare const WS_CLOSE_NO_PROTOCOL = 1003;
|
|
8
|
+
declare const WS_CLOSE_AUTH_FAILED = 1008;
|
|
9
|
+
declare const WS_CLOSE_SERVER_ERROR = 1011;
|
|
10
|
+
declare const WS_CLOSE_OVER_CAPACITY = 1013;
|
|
11
|
+
declare function sessionStatusWord(session: AgentSessionWire): string;
|
|
12
|
+
type StreamOutcome = {
|
|
13
|
+
type: 'done';
|
|
14
|
+
status: string;
|
|
15
|
+
exitStatus: string | null;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'error';
|
|
18
|
+
message: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'aborted';
|
|
21
|
+
};
|
|
22
|
+
declare class StreamUnavailableError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
declare class StreamAuthError extends Error {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
interface StreamSocket {
|
|
29
|
+
onOpen(cb: () => void): void;
|
|
30
|
+
onMessage(cb: (data: string) => void): void;
|
|
31
|
+
onClose(cb: (code: number) => void): void;
|
|
32
|
+
onError(cb: (err: Error) => void): void;
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
|
35
|
+
type OpenSocket = (args: {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
afterSeq: number;
|
|
38
|
+
query: string;
|
|
39
|
+
}) => StreamSocket | Promise<StreamSocket>;
|
|
40
|
+
declare function streamQuery(afterSeq: number): string;
|
|
41
|
+
type CloseKind = 'normal' | 'auth' | 'unsupported' | 'retry';
|
|
42
|
+
declare function classifyCloseCode(code: number): CloseKind;
|
|
43
|
+
declare function nextReconnectDelayMs(attempt: number): number;
|
|
44
|
+
interface ReconnectDecision {
|
|
45
|
+
action: 'reconnect' | 'fallback' | 'fail-auth';
|
|
46
|
+
delayMs?: number;
|
|
47
|
+
}
|
|
48
|
+
declare function decideReconnect(params: {
|
|
49
|
+
closeKind?: CloseKind;
|
|
50
|
+
everReceivedFrame: boolean;
|
|
51
|
+
attempt: number;
|
|
52
|
+
maxReconnects: number;
|
|
53
|
+
}): ReconnectDecision;
|
|
54
|
+
interface StreamSessionOptions {
|
|
55
|
+
sessionId: string;
|
|
56
|
+
openSocket: OpenSocket;
|
|
57
|
+
onFrame: (frame: StreamFrame) => void;
|
|
58
|
+
afterSeq?: number;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
maxReconnects?: number;
|
|
61
|
+
}
|
|
62
|
+
declare function streamSession(opts: StreamSessionOptions): Promise<StreamOutcome>;
|
|
63
|
+
|
|
64
|
+
export { type CloseKind, type OpenSocket, type ReconnectDecision, SESSION_STREAM_PROTOCOL_VERSION, StreamAuthError, StreamFrame, type StreamOutcome, type StreamSessionOptions, type StreamSocket, StreamUnavailableError, WS_CLOSE_AUTH_FAILED, WS_CLOSE_GOING_AWAY, WS_CLOSE_NORMAL, WS_CLOSE_NO_PROTOCOL, WS_CLOSE_OVER_CAPACITY, WS_CLOSE_SERVER_ERROR, WS_CLOSE_UNSUPPORTED_PROTOCOL_VERSION, classifyCloseCode, decideReconnect, nextReconnectDelayMs, sessionStatusWord, streamQuery, streamSession };
|