@pasko70/pibo 3.4.4 → 3.5.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/agent-runtime/routed-session.js +37 -12
- package/dist/agent-runtimes/pi/adapter.js +1 -0
- package/dist/agent-runtimes/pi/routed-session.js +2 -2
- package/dist/apps/chat/bounded-event-stream.js +98 -0
- package/dist/apps/chat/chat-settings-routes.js +3 -3
- package/dist/apps/chat/data/chat-data-mappers.js +32 -13
- package/dist/apps/chat/data/event-command-service.js +30 -21
- package/dist/apps/chat/data/history-query-service.js +34 -25
- package/dist/apps/chat/data/read-state-service.js +13 -0
- package/dist/apps/chat/data/session-query-service.js +19 -11
- package/dist/apps/chat/data/timeline-query-service.js +19 -7
- package/dist/apps/chat/message-command-dispatcher.js +134 -0
- package/dist/apps/chat/output-compactor.js +9 -0
- package/dist/apps/chat/output-event-policy.js +9 -1
- package/dist/apps/chat/stream.js +21 -3
- package/dist/apps/chat/telemetry-retention-service.js +113 -8
- package/dist/apps/chat/trace-response-cache.js +46 -0
- package/dist/apps/chat/trace-v2.js +12 -6
- package/dist/apps/chat/trace.js +1 -1
- package/dist/apps/chat/web-app.js +608 -281
- package/dist/apps/chat-ui/assets/{dist-Bf2KScPo.js → dist-BG0n7zLd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-23lt_7qm.js → dist-D6TjFhAm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-V06sfuZa.js → dist-D79vyxSX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CH3SvpYV.js → dist-DFZ8cwh0.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DsgL8w-W.js → dist-cOjokPrK.js} +1 -1
- package/dist/apps/chat-ui/assets/index-RMHUTJ62.js +229 -0
- package/dist/apps/chat-ui/assets/{index-BOceJ0jM.css → index-hEkrlRk-.css} +1 -1
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-xacbCyTx.js +44 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/compute/pool/seeds.js +25 -3
- package/dist/core/events.js +4 -0
- package/dist/core/output-render-sequence.js +2 -0
- package/dist/core/provider-capacity.js +33 -0
- package/dist/core/provider-telemetry.js +21 -6
- package/dist/core/runtime-capacity.js +174 -0
- package/dist/core/runtime-telemetry.js +40 -12
- package/dist/core/session-router.js +79 -2
- package/dist/data/async-chat-reads.js +38 -0
- package/dist/data/async-chat-storage.js +91 -0
- package/dist/data/async-telemetry-maintenance.js +9 -0
- package/dist/data/bounded-worker-client.js +256 -0
- package/dist/data/chat-read-projections.js +159 -0
- package/dist/data/chat-read-worker.js +73 -0
- package/dist/data/chat-storage-worker.js +146 -0
- package/dist/data/ingest-service.js +79 -14
- package/dist/data/message-command-store.js +148 -0
- package/dist/data/payload-store.js +92 -11
- package/dist/data/pibo-store.js +10 -8
- package/dist/data/schema.js +16 -2
- package/dist/data/session-store.js +3 -1
- package/dist/data/storage-backup.js +278 -0
- package/dist/data/telemetry-capture.js +188 -0
- package/dist/data/telemetry-command.js +3 -0
- package/dist/data/telemetry-maintenance-worker.js +40 -0
- package/dist/data/telemetry-maintenance.js +110 -0
- package/dist/data/telemetry-retention.js +16 -7
- package/dist/data/telemetry-worker.js +111 -0
- package/dist/data/telemetry-writer.js +150 -83
- package/dist/data/telemetry.js +5 -0
- package/dist/debug/index.js +52 -0
- package/dist/debug/storage-backup.js +33 -0
- package/dist/debug/telemetry-capture.js +66 -0
- package/dist/gateway/server.js +2 -0
- package/dist/providers/openai-gpt56.js +11 -6
- package/dist/session-ui/terminalRows.js +48 -6
- package/dist/sessions/pibo-data-store.js +1 -0
- package/dist/shared/debug-features.js +4 -0
- package/dist/shared/model-inference-metrics.js +23 -0
- package/dist/shared/trace-event-projection.js +59 -2
- package/dist/shared/trace-history.js +9 -0
- package/dist/shared/trace-live-reducer.js +1 -0
- package/dist/shared/trace-patch-nodes.js +19 -0
- package/dist/web/channel.js +3 -0
- package/dist/web/http.js +36 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-BOemYq-V.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-CMwTB8o8.js +0 -43
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { createStorageBackup, readStorageBackupManifest, verifyStorageBackup, restoreStorageBackup } from "../data/storage-backup.js";
|
|
3
|
+
export async function runStorageBackupCli(args) {
|
|
4
|
+
const action = args[0];
|
|
5
|
+
if (!action || args.includes("--help") || args.includes("-h")) {
|
|
6
|
+
console.log(`pibo debug backup - explicit SQLite snapshot and payload backup
|
|
7
|
+
Commands:
|
|
8
|
+
create --source <sqlite> --payload-root <dir> --destination <new-dir>
|
|
9
|
+
[--resume] [--max-bytes <n>] [--max-payloads <n>] [--max-ms <n>] [--max-wal-growth <n>]
|
|
10
|
+
inspect --archive <dir> Read the manifest only
|
|
11
|
+
verify --archive <dir> Verify database and all payload hashes
|
|
12
|
+
restore --archive <dir> --destination <new-dir>
|
|
13
|
+
Defaults: 1 GiB data, 100000 payloads, 60000 ms and 64 MiB extra source WAL per create attempt. Resume preserves the original snapshot.
|
|
14
|
+
Each archive contains one database snapshot. Product and reliability snapshots are separate cuts.
|
|
15
|
+
No source deletion, vacuum, or overwrite of an existing restore destination.`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const { values } = parseArgs({ args: args.slice(1), options: { source: { type: "string" }, "payload-root": { type: "string" }, destination: { type: "string" }, archive: { type: "string" }, resume: { type: "boolean" }, "max-bytes": { type: "string" }, "max-payloads": { type: "string" }, "max-ms": { type: "string" }, "max-wal-growth": { type: "string" }, json: { type: "boolean" } } });
|
|
19
|
+
const required = (name) => { const value = values[name]; if (typeof value !== "string" || !value)
|
|
20
|
+
throw Error(`Missing --${name}`); return value; };
|
|
21
|
+
let result;
|
|
22
|
+
if (action === "create")
|
|
23
|
+
result = await createStorageBackup({ source: required("source"), payloadRoot: required("payload-root"), destination: required("destination"), resume: values.resume, maxBytes: values["max-bytes"] ? Number(values["max-bytes"]) : undefined, maxPayloads: values["max-payloads"] ? Number(values["max-payloads"]) : undefined, maxMilliseconds: values["max-ms"] ? Number(values["max-ms"]) : undefined, maxWalGrowthBytes: values["max-wal-growth"] ? Number(values["max-wal-growth"]) : undefined });
|
|
24
|
+
else if (action === "inspect")
|
|
25
|
+
result = await readStorageBackupManifest(required("archive"));
|
|
26
|
+
else if (action === "verify")
|
|
27
|
+
result = await verifyStorageBackup(required("archive"), AbortSignal.timeout(60000));
|
|
28
|
+
else if (action === "restore")
|
|
29
|
+
result = await restoreStorageBackup(required("archive"), required("destination"), AbortSignal.timeout(60000));
|
|
30
|
+
else
|
|
31
|
+
throw Error("Unknown backup action; use backup --help");
|
|
32
|
+
console.log(JSON.stringify(result, null, 2));
|
|
33
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { opendir } from "node:fs/promises";
|
|
4
|
+
import { resolveDebugStore } from "./stores.js";
|
|
5
|
+
import { startTelemetryCapture, inspectTelemetryCapture, finalizeTelemetryCapture, readTelemetryCapturePage } from "../data/telemetry-capture.js";
|
|
6
|
+
export async function runTelemetryCaptureCli(args) {
|
|
7
|
+
const action = args[0];
|
|
8
|
+
if (!action || args.includes("--help") || args.includes("-h")) {
|
|
9
|
+
console.log(`pibo debug telemetry capture - scoped provider metadata in isolated stores
|
|
10
|
+
Commands:
|
|
11
|
+
start --session <ps_...> --owner <name> --duration-ms <n> --max-bytes <n> --max-rows <n>
|
|
12
|
+
stop <capture-id> Fence appends and finalize an independent closed archive
|
|
13
|
+
list List up to 100 manifests without opening databases
|
|
14
|
+
inspect <capture-id> Read one manifest without opening its database
|
|
15
|
+
page <capture-id> [--after <sequence>] [--limit <1..100>]
|
|
16
|
+
Limits: one active capture, at most one hour, 64 MiB logical data and 100000 rows per run.
|
|
17
|
+
Detail is allow-listed provider metadata only. Arbitrary provider bodies, credentials and prompts are excluded.
|
|
18
|
+
Stopping/finalizing is explicit; archived data is never opened by normal gateway reads.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { values, positionals } = parseArgs({ args: args.slice(1), allowPositionals: true, options: { session: { type: "string" }, owner: { type: "string" }, "duration-ms": { type: "string" }, "max-bytes": { type: "string" }, "max-rows": { type: "string" }, after: { type: "string" }, limit: { type: "string" }, json: { type: "boolean" } } });
|
|
22
|
+
const root = join(dirname(resolveDebugStore("pibo-data").path), "telemetry-captures");
|
|
23
|
+
let result;
|
|
24
|
+
if (action === "start")
|
|
25
|
+
result = startTelemetryCapture(root, { sessionId: values.session ?? "", owner: values.owner ?? "", durationMs: Number(values["duration-ms"]), maxBytes: Number(values["max-bytes"]), maxRows: Number(values["max-rows"]) });
|
|
26
|
+
else if (action === "list") {
|
|
27
|
+
const entries = [];
|
|
28
|
+
let scanned = 0, truncated = false;
|
|
29
|
+
try {
|
|
30
|
+
const directory = await opendir(root);
|
|
31
|
+
for await (const entry of directory) {
|
|
32
|
+
if (++scanned > 1000 || entries.length >= 100) {
|
|
33
|
+
truncated = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (!entry.isDirectory() || !entry.name.startsWith("capture_"))
|
|
37
|
+
continue;
|
|
38
|
+
try {
|
|
39
|
+
entries.push(inspectTelemetryCapture(root, entry.name));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
entries.push({ id: entry.name, status: "manifest_unavailable" });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error.code !== "ENOENT")
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
result = { entries, truncated };
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const id = positionals[0];
|
|
54
|
+
if (!id)
|
|
55
|
+
throw Error("Capture id is required");
|
|
56
|
+
if (action === "stop")
|
|
57
|
+
result = await finalizeTelemetryCapture(root, id);
|
|
58
|
+
else if (action === "inspect")
|
|
59
|
+
result = inspectTelemetryCapture(root, id);
|
|
60
|
+
else if (action === "page")
|
|
61
|
+
result = await readTelemetryCapturePage(root, id, Number(values.after ?? 0), Number(values.limit ?? 50));
|
|
62
|
+
else
|
|
63
|
+
throw Error("Unknown capture action; use capture --help");
|
|
64
|
+
}
|
|
65
|
+
console.log(JSON.stringify(result, null, 2));
|
|
66
|
+
}
|
package/dist/gateway/server.js
CHANGED
|
@@ -360,6 +360,7 @@ export class PiboGatewayServer {
|
|
|
360
360
|
},
|
|
361
361
|
findSessions: (input) => this.requireSessionStore().find(input),
|
|
362
362
|
listSessions: () => this.requireSessionStore().list?.() ?? [],
|
|
363
|
+
getSessionStructureRevision: () => this.requireSessionStore().getStructureRevision?.(),
|
|
363
364
|
getSessionRuntimeBinding: (piboSessionId) => this.requireRouter().getSessionRuntimeBinding(piboSessionId),
|
|
364
365
|
getSessionRuntimeProfile: (piboSessionId) => this.requireRouter().getSessionRuntimeProfile(piboSessionId),
|
|
365
366
|
inspectSessionRuntimeHistory: async (piboSessionId) => {
|
|
@@ -424,6 +425,7 @@ export class PiboGatewayServer {
|
|
|
424
425
|
getSessionStatusSnapshot: (piboSessionId, options) => this.requireRouter().getSessionStatusSnapshot(piboSessionId, options),
|
|
425
426
|
getSessionForkCandidates: (piboSessionId) => this.requireRouter().getSessionForkCandidates(piboSessionId),
|
|
426
427
|
listSessionRuntimeStatuses: () => this.requireRouter().listSessionRuntimeStatuses(),
|
|
428
|
+
getRuntimeCapacityStatus: () => this.requireRouter().getRuntimeCapacityStatus(),
|
|
427
429
|
listRuns: (options) => this.requireRouter().listRuns(options),
|
|
428
430
|
snapshotSignalSession: (piboSessionId) => this.requireRouter().snapshotSignalSession(piboSessionId),
|
|
429
431
|
snapshotSignalTree: (rootPiboSessionId) => this.requireRouter().snapshotSignalTree(rootPiboSessionId),
|
|
@@ -38,6 +38,9 @@ export const OPENAI_CODEX_GPT_6_ASTRA_MODEL = {
|
|
|
38
38
|
name: "GPT-6-Astra",
|
|
39
39
|
cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
40
40
|
};
|
|
41
|
+
// Codex serves the Reserve quota alias from the Luna-class model, so Reserve borrows Luna's metadata.
|
|
42
|
+
export const OPENAI_CODEX_RESERVE_MODEL = { id: "gpt-reserve", name: "Luna Reserve" };
|
|
43
|
+
const OPENAI_CODEX_RESERVE_BASE_MODEL_ID = "gpt-5.6-luna";
|
|
41
44
|
const OPENAI_GPT_56_MODEL_IDS = new Set(OPENAI_GPT_56_MODELS.map((model) => model.id));
|
|
42
45
|
export function getBuiltInOpenAiModels() {
|
|
43
46
|
return getBuiltInProviderModels(OPENAI_PROVIDER_ID);
|
|
@@ -69,6 +72,10 @@ export function buildOpenAiCodexGpt56Models(baseModels = getBuiltInOpenAiCodexMo
|
|
|
69
72
|
}
|
|
70
73
|
export function buildOpenAiCodexSupplementalModels(baseModels = getBuiltInOpenAiCodexModels()) {
|
|
71
74
|
const models = buildOpenAiCodexGpt56Models(baseModels);
|
|
75
|
+
const reserveBase = models.find((model) => model.id === OPENAI_CODEX_RESERVE_BASE_MODEL_ID);
|
|
76
|
+
if (reserveBase && !models.some((model) => model.id === OPENAI_CODEX_RESERVE_MODEL.id)) {
|
|
77
|
+
models.push({ ...cloneModel(reserveBase), ...OPENAI_CODEX_RESERVE_MODEL });
|
|
78
|
+
}
|
|
72
79
|
if (models.some((model) => model.id === OPENAI_CODEX_GPT_6_ASTRA_MODEL.id))
|
|
73
80
|
return models;
|
|
74
81
|
return [...models, openAiCodexAstraModelToRegistryModel()];
|
|
@@ -76,7 +83,7 @@ export function buildOpenAiCodexSupplementalModels(baseModels = getBuiltInOpenAi
|
|
|
76
83
|
export function registerOpenAiSupplementalModels(modelRegistry, options = {}) {
|
|
77
84
|
const baseOpenAiModels = options.baseOpenAiModels ?? getBuiltInOpenAiModels();
|
|
78
85
|
const openAiModels = buildOpenAiGpt56Models(baseOpenAiModels);
|
|
79
|
-
const openAiAdded =
|
|
86
|
+
const openAiAdded = countAddedModels(baseOpenAiModels, OPENAI_PROVIDER_ID, openAiModels);
|
|
80
87
|
modelRegistry.registerProvider(OPENAI_PROVIDER_ID, {
|
|
81
88
|
baseUrl: OPENAI_BASE_URL,
|
|
82
89
|
api: OPENAI_RESPONSES_API,
|
|
@@ -85,9 +92,7 @@ export function registerOpenAiSupplementalModels(modelRegistry, options = {}) {
|
|
|
85
92
|
});
|
|
86
93
|
const baseOpenAiCodexModels = options.baseOpenAiCodexModels ?? getBuiltInOpenAiCodexModels();
|
|
87
94
|
const openAiCodexModels = buildOpenAiCodexSupplementalModels(baseOpenAiCodexModels);
|
|
88
|
-
const
|
|
89
|
-
const openAiCodexAdded = countMissingGpt56Models(baseOpenAiCodexModels, OPENAI_CODEX_PROVIDER_ID)
|
|
90
|
-
+ (hasBuiltInAstra ? 0 : 1);
|
|
95
|
+
const openAiCodexAdded = countAddedModels(baseOpenAiCodexModels, OPENAI_CODEX_PROVIDER_ID, openAiCodexModels);
|
|
91
96
|
modelRegistry.registerProvider(OPENAI_CODEX_PROVIDER_ID, {
|
|
92
97
|
baseUrl: OPENAI_CODEX_BASE_URL,
|
|
93
98
|
api: OPENAI_CODEX_RESPONSES_API,
|
|
@@ -128,9 +133,9 @@ function buildProviderGpt56Models(options) {
|
|
|
128
133
|
.map((model) => openAiGpt56ModelToRegistryModel(model, options));
|
|
129
134
|
return [...providerBaseModels, ...additions];
|
|
130
135
|
}
|
|
131
|
-
function
|
|
136
|
+
function countAddedModels(baseModels, providerId, registeredModels) {
|
|
132
137
|
const existingIds = new Set(baseModels.filter((model) => model.provider === providerId).map((model) => model.id));
|
|
133
|
-
return
|
|
138
|
+
return registeredModels.filter((model) => !existingIds.has(model.id)).length;
|
|
134
139
|
}
|
|
135
140
|
function cloneModel(model) {
|
|
136
141
|
return {
|
|
@@ -16,13 +16,14 @@ export function buildCompactTerminalRows(traceView, options) {
|
|
|
16
16
|
if (!traceView)
|
|
17
17
|
return [];
|
|
18
18
|
const turnById = mapTurnNodes(traceView.nodes);
|
|
19
|
+
const showToolDebugMetrics = Boolean(options.debugMode && (options.debugFeatures?.toolMetrics ?? true));
|
|
19
20
|
const flatNodes = flattenTraceNodes(traceView.nodes)
|
|
20
21
|
.sort((left, right) => compareTraceNodes(left.node, right.node))
|
|
21
22
|
.filter((item) => item.node.type !== "agent.turn" && (options.showThinking || item.node.type !== "model.reasoning"));
|
|
22
23
|
const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
|
|
23
24
|
applyCompletedTurnTiming(candidates, turnById);
|
|
24
25
|
const reconciled = reconcileConceptualRowCandidates(candidates);
|
|
25
|
-
const rows = !
|
|
26
|
+
const rows = !showToolDebugMetrics && (options.toolDisplayMode ?? "default") === "default"
|
|
26
27
|
? groupRelatedToolCandidates(reconciled).map((candidate) => candidate.row)
|
|
27
28
|
: reconciled.map((candidate) => candidate.row);
|
|
28
29
|
return applyToolDisplayMode(rows, options.toolDisplayMode ?? "default");
|
|
@@ -159,18 +160,34 @@ function createRowCandidate(node, turnId) {
|
|
|
159
160
|
};
|
|
160
161
|
break;
|
|
161
162
|
}
|
|
163
|
+
const isToolCall = node.type === "tool.call" || node.type === "tool.result" || (node.type === "agent.delegation" && Boolean(node.toolCallId));
|
|
164
|
+
const reference = isToolCall ? toolCallReference(node) : undefined;
|
|
162
165
|
return {
|
|
163
166
|
...candidate,
|
|
164
167
|
row: {
|
|
165
168
|
...candidate.row,
|
|
166
169
|
id: compactTerminalRowIdentity(node),
|
|
167
170
|
intent: node.intent,
|
|
168
|
-
isToolCall
|
|
171
|
+
isToolCall,
|
|
172
|
+
toolCallReference: reference,
|
|
173
|
+
expandable: reference || Object.values(node.payloadRefs ?? {}).some(Boolean) ? true : candidate.row.expandable,
|
|
169
174
|
toolMetrics: node.toolMetrics,
|
|
175
|
+
modelInferences: node.modelInferences,
|
|
170
176
|
...debugFields(node),
|
|
171
177
|
},
|
|
172
178
|
};
|
|
173
179
|
}
|
|
180
|
+
function toolCallReference(node) {
|
|
181
|
+
if (!node.toolCallId)
|
|
182
|
+
return undefined;
|
|
183
|
+
const parsed = parseTraceToolNodeIdentity(node.id);
|
|
184
|
+
return {
|
|
185
|
+
traceNodeId: node.id,
|
|
186
|
+
toolCallId: node.toolCallId,
|
|
187
|
+
eventId: node.eventId ?? parsed?.qualifier?.eventId,
|
|
188
|
+
invocationOrdinal: node.toolInvocationOrdinal ?? parsed?.qualifier?.invocationOrdinal,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
174
191
|
export function compactTerminalRowIdentity(node) {
|
|
175
192
|
if (node.type === "tool.call" || node.type === "tool.result" || node.type === "agent.delegation") {
|
|
176
193
|
if (node.toolCallId) {
|
|
@@ -226,6 +243,7 @@ function reconcileConceptualRowCandidates(candidates) {
|
|
|
226
243
|
output: candidate.row.output ?? existing.row.output,
|
|
227
244
|
error: candidate.row.error ?? existing.row.error,
|
|
228
245
|
payloadRefs: { ...existing.row.payloadRefs, ...candidate.row.payloadRefs },
|
|
246
|
+
modelInferences: mergeModelInferences(existing.row.modelInferences, candidate.row.modelInferences),
|
|
229
247
|
imagePreviews: mergeImagePreviews(existing.row.imagePreviews, candidate.row.imagePreviews),
|
|
230
248
|
},
|
|
231
249
|
};
|
|
@@ -310,6 +328,16 @@ function assistantPartIndex(rowId) {
|
|
|
310
328
|
const index = Number(match[1]);
|
|
311
329
|
return Number.isSafeInteger(index) ? index : undefined;
|
|
312
330
|
}
|
|
331
|
+
function mergeModelInferences(existing, candidate) {
|
|
332
|
+
if (!existing?.length)
|
|
333
|
+
return candidate;
|
|
334
|
+
if (!candidate?.length)
|
|
335
|
+
return existing;
|
|
336
|
+
const merged = new Map(existing.map((record) => [record.id, record]));
|
|
337
|
+
for (const record of candidate)
|
|
338
|
+
merged.set(record.id, record);
|
|
339
|
+
return [...merged.values()];
|
|
340
|
+
}
|
|
313
341
|
function mergeImagePreviews(existing, candidate) {
|
|
314
342
|
if (!existing?.length)
|
|
315
343
|
return candidate?.slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS);
|
|
@@ -329,6 +357,9 @@ function applyCompletedTurnTiming(candidates, turnById) {
|
|
|
329
357
|
continue;
|
|
330
358
|
const turnCandidates = candidates.filter((candidate) => candidate.turnId === turn.id);
|
|
331
359
|
const finalCandidate = turnCandidates.at(-1);
|
|
360
|
+
if (finalCandidate && turn.modelInferences?.length) {
|
|
361
|
+
finalCandidate.row.modelInferences = mergeModelInferences(finalCandidate.row.modelInferences, turn.modelInferences);
|
|
362
|
+
}
|
|
332
363
|
if (finalCandidate?.row.kind !== "message.assistant" || finalCandidate.row.status === "running")
|
|
333
364
|
continue;
|
|
334
365
|
finalCandidate.row.startedAt = turn.startedAt;
|
|
@@ -362,7 +393,8 @@ function createUserMessageRow(node) {
|
|
|
362
393
|
lines: [{ prefix: "prompt", tokens: [token(text)] }],
|
|
363
394
|
sourceNodeIds: [node.id],
|
|
364
395
|
forkEntryId: node.entryId,
|
|
365
|
-
pendingMessageDelivery: pendingUserMessageDelivery(node),
|
|
396
|
+
pendingMessageDelivery: pendingUserMessageDelivery(node) ?? (node.status === "running" && node.messageDeliveryState ? "queue" : undefined),
|
|
397
|
+
messageDeliveryState: node.messageDeliveryState,
|
|
366
398
|
startedAt: node.startedAt,
|
|
367
399
|
output: text,
|
|
368
400
|
payloadRefs: node.payloadRefs,
|
|
@@ -657,9 +689,16 @@ function createCompactionRow(node) {
|
|
|
657
689
|
input: node.input,
|
|
658
690
|
output: node.output,
|
|
659
691
|
error: node.error,
|
|
660
|
-
|
|
692
|
+
compactionStats: node.compactionStats,
|
|
693
|
+
compactionMarkdown: compactionMarkdown(node.output),
|
|
694
|
+
expandable: node.status === "error" && (node.input !== undefined || node.output !== undefined || Boolean(node.error)),
|
|
661
695
|
};
|
|
662
696
|
}
|
|
697
|
+
function compactionMarkdown(value) {
|
|
698
|
+
if (!isRecord(value))
|
|
699
|
+
return undefined;
|
|
700
|
+
return stringValue(value.summary);
|
|
701
|
+
}
|
|
663
702
|
function createExecutionCommandRow(node) {
|
|
664
703
|
if (node.title === "status") {
|
|
665
704
|
return createStatusToolRow(node);
|
|
@@ -966,13 +1005,14 @@ function createExploringGroup(candidates) {
|
|
|
966
1005
|
}] : []),
|
|
967
1006
|
],
|
|
968
1007
|
sourceNodeIds: candidates.flatMap((candidate) => candidate.row.sourceNodeIds),
|
|
1008
|
+
modelInferences: candidates.reduce((records, candidate) => mergeModelInferences(records, candidate.row.modelInferences), undefined),
|
|
969
1009
|
eventId: firstRow?.eventId,
|
|
970
1010
|
runId: firstRow?.runId,
|
|
971
1011
|
orderSource: firstRow?.orderSource,
|
|
972
1012
|
orderStreamId: firstRow?.orderStreamId,
|
|
973
1013
|
orderStreamFrameIndex: firstRow?.orderStreamFrameIndex,
|
|
974
1014
|
detailItems,
|
|
975
|
-
expandable: detailItems.some((item) => item.input !== undefined || item.output !== undefined || Boolean(item.error)),
|
|
1015
|
+
expandable: detailItems.some((item) => item.toolCallReference || item.input !== undefined || item.output !== undefined || Boolean(item.error)),
|
|
976
1016
|
previewOmission: omittedDetailCount > 0 ? {
|
|
977
1017
|
source: "details",
|
|
978
1018
|
visibleLineCount: visibleDetailItems.length,
|
|
@@ -1014,13 +1054,14 @@ function createImageGroup(candidates) {
|
|
|
1014
1054
|
}] : []),
|
|
1015
1055
|
],
|
|
1016
1056
|
sourceNodeIds: candidates.flatMap((candidate) => candidate.row.sourceNodeIds),
|
|
1057
|
+
modelInferences: candidates.reduce((records, candidate) => mergeModelInferences(records, candidate.row.modelInferences), undefined),
|
|
1017
1058
|
eventId: firstRow?.eventId,
|
|
1018
1059
|
runId: firstRow?.runId,
|
|
1019
1060
|
orderSource: firstRow?.orderSource,
|
|
1020
1061
|
orderStreamId: firstRow?.orderStreamId,
|
|
1021
1062
|
orderStreamFrameIndex: firstRow?.orderStreamFrameIndex,
|
|
1022
1063
|
detailItems,
|
|
1023
|
-
expandable: detailItems.some((item) => item.input !== undefined || item.output !== undefined || Boolean(item.error)),
|
|
1064
|
+
expandable: detailItems.some((item) => item.toolCallReference || item.input !== undefined || item.output !== undefined || Boolean(item.error)),
|
|
1024
1065
|
imagePreviews: detailItems.flatMap((item) => item.imagePreviews ?? []).slice(0, MAX_COMPACT_TERMINAL_IMAGE_PREVIEWS),
|
|
1025
1066
|
previewOmission: omittedDetailCount > 0 ? {
|
|
1026
1067
|
source: "details",
|
|
@@ -1043,6 +1084,7 @@ function detailItemsForGroup(candidates, kind) {
|
|
|
1043
1084
|
error: candidate.row.error,
|
|
1044
1085
|
payloadRefs: candidate.row.payloadRefs,
|
|
1045
1086
|
linkedPiboSessionId: candidate.row.linkedPiboSessionId,
|
|
1087
|
+
toolCallReference: candidate.row.toolCallReference,
|
|
1046
1088
|
previewOmission: candidate.row.previewOmission,
|
|
1047
1089
|
imagePreviews: candidate.row.imagePreviews,
|
|
1048
1090
|
};
|
|
@@ -49,6 +49,7 @@ export class PiboDataSessionStore {
|
|
|
49
49
|
const row = this.db.prepare(`${SESSION_SELECT} WHERE s.id = ? AND s.deleted_at IS NULL`).get(id);
|
|
50
50
|
return row ? sessionFromRow(row) : undefined;
|
|
51
51
|
}
|
|
52
|
+
getStructureRevision() { return this.db.prepare("SELECT revision FROM chat_navigation_clock WHERE id=1").get().revision; }
|
|
52
53
|
list() {
|
|
53
54
|
return this.db.prepare(`${SESSION_SELECT} WHERE s.deleted_at IS NULL ORDER BY s.updated_at DESC`).all().map(sessionFromRow);
|
|
54
55
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function modelInferenceInputTokens(metrics) {
|
|
2
|
+
const total = tokenCount(metrics?.totalTokens);
|
|
3
|
+
const output = tokenCount(metrics?.outputTokens);
|
|
4
|
+
if (total !== undefined && output !== undefined)
|
|
5
|
+
return Math.max(0, total - output);
|
|
6
|
+
return tokenCount(metrics?.inputTokens);
|
|
7
|
+
}
|
|
8
|
+
export function modelInferenceCachedInputTokens(metrics) {
|
|
9
|
+
const cacheRead = tokenCount(metrics?.cacheReadTokens);
|
|
10
|
+
const cacheWrite = tokenCount(metrics?.cacheWriteTokens);
|
|
11
|
+
if (cacheRead === undefined && cacheWrite === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
return (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
14
|
+
}
|
|
15
|
+
export function modelInferenceUncachedInputTokens(metrics) {
|
|
16
|
+
const input = modelInferenceInputTokens(metrics);
|
|
17
|
+
if (input === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
return Math.max(0, input - (modelInferenceCachedInputTokens(metrics) ?? 0));
|
|
20
|
+
}
|
|
21
|
+
function tokenCount(value) {
|
|
22
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
|
|
23
|
+
}
|
|
@@ -6,6 +6,10 @@ import { findLikelyTraceChildSession, isSubagentToolName, } from "./trace-subage
|
|
|
6
6
|
import { qualifiedToolNodeId } from "./trace-tool-identity.js";
|
|
7
7
|
export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openTranscriptEventIds, sessionStatus) {
|
|
8
8
|
const payload = storedEvent.payload;
|
|
9
|
+
if (payload.type === "assistant_usage") {
|
|
10
|
+
attachModelInferenceToLatestOutput(nodes, byId, payload, storedEvent);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
9
13
|
if (payload.type === "message_started" && payload.source === "user" && payload.eventId) {
|
|
10
14
|
const persistedUser = flattenTraceNodes([...nodes]).find((node) => node.type === "user.message"
|
|
11
15
|
&& isPersistedHistorySource(node.source)
|
|
@@ -42,6 +46,7 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
|
|
|
42
46
|
}
|
|
43
47
|
if (payload.type === "assistant_message") {
|
|
44
48
|
const node = assistantMessageNodeFromEvent(piboSessionId, payload, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource, storedEvent.id);
|
|
49
|
+
applyStoredPayloadRef(node, payload, storedEvent.storedPayloadRef);
|
|
45
50
|
const existing = byId.get(node.id) ?? findMatchingContentNode(byId, node);
|
|
46
51
|
if (existing) {
|
|
47
52
|
mergeAssistantMessageEvent(existing, node);
|
|
@@ -153,8 +158,12 @@ function applyRenderSequenceToHistoryNode(byId, storedEvent, event) {
|
|
|
153
158
|
&& typeof event.toolCallId === "string"
|
|
154
159
|
&& node.toolCallId === event.toolCallId
|
|
155
160
|
&& (!eventId || !node.eventId || node.eventId === eventId)));
|
|
156
|
-
if (target)
|
|
157
|
-
|
|
161
|
+
if (!target)
|
|
162
|
+
return;
|
|
163
|
+
applyRenderSequence(target, storedEvent, event);
|
|
164
|
+
if (event.type === "tool_execution_finished" && event.toolMetrics) {
|
|
165
|
+
target.toolMetrics = event.toolMetrics;
|
|
166
|
+
}
|
|
158
167
|
}
|
|
159
168
|
function applyRenderSequence(node, storedEvent, event) {
|
|
160
169
|
const renderSequence = storedEvent.renderSequence ?? event.renderSequence;
|
|
@@ -171,6 +180,9 @@ function applyRenderSequence(node, storedEvent, event) {
|
|
|
171
180
|
function applyStoredPayloadRef(node, event, storedPayloadRef) {
|
|
172
181
|
if (!storedPayloadRef)
|
|
173
182
|
return;
|
|
183
|
+
if (event.type === "assistant_message" || event.type === "thinking_finished") {
|
|
184
|
+
node.payloadRefs = { ...node.payloadRefs, [storedPayloadRef.payloadKind ?? "output"]: storedPayloadRef };
|
|
185
|
+
}
|
|
174
186
|
if (event.type === "tool_call" || event.type === "tool_execution_started") {
|
|
175
187
|
node.payloadRefs = { ...node.payloadRefs, input: storedPayloadRef };
|
|
176
188
|
}
|
|
@@ -524,6 +536,47 @@ export function latestTraceStreamId(events, initial) {
|
|
|
524
536
|
}
|
|
525
537
|
return latest;
|
|
526
538
|
}
|
|
539
|
+
function attachModelInferenceToLatestOutput(nodes, byId, event, storedEvent) {
|
|
540
|
+
const eventId = event.eventId;
|
|
541
|
+
const candidates = flattenTraceNodes(nodes)
|
|
542
|
+
.filter((node) => node.eventId === eventId && traceNodeStartedBeforeInference(node, storedEvent) && (node.type === "assistant.message"
|
|
543
|
+
|| node.type === "model.reasoning"
|
|
544
|
+
|| node.type === "tool.call"
|
|
545
|
+
|| node.type === "agent.delegation"))
|
|
546
|
+
.sort(compareTraceNodes);
|
|
547
|
+
const target = candidates.at(-1) ?? (eventId ? byId.get(messageTurnNodeId(eventId)) : undefined);
|
|
548
|
+
if (!target)
|
|
549
|
+
return;
|
|
550
|
+
const id = eventId ? `${eventId}:usage:${event.usageIndex ?? 0}` : storedEvent.id;
|
|
551
|
+
const record = {
|
|
552
|
+
id,
|
|
553
|
+
completedAt: storedEvent.createdAt,
|
|
554
|
+
metrics: {
|
|
555
|
+
...(event.inputTokens === undefined ? {} : { inputTokens: event.inputTokens }),
|
|
556
|
+
...(event.outputTokens === undefined ? {} : { outputTokens: event.outputTokens }),
|
|
557
|
+
...(event.cacheReadTokens === undefined ? {} : { cacheReadTokens: event.cacheReadTokens }),
|
|
558
|
+
...(event.cacheWriteTokens === undefined ? {} : { cacheWriteTokens: event.cacheWriteTokens }),
|
|
559
|
+
...(event.reasoningTokens === undefined ? {} : { reasoningTokens: event.reasoningTokens }),
|
|
560
|
+
totalTokens: event.totalTokens,
|
|
561
|
+
...(event.costUsd === undefined ? {} : { costUsd: event.costUsd }),
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
target.modelInferences = [...(target.modelInferences ?? []).filter((item) => item.id !== id), record];
|
|
565
|
+
}
|
|
566
|
+
function traceNodeStartedBeforeInference(node, storedEvent) {
|
|
567
|
+
const inferenceRenderSequence = storedEvent.renderSequence;
|
|
568
|
+
const nodeRenderSequence = node.orderKey?.renderSequence;
|
|
569
|
+
if (inferenceRenderSequence !== undefined && nodeRenderSequence !== undefined) {
|
|
570
|
+
return nodeRenderSequence <= inferenceRenderSequence;
|
|
571
|
+
}
|
|
572
|
+
const inferenceSequence = storedEvent.eventSequence ?? storedEvent.streamId;
|
|
573
|
+
const nodeSequence = node.orderKey?.eventSequence ?? node.orderKey?.streamId;
|
|
574
|
+
if (inferenceSequence !== undefined && nodeSequence !== undefined)
|
|
575
|
+
return nodeSequence <= inferenceSequence;
|
|
576
|
+
const inferenceTime = Date.parse(storedEvent.createdAt);
|
|
577
|
+
const nodeTime = Date.parse(node.startedAt ?? node.completedAt ?? "");
|
|
578
|
+
return !Number.isFinite(inferenceTime) || !Number.isFinite(nodeTime) || nodeTime <= inferenceTime;
|
|
579
|
+
}
|
|
527
580
|
// ── event → node helpers ─────────────────────────────────────────
|
|
528
581
|
function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByToolCallId, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex, traceSource, storedInstanceId) {
|
|
529
582
|
const eventId = "eventId" in event && typeof event.eventId === "string" ? event.eventId : undefined;
|
|
@@ -562,6 +615,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
|
|
|
562
615
|
type: "user.message",
|
|
563
616
|
title: "User Message",
|
|
564
617
|
status: isOptimisticUserMessageEvent(event) ? "running" : "done",
|
|
618
|
+
messageDeliveryState: isOptimisticUserMessageEvent(event) ? "sending" : undefined,
|
|
565
619
|
summary: event.text,
|
|
566
620
|
output: event.text,
|
|
567
621
|
};
|
|
@@ -711,6 +765,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
|
|
|
711
765
|
input: { reason: event.reason },
|
|
712
766
|
output: event.result,
|
|
713
767
|
error: event.errorMessage,
|
|
768
|
+
compactionStats: event.compactionStats,
|
|
714
769
|
stableKey: `compaction:${eventInstanceKey}`,
|
|
715
770
|
};
|
|
716
771
|
}
|
|
@@ -768,6 +823,7 @@ function findMatchingContentNode(byId, update) {
|
|
|
768
823
|
return [...byId.values()].find((candidate) => candidate.type === update.type && candidate.stableKey === update.stableKey);
|
|
769
824
|
}
|
|
770
825
|
function mergeAssistantMessageEvent(target, update) {
|
|
826
|
+
target.payloadRefs = { ...target.payloadRefs, ...update.payloadRefs };
|
|
771
827
|
target.status = update.status;
|
|
772
828
|
target.summary = update.summary ?? target.summary;
|
|
773
829
|
target.output = update.output ?? target.output;
|
|
@@ -1163,6 +1219,7 @@ function mergeCompactionEvent(target, update) {
|
|
|
1163
1219
|
target.input = update.input ?? target.input;
|
|
1164
1220
|
target.output = update.output ?? target.output;
|
|
1165
1221
|
target.error = update.error ?? target.error;
|
|
1222
|
+
target.compactionStats = update.compactionStats ?? target.compactionStats;
|
|
1166
1223
|
target.completedAt = update.completedAt ?? target.completedAt;
|
|
1167
1224
|
}
|
|
1168
1225
|
function hasVisibleText(value) {
|
|
@@ -710,6 +710,7 @@ function createUserMessageNode(piboSessionId, entry, entryIndex, timing, fallbac
|
|
|
710
710
|
startedAt: entry.createdAt,
|
|
711
711
|
summary: text,
|
|
712
712
|
output: text,
|
|
713
|
+
payloadRefs: historyContentPayloadRef(entry),
|
|
713
714
|
error: entry.error,
|
|
714
715
|
source,
|
|
715
716
|
stableKey: eventIdentity ?? historyEntryStableKey(entry),
|
|
@@ -902,6 +903,7 @@ function createAssistantMessageNode(input) {
|
|
|
902
903
|
completedAt: input.completedAt,
|
|
903
904
|
summary: input.text,
|
|
904
905
|
output: input.text,
|
|
906
|
+
payloadRefs: historyContentPayloadRef(input.entry),
|
|
905
907
|
error: input.error,
|
|
906
908
|
source,
|
|
907
909
|
stableKey: eventIdentity ? `assistant:${eventIdentity}` : `${historyEntryStableKey(input.entry)}:response:${input.contentPartIndex}`,
|
|
@@ -909,6 +911,13 @@ function createAssistantMessageNode(input) {
|
|
|
909
911
|
children: input.children ?? [],
|
|
910
912
|
};
|
|
911
913
|
}
|
|
914
|
+
/** Preserve a stored full body when the bounded history page contains its preview. */
|
|
915
|
+
function historyContentPayloadRef(entry) {
|
|
916
|
+
const ref = entry.source === "product" ? entry.metadata?.tracePayloadRef : undefined;
|
|
917
|
+
if (!ref || typeof ref !== "object" || Array.isArray(ref) || typeof ref.ref !== "string" || typeof ref.byteLength !== "number" || typeof ref.preview !== "string")
|
|
918
|
+
return undefined;
|
|
919
|
+
return { output: ref };
|
|
920
|
+
}
|
|
912
921
|
function historyMessageParts(entry) {
|
|
913
922
|
return typeof entry.content === "string" ? [{ type: "text", text: entry.content }] : [...entry.content];
|
|
914
923
|
}
|
|
@@ -165,6 +165,7 @@ function makeStored(streamEvent, piboSessionId, type, payload, nextSequence, now
|
|
|
165
165
|
streamId: typeof streamEvent.streamId === "number" ? streamEvent.streamId : undefined,
|
|
166
166
|
streamFrameIndex,
|
|
167
167
|
traceSource: "live",
|
|
168
|
+
storedPayloadRef: streamEvent.storedPayloadRef,
|
|
168
169
|
eventId: typeof positionedPayload.eventId === "string" ? positionedPayload.eventId : undefined,
|
|
169
170
|
type,
|
|
170
171
|
createdAt: streamEventCreatedAt(streamEvent) ?? now(),
|
|
@@ -64,6 +64,7 @@ function traceNodeShallowEqual(left, right) {
|
|
|
64
64
|
left.toolMetrics?.inputTokens === right.toolMetrics?.inputTokens &&
|
|
65
65
|
left.toolMetrics?.outputTokens === right.toolMetrics?.outputTokens &&
|
|
66
66
|
left.toolMetrics?.tokenBasis === right.toolMetrics?.tokenBasis &&
|
|
67
|
+
modelInferenceRecordsEqual(left.modelInferences, right.modelInferences) &&
|
|
67
68
|
left.summary === right.summary &&
|
|
68
69
|
left.input === right.input &&
|
|
69
70
|
left.output === right.output &&
|
|
@@ -73,6 +74,24 @@ function traceNodeShallowEqual(left, right) {
|
|
|
73
74
|
left.stableKey === right.stableKey &&
|
|
74
75
|
traceOrderKeyEqual(left.orderKey, right.orderKey));
|
|
75
76
|
}
|
|
77
|
+
function modelInferenceRecordsEqual(left, right) {
|
|
78
|
+
if (left === right)
|
|
79
|
+
return true;
|
|
80
|
+
if (!left || !right || left.length !== right.length)
|
|
81
|
+
return false;
|
|
82
|
+
return left.every((item, index) => {
|
|
83
|
+
const other = right[index];
|
|
84
|
+
return item.id === other?.id
|
|
85
|
+
&& item.completedAt === other.completedAt
|
|
86
|
+
&& item.metrics.inputTokens === other.metrics.inputTokens
|
|
87
|
+
&& item.metrics.outputTokens === other.metrics.outputTokens
|
|
88
|
+
&& item.metrics.cacheReadTokens === other.metrics.cacheReadTokens
|
|
89
|
+
&& item.metrics.cacheWriteTokens === other.metrics.cacheWriteTokens
|
|
90
|
+
&& item.metrics.reasoningTokens === other.metrics.reasoningTokens
|
|
91
|
+
&& item.metrics.totalTokens === other.metrics.totalTokens
|
|
92
|
+
&& item.metrics.costUsd === other.metrics.costUsd;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
76
95
|
function traceOrderKeyEqual(left, right) {
|
|
77
96
|
if (left === right)
|
|
78
97
|
return true;
|
package/dist/web/channel.js
CHANGED
|
@@ -199,6 +199,7 @@ function createGatewayStatusResponse(channelContext, options, generation) {
|
|
|
199
199
|
generation,
|
|
200
200
|
health: { status: "ok", mode },
|
|
201
201
|
runtimeStatuses: createGatewayRuntimeStatuses(channelContext),
|
|
202
|
+
...(channelContext.getRuntimeCapacityStatus ? { runtimeCapacity: channelContext.getRuntimeCapacityStatus() } : {}),
|
|
202
203
|
activeRuns: collectActiveRuns(channelContext),
|
|
203
204
|
});
|
|
204
205
|
}
|
|
@@ -401,6 +402,8 @@ export function createWebHostChannel(options = {}) {
|
|
|
401
402
|
return;
|
|
402
403
|
shuttingDown = false;
|
|
403
404
|
context = channelContext;
|
|
405
|
+
for (const app of channelContext.getWebApps())
|
|
406
|
+
await app.initialize?.(createAppContext(channelContext));
|
|
404
407
|
server = createServer((request, response) => {
|
|
405
408
|
void handleRequest(request, response);
|
|
406
409
|
});
|
package/dist/web/http.js
CHANGED
|
@@ -107,11 +107,11 @@ export async function sendWebResponse(response, webResponse, options = {}) {
|
|
|
107
107
|
}
|
|
108
108
|
const reader = webResponse.body.getReader();
|
|
109
109
|
const cancel = () => {
|
|
110
|
-
void reader.cancel();
|
|
110
|
+
void reader.cancel().catch(() => undefined);
|
|
111
111
|
};
|
|
112
112
|
const abort = () => {
|
|
113
113
|
const socket = response.socket;
|
|
114
|
-
void reader.cancel();
|
|
114
|
+
void reader.cancel().catch(() => undefined);
|
|
115
115
|
if (!response.writableEnded)
|
|
116
116
|
response.end();
|
|
117
117
|
socket?.end();
|
|
@@ -125,7 +125,20 @@ export async function sendWebResponse(response, webResponse, options = {}) {
|
|
|
125
125
|
const { done, value } = await reader.read();
|
|
126
126
|
if (done)
|
|
127
127
|
break;
|
|
128
|
-
|
|
128
|
+
const bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
129
|
+
for (let offset = 0; offset < bytes.length; offset += 64 * 1024) {
|
|
130
|
+
if (response.destroyed || response.writableEnded)
|
|
131
|
+
return;
|
|
132
|
+
if (!response.write(bytes.subarray(offset, offset + 64 * 1024))) {
|
|
133
|
+
const drained = await waitForResponseDrain(response, (webResponse.headers.get("content-type") ?? "").startsWith("text/event-stream") ? 5000 : undefined, options.signal);
|
|
134
|
+
if (!drained) {
|
|
135
|
+
void reader.cancel().catch(() => undefined);
|
|
136
|
+
if (!response.destroyed)
|
|
137
|
+
response.destroy();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
129
142
|
}
|
|
130
143
|
if (!response.writableEnded)
|
|
131
144
|
response.end();
|
|
@@ -206,3 +219,23 @@ function appendVary(existing, value) {
|
|
|
206
219
|
function appendServerTiming(existing, value) {
|
|
207
220
|
return existing ? `${existing}, ${value}` : value;
|
|
208
221
|
}
|
|
222
|
+
/** Honor socket pressure before pulling another application frame. */
|
|
223
|
+
function waitForResponseDrain(response, maximumAgeMs, signal) {
|
|
224
|
+
if (response.destroyed || response.writableEnded || signal?.aborted)
|
|
225
|
+
return Promise.resolve(false);
|
|
226
|
+
return new Promise(resolve => {
|
|
227
|
+
let timer;
|
|
228
|
+
const finish = (ready) => { if (timer)
|
|
229
|
+
clearTimeout(timer); response.off("drain", drain); response.off("close", close); response.off("error", close); signal?.removeEventListener("abort", close); resolve(ready); };
|
|
230
|
+
const drain = () => finish(true);
|
|
231
|
+
const close = () => finish(false);
|
|
232
|
+
response.once("drain", drain);
|
|
233
|
+
response.once("close", close);
|
|
234
|
+
response.once("error", close);
|
|
235
|
+
signal?.addEventListener("abort", close, { once: true });
|
|
236
|
+
if (maximumAgeMs !== undefined) {
|
|
237
|
+
timer = setTimeout(close, maximumAgeMs);
|
|
238
|
+
timer.unref();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.5.0",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|