@cesarus777/opencode-cgp 0.1.0 → 0.2.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/README.md +6 -2
- package/dist/cli.js +1411 -0
- package/dist/core.js +424 -3
- package/dist/index.js +1087 -29
- package/dist/rpc.js +159 -0
- package/dist/tui.js +461 -49
- package/dist/types/adapter.d.ts +2 -0
- package/dist/types/chartreport.d.ts +14 -0
- package/dist/types/charts.d.ts +54 -0
- package/dist/types/core.d.ts +2 -0
- package/dist/types/family.d.ts +1 -1
- package/dist/types/history.d.ts +59 -0
- package/dist/types/historycharts.d.ts +17 -0
- package/dist/types/historyserver.d.ts +7 -0
- package/dist/types/performance.d.ts +57 -0
- package/dist/types/rpc.d.ts +78 -0
- package/dist/types/snapshot.d.ts +4 -1
- package/dist/types/snapshotserver.d.ts +3 -3
- package/dist/types/tui/chartformat.d.ts +11 -0
- package/dist/types/tui/charts.d.ts +10 -0
- package/dist/types/tui/format.d.ts +1 -0
- package/dist/types/tui/state.d.ts +1 -0
- package/dist/types/types.d.ts +11 -0
- package/package.json +9 -3
package/dist/core.js
CHANGED
|
@@ -46,7 +46,16 @@ function threshold(v, path) {
|
|
|
46
46
|
}
|
|
47
47
|
function parseConfig(value = {}) {
|
|
48
48
|
const o = object(value, "options");
|
|
49
|
-
keys(o, [
|
|
49
|
+
keys(o, [
|
|
50
|
+
"enabled",
|
|
51
|
+
"mode",
|
|
52
|
+
"defaults",
|
|
53
|
+
"policies",
|
|
54
|
+
"safety",
|
|
55
|
+
"telemetry",
|
|
56
|
+
"metrics",
|
|
57
|
+
"history"
|
|
58
|
+
], "options");
|
|
50
59
|
if (o.mode !== undefined && o.mode !== "observe" && o.mode !== "enforce")
|
|
51
60
|
throw new Error("mode: expected observe or enforce");
|
|
52
61
|
const d = object(o.defaults ?? {}, "defaults");
|
|
@@ -62,6 +71,12 @@ function parseConfig(value = {}) {
|
|
|
62
71
|
"sessionTtlMs",
|
|
63
72
|
"outputReserveTokens"
|
|
64
73
|
], "safety");
|
|
74
|
+
const metrics = object(o.metrics ?? {}, "metrics");
|
|
75
|
+
keys(metrics, ["enabled", "sampleIntervalMs", "rollingWindowMs"], "metrics");
|
|
76
|
+
const history = object(o.history ?? {}, "history");
|
|
77
|
+
keys(history, ["enabled", "retentionDays", "maxBytes", "path"], "history");
|
|
78
|
+
if (history.path !== undefined && (typeof history.path !== "string" || !history.path.startsWith("/")))
|
|
79
|
+
throw new Error("history.path: expected absolute directory path");
|
|
65
80
|
const t = object(o.telemetry ?? {}, "telemetry");
|
|
66
81
|
keys(t, ["enabled", "jsonl", "path", "maxBytes"], "telemetry");
|
|
67
82
|
if (t.path !== undefined && (typeof t.path !== "string" || !t.path.startsWith("/")))
|
|
@@ -109,6 +124,17 @@ function parseConfig(value = {}) {
|
|
|
109
124
|
outputReserveTokens: number(s.outputReserveTokens, 0, "outputReserveTokens")
|
|
110
125
|
}
|
|
111
126
|
},
|
|
127
|
+
metrics: {
|
|
128
|
+
enabled: bool(metrics.enabled, true, "metrics.enabled"),
|
|
129
|
+
sampleIntervalMs: number(metrics.sampleIntervalMs, 500, "metrics.sampleIntervalMs", 100),
|
|
130
|
+
rollingWindowMs: number(metrics.rollingWindowMs, 2000, "metrics.rollingWindowMs", 100)
|
|
131
|
+
},
|
|
132
|
+
history: {
|
|
133
|
+
enabled: bool(history.enabled, true, "history.enabled"),
|
|
134
|
+
retentionDays: number(history.retentionDays, 7, "history.retentionDays", 1),
|
|
135
|
+
maxBytes: number(history.maxBytes, 64 * 1024 * 1024, "history.maxBytes", 4096),
|
|
136
|
+
...typeof history.path === "string" ? { path: history.path } : {}
|
|
137
|
+
},
|
|
112
138
|
telemetry: {
|
|
113
139
|
enabled: bool(t.enabled, true, "telemetry.enabled"),
|
|
114
140
|
jsonl: bool(t.jsonl, true, "telemetry.jsonl"),
|
|
@@ -116,6 +142,8 @@ function parseConfig(value = {}) {
|
|
|
116
142
|
...typeof t.path === "string" ? { path: t.path } : {}
|
|
117
143
|
}
|
|
118
144
|
};
|
|
145
|
+
if (config.metrics.rollingWindowMs < config.metrics.sampleIntervalMs || config.metrics.rollingWindowMs > 60000)
|
|
146
|
+
throw new Error("metrics.rollingWindowMs must be >= sampleIntervalMs and <= 60000");
|
|
119
147
|
for (const policy of [config.defaults, ...config.policies]) {
|
|
120
148
|
if (policy.threshold.type === "tokens" && config.safety.minReclaimTokens >= policy.threshold.value)
|
|
121
149
|
throw new Error("minReclaimTokens must be smaller than each absolute threshold");
|
|
@@ -570,6 +598,7 @@ class SnapshotStore {
|
|
|
570
598
|
config;
|
|
571
599
|
sessions;
|
|
572
600
|
now;
|
|
601
|
+
performanceSnapshot;
|
|
573
602
|
epoch;
|
|
574
603
|
sequence = 0;
|
|
575
604
|
revision = 0;
|
|
@@ -579,10 +608,11 @@ class SnapshotStore {
|
|
|
579
608
|
listeners = new Set;
|
|
580
609
|
healthKey = "";
|
|
581
610
|
reasons = new Set;
|
|
582
|
-
constructor(config, sessions, now = Date.now, epoch = crypto.randomUUID()) {
|
|
611
|
+
constructor(config, sessions, now = Date.now, epoch = crypto.randomUUID(), performanceSnapshot) {
|
|
583
612
|
this.config = config;
|
|
584
613
|
this.sessions = sessions;
|
|
585
614
|
this.now = now;
|
|
615
|
+
this.performanceSnapshot = performanceSnapshot;
|
|
586
616
|
this.epoch = epoch;
|
|
587
617
|
}
|
|
588
618
|
health() {
|
|
@@ -643,7 +673,11 @@ class SnapshotStore {
|
|
|
643
673
|
const s = resident.get(id);
|
|
644
674
|
if (!s)
|
|
645
675
|
continue;
|
|
646
|
-
const
|
|
676
|
+
const performance = this.performanceSnapshot?.(id);
|
|
677
|
+
const next = freezeSnapshot({
|
|
678
|
+
...project(s, this.config, this.now()),
|
|
679
|
+
...performance ? { performance } : {}
|
|
680
|
+
});
|
|
647
681
|
const old = this.rows.get(id);
|
|
648
682
|
if (old && JSON.stringify({ ...old, revision: 0 }) === JSON.stringify(next))
|
|
649
683
|
continue;
|
|
@@ -693,6 +727,8 @@ class SnapshotStore {
|
|
|
693
727
|
}
|
|
694
728
|
}
|
|
695
729
|
function freezeSnapshot(row) {
|
|
730
|
+
if (row.performance)
|
|
731
|
+
Object.freeze(row.performance);
|
|
696
732
|
if (row.measurement)
|
|
697
733
|
Object.freeze(row.measurement);
|
|
698
734
|
if (row.limits)
|
|
@@ -807,19 +843,404 @@ function executionStatus(known, nativeRunning, evidence) {
|
|
|
807
843
|
return evidence.outcome === "succeeded" ? "completed" : evidence.outcome === "failed" ? "failed" : "unknown";
|
|
808
844
|
return known ? "idle" : "unknown";
|
|
809
845
|
}
|
|
846
|
+
// src/performance.ts
|
|
847
|
+
var valid = (n) => Number.isFinite(n) && n >= 0;
|
|
848
|
+
var rate = (tokens, ms) => valid(tokens) && ms > 0 ? tokens * 1000 / ms : undefined;
|
|
849
|
+
function outputTokens(tokens) {
|
|
850
|
+
const n = tokens.output + tokens.reasoning;
|
|
851
|
+
return Number.isSafeInteger(tokens.output) && tokens.output >= 0 && Number.isSafeInteger(tokens.reasoning) && tokens.reasoning >= 0 && Number.isSafeInteger(n) ? n : undefined;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
class PerformanceTracker {
|
|
855
|
+
maxSessions;
|
|
856
|
+
windowMs;
|
|
857
|
+
now;
|
|
858
|
+
rows = new Map;
|
|
859
|
+
constructor(maxSessions, windowMs = 2000, now = Date.now) {
|
|
860
|
+
this.maxSessions = maxSessions;
|
|
861
|
+
this.windowMs = windowMs;
|
|
862
|
+
this.now = now;
|
|
863
|
+
}
|
|
864
|
+
observe(id, created, cumulative) {
|
|
865
|
+
let row = this.rows.get(id);
|
|
866
|
+
if (!row) {
|
|
867
|
+
if (this.rows.size >= this.maxSessions)
|
|
868
|
+
return;
|
|
869
|
+
row = {
|
|
870
|
+
observedSince: this.now(),
|
|
871
|
+
created,
|
|
872
|
+
partial: cumulative !== 0,
|
|
873
|
+
output: 0,
|
|
874
|
+
duration: 0,
|
|
875
|
+
completed: 0,
|
|
876
|
+
sequence: -1
|
|
877
|
+
};
|
|
878
|
+
this.rows.set(id, row);
|
|
879
|
+
}
|
|
880
|
+
if (valid(created))
|
|
881
|
+
row.created = created;
|
|
882
|
+
if (cumulative !== undefined && valid(cumulative))
|
|
883
|
+
row.cumulative = cumulative;
|
|
884
|
+
}
|
|
885
|
+
accept(id, sequence) {
|
|
886
|
+
const row = this.rows.get(id);
|
|
887
|
+
if (!row || sequence <= row.sequence)
|
|
888
|
+
return false;
|
|
889
|
+
row.sequence = sequence;
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
start(id, requestId, agent, model, started) {
|
|
893
|
+
const row = this.rows.get(id);
|
|
894
|
+
if (!row || !valid(started))
|
|
895
|
+
return;
|
|
896
|
+
const old = row.request;
|
|
897
|
+
if (old?.id === requestId && old.started === started)
|
|
898
|
+
return;
|
|
899
|
+
if (old && !old.settled)
|
|
900
|
+
row.partial = true;
|
|
901
|
+
row.request = {
|
|
902
|
+
id: requestId,
|
|
903
|
+
agent,
|
|
904
|
+
model,
|
|
905
|
+
started,
|
|
906
|
+
chars: 0,
|
|
907
|
+
settled: false,
|
|
908
|
+
events: new Set,
|
|
909
|
+
buckets: new Map
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
delta(id, requestId, eventId, at, characters) {
|
|
913
|
+
const row = this.rows.get(id), r = row?.request;
|
|
914
|
+
if (!row || !r || r.id !== requestId || r.settled || r.ended !== undefined || !valid(at) || at < r.started || !valid(characters) || !characters || r.events.has(eventId))
|
|
915
|
+
return;
|
|
916
|
+
if (r.events.size >= 256)
|
|
917
|
+
r.events.delete(r.events.values().next().value ?? "");
|
|
918
|
+
r.events.add(eventId);
|
|
919
|
+
r.first = r.first === undefined ? at : Math.min(r.first, at);
|
|
920
|
+
r.chars += characters;
|
|
921
|
+
const bucket = Math.floor(at / 100) * 100;
|
|
922
|
+
r.buckets.set(bucket, (r.buckets.get(bucket) ?? 0) + characters);
|
|
923
|
+
this.prune(r, Math.max(at, this.now()));
|
|
924
|
+
while (r.buckets.size > Math.ceil(this.windowMs / 100) + 2) {
|
|
925
|
+
r.buckets.delete(Math.min(...r.buckets.keys()));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
streamed(id, requestId, at) {
|
|
929
|
+
const row = this.rows.get(id), r = row?.request;
|
|
930
|
+
if (!row || !r || r.id !== requestId || r.settled || r.ended !== undefined)
|
|
931
|
+
return;
|
|
932
|
+
if (!valid(at) || at <= r.started || r.first !== undefined && at < r.first) {
|
|
933
|
+
row.partial = true;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
r.ended = at;
|
|
937
|
+
}
|
|
938
|
+
settle(id, requestId, tokens) {
|
|
939
|
+
const row = this.rows.get(id), r = row?.request;
|
|
940
|
+
if (!row)
|
|
941
|
+
return;
|
|
942
|
+
if (!r || r.id !== requestId) {
|
|
943
|
+
row.partial = true;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (r.settled)
|
|
947
|
+
return;
|
|
948
|
+
r.settled = true;
|
|
949
|
+
if (tokens !== undefined && valid(tokens))
|
|
950
|
+
r.output = tokens;
|
|
951
|
+
if (tokens !== undefined && valid(tokens) && r.ended !== undefined) {
|
|
952
|
+
row.output += tokens;
|
|
953
|
+
row.duration += r.ended - r.started;
|
|
954
|
+
row.completed++;
|
|
955
|
+
} else
|
|
956
|
+
row.partial = true;
|
|
957
|
+
const snapshot = this.snapshot(id);
|
|
958
|
+
if (snapshot) {
|
|
959
|
+
const { latestRequestTps: _previousRate, ...current } = snapshot;
|
|
960
|
+
row.latest = {
|
|
961
|
+
...current,
|
|
962
|
+
tokenSource: tokens === undefined ? "unavailable" : "reported",
|
|
963
|
+
...tokens !== undefined && r.ended !== undefined ? { latestRequestTps: tokens * 1000 / (r.ended - r.started) } : {}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
r.events.clear();
|
|
967
|
+
r.buckets.clear();
|
|
968
|
+
}
|
|
969
|
+
gap(id) {
|
|
970
|
+
for (const [key, row] of this.rows) {
|
|
971
|
+
if (id !== undefined && key !== id)
|
|
972
|
+
continue;
|
|
973
|
+
row.partial = true;
|
|
974
|
+
delete row.request;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
prune(r, at) {
|
|
978
|
+
for (const time of r.buckets.keys())
|
|
979
|
+
if (time < at - this.windowMs)
|
|
980
|
+
r.buckets.delete(time);
|
|
981
|
+
}
|
|
982
|
+
snapshot(id) {
|
|
983
|
+
const row = this.rows.get(id);
|
|
984
|
+
if (!row)
|
|
985
|
+
return;
|
|
986
|
+
const at = this.now(), r = row.request;
|
|
987
|
+
const active = r && !r.settled && r.ended === undefined;
|
|
988
|
+
if (r)
|
|
989
|
+
this.prune(r, at);
|
|
990
|
+
const live = active ? rate([...r.buckets.values()].reduce((a, b) => a + b, 0) / 4, Math.min(this.windowMs, at - r.started)) : 0;
|
|
991
|
+
const averageModelTps = rate(row.output, row.duration);
|
|
992
|
+
const averageSessionTps = row.cumulative === undefined ? undefined : rate(row.cumulative, at - row.created);
|
|
993
|
+
return {
|
|
994
|
+
observedSince: row.observedSince,
|
|
995
|
+
at,
|
|
996
|
+
sessionCreatedAt: row.created,
|
|
997
|
+
...row.cumulative === undefined ? {} : { sessionOutputTokens: row.cumulative },
|
|
998
|
+
status: active ? r.first === undefined ? "waiting" : "streaming" : !r && row.partial ? "unavailable" : "idle",
|
|
999
|
+
partial: row.partial,
|
|
1000
|
+
...live === undefined || !r && row.partial ? {} : { liveTps: live },
|
|
1001
|
+
...averageModelTps === undefined ? {} : { averageModelTps },
|
|
1002
|
+
...averageSessionTps === undefined ? {} : { averageSessionTps },
|
|
1003
|
+
...r ? {
|
|
1004
|
+
requestId: r.id,
|
|
1005
|
+
requestAgent: r.agent,
|
|
1006
|
+
requestModel: r.model,
|
|
1007
|
+
requestStartedAt: r.started,
|
|
1008
|
+
...r.output === undefined ? {} : { requestOutputTokens: r.output },
|
|
1009
|
+
...r.first === undefined ? {} : { firstOutputAt: r.first, ttftMs: r.first - r.started },
|
|
1010
|
+
...r.ended === undefined ? {} : { streamEndedAt: r.ended }
|
|
1011
|
+
} : row.latest ? {
|
|
1012
|
+
...row.latest.ttftMs === undefined ? {} : { ttftMs: row.latest.ttftMs }
|
|
1013
|
+
} : {},
|
|
1014
|
+
...row.latest?.latestRequestTps === undefined ? {} : { latestRequestTps: row.latest.latestRequestTps },
|
|
1015
|
+
estimatedOutputTokens: r ? r.chars / 4 : 0,
|
|
1016
|
+
modelOutputTokens: row.output,
|
|
1017
|
+
modelDurationMs: row.duration,
|
|
1018
|
+
completedRequests: row.completed,
|
|
1019
|
+
tokenSource: r && !r.settled ? "estimated" : row.latest?.tokenSource ?? "unavailable"
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
checkpoint() {
|
|
1023
|
+
return [...this.rows].map(([sessionId, r]) => ({
|
|
1024
|
+
sessionId,
|
|
1025
|
+
observedSince: r.observedSince,
|
|
1026
|
+
created: r.created,
|
|
1027
|
+
output: r.output,
|
|
1028
|
+
duration: r.duration,
|
|
1029
|
+
completed: r.completed,
|
|
1030
|
+
sequence: r.sequence
|
|
1031
|
+
}));
|
|
1032
|
+
}
|
|
1033
|
+
restore(value) {
|
|
1034
|
+
if (!Array.isArray(value))
|
|
1035
|
+
return;
|
|
1036
|
+
for (const item of value.slice(0, this.maxSessions)) {
|
|
1037
|
+
if (!item || typeof item !== "object")
|
|
1038
|
+
continue;
|
|
1039
|
+
const r = item;
|
|
1040
|
+
if (typeof r.sessionId !== "string" || !r.sessionId.length || r.sessionId.length > 512 || ![r.observedSince, r.created, r.output, r.duration, r.completed].every((n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0) || typeof r.sequence !== "number" || !Number.isSafeInteger(r.sequence) || r.sequence < -1)
|
|
1041
|
+
continue;
|
|
1042
|
+
const { observedSince, created, output, duration, completed, sequence } = r;
|
|
1043
|
+
if (typeof observedSince !== "number" || typeof created !== "number" || typeof output !== "number" || typeof duration !== "number" || typeof completed !== "number")
|
|
1044
|
+
continue;
|
|
1045
|
+
this.rows.set(r.sessionId, {
|
|
1046
|
+
observedSince,
|
|
1047
|
+
created,
|
|
1048
|
+
output,
|
|
1049
|
+
duration,
|
|
1050
|
+
completed,
|
|
1051
|
+
sequence,
|
|
1052
|
+
partial: true
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
remove(id) {
|
|
1057
|
+
this.rows.delete(id);
|
|
1058
|
+
}
|
|
1059
|
+
ids() {
|
|
1060
|
+
return this.rows.keys();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
// src/charts.ts
|
|
1064
|
+
var rateFields = [
|
|
1065
|
+
"liveTps",
|
|
1066
|
+
"averageModelTps",
|
|
1067
|
+
"averageSessionTps",
|
|
1068
|
+
"sessionOutputTokens"
|
|
1069
|
+
];
|
|
1070
|
+
function downsamplePoints(points, limit) {
|
|
1071
|
+
if (!Number.isSafeInteger(limit) || limit < 16 || limit > 5000)
|
|
1072
|
+
throw new Error("maxPoints must be an integer from 16 to 5000");
|
|
1073
|
+
if (points.length <= limit)
|
|
1074
|
+
return [...points];
|
|
1075
|
+
const buckets = Math.max(1, Math.floor(limit / (2 + rateFields.length * 2)));
|
|
1076
|
+
const selected = new Set;
|
|
1077
|
+
for (let b = 0;b < buckets; b++) {
|
|
1078
|
+
const start = Math.floor(b * points.length / buckets), end = Math.floor((b + 1) * points.length / buckets);
|
|
1079
|
+
selected.add(start);
|
|
1080
|
+
selected.add(end - 1);
|
|
1081
|
+
for (const field of rateFields) {
|
|
1082
|
+
let low, high;
|
|
1083
|
+
for (let i = start;i < end; i++) {
|
|
1084
|
+
const value = points[i]?.[field];
|
|
1085
|
+
if (value === undefined)
|
|
1086
|
+
continue;
|
|
1087
|
+
if (low === undefined || value < (points[low]?.[field] ?? Infinity))
|
|
1088
|
+
low = i;
|
|
1089
|
+
if (high === undefined || value > (points[high]?.[field] ?? -Infinity))
|
|
1090
|
+
high = i;
|
|
1091
|
+
}
|
|
1092
|
+
if (low !== undefined)
|
|
1093
|
+
selected.add(low);
|
|
1094
|
+
if (high !== undefined)
|
|
1095
|
+
selected.add(high);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
let previous = -1;
|
|
1099
|
+
return [...selected].sort((a, b) => a - b).flatMap((index) => {
|
|
1100
|
+
const point = points[index];
|
|
1101
|
+
if (!point)
|
|
1102
|
+
return [];
|
|
1103
|
+
const skippedGap = points.slice(previous + 1, index + 1).some((p) => p.breakBefore);
|
|
1104
|
+
previous = index;
|
|
1105
|
+
return [{ ...point, breakBefore: point.breakBefore || skippedGap }];
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
function buildChartData(records, options = {}) {
|
|
1109
|
+
const maxPoints = options.maxPoints ?? 1000, maxSessions = options.maxSessions ?? 128;
|
|
1110
|
+
if (!Number.isSafeInteger(maxSessions) || maxSessions < 1 || maxSessions > 1024)
|
|
1111
|
+
throw new Error("maxSessions must be from 1 to 1024");
|
|
1112
|
+
downsamplePoints([], maxPoints);
|
|
1113
|
+
const sorted = [...records].sort((a, b) => a.time - b.time || a.recordId.localeCompare(b.recordId));
|
|
1114
|
+
const lastSeen = new Map;
|
|
1115
|
+
for (const r of sorted)
|
|
1116
|
+
if (r.performance && (!options.sessionId || r.sessionId === options.sessionId))
|
|
1117
|
+
lastSeen.set(r.sessionId, r.time);
|
|
1118
|
+
const sessions = [...lastSeen].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
1119
|
+
const selected = new Set(sessions.slice(0, maxSessions).map(([id]) => id));
|
|
1120
|
+
const series = new Map, gaps = [];
|
|
1121
|
+
const seen = new Set, requestSeen = new Set;
|
|
1122
|
+
const pendingGaps = new Set;
|
|
1123
|
+
const observedSince = new Map;
|
|
1124
|
+
for (const r of sorted) {
|
|
1125
|
+
if (seen.has(r.recordId))
|
|
1126
|
+
continue;
|
|
1127
|
+
seen.add(r.recordId);
|
|
1128
|
+
if (r.type === "gap") {
|
|
1129
|
+
if (r.sessionId === "__history__" || selected.has(r.sessionId)) {
|
|
1130
|
+
gaps.push({
|
|
1131
|
+
time: r.time,
|
|
1132
|
+
sessionId: r.sessionId,
|
|
1133
|
+
reason: r.event ?? "observation-gap"
|
|
1134
|
+
});
|
|
1135
|
+
if (r.sessionId === "__history__")
|
|
1136
|
+
for (const id of selected)
|
|
1137
|
+
pendingGaps.add(id);
|
|
1138
|
+
else
|
|
1139
|
+
pendingGaps.add(r.sessionId);
|
|
1140
|
+
}
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const p = r.performance;
|
|
1144
|
+
if (!p || !selected.has(r.sessionId))
|
|
1145
|
+
continue;
|
|
1146
|
+
let s = series.get(r.sessionId);
|
|
1147
|
+
if (!s) {
|
|
1148
|
+
s = {
|
|
1149
|
+
sessionId: r.sessionId,
|
|
1150
|
+
...r.parentId ? { parentId: r.parentId } : {},
|
|
1151
|
+
agents: [],
|
|
1152
|
+
models: [],
|
|
1153
|
+
points: [],
|
|
1154
|
+
requests: [],
|
|
1155
|
+
originalPoints: 0
|
|
1156
|
+
};
|
|
1157
|
+
series.set(r.sessionId, s);
|
|
1158
|
+
}
|
|
1159
|
+
const agent = p.requestAgent ?? r.agent, model = p.requestModel ?? r.model;
|
|
1160
|
+
if (agent && !s.agents.includes(agent))
|
|
1161
|
+
s.agents.push(agent);
|
|
1162
|
+
if (model && !s.models.includes(model))
|
|
1163
|
+
s.models.push(model);
|
|
1164
|
+
const previous = s.points.at(-1);
|
|
1165
|
+
const restart = observedSince.has(r.sessionId) && observedSince.get(r.sessionId) !== p.observedSince;
|
|
1166
|
+
const reset = previous?.sessionOutputTokens !== undefined && p.sessionOutputTokens !== undefined && p.sessionOutputTokens < previous.sessionOutputTokens;
|
|
1167
|
+
const discontinuity = pendingGaps.has(r.sessionId) || restart || reset || !previous || previous.partial !== p.partial || previous.liveTps === undefined || p.liveTps === undefined || r.time - previous.time > 2500 || previous.agent !== agent || previous.model !== model;
|
|
1168
|
+
const point = {
|
|
1169
|
+
time: r.time,
|
|
1170
|
+
...agent ? { agent } : {},
|
|
1171
|
+
...model ? { model } : {},
|
|
1172
|
+
breakBefore: discontinuity,
|
|
1173
|
+
partial: p.partial,
|
|
1174
|
+
...p.liveTps === undefined ? {} : { liveTps: p.liveTps },
|
|
1175
|
+
...p.averageModelTps === undefined ? {} : { averageModelTps: p.averageModelTps },
|
|
1176
|
+
...p.averageSessionTps === undefined ? {} : { averageSessionTps: p.averageSessionTps },
|
|
1177
|
+
...p.sessionOutputTokens === undefined ? {} : { sessionOutputTokens: p.sessionOutputTokens }
|
|
1178
|
+
};
|
|
1179
|
+
if (previous?.time === point.time)
|
|
1180
|
+
s.points[s.points.length - 1] = {
|
|
1181
|
+
...point,
|
|
1182
|
+
breakBefore: point.breakBefore || previous.breakBefore
|
|
1183
|
+
};
|
|
1184
|
+
else
|
|
1185
|
+
s.points.push(point);
|
|
1186
|
+
observedSince.set(r.sessionId, p.observedSince);
|
|
1187
|
+
pendingGaps.delete(r.sessionId);
|
|
1188
|
+
if (r.type === "request" && p.requestId !== undefined && p.requestStartedAt !== undefined) {
|
|
1189
|
+
const key = JSON.stringify([
|
|
1190
|
+
r.sessionId,
|
|
1191
|
+
p.requestId,
|
|
1192
|
+
p.requestStartedAt
|
|
1193
|
+
]);
|
|
1194
|
+
if (requestSeen.has(key))
|
|
1195
|
+
continue;
|
|
1196
|
+
requestSeen.add(key);
|
|
1197
|
+
const duration = p.streamEndedAt === undefined ? undefined : p.streamEndedAt - p.requestStartedAt;
|
|
1198
|
+
const validDuration = duration !== undefined && duration > 0;
|
|
1199
|
+
const output = p.tokenSource === "reported" ? p.requestOutputTokens : undefined;
|
|
1200
|
+
s.requests.push({
|
|
1201
|
+
time: r.time,
|
|
1202
|
+
requestId: p.requestId,
|
|
1203
|
+
started: p.requestStartedAt,
|
|
1204
|
+
...agent ? { agent } : {},
|
|
1205
|
+
...model ? { model } : {},
|
|
1206
|
+
partial: p.partial,
|
|
1207
|
+
...p.ttftMs === undefined ? {} : { ttftMs: p.ttftMs },
|
|
1208
|
+
...output === undefined ? {} : { outputTokens: output },
|
|
1209
|
+
...validDuration ? { durationMs: duration } : {},
|
|
1210
|
+
...output !== undefined && validDuration ? { tps: output * 1000 / duration } : {}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return {
|
|
1215
|
+
schema: 1,
|
|
1216
|
+
series: [...series.values()].map((s) => ({
|
|
1217
|
+
...s,
|
|
1218
|
+
originalPoints: s.points.length,
|
|
1219
|
+
points: downsamplePoints(s.points, maxPoints)
|
|
1220
|
+
})),
|
|
1221
|
+
gaps: gaps.slice(-1024),
|
|
1222
|
+
omittedGaps: Math.max(0, gaps.length - 1024),
|
|
1223
|
+
omittedSessions: Math.max(0, sessions.length - selected.size),
|
|
1224
|
+
maxPoints
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
810
1227
|
export {
|
|
811
1228
|
usableInput,
|
|
812
1229
|
promptTokens,
|
|
813
1230
|
project,
|
|
814
1231
|
parseConfig,
|
|
1232
|
+
outputTokens,
|
|
815
1233
|
metric,
|
|
816
1234
|
glob,
|
|
817
1235
|
familyRows,
|
|
818
1236
|
executionStatus,
|
|
819
1237
|
evaluateThreshold,
|
|
820
1238
|
estimate,
|
|
1239
|
+
downsamplePoints,
|
|
1240
|
+
buildChartData,
|
|
821
1241
|
SnapshotStore,
|
|
822
1242
|
PolicyResolver,
|
|
1243
|
+
PerformanceTracker,
|
|
823
1244
|
NoopBudgetController,
|
|
824
1245
|
ContextMonitor,
|
|
825
1246
|
CompactionScheduler
|