@pasko70/pibo 3.5.1 → 3.6.1
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/data/history-query-service.js +63 -35
- package/dist/apps/chat-ui/assets/{dist-BA_dsINH.js → dist-CCgwGNBo.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B-auLrzD.js → dist-DFtTPSEB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-eJZar_0-.js → dist-XSlRIuRd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-wNNR2Bci.js → dist-YQ6IdwhV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-zcmEsIEp.js → dist-mpK-CrNR.js} +1 -1
- package/dist/apps/chat-ui/assets/index-B0BS_H88.js +229 -0
- package/dist/apps/chat-ui/assets/index-DRaSMxCz.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-CxKxukk_.js +44 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/debug/cache.js +136 -0
- package/dist/debug/index.js +46 -0
- package/dist/debug/session.js +2 -0
- package/dist/debug/summary.js +1 -0
- package/dist/debug/trace.js +28 -0
- package/dist/session-ui/terminalRows.js +5 -4
- package/dist/shared/cache-observability.js +73 -0
- package/dist/shared/model-inference-metrics.js +20 -7
- package/dist/shared/trace-event-projection.js +35 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-DEkbN5Vo.js +0 -229
- package/dist/apps/chat-ui/assets/index-DZK6Tzil.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-DZgW1fCB.js +0 -44
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-CxKxukk_.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { cacheUsageWarningText } from "../shared/cache-observability.js";
|
|
2
|
+
import { compareInferenceCompletion, modelInferenceCacheReadRatio, modelInferenceCachedInputTokens, modelInferenceInputTokens, modelInferenceUncachedInputTokens, } from "../shared/model-inference-metrics.js";
|
|
3
|
+
import { inspectDebugTrace } from "./trace.js";
|
|
4
|
+
import { formatNextCommands } from "./next-commands.js";
|
|
5
|
+
function finiteToken(value) {
|
|
6
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
|
|
7
|
+
}
|
|
8
|
+
export async function inspectDebugCache(piboSessionId, stores, options = {}) {
|
|
9
|
+
const trace = await inspectDebugTrace(piboSessionId, stores);
|
|
10
|
+
const byId = new Map();
|
|
11
|
+
for (const node of trace.nodes) {
|
|
12
|
+
for (const record of node.modelInferences ?? [])
|
|
13
|
+
byId.set(record.id, { nodeId: node.id, nodeTitle: node.title, record });
|
|
14
|
+
}
|
|
15
|
+
const located = [...byId.values()].sort((left, right) => compareInferenceCompletion(left.record, right.record));
|
|
16
|
+
let reportedInput = 0;
|
|
17
|
+
let reportedRead = 0;
|
|
18
|
+
let reportedCount = 0;
|
|
19
|
+
let unreportedCount = 0;
|
|
20
|
+
let writeTotal = 0;
|
|
21
|
+
let writeReported = false;
|
|
22
|
+
let possibleDropCount = 0;
|
|
23
|
+
for (const { record } of located) {
|
|
24
|
+
const input = modelInferenceInputTokens(record.metrics);
|
|
25
|
+
const read = modelInferenceCachedInputTokens(record.metrics);
|
|
26
|
+
if (input !== undefined && input > 0 && read !== undefined && read <= input) {
|
|
27
|
+
reportedInput += input;
|
|
28
|
+
reportedRead += read;
|
|
29
|
+
reportedCount++;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
unreportedCount++;
|
|
33
|
+
}
|
|
34
|
+
const write = finiteToken(record.metrics.cacheWriteTokens);
|
|
35
|
+
if (write !== undefined) {
|
|
36
|
+
writeTotal += write;
|
|
37
|
+
writeReported = true;
|
|
38
|
+
}
|
|
39
|
+
if (record.cacheObservation?.warning === "possible-cache-read-drop")
|
|
40
|
+
possibleDropCount++;
|
|
41
|
+
}
|
|
42
|
+
const limit = options.limit ?? 20;
|
|
43
|
+
const selected = located.slice(Math.max(0, located.length - limit));
|
|
44
|
+
return {
|
|
45
|
+
piboSessionId,
|
|
46
|
+
runtimeInstanceId: trace.runtimeInstanceId,
|
|
47
|
+
runtimeAdapterId: trace.runtimeAdapterId,
|
|
48
|
+
source: "provider-reported-usage",
|
|
49
|
+
summary: {
|
|
50
|
+
inferenceCount: located.length,
|
|
51
|
+
cacheReadReportedCount: reportedCount,
|
|
52
|
+
cacheReadUnreportedCount: unreportedCount,
|
|
53
|
+
possibleDropCount,
|
|
54
|
+
...(reportedCount ? {
|
|
55
|
+
inputTokens: reportedInput,
|
|
56
|
+
cacheReadTokens: reportedRead,
|
|
57
|
+
uncachedInputTokens: reportedInput - reportedRead,
|
|
58
|
+
cacheReadRatio: reportedRead / reportedInput,
|
|
59
|
+
} : {}),
|
|
60
|
+
...(writeReported ? { cacheWriteTokens: writeTotal } : {}),
|
|
61
|
+
},
|
|
62
|
+
inferences: selected.map(({ nodeId, nodeTitle, record }) => ({
|
|
63
|
+
id: record.id,
|
|
64
|
+
nodeId,
|
|
65
|
+
nodeTitle,
|
|
66
|
+
completedAt: record.completedAt,
|
|
67
|
+
inputTokens: modelInferenceInputTokens(record.metrics),
|
|
68
|
+
cacheReadTokens: modelInferenceCachedInputTokens(record.metrics),
|
|
69
|
+
cacheWriteTokens: finiteToken(record.metrics.cacheWriteTokens),
|
|
70
|
+
uncachedInputTokens: modelInferenceUncachedInputTokens(record.metrics),
|
|
71
|
+
cacheReadRatio: modelInferenceCacheReadRatio(record.metrics),
|
|
72
|
+
previousCacheReadRatio: record.cacheObservation?.previousCacheReadRatio,
|
|
73
|
+
elapsedMs: record.cacheObservation?.elapsedMs,
|
|
74
|
+
cacheState: record.cacheObservation?.cacheState ?? "unknown",
|
|
75
|
+
warning: record.cacheObservation?.warning ?? "none",
|
|
76
|
+
warningText: record.cacheObservation ? cacheUsageWarningText(record.cacheObservation) : undefined,
|
|
77
|
+
explanation: record.cacheObservation?.explanation ?? "insufficient-data",
|
|
78
|
+
})),
|
|
79
|
+
limitations: [
|
|
80
|
+
"Cache counters are reported by the runtime or provider; missing counters remain unknown.",
|
|
81
|
+
"A cache-read drop does not identify whether Pibo, the runtime, the provider, or eviction caused it.",
|
|
82
|
+
"This command does not inspect or alter runtime source code, prompts, or provider cache keys.",
|
|
83
|
+
],
|
|
84
|
+
nextCommands: [
|
|
85
|
+
`pibo debug trace ${piboSessionId} --medium`,
|
|
86
|
+
`pibo debug events ${piboSessionId} --type assistant_usage --fields inputTokens,cacheReadTokens,cacheWriteTokens,totalTokens`,
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function metric(value) {
|
|
91
|
+
return value === undefined ? "unknown" : String(value);
|
|
92
|
+
}
|
|
93
|
+
function ratio(value) {
|
|
94
|
+
return value === undefined ? "unknown" : `${(value * 100).toFixed(1)}%`;
|
|
95
|
+
}
|
|
96
|
+
export function formatDebugCache(result) {
|
|
97
|
+
const lines = [
|
|
98
|
+
`piboSessionId: ${result.piboSessionId}`,
|
|
99
|
+
...(result.runtimeInstanceId ? [`runtimeInstanceId: ${result.runtimeInstanceId}`] : []),
|
|
100
|
+
...(result.runtimeAdapterId ? [`runtimeAdapterId: ${result.runtimeAdapterId}`] : []),
|
|
101
|
+
`source: ${result.source}`,
|
|
102
|
+
`inferences: ${result.summary.inferenceCount}`,
|
|
103
|
+
`cacheReadReported: ${result.summary.cacheReadReportedCount}`,
|
|
104
|
+
`cacheReadUnreported: ${result.summary.cacheReadUnreportedCount}`,
|
|
105
|
+
`possibleDrops: ${result.summary.possibleDropCount}`,
|
|
106
|
+
`inputTokens: ${metric(result.summary.inputTokens)}`,
|
|
107
|
+
`cacheReadTokens: ${metric(result.summary.cacheReadTokens)}`,
|
|
108
|
+
`uncachedInputTokens: ${metric(result.summary.uncachedInputTokens)}`,
|
|
109
|
+
`cacheWriteTokens: ${metric(result.summary.cacheWriteTokens)}`,
|
|
110
|
+
`cacheReadRatio: ${ratio(result.summary.cacheReadRatio)}`,
|
|
111
|
+
"",
|
|
112
|
+
];
|
|
113
|
+
if (result.inferences.length) {
|
|
114
|
+
lines.push("completedAt\tstate\tinput\tcacheRead\tuncached\tcacheWrite\tratio\tid\tnode");
|
|
115
|
+
for (const inference of result.inferences) {
|
|
116
|
+
lines.push([
|
|
117
|
+
inference.completedAt ?? "unknown",
|
|
118
|
+
inference.cacheState,
|
|
119
|
+
metric(inference.inputTokens),
|
|
120
|
+
metric(inference.cacheReadTokens),
|
|
121
|
+
metric(inference.uncachedInputTokens),
|
|
122
|
+
metric(inference.cacheWriteTokens),
|
|
123
|
+
ratio(inference.cacheReadRatio),
|
|
124
|
+
inference.id,
|
|
125
|
+
inference.nodeTitle,
|
|
126
|
+
].join("\t"));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const inference of result.inferences) {
|
|
130
|
+
if (inference.warningText)
|
|
131
|
+
lines.push(`cache-warning\t${inference.id}\t${inference.warningText}`);
|
|
132
|
+
}
|
|
133
|
+
lines.push("", "Limitations:", ...result.limitations.map((item) => `- ${item}`));
|
|
134
|
+
lines.push(...formatNextCommands(result.nextCommands));
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
package/dist/debug/index.js
CHANGED
|
@@ -34,6 +34,10 @@ export async function runDebugCli(argv = process.argv) {
|
|
|
34
34
|
await runDebugTrace(args.slice(1));
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
+
if (args[0] === "cache") {
|
|
38
|
+
await runDebugCache(args.slice(1));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
37
41
|
if (args[0] === "summary") {
|
|
38
42
|
await runDebugSummary(args.slice(1));
|
|
39
43
|
return;
|
|
@@ -815,6 +819,29 @@ async function runDebugTelemetry(args) {
|
|
|
815
819
|
}
|
|
816
820
|
throw new Error(`Unknown pibo debug telemetry command "${command}". Run pibo debug telemetry --help.`);
|
|
817
821
|
}
|
|
822
|
+
async function runDebugCache(args) {
|
|
823
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
824
|
+
printDebugCacheDiscovery();
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const options = parseOptions(args);
|
|
828
|
+
const piboSessionId = options.positionals[0];
|
|
829
|
+
if (!piboSessionId)
|
|
830
|
+
throw new Error("pibo debug cache requires <pibo-session-id>");
|
|
831
|
+
if (options.positionals.length > 1)
|
|
832
|
+
throw new Error("pibo debug cache accepts one <pibo-session-id>. Run pibo debug cache --help.");
|
|
833
|
+
const limit = options.limit === undefined ? undefined : Number(options.limit);
|
|
834
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1 || limit > 200))
|
|
835
|
+
throw new Error("--limit must be an integer from 1 to 200");
|
|
836
|
+
const stores = { sessions: resolveDebugStore("sessions"), chat: resolveDebugStore("chat") };
|
|
837
|
+
const { formatJson } = await import("./sql.js");
|
|
838
|
+
const { formatDebugCache, inspectDebugCache } = await import("./cache.js");
|
|
839
|
+
const result = await inspectDebugCache(piboSessionId, stores, { limit });
|
|
840
|
+
if (options.json)
|
|
841
|
+
console.log(formatJson(result));
|
|
842
|
+
else
|
|
843
|
+
console.log(formatDebugCache(result));
|
|
844
|
+
}
|
|
818
845
|
async function runDebugTrace(args) {
|
|
819
846
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
820
847
|
printDebugTraceDiscovery();
|
|
@@ -1514,6 +1541,7 @@ Commands:
|
|
|
1514
1541
|
messages List or show stored user/assistant messages
|
|
1515
1542
|
final Show the latest assistant message
|
|
1516
1543
|
trace Rebuild the Chat Web trace view for one Pibo Session
|
|
1544
|
+
cache Summarize provider-reported cache usage for one Pibo Session
|
|
1517
1545
|
events Inspect compact event payload fields for one Pibo Session
|
|
1518
1546
|
agents Inspect delegated child agents and their persisted activity
|
|
1519
1547
|
tool Inspect one grouped tool call
|
|
@@ -1536,6 +1564,7 @@ Next:
|
|
|
1536
1564
|
pibo debug final <pibo-session-id>
|
|
1537
1565
|
pibo debug messages <pibo-session-id> list
|
|
1538
1566
|
pibo debug trace <pibo-session-id> --running-only
|
|
1567
|
+
pibo debug cache <pibo-session-id>
|
|
1539
1568
|
pibo debug events stream --topic pibo.output
|
|
1540
1569
|
pibo debug persistence
|
|
1541
1570
|
pibo debug repair output <pibo-session-id> <event-id> --dry-run
|
|
@@ -1807,6 +1836,23 @@ Next:
|
|
|
1807
1836
|
pibo debug tool ps_... <tool-call-id> --output
|
|
1808
1837
|
`);
|
|
1809
1838
|
}
|
|
1839
|
+
function printDebugCacheDiscovery() {
|
|
1840
|
+
console.log(`pibo debug cache - summarize provider-reported cache usage
|
|
1841
|
+
|
|
1842
|
+
Usage:
|
|
1843
|
+
pibo debug cache <pibo-session-id> [--limit n] [--json]
|
|
1844
|
+
|
|
1845
|
+
Reports:
|
|
1846
|
+
Per-inference input, cache-read, cache-write and uncached token counts.
|
|
1847
|
+
Warns when large consecutive requests move from mostly cached to mostly uncached input.
|
|
1848
|
+
Provider metrics cannot identify the cause of a cache-read drop.
|
|
1849
|
+
|
|
1850
|
+
Next:
|
|
1851
|
+
pibo debug cache ps_... --json
|
|
1852
|
+
pibo debug trace ps_... --medium
|
|
1853
|
+
pibo debug events ps_... --type assistant_usage --fields inputTokens,cacheReadTokens,cacheWriteTokens,totalTokens
|
|
1854
|
+
`);
|
|
1855
|
+
}
|
|
1810
1856
|
function printDebugTraceDiscovery() {
|
|
1811
1857
|
console.log(`pibo debug trace - rebuild one Chat Web trace view
|
|
1812
1858
|
|
package/dist/debug/session.js
CHANGED
|
@@ -48,6 +48,7 @@ export function inspectDebugSession(input, stores, options = {}) {
|
|
|
48
48
|
`pibo debug messages ${parsed.piboSessionId} list`,
|
|
49
49
|
`pibo debug final ${parsed.piboSessionId}`,
|
|
50
50
|
`pibo debug trace ${parsed.piboSessionId} --check`,
|
|
51
|
+
`pibo debug cache ${parsed.piboSessionId}`,
|
|
51
52
|
`pibo debug failures ${parsed.piboSessionId}`,
|
|
52
53
|
`pibo debug events ${parsed.piboSessionId} --limit 20`,
|
|
53
54
|
];
|
|
@@ -114,6 +115,7 @@ export function inspectDebugSessionRuntime(input, stores) {
|
|
|
114
115
|
nextCommands: [
|
|
115
116
|
`pibo debug trace ${parsed.piboSessionId} --check`,
|
|
116
117
|
`pibo debug trace ${parsed.piboSessionId} --native-history --check`,
|
|
118
|
+
`pibo debug cache ${parsed.piboSessionId}`,
|
|
117
119
|
`pibo debug events ${parsed.piboSessionId} --limit 20`,
|
|
118
120
|
],
|
|
119
121
|
};
|
package/dist/debug/summary.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function inspectDebugSummary(input, stores) {
|
|
|
42
42
|
`pibo debug failures ${parsed.piboSessionId}`,
|
|
43
43
|
`pibo debug messages ${parsed.piboSessionId} list`,
|
|
44
44
|
`pibo debug trace ${parsed.piboSessionId} --check`,
|
|
45
|
+
`pibo debug cache ${parsed.piboSessionId}`,
|
|
45
46
|
`pibo debug events ${parsed.piboSessionId} --limit 20`,
|
|
46
47
|
],
|
|
47
48
|
};
|
package/dist/debug/trace.js
CHANGED
|
@@ -7,6 +7,8 @@ import { compareTraceNodes } from "../shared/trace-nodes.js";
|
|
|
7
7
|
import { openReadOnlyDebugDatabase, withStorePath } from "./sql.js";
|
|
8
8
|
import { formatNextCommands } from "./next-commands.js";
|
|
9
9
|
import { resolveDebugTraceSessionStatus, summarizeDebugTraceStatus } from "./trace-status.js";
|
|
10
|
+
import { cacheUsageWarningText } from "../shared/cache-observability.js";
|
|
11
|
+
import { modelInferenceCacheReadRatio, modelInferenceCachedInputTokens, modelInferenceInputTokens, modelInferenceUncachedInputTokens, } from "../shared/model-inference-metrics.js";
|
|
10
12
|
export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
11
13
|
if (!stores.sessions.exists)
|
|
12
14
|
throw new Error(`Debug store "sessions" not found at ${stores.sessions.path}`);
|
|
@@ -113,6 +115,23 @@ export async function inspectDebugTraceNode(piboSessionId, stores, nodeId) {
|
|
|
113
115
|
nextCommands: node ? buildNodeNextCommands(piboSessionId, node) : [`pibo debug trace ${piboSessionId}`],
|
|
114
116
|
};
|
|
115
117
|
}
|
|
118
|
+
function formatMetric(value) {
|
|
119
|
+
return value === undefined ? "unknown" : String(value);
|
|
120
|
+
}
|
|
121
|
+
function formatRatio(value) {
|
|
122
|
+
return value === undefined ? "unknown" : `${(value * 100).toFixed(1)}%`;
|
|
123
|
+
}
|
|
124
|
+
function formatModelInference(record) {
|
|
125
|
+
return [
|
|
126
|
+
record.id,
|
|
127
|
+
`input=${formatMetric(modelInferenceInputTokens(record.metrics))}`,
|
|
128
|
+
`cacheRead=${formatMetric(modelInferenceCachedInputTokens(record.metrics))}`,
|
|
129
|
+
`uncached=${formatMetric(modelInferenceUncachedInputTokens(record.metrics))}`,
|
|
130
|
+
`cacheWrite=${formatMetric(record.metrics.cacheWriteTokens)}`,
|
|
131
|
+
`output=${formatMetric(record.metrics.outputTokens)}`,
|
|
132
|
+
`cacheReadRatio=${formatRatio(modelInferenceCacheReadRatio(record.metrics))}`,
|
|
133
|
+
].join("\t");
|
|
134
|
+
}
|
|
116
135
|
export function formatDebugTrace(result, options = {}) {
|
|
117
136
|
const lines = [
|
|
118
137
|
`piboSessionId: ${result.piboSessionId}`,
|
|
@@ -152,6 +171,12 @@ export function formatDebugTrace(result, options = {}) {
|
|
|
152
171
|
order: node.order,
|
|
153
172
|
};
|
|
154
173
|
lines.push(columns.map((column) => values[column] ?? "").join("\t"));
|
|
174
|
+
for (const inference of node.modelInferences ?? []) {
|
|
175
|
+
lines.push(`model-inference\t${formatModelInference(inference)}`);
|
|
176
|
+
const warning = inference.cacheObservation && cacheUsageWarningText(inference.cacheObservation);
|
|
177
|
+
if (warning)
|
|
178
|
+
lines.push(`cache-warning\t${inference.id}\t${warning}`);
|
|
179
|
+
}
|
|
155
180
|
}
|
|
156
181
|
lines.push(`nodes: ${result.nodes.length}${result.nodes.length !== result.rawNodeCount ? ` of ${result.rawNodeCount}` : ""}`);
|
|
157
182
|
if (result.checks) {
|
|
@@ -194,6 +219,8 @@ export function formatDebugTraceNode(result) {
|
|
|
194
219
|
lines.push(`runId: ${node.runId}`);
|
|
195
220
|
if (node.toolCallId)
|
|
196
221
|
lines.push(`toolCallId: ${node.toolCallId}`);
|
|
222
|
+
for (const inference of node.modelInferences ?? [])
|
|
223
|
+
lines.push(`modelInference: ${JSON.stringify(inference)}`);
|
|
197
224
|
lines.push(...formatNextCommands(result.nextCommands));
|
|
198
225
|
return lines.join("\n");
|
|
199
226
|
}
|
|
@@ -214,6 +241,7 @@ function flattenTraceNodes(nodes, depth = 0) {
|
|
|
214
241
|
startedAt: node.startedAt,
|
|
215
242
|
completedAt: node.completedAt,
|
|
216
243
|
childrenCount: node.children.length,
|
|
244
|
+
...(node.modelInferences?.length ? { modelInferences: node.modelInferences } : {}),
|
|
217
245
|
depth,
|
|
218
246
|
},
|
|
219
247
|
...flattenTraceNodes(node.children, depth + 1),
|
|
@@ -23,10 +23,11 @@ export function buildCompactTerminalRows(traceView, options) {
|
|
|
23
23
|
const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
|
|
24
24
|
applyCompletedTurnTiming(candidates, turnById);
|
|
25
25
|
const reconciled = reconcileConceptualRowCandidates(candidates);
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const toolDisplayMode = options.toolDisplayMode ?? "default";
|
|
27
|
+
const rows = toolDisplayMode === "default" || toolDisplayMode === "slim"
|
|
28
|
+
? groupRelatedToolCandidates(reconciled, showToolDebugMetrics || toolDisplayMode === "slim").map((candidate) => candidate.row)
|
|
28
29
|
: reconciled.map((candidate) => candidate.row);
|
|
29
|
-
return applyToolDisplayMode(rows,
|
|
30
|
+
return applyToolDisplayMode(rows, toolDisplayMode);
|
|
30
31
|
}
|
|
31
32
|
function applyToolDisplayMode(rows, mode) {
|
|
32
33
|
if (mode === "default")
|
|
@@ -1041,7 +1042,7 @@ function createImageGroup(candidates) {
|
|
|
1041
1042
|
lines: [
|
|
1042
1043
|
{
|
|
1043
1044
|
prefix: "bullet",
|
|
1044
|
-
tokens: [token(status === "running" ? `Viewing ${detailItems.length} images` : status === "error" ? `${detailItems.length} image reads · error` : `${detailItems.length} ${detailItems.length === 1 ? "Image
|
|
1045
|
+
tokens: [token(status === "running" ? `Viewing ${detailItems.length} images` : status === "error" ? `${detailItems.length} image reads · error` : `${detailItems.length} Viewed ${detailItems.length === 1 ? "Image" : "Images"}`, toneForStatus(status), "semibold")],
|
|
1045
1046
|
},
|
|
1046
1047
|
...visibleDetailItems.map((item, index) => ({
|
|
1047
1048
|
prefix: index === 0 ? "detail" : "continuation",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { modelInferenceCachedInputTokens, modelInferenceInputTokens, modelInferenceUncachedInputTokens, } from "./model-inference-metrics.js";
|
|
2
|
+
export const CACHE_READ_DROP_RULE = "provider-cache-read-drop-v1";
|
|
3
|
+
export const CACHE_READ_DROP_MIN_INPUT_TOKENS = 16_384;
|
|
4
|
+
function cacheState(ratio) {
|
|
5
|
+
if (ratio === undefined)
|
|
6
|
+
return "unknown";
|
|
7
|
+
if (ratio <= 0.1)
|
|
8
|
+
return "cold";
|
|
9
|
+
if (ratio >= 0.8)
|
|
10
|
+
return "warm";
|
|
11
|
+
return "partial";
|
|
12
|
+
}
|
|
13
|
+
function elapsedMs(current, previous) {
|
|
14
|
+
if (!previous?.completedAt || !current.completedAt)
|
|
15
|
+
return undefined;
|
|
16
|
+
const before = Date.parse(previous.completedAt);
|
|
17
|
+
const after = Date.parse(current.completedAt);
|
|
18
|
+
return Number.isFinite(before) && Number.isFinite(after) && after >= before ? after - before : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compares bounded provider usage counters. It does not inspect prompts, infer a
|
|
22
|
+
* provider cache key, or identify why a cache read changed.
|
|
23
|
+
*/
|
|
24
|
+
export function observeCacheUsage(current, previous, options = {}) {
|
|
25
|
+
const inputTokens = modelInferenceInputTokens(current.metrics);
|
|
26
|
+
const cacheReadTokens = modelInferenceCachedInputTokens(current.metrics);
|
|
27
|
+
const uncachedInputTokens = modelInferenceUncachedInputTokens(current.metrics);
|
|
28
|
+
const cacheWriteTokens = Number.isFinite(current.metrics.cacheWriteTokens) && Number(current.metrics.cacheWriteTokens) >= 0
|
|
29
|
+
? Math.floor(Number(current.metrics.cacheWriteTokens))
|
|
30
|
+
: undefined;
|
|
31
|
+
const ratio = inputTokens !== undefined && inputTokens > 0 && cacheReadTokens !== undefined && cacheReadTokens <= inputTokens
|
|
32
|
+
? cacheReadTokens / inputTokens
|
|
33
|
+
: undefined;
|
|
34
|
+
const previousInput = modelInferenceInputTokens(previous?.metrics);
|
|
35
|
+
const previousRead = modelInferenceCachedInputTokens(previous?.metrics);
|
|
36
|
+
const previousRatio = previousInput !== undefined && previousInput > 0 && previousRead !== undefined && previousRead <= previousInput
|
|
37
|
+
? previousRead / previousInput
|
|
38
|
+
: undefined;
|
|
39
|
+
const elapsed = elapsedMs(current, previous);
|
|
40
|
+
const observation = {
|
|
41
|
+
rule: CACHE_READ_DROP_RULE,
|
|
42
|
+
source: "provider-reported-usage",
|
|
43
|
+
cacheState: cacheState(ratio),
|
|
44
|
+
warning: "none",
|
|
45
|
+
explanation: ratio === undefined ? "insufficient-data" : "provider-usage",
|
|
46
|
+
...(inputTokens === undefined ? {} : { inputTokens }),
|
|
47
|
+
...(cacheReadTokens === undefined ? {} : { cacheReadTokens }),
|
|
48
|
+
...(cacheWriteTokens === undefined ? {} : { cacheWriteTokens }),
|
|
49
|
+
...(uncachedInputTokens === undefined ? {} : { uncachedInputTokens }),
|
|
50
|
+
...(ratio === undefined ? {} : { cacheReadRatio: ratio }),
|
|
51
|
+
...(previous ? { previousInferenceId: previous.id } : {}),
|
|
52
|
+
...(previousRatio === undefined ? {} : { previousCacheReadRatio: previousRatio }),
|
|
53
|
+
...(elapsed === undefined ? {} : { elapsedMs: elapsed }),
|
|
54
|
+
};
|
|
55
|
+
if (options.compactionBetween) {
|
|
56
|
+
observation.explanation = "compaction-between-inferences";
|
|
57
|
+
return observation;
|
|
58
|
+
}
|
|
59
|
+
if (inputTokens !== undefined && previousInput !== undefined && cacheReadTokens !== undefined && previousRead !== undefined
|
|
60
|
+
&& inputTokens >= CACHE_READ_DROP_MIN_INPUT_TOKENS && previousInput >= CACHE_READ_DROP_MIN_INPUT_TOKENS
|
|
61
|
+
&& ratio !== undefined && previousRatio !== undefined && previousRatio >= 0.8 && ratio <= 0.1 && cacheReadTokens < previousRead) {
|
|
62
|
+
observation.warning = "possible-cache-read-drop";
|
|
63
|
+
observation.explanation = "cache-read-ratio-dropped";
|
|
64
|
+
}
|
|
65
|
+
return observation;
|
|
66
|
+
}
|
|
67
|
+
export function cacheUsageWarningText(observation) {
|
|
68
|
+
if (observation.warning === "none")
|
|
69
|
+
return undefined;
|
|
70
|
+
const current = observation.cacheReadRatio === undefined ? "unknown" : `${(observation.cacheReadRatio * 100).toFixed(1)}%`;
|
|
71
|
+
const previous = observation.previousCacheReadRatio === undefined ? "unknown" : `${(observation.previousCacheReadRatio * 100).toFixed(1)}%`;
|
|
72
|
+
return `Possible cache-read drop: provider-reported cached input fell from ${previous} to ${current}. The provider metrics do not identify the cause.`;
|
|
73
|
+
}
|
|
@@ -6,18 +6,31 @@ export function modelInferenceInputTokens(metrics) {
|
|
|
6
6
|
return tokenCount(metrics?.inputTokens);
|
|
7
7
|
}
|
|
8
8
|
export function modelInferenceCachedInputTokens(metrics) {
|
|
9
|
-
|
|
10
|
-
const cacheWrite = tokenCount(metrics?.cacheWriteTokens);
|
|
11
|
-
if (cacheRead === undefined && cacheWrite === undefined)
|
|
12
|
-
return undefined;
|
|
13
|
-
return (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
9
|
+
return tokenCount(metrics?.cacheReadTokens);
|
|
14
10
|
}
|
|
15
11
|
export function modelInferenceUncachedInputTokens(metrics) {
|
|
16
12
|
const input = modelInferenceInputTokens(metrics);
|
|
17
|
-
|
|
13
|
+
const cached = modelInferenceCachedInputTokens(metrics);
|
|
14
|
+
if (input === undefined || cached === undefined || cached > input)
|
|
18
15
|
return undefined;
|
|
19
|
-
return
|
|
16
|
+
return input - cached;
|
|
17
|
+
}
|
|
18
|
+
export function modelInferenceCacheReadRatio(metrics) {
|
|
19
|
+
const input = modelInferenceInputTokens(metrics);
|
|
20
|
+
const cached = modelInferenceCachedInputTokens(metrics);
|
|
21
|
+
if (input === undefined || input === 0 || cached === undefined || cached > input)
|
|
22
|
+
return undefined;
|
|
23
|
+
return cached / input;
|
|
20
24
|
}
|
|
21
25
|
function tokenCount(value) {
|
|
22
26
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
|
|
23
27
|
}
|
|
28
|
+
export function compareInferenceCompletion(left, right) {
|
|
29
|
+
const time = Date.parse(left.completedAt ?? "") - Date.parse(right.completedAt ?? "");
|
|
30
|
+
if (Number.isFinite(time) && time !== 0)
|
|
31
|
+
return time;
|
|
32
|
+
if (left.completedSequence !== undefined && right.completedSequence !== undefined) {
|
|
33
|
+
return left.completedSequence - right.completedSequence;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
@@ -4,6 +4,8 @@ import { attachAsyncAgentRunNode, isRunStartToolNode } from "./trace-async-agent
|
|
|
4
4
|
import { createRunNotificationNode, parseRunNotificationText } from "./trace-run-notifications.js";
|
|
5
5
|
import { findLikelyTraceChildSession, isSubagentToolName, } from "./trace-subagent-links.js";
|
|
6
6
|
import { qualifiedToolNodeId } from "./trace-tool-identity.js";
|
|
7
|
+
import { observeCacheUsage } from "./cache-observability.js";
|
|
8
|
+
import { compareInferenceCompletion } from "./model-inference-metrics.js";
|
|
7
9
|
export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openTranscriptEventIds, sessionStatus) {
|
|
8
10
|
const payload = storedEvent.payload;
|
|
9
11
|
if (payload.type === "assistant_usage") {
|
|
@@ -538,7 +540,11 @@ export function latestTraceStreamId(events, initial) {
|
|
|
538
540
|
}
|
|
539
541
|
function attachModelInferenceToLatestOutput(nodes, byId, event, storedEvent) {
|
|
540
542
|
const eventId = event.eventId;
|
|
541
|
-
const
|
|
543
|
+
const flattened = flattenTraceNodes(nodes);
|
|
544
|
+
const id = event.inferenceId
|
|
545
|
+
? `${eventId ?? storedEvent.piboSessionId}:inference:${event.inferenceId}`
|
|
546
|
+
: eventId ? `${eventId}:usage:${event.usageIndex ?? 0}` : storedEvent.id;
|
|
547
|
+
const candidates = flattened
|
|
542
548
|
.filter((node) => node.eventId === eventId && (event.inferenceTarget || traceNodeStartedBeforeInference(node, storedEvent)) && (node.type === "assistant.message"
|
|
543
549
|
|| node.type === "model.reasoning"
|
|
544
550
|
|| node.type === "tool.call"
|
|
@@ -546,21 +552,23 @@ function attachModelInferenceToLatestOutput(nodes, byId, event, storedEvent) {
|
|
|
546
552
|
.sort(compareTraceNodes);
|
|
547
553
|
const anchor = event.inferenceTarget;
|
|
548
554
|
const turnNode = eventId ? byId.get(messageTurnNodeId(eventId)) : undefined;
|
|
549
|
-
const
|
|
555
|
+
const anchoredTarget = anchor
|
|
550
556
|
? (anchor.type === "tool"
|
|
551
557
|
? candidates.find((node) => node.toolCallId === anchor.toolCallId)
|
|
552
558
|
: anchor.type === "assistant"
|
|
553
559
|
? candidates.find((node) => node.stableKey === `assistant:${eventId}:assistant:${anchor.assistantIndex}`)
|
|
554
560
|
: turnNode) ?? turnNode
|
|
555
561
|
: candidates.at(-1) ?? turnNode;
|
|
562
|
+
// A repeated provider receipt updates its original inference instead of moving
|
|
563
|
+
// the usage record to whichever output happened to render last.
|
|
564
|
+
const target = flattened.find((node) => node.modelInferences?.some((item) => item.id === id)) ?? anchoredTarget;
|
|
556
565
|
if (!target)
|
|
557
566
|
return;
|
|
558
|
-
const
|
|
559
|
-
? `${eventId ?? storedEvent.piboSessionId}:inference:${event.inferenceId}`
|
|
560
|
-
: eventId ? `${eventId}:usage:${event.usageIndex ?? 0}` : storedEvent.id;
|
|
567
|
+
const existingRecord = target.modelInferences?.find((item) => item.id === id);
|
|
561
568
|
const record = {
|
|
562
569
|
id,
|
|
563
|
-
completedAt: storedEvent.createdAt,
|
|
570
|
+
completedAt: existingRecord?.completedAt ?? storedEvent.createdAt,
|
|
571
|
+
completedSequence: existingRecord?.completedSequence ?? storedEvent.eventSequence ?? storedEvent.streamId,
|
|
564
572
|
metrics: {
|
|
565
573
|
...(event.inputTokens === undefined ? {} : { inputTokens: event.inputTokens }),
|
|
566
574
|
...(event.outputTokens === undefined ? {} : { outputTokens: event.outputTokens }),
|
|
@@ -571,7 +579,27 @@ function attachModelInferenceToLatestOutput(nodes, byId, event, storedEvent) {
|
|
|
571
579
|
...(event.costUsd === undefined ? {} : { costUsd: event.costUsd }),
|
|
572
580
|
},
|
|
573
581
|
};
|
|
574
|
-
|
|
582
|
+
const currentAt = Date.parse(record.completedAt ?? "");
|
|
583
|
+
let previous;
|
|
584
|
+
for (const node of flattened) {
|
|
585
|
+
for (const item of node.modelInferences ?? []) {
|
|
586
|
+
if (item.id !== id && compareInferenceCompletion(item, record) < 0
|
|
587
|
+
&& (!previous || compareInferenceCompletion(item, previous) > 0)) {
|
|
588
|
+
previous = item;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const compactionBetween = previous !== undefined && Number.isFinite(currentAt) && flattened.some((node) => {
|
|
593
|
+
if (node.type !== "execution.compaction")
|
|
594
|
+
return false;
|
|
595
|
+
const boundary = {
|
|
596
|
+
completedAt: node.startedAt ?? node.completedAt,
|
|
597
|
+
completedSequence: node.orderKey?.eventSequence ?? node.orderKey?.streamId,
|
|
598
|
+
};
|
|
599
|
+
return compareInferenceCompletion(boundary, previous) > 0 && compareInferenceCompletion(boundary, record) <= 0;
|
|
600
|
+
});
|
|
601
|
+
record.cacheObservation = observeCacheUsage(record, previous, { compactionBetween });
|
|
602
|
+
target.modelInferences = [...(target.modelInferences ?? []).filter((item) => item.id !== id), record].sort(compareInferenceCompletion);
|
|
575
603
|
}
|
|
576
604
|
function traceNodeStartedBeforeInference(node, storedEvent) {
|
|
577
605
|
const inferenceRenderSequence = storedEvent.renderSequence;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.6.1",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|