@pasko70/pibo 1.6.0 → 1.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/apps/chat/chat-trace-helpers.js +47 -4
- package/dist/apps/chat/trace-v2.js +302 -0
- package/dist/apps/chat/trace.js +31 -2
- package/dist/apps/chat/web-app.js +368 -8
- package/dist/apps/chat-ui/assets/{dist-D87he9he.js → dist-BFmfAHTa.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dm6UhVM6.js → dist-BHS5hqHn.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-aLS3alom.js → dist-BKWZVzkX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CFoGUjhX.js → dist-BgiyRvMc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Yx8i6oQ1.js → dist-C1irMXX8.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B0i7AU1O.js → dist-C3CzqW75.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C-UobPQQ.js → dist-DD6PyrDV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-wyhLeLW6.js → dist-DYHLT66j.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DYqJWYtm.js → dist-DbMhwIF-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BwZj_mI4.js → dist-Dp3T6E_K.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BbCXk-v9.js → dist-v8XQydKe.js} +1 -1
- package/dist/apps/chat-ui/assets/index-D8itqvK_.css +1 -0
- package/dist/apps/chat-ui/assets/index-DZdXJmcO.js +166 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-KKfk8l1P.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.7.0.vsix +0 -0
- package/dist/cli.js +10 -0
- package/dist/data/payload-store.js +18 -9
- package/dist/session-ui/terminalRows.js +5 -0
- package/dist/web/http.js +17 -6
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-CNDX-4Kp.js +0 -166
- package/dist/apps/chat-ui/assets/index-DvBWSeIO.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-lA76A7Pc.js +0 -41
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.6.0.vsix +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { patchTraceViewWithEvent } from "../../shared/trace-engine.js";
|
|
3
|
-
export const DEFAULT_TRACE_EVENTS_PAGE_SIZE =
|
|
4
|
-
export const MAX_TRACE_EVENTS_PER_REQUEST =
|
|
3
|
+
export const DEFAULT_TRACE_EVENTS_PAGE_SIZE = 50;
|
|
4
|
+
export const MAX_TRACE_EVENTS_PER_REQUEST = 1000;
|
|
5
|
+
export const TRACE_CACHE_MAX_BYTES = 8 * 1024 * 1024;
|
|
5
6
|
export function etagForVersion(version) {
|
|
6
7
|
return `"${version}"`;
|
|
7
8
|
}
|
|
@@ -80,15 +81,57 @@ export function annotateTracePage(trace, events, input) {
|
|
|
80
81
|
hasOlderEvents: firstEventSequence !== undefined ? firstEventSequence > 1 : false,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
export function setTraceCache(cache, key, trace, maxEntries) {
|
|
84
|
+
export function setTraceCache(cache, key, trace, maxEntries, maxBytes = TRACE_CACHE_MAX_BYTES) {
|
|
84
85
|
if (trace.rawEvents.length > 0)
|
|
85
86
|
return;
|
|
86
87
|
cache.delete(key);
|
|
87
88
|
cache.set(key, trace);
|
|
88
|
-
while (cache.size > maxEntries) {
|
|
89
|
+
while (cache.size > maxEntries || traceCacheEstimatedBytes(cache) > maxBytes) {
|
|
89
90
|
const oldestKey = cache.keys().next().value;
|
|
90
91
|
if (typeof oldestKey !== "string")
|
|
91
92
|
break;
|
|
92
93
|
cache.delete(oldestKey);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
96
|
+
export function traceCacheEstimatedBytes(cache) {
|
|
97
|
+
let bytes = 0;
|
|
98
|
+
for (const trace of cache.values())
|
|
99
|
+
bytes += estimateTraceViewBytes(trace);
|
|
100
|
+
return bytes;
|
|
101
|
+
}
|
|
102
|
+
export function estimateTraceViewBytes(trace) {
|
|
103
|
+
let bytes = 512;
|
|
104
|
+
bytes += byteLength(trace.piboSessionId) + byteLength(trace.piSessionId) + byteLength(trace.title) + byteLength(trace.version);
|
|
105
|
+
for (const node of flattenEstimateNodes(trace.nodes)) {
|
|
106
|
+
bytes += 256;
|
|
107
|
+
bytes += byteLength(node.id) + byteLength(node.parentId) + byteLength(node.title) + byteLength(node.summary);
|
|
108
|
+
bytes += estimatePayloadBytes(node.input) + estimatePayloadBytes(node.output) + byteLength(node.error);
|
|
109
|
+
}
|
|
110
|
+
return bytes;
|
|
111
|
+
}
|
|
112
|
+
function flattenEstimateNodes(nodes) {
|
|
113
|
+
const result = [];
|
|
114
|
+
const visit = (items) => {
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
result.push(item);
|
|
117
|
+
visit(item.children);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
visit(nodes);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function estimatePayloadBytes(value) {
|
|
124
|
+
if (value === undefined || value === null)
|
|
125
|
+
return 0;
|
|
126
|
+
if (typeof value === "string")
|
|
127
|
+
return byteLength(value);
|
|
128
|
+
try {
|
|
129
|
+
return byteLength(JSON.stringify(value));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return byteLength(String(value));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function byteLength(value) {
|
|
136
|
+
return value ? Buffer.byteLength(value, "utf8") : 0;
|
|
137
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const TRACE_V2_DEFAULT_TIMELINE_LIMIT = 50;
|
|
3
|
+
export const TRACE_V2_MAX_TIMELINE_LIMIT = 240;
|
|
4
|
+
export const TRACE_V2_TIMELINE_HARD_BYTES = 256 * 1024;
|
|
5
|
+
export const TRACE_V2_PREVIEW_CHARS = 64;
|
|
6
|
+
export const TRACE_V2_INLINE_PAYLOAD_MAX_BYTES = 8 * 1024;
|
|
7
|
+
export const TRACE_V2_INLINE_TRANSCRIPT_PAYLOAD_MAX_BYTES = 64 * 1024;
|
|
8
|
+
export const TRACE_V2_PAYLOAD_REF_THRESHOLD_BYTES = 4096;
|
|
9
|
+
export const TRACE_V2_PAYLOAD_DEFAULT_LIMIT_BYTES = 64 * 1024;
|
|
10
|
+
export const TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES = 1024 * 1024;
|
|
11
|
+
export const TRACE_V2_RAW_EVENTS_DEFAULT_LIMIT = 80;
|
|
12
|
+
export const TRACE_V2_RAW_EVENTS_MAX_LIMIT = 500;
|
|
13
|
+
export const TRACE_V2_RAW_EVENTS_HARD_BYTES = 256 * 1024;
|
|
14
|
+
export function traceTimelinePageFromView(input) {
|
|
15
|
+
const byteLimit = input.byteLimit ?? TRACE_V2_TIMELINE_HARD_BYTES;
|
|
16
|
+
const nodes = compactTraceNodes({
|
|
17
|
+
nodes: input.trace.nodes,
|
|
18
|
+
payloadStore: input.payloadStore,
|
|
19
|
+
piboSessionId: input.trace.piboSessionId,
|
|
20
|
+
limit: Math.max(1, Math.min(input.limit, TRACE_V2_MAX_TIMELINE_LIMIT)),
|
|
21
|
+
fromTail: input.fromTail,
|
|
22
|
+
});
|
|
23
|
+
let page = {
|
|
24
|
+
piboSessionId: input.trace.piboSessionId,
|
|
25
|
+
piSessionId: input.trace.piSessionId,
|
|
26
|
+
title: input.trace.title,
|
|
27
|
+
version: input.trace.version,
|
|
28
|
+
latestStreamId: input.trace.latestStreamId,
|
|
29
|
+
projectionStatus: "ready",
|
|
30
|
+
cursor: {
|
|
31
|
+
before: input.trace.nextBeforeSequence !== undefined ? String(input.trace.nextBeforeSequence) : undefined,
|
|
32
|
+
after: input.trace.lastEventSequence !== undefined ? String(input.trace.lastEventSequence) : undefined,
|
|
33
|
+
hasOlder: input.trace.hasOlderEvents === true,
|
|
34
|
+
hasNewer: false,
|
|
35
|
+
},
|
|
36
|
+
nodes,
|
|
37
|
+
responseBudget: {
|
|
38
|
+
nodeLimit: input.limit,
|
|
39
|
+
byteLimit,
|
|
40
|
+
truncatedByBytes: false,
|
|
41
|
+
},
|
|
42
|
+
eventCount: input.trace.eventCount,
|
|
43
|
+
pageSize: input.trace.pageSize,
|
|
44
|
+
firstEventSequence: input.trace.firstEventSequence,
|
|
45
|
+
lastEventSequence: input.trace.lastEventSequence,
|
|
46
|
+
nextBeforeSequence: input.trace.nextBeforeSequence,
|
|
47
|
+
hasOlderEvents: input.trace.hasOlderEvents,
|
|
48
|
+
};
|
|
49
|
+
while (Buffer.byteLength(JSON.stringify(page), "utf8") > byteLimit && page.nodes.length > 1) {
|
|
50
|
+
page = {
|
|
51
|
+
...page,
|
|
52
|
+
nodes: page.nodes.slice(Math.ceil(page.nodes.length / 4)),
|
|
53
|
+
responseBudget: { ...page.responseBudget, truncatedByBytes: true },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return page;
|
|
57
|
+
}
|
|
58
|
+
export function traceRawEventsPageFromEvents(input) {
|
|
59
|
+
const byteLimit = input.byteLimit ?? TRACE_V2_RAW_EVENTS_HARD_BYTES;
|
|
60
|
+
const limited = input.events.slice(-Math.max(1, Math.min(input.limit, TRACE_V2_RAW_EVENTS_MAX_LIMIT)));
|
|
61
|
+
let page = {
|
|
62
|
+
piboSessionId: input.piboSessionId,
|
|
63
|
+
cursor: {
|
|
64
|
+
before: limited[0]?.eventSequence !== undefined ? String(limited[0].eventSequence) : undefined,
|
|
65
|
+
hasOlder: (limited[0]?.eventSequence ?? 1) > 1,
|
|
66
|
+
},
|
|
67
|
+
limit: input.limit,
|
|
68
|
+
events: limited.map((event) => compactRawEvent(event, input.payloadStore, input.piboSessionId)),
|
|
69
|
+
responseBudget: {
|
|
70
|
+
byteLimit,
|
|
71
|
+
truncatedByBytes: false,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
while (Buffer.byteLength(JSON.stringify(page), "utf8") > byteLimit && page.events.length > 1) {
|
|
75
|
+
page = {
|
|
76
|
+
...page,
|
|
77
|
+
events: page.events.slice(Math.ceil(page.events.length / 4)),
|
|
78
|
+
responseBudget: { ...page.responseBudget, truncatedByBytes: true },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return page;
|
|
82
|
+
}
|
|
83
|
+
export function parseTracePayloadRef(ref) {
|
|
84
|
+
if (!ref.startsWith("trace_"))
|
|
85
|
+
return undefined;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(Buffer.from(ref.slice("trace_".length), "base64url").toString("utf8"));
|
|
88
|
+
if (!parsed || typeof parsed !== "object")
|
|
89
|
+
return undefined;
|
|
90
|
+
const record = parsed;
|
|
91
|
+
return typeof record.p === "string" && typeof record.id === "string"
|
|
92
|
+
? { piboSessionId: record.p, payloadId: record.id }
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function readTracePayloadChunk(input) {
|
|
100
|
+
const parsed = parseTracePayloadRef(input.ref);
|
|
101
|
+
if (!parsed)
|
|
102
|
+
return undefined;
|
|
103
|
+
const payload = input.payloadStore.getPayload(parsed.payloadId);
|
|
104
|
+
if (!payload)
|
|
105
|
+
return undefined;
|
|
106
|
+
const bytes = Buffer.from(input.payloadStore.readPayloadBytes(parsed.payloadId));
|
|
107
|
+
const offset = Math.max(0, Math.min(input.offset, bytes.byteLength));
|
|
108
|
+
const limit = Math.max(1, Math.min(input.limit, TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES));
|
|
109
|
+
const chunk = bytes.subarray(offset, Math.min(bytes.byteLength, offset + limit));
|
|
110
|
+
const preview = payload.previewText ?? "";
|
|
111
|
+
const traceRef = {
|
|
112
|
+
ref: input.ref,
|
|
113
|
+
contentType: normalizeContentType(payload.contentType),
|
|
114
|
+
byteLength: payload.byteSize,
|
|
115
|
+
preview,
|
|
116
|
+
truncatedPreview: Buffer.byteLength(preview, "utf8") < payload.byteSize,
|
|
117
|
+
hash: payload.sha256,
|
|
118
|
+
};
|
|
119
|
+
const nextOffset = offset + chunk.byteLength < bytes.byteLength ? offset + chunk.byteLength : undefined;
|
|
120
|
+
return {
|
|
121
|
+
ref: traceRef,
|
|
122
|
+
offset,
|
|
123
|
+
limit,
|
|
124
|
+
data: chunk.toString("utf8"),
|
|
125
|
+
byteLength: chunk.byteLength,
|
|
126
|
+
nextOffset,
|
|
127
|
+
hasMore: nextOffset !== undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function compactTraceNodes(input) {
|
|
131
|
+
const result = [];
|
|
132
|
+
const visit = (nodes, depth) => {
|
|
133
|
+
for (const node of nodes) {
|
|
134
|
+
result.push(compactTraceNode(node, input.payloadStore, input.piboSessionId, depth));
|
|
135
|
+
visit(node.children, depth + 1);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
visit(input.nodes, 0);
|
|
139
|
+
return input.fromTail ? result.slice(-input.limit) : result.slice(0, input.limit);
|
|
140
|
+
}
|
|
141
|
+
function compactTraceNode(node, payloadStore, piboSessionId, depth) {
|
|
142
|
+
const outputKind = node.type === "model.reasoning" ? "reasoning" : "output";
|
|
143
|
+
const inlinePayloads = compactObject({
|
|
144
|
+
input: inlinePayloadForNodeValue(node, "input", node.input),
|
|
145
|
+
[outputKind]: inlinePayloadForNodeValue(node, outputKind, node.output),
|
|
146
|
+
error: inlinePayloadForNodeValue(node, "error", node.error),
|
|
147
|
+
});
|
|
148
|
+
const payloadRefs = compactObject({
|
|
149
|
+
input: inlinePayloads.input === undefined
|
|
150
|
+
? payloadRefForValue({ store: payloadStore, piboSessionId, nodeId: node.id, kind: "input", value: node.input })
|
|
151
|
+
: undefined,
|
|
152
|
+
[outputKind]: inlinePayloads[outputKind] === undefined
|
|
153
|
+
? payloadRefForValue({ store: payloadStore, piboSessionId, nodeId: node.id, kind: outputKind, value: node.output })
|
|
154
|
+
: undefined,
|
|
155
|
+
error: inlinePayloads.error === undefined
|
|
156
|
+
? payloadRefForValue({ store: payloadStore, piboSessionId, nodeId: node.id, kind: "error", value: node.error })
|
|
157
|
+
: undefined,
|
|
158
|
+
});
|
|
159
|
+
const preview = previewForNode(node, payloadRefs);
|
|
160
|
+
return compactObject({
|
|
161
|
+
nodeId: node.id,
|
|
162
|
+
parentId: node.parentId,
|
|
163
|
+
piboSessionId: node.piboSessionId,
|
|
164
|
+
type: node.type,
|
|
165
|
+
status: node.status,
|
|
166
|
+
title: node.title,
|
|
167
|
+
startedAt: node.startedAt,
|
|
168
|
+
completedAt: node.completedAt,
|
|
169
|
+
durationMs: node.durationMs,
|
|
170
|
+
orderKey: node.orderKey,
|
|
171
|
+
depth,
|
|
172
|
+
hasChildren: node.children.length > 0,
|
|
173
|
+
childCount: node.children.length || undefined,
|
|
174
|
+
preview,
|
|
175
|
+
inlinePayloads: Object.keys(inlinePayloads).length ? inlinePayloads : undefined,
|
|
176
|
+
payloadRefs: Object.keys(payloadRefs).length ? payloadRefs : undefined,
|
|
177
|
+
linkedPiboSessionId: node.linkedPiboSessionId,
|
|
178
|
+
toolCallId: node.toolCallId,
|
|
179
|
+
runId: node.runId,
|
|
180
|
+
eventId: node.eventId,
|
|
181
|
+
entryId: node.entryId,
|
|
182
|
+
source: node.source,
|
|
183
|
+
stableKey: node.stableKey,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function inlinePayloadForNodeValue(node, kind, value) {
|
|
187
|
+
return inlinePayloadForValue(value, inlinePayloadByteLimit(node, kind));
|
|
188
|
+
}
|
|
189
|
+
function inlinePayloadByteLimit(node, kind) {
|
|
190
|
+
if ((node.type === "user.message" || node.type === "assistant.message" || node.type === "model.reasoning") && kind !== "error") {
|
|
191
|
+
return TRACE_V2_INLINE_TRANSCRIPT_PAYLOAD_MAX_BYTES;
|
|
192
|
+
}
|
|
193
|
+
return TRACE_V2_INLINE_PAYLOAD_MAX_BYTES;
|
|
194
|
+
}
|
|
195
|
+
function inlinePayloadForValue(value, maxBytes = TRACE_V2_INLINE_PAYLOAD_MAX_BYTES) {
|
|
196
|
+
if (value === undefined || value === null || value === "")
|
|
197
|
+
return undefined;
|
|
198
|
+
const { bytes } = payloadBytes(value);
|
|
199
|
+
if (bytes.byteLength > maxBytes)
|
|
200
|
+
return undefined;
|
|
201
|
+
return toPayloadValue(value);
|
|
202
|
+
}
|
|
203
|
+
function payloadRefForValue(input) {
|
|
204
|
+
if (input.value === undefined || input.value === null || input.value === "")
|
|
205
|
+
return undefined;
|
|
206
|
+
const { text, bytes, contentType } = payloadBytes(input.value);
|
|
207
|
+
const preview = textPreview(text);
|
|
208
|
+
const truncatedPreview = normalizedPreviewText(text).length > preview.length;
|
|
209
|
+
if (!truncatedPreview && bytes.byteLength <= TRACE_V2_PAYLOAD_REF_THRESHOLD_BYTES) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const payload = input.store.writePayload({
|
|
213
|
+
value: toPayloadValue(input.value),
|
|
214
|
+
contentType,
|
|
215
|
+
retentionClass: "trace_event",
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
ref: encodeTracePayloadRef(input.piboSessionId, payload.id),
|
|
219
|
+
contentType: normalizeContentType(contentType),
|
|
220
|
+
byteLength: bytes.byteLength,
|
|
221
|
+
preview,
|
|
222
|
+
truncatedPreview,
|
|
223
|
+
hash: createHash("sha256").update(bytes).digest("hex"),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function previewForNode(node, payloadRefs) {
|
|
227
|
+
if (node.error) {
|
|
228
|
+
const text = textPreview(String(node.error));
|
|
229
|
+
return { text, source: "error", truncated: String(node.error).length > text.length };
|
|
230
|
+
}
|
|
231
|
+
const payloadPreview = payloadRefs.output?.preview ?? payloadRefs.reasoning?.preview ?? payloadRefs.input?.preview;
|
|
232
|
+
if (payloadPreview)
|
|
233
|
+
return { text: payloadPreview, source: "payload", truncated: true };
|
|
234
|
+
const candidate = node.output ?? node.summary ?? node.input ?? node.title;
|
|
235
|
+
const text = textPreview(textForPreview(candidate));
|
|
236
|
+
if (!text)
|
|
237
|
+
return undefined;
|
|
238
|
+
return { text, source: node.summary !== undefined && node.output === undefined ? "summary" : "payload", truncated: textForPreview(candidate).length > text.length };
|
|
239
|
+
}
|
|
240
|
+
function compactRawEvent(event, payloadStore, piboSessionId) {
|
|
241
|
+
const payloadRef = payloadRefForValue({ store: payloadStore, piboSessionId, nodeId: event.id, kind: "raw", value: event.payload });
|
|
242
|
+
if (!payloadRef)
|
|
243
|
+
return event;
|
|
244
|
+
return {
|
|
245
|
+
...event,
|
|
246
|
+
payload: {
|
|
247
|
+
type: event.type,
|
|
248
|
+
payloadRef,
|
|
249
|
+
preview: payloadRef.preview,
|
|
250
|
+
byteLength: payloadRef.byteLength,
|
|
251
|
+
truncated: true,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function encodeTracePayloadRef(piboSessionId, payloadId) {
|
|
256
|
+
return `trace_${Buffer.from(JSON.stringify({ p: piboSessionId, id: payloadId }), "utf8").toString("base64url")}`;
|
|
257
|
+
}
|
|
258
|
+
function payloadBytes(value) {
|
|
259
|
+
if (typeof value === "string") {
|
|
260
|
+
const bytes = Buffer.from(value, "utf8");
|
|
261
|
+
return { text: value, bytes, contentType: "text/plain; charset=utf-8" };
|
|
262
|
+
}
|
|
263
|
+
const text = JSON.stringify(value);
|
|
264
|
+
return { text, bytes: Buffer.from(text, "utf8"), contentType: "application/json" };
|
|
265
|
+
}
|
|
266
|
+
function textForPreview(value) {
|
|
267
|
+
if (typeof value === "string")
|
|
268
|
+
return value;
|
|
269
|
+
if (value === undefined || value === null)
|
|
270
|
+
return "";
|
|
271
|
+
try {
|
|
272
|
+
return JSON.stringify(value);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return String(value);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function textPreview(value) {
|
|
279
|
+
return normalizedPreviewText(value).slice(0, TRACE_V2_PREVIEW_CHARS);
|
|
280
|
+
}
|
|
281
|
+
function normalizedPreviewText(value) {
|
|
282
|
+
return value.replace(/\s+/g, " ").trim();
|
|
283
|
+
}
|
|
284
|
+
function toPayloadValue(value) {
|
|
285
|
+
if (typeof value === "string")
|
|
286
|
+
return value;
|
|
287
|
+
return JSON.parse(JSON.stringify(value));
|
|
288
|
+
}
|
|
289
|
+
function normalizeContentType(contentType) {
|
|
290
|
+
if (contentType.includes("json"))
|
|
291
|
+
return "application/json";
|
|
292
|
+
if (contentType.startsWith("text/markdown"))
|
|
293
|
+
return "text/markdown";
|
|
294
|
+
if (contentType.startsWith("text/"))
|
|
295
|
+
return "text/plain";
|
|
296
|
+
if (contentType.includes("x-ndjson"))
|
|
297
|
+
return "application/x-ndjson";
|
|
298
|
+
return "application/octet-stream";
|
|
299
|
+
}
|
|
300
|
+
function compactObject(value) {
|
|
301
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
302
|
+
}
|
package/dist/apps/chat/trace.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { parseSessionEntries, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
@@ -7,6 +7,7 @@ import { buildTraceViewFromEvents } from "../../shared/trace-engine.js";
|
|
|
7
7
|
import { isChatWebSessionArchived } from "./session-metadata.js";
|
|
8
8
|
import { workflowSessionKindFromMetadata } from "../../sessions/workflow-session-kind.js";
|
|
9
9
|
export { compareTraceNodes, sortTraceNodes, nestTraceNodes, flattenTraceNodes, mapTraceNodesById, buildTraceViewFromEvents, traceNodesFromEntries, } from "../../shared/trace-engine.js";
|
|
10
|
+
export const TRACE_TRANSCRIPT_TAIL_MAX_BYTES = 2 * 1024 * 1024;
|
|
10
11
|
export async function loadPiSessionMetadata(session, cwd = process.cwd()) {
|
|
11
12
|
const piSession = await findPiSession(session, cwd);
|
|
12
13
|
return metadataFromPiSession(piSession);
|
|
@@ -109,7 +110,7 @@ function sessionNodeStatus(indexedStatus) {
|
|
|
109
110
|
}
|
|
110
111
|
export async function buildTraceView(input) {
|
|
111
112
|
const metadata = input.metadata ?? (await loadPiSessionMetadata(input.session, input.session.workspace ?? input.cwd));
|
|
112
|
-
const allEntries = metadata.sessionPath ? readEntries(metadata.sessionPath) : [];
|
|
113
|
+
const allEntries = input.transcriptEntries ?? (metadata.sessionPath ? readEntries(metadata.sessionPath) : []);
|
|
113
114
|
const sessionStatus = input.status ?? "idle";
|
|
114
115
|
const view = buildTraceViewFromEvents({
|
|
115
116
|
session: {
|
|
@@ -145,6 +146,12 @@ export async function buildTraceView(input) {
|
|
|
145
146
|
}),
|
|
146
147
|
};
|
|
147
148
|
}
|
|
149
|
+
export async function loadPiSessionTailEntries(session, cwd = process.cwd(), maxBytes = TRACE_TRANSCRIPT_TAIL_MAX_BYTES) {
|
|
150
|
+
const metadata = await loadPiSessionMetadata(session, cwd);
|
|
151
|
+
if (!metadata.sessionPath)
|
|
152
|
+
return { metadata, entries: [] };
|
|
153
|
+
return { metadata, entries: readTailEntries(metadata.sessionPath, maxBytes) };
|
|
154
|
+
}
|
|
148
155
|
export function createTraceViewVersion(input) {
|
|
149
156
|
const relevantSessions = input.sessions
|
|
150
157
|
.map((session) => ({
|
|
@@ -295,6 +302,28 @@ function readEntries(path) {
|
|
|
295
302
|
const content = readFileSync(path, "utf8");
|
|
296
303
|
return parseSessionEntries(content).filter((entry) => entry.type !== "session");
|
|
297
304
|
}
|
|
305
|
+
export function readTailEntries(path, maxBytes = TRACE_TRANSCRIPT_TAIL_MAX_BYTES) {
|
|
306
|
+
if (!existsSync(path))
|
|
307
|
+
return [];
|
|
308
|
+
const stats = statSync(path);
|
|
309
|
+
if (stats.size <= maxBytes)
|
|
310
|
+
return readEntries(path);
|
|
311
|
+
const length = Math.max(1, Math.min(maxBytes, stats.size));
|
|
312
|
+
const start = stats.size - length;
|
|
313
|
+
const buffer = Buffer.alloc(length);
|
|
314
|
+
const fd = openSync(path, "r");
|
|
315
|
+
try {
|
|
316
|
+
const bytesRead = readSync(fd, buffer, 0, length, start);
|
|
317
|
+
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
318
|
+
const firstNewline = content.indexOf("\n");
|
|
319
|
+
if (start > 0 && firstNewline >= 0)
|
|
320
|
+
content = content.slice(firstNewline + 1);
|
|
321
|
+
return parseSessionEntries(content).filter((entry) => entry.type !== "session");
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
closeSync(fd);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
298
327
|
function annotateForkableUserMessageNodes(nodes, entries) {
|
|
299
328
|
const candidates = userMessageForkCandidates(entries);
|
|
300
329
|
if (!candidates.length)
|