@chenlongapps/opencode-token-usage 0.4.1 → 0.4.3
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 +11 -7
- package/README.zh-CN.md +10 -6
- package/ROADMAP.md +13 -0
- package/dist/aliases.d.ts +6 -0
- package/dist/aliases.js +32 -0
- package/dist/context-capture.d.ts +43 -0
- package/dist/context-capture.js +111 -0
- package/dist/controller.d.ts +4 -0
- package/dist/controller.js +43 -15
- package/dist/index.js +29 -8
- package/dist/overrides.d.ts +22 -0
- package/dist/overrides.js +181 -0
- package/dist/performance.d.ts +9 -0
- package/dist/performance.js +61 -26
- package/dist/prices.generated.d.ts +3454 -0
- package/dist/prices.generated.js +294 -0
- package/dist/pricing.d.ts +14 -189
- package/dist/pricing.js +86 -580
- package/dist/source.d.ts +3 -2
- package/dist/source.js +31 -16
- package/dist/tui.js +209 -153
- package/dist/usage.d.ts +24 -2
- package/dist/usage.js +93 -16
- package/docs/pricing.md +24 -290
- package/docs/verification.md +36 -0
- package/package.json +2 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** models.dev has no manufacturer-of-model field. Only these first-party
|
|
2
|
+
* catalogs and model families can supply fallback prices. A manufacturer
|
|
3
|
+
* listing somebody else's model (e.g. Alibaba's Kimi) is not its origin. */
|
|
4
|
+
export const ORIGINAL_PROVIDERS = {
|
|
5
|
+
ai21: /^jamba-/i,
|
|
6
|
+
alibaba: /^(?:qwen|qwq|qvq)/i,
|
|
7
|
+
anthropic: /^claude-/i,
|
|
8
|
+
arcee: /^trinity-/i,
|
|
9
|
+
cohere: /^(?:command-|north-)/i,
|
|
10
|
+
deepseek: /^deepseek-/i,
|
|
11
|
+
google: /^(?:gemini-|gemma-|deep-research-)/i,
|
|
12
|
+
inception: /^mercury-/i,
|
|
13
|
+
longcat: /^longcat-/i,
|
|
14
|
+
meta: /^muse-/i,
|
|
15
|
+
minimax: /^minimax-/i,
|
|
16
|
+
mistral: /^(?:open-mistral-|open-mixtral-|mistral-|ministral-|magistral-|codestral-|devstral-|labs-devstral-|pixtral-|voxtral-)/i,
|
|
17
|
+
moonshotai: /^kimi-/i,
|
|
18
|
+
nvidia: /^nvidia\/(?:nemotron-|llama-[\w.-]*nemotron-)/i,
|
|
19
|
+
openai: /^(?:gpt-|o\d|chat-latest$)/i,
|
|
20
|
+
perplexity: /^sonar(?:-|$)/i,
|
|
21
|
+
sakana: /^(?:fugu|sakana-namazu)/i,
|
|
22
|
+
"stepfun-ai": /^step-/i,
|
|
23
|
+
// TokenHub's hy3 zero is a subscription-plan quote, not a token tariff;
|
|
24
|
+
// hy3 pay-as-you-go is a reviewed exception below.
|
|
25
|
+
"tencent-tokenhub": /^hy4-preview$/i,
|
|
26
|
+
upstage: /^solar-/i,
|
|
27
|
+
volcengine: /^doubao-seed-/i,
|
|
28
|
+
xai: /^grok-/i,
|
|
29
|
+
xiaomi: /^mimo-/i,
|
|
30
|
+
zai: /^glm-/i,
|
|
31
|
+
};
|
|
32
|
+
/** Rates absent from the first-party models.dev listing or contradicted by a
|
|
33
|
+
* specifically verified manufacturer tariff. Never use a reseller's quote. */
|
|
34
|
+
export const OVERRIDES_VERIFIED = "2026-09-23";
|
|
35
|
+
export const PRICE_OVERRIDES = [
|
|
36
|
+
{
|
|
37
|
+
providerID: "openai", id: "chat-latest", released: "2026-05-05",
|
|
38
|
+
source: "https://developers.openai.com/api/docs/pricing", reason: "Official ChatGPT API SKU missing from the first-party catalog",
|
|
39
|
+
prices: [{ input: 5, output: 30, cache: { read: 0.5 } }],
|
|
40
|
+
},
|
|
41
|
+
// The first-party API records Fast as a mode, but not its long-context
|
|
42
|
+
// schedule. Keep the published Fast schedule restricted to direct API IDs.
|
|
43
|
+
...[
|
|
44
|
+
["gpt-5.5", 12.5, 75, 1.25, undefined, undefined, undefined, undefined, undefined],
|
|
45
|
+
["gpt-5.6-sol", 8, 40, 0.8, 10, 16, 60, 1.6, 20],
|
|
46
|
+
["gpt-5.6-terra", 4, 24, 0.4, 5, 8, 36, 0.8, 10],
|
|
47
|
+
["gpt-5.6-luna", 0.4, 2.4, 0.04, 0.5, 0.8, 3.6, 0.08, 1],
|
|
48
|
+
["gpt-6-astra", 20, 100, 2, 25, 40, 150, 4, 50],
|
|
49
|
+
["gpt-6-sol", 4, 20, 0.4, 5, 8, 30, 0.8, 10],
|
|
50
|
+
["gpt-6-luna", 0.2, 1, 0.02, 0.25, 0.4, 1.5, 0.04, 0.5],
|
|
51
|
+
].map(([id, input, output, read, write, longInput, longOutput, longRead, longWrite]) => ({
|
|
52
|
+
providerID: "openai", id: `${id}-fast`, providers: ["openai"],
|
|
53
|
+
source: "https://developers.openai.com/api/docs/pricing",
|
|
54
|
+
reason: "Published first-party Fast rate and long-context boundary (not in mode.cost)",
|
|
55
|
+
prices: [
|
|
56
|
+
{ input, output, cache: { read, ...(write === undefined ? {} : { write }) } },
|
|
57
|
+
{ tier: { type: "context", size: 272_000 },
|
|
58
|
+
...(longInput === undefined ? {} : { input: longInput, output: longOutput, cache: { read: longRead, write: longWrite } }) },
|
|
59
|
+
],
|
|
60
|
+
})),
|
|
61
|
+
{
|
|
62
|
+
providerID: "deepseek", id: "deepseek-v4.1-flash", released: "2026-09-10",
|
|
63
|
+
source: "https://api-docs.deepseek.com/quick_start/pricing",
|
|
64
|
+
reason: "Peak synchronous price; the hourly billing tier is not recorded in historical messages",
|
|
65
|
+
prices: [{ input: 0.3, output: 1.2, cache: { read: 0.006 } }],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
providerID: "deepseek", id: "deepseek-v4-pro-0813", released: "2026-08-13",
|
|
69
|
+
source: "https://api-docs.deepseek.com/quick_start/pricing",
|
|
70
|
+
reason: "Peak synchronous price; the hourly billing tier is not recorded in historical messages",
|
|
71
|
+
prices: [{ input: 1.32, output: 3.96, cache: { read: 0.044 } }],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
providerID: "moonshotai", id: "kimi-k3",
|
|
75
|
+
source: "https://platform.moonshot.ai/docs/pricing",
|
|
76
|
+
reason: "Published five-minute cache-write rate omitted by models.dev",
|
|
77
|
+
prices: [{ input: 3, output: 15, cache: { read: 0.3, write: 3 } }],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
providerID: "stepfun-ai", id: "step-5-preview",
|
|
81
|
+
source: "https://platform.stepfun.ai/docs/en/guides/pricing/details",
|
|
82
|
+
reason: "Cache-miss input includes new cache writes; the API omits the write rate",
|
|
83
|
+
prices: [{ input: 1, output: 2.7, cache: { read: 0.05, write: 1 } }],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
providerID: "tencent-tokenhub", id: "hy3", released: "2026-07-06",
|
|
87
|
+
source: "https://intl.cloud.tencent.com/document/product/1300/78937",
|
|
88
|
+
reason: "Published pay-as-you-go tariff, rather than the catalog's subscription-plan zero",
|
|
89
|
+
prices: [{ input: 0.132, output: 0.528, cache: { read: 0.033 } }],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
providerID: "longcat", id: "longcat-2.0",
|
|
93
|
+
source: "https://longcat.chat/platform/docs/Pricing/LongCat-2.0.html",
|
|
94
|
+
reason: "Manufacturer's billed promotion, verified 2026-09-23; review on refresh",
|
|
95
|
+
prices: [{ input: 0.3, output: 1.2, cache: { read: 0.006 } }],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
providerID: "mistral", id: "mistral-medium-2604",
|
|
99
|
+
source: "https://docs.mistral.ai/inference/pricing",
|
|
100
|
+
reason: "Manufacturer cache-hit price missing from models.dev",
|
|
101
|
+
prices: [{ input: 1.5, output: 7.5, cache: { read: 0.15 } }],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
providerID: "alibaba", id: "qwen3.7-flash", released: "2026-07-15",
|
|
105
|
+
source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
|
|
106
|
+
reason: "International pay-as-you-go SKU absent from the international catalog (not the CN tariff)",
|
|
107
|
+
prices: [
|
|
108
|
+
{ input: 0.03, output: 0.13, cache: { read: 0.006 } },
|
|
109
|
+
{ tier: { type: "context", size: 32_000 }, input: 0.1, output: 0.4, cache: { read: 0.02 } },
|
|
110
|
+
{ tier: { type: "context", size: 256_000 }, input: 0.2, output: 0.8, cache: { read: 0.04 } },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
providerID: "alibaba", id: "qwen3.8-2.4t-a95b", released: "2026-08-12",
|
|
115
|
+
source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
|
|
116
|
+
reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
|
|
117
|
+
prices: [{ input: 2, output: 6 }],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
providerID: "alibaba", id: "qwen3.8-27b", released: "2026-08-14",
|
|
121
|
+
source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
|
|
122
|
+
reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
|
|
123
|
+
prices: [{ input: 0.5, output: 3, cache: { read: 0.1 } }],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
providerID: "alibaba", id: "qwen3.5-plus-2026-04-20", released: "2026-04-20",
|
|
127
|
+
source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
|
|
128
|
+
reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
|
|
129
|
+
prices: [
|
|
130
|
+
{ input: 0.4, output: 2.4 },
|
|
131
|
+
{ tier: { type: "context", size: 256_000 }, input: 0.5, output: 3 },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
providerID: "volcengine", id: "dola-seed-2-1-turbo", released: "2026-06-23",
|
|
136
|
+
source: "https://docs.byteplus.com/en/docs/ModelArk/1099320",
|
|
137
|
+
reason: "BytePlus manufacturer SKU uses a different ID from the regional Volcengine catalog",
|
|
138
|
+
prices: [{ input: 0.5, output: 2.5, cache: { read: 0.1 } }],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
providerID: "sakana", id: "fugu-ultra-v2.0", released: "2026-09-11",
|
|
142
|
+
source: "https://console.sakana.ai/pricing",
|
|
143
|
+
reason: "Manufacturer versioned SKU not present under this ID; higher tier starts at 272K",
|
|
144
|
+
prices: [
|
|
145
|
+
{ input: 5, output: 30, cache: { read: 0.5 } },
|
|
146
|
+
{ tier: { type: "context", size: 271_999 }, input: 10, output: 45, cache: { read: 1 } },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
providerID: "sakana", id: "fugu-max-v1.0", released: "2026-09-11",
|
|
151
|
+
source: "https://console.sakana.ai/pricing",
|
|
152
|
+
reason: "Manufacturer versioned SKU absent from models.dev",
|
|
153
|
+
prices: [{ input: 2, output: 6, cache: { read: 0.25 } }],
|
|
154
|
+
},
|
|
155
|
+
...[
|
|
156
|
+
["aion-3.0", "2026-05-05", 3, 6, 0.75],
|
|
157
|
+
["aion-3.0-mini", "2026-05-14", 0.7, 1.4, 0.18],
|
|
158
|
+
].map(([id, released, input, output, read]) => ({
|
|
159
|
+
providerID: "aion-labs", id, released,
|
|
160
|
+
source: "https://www.aionlabs.ai/docs/models",
|
|
161
|
+
reason: "First-party hosted API price; models.dev has no Aion manufacturer provider",
|
|
162
|
+
prices: [{ input, output, cache: { read } }],
|
|
163
|
+
})),
|
|
164
|
+
];
|
|
165
|
+
/** A catalog zero may mean an unmetered subscription, or that cache storage is
|
|
166
|
+
* billed by time rather than per write token. Do not invent a free write rate. */
|
|
167
|
+
export function adjustOriginalPrice(providerID, price) {
|
|
168
|
+
const ambiguousWrite = (providerID === "zai" && price.cache?.write === 0)
|
|
169
|
+
|| (providerID === "alibaba" && price.cache?.write !== undefined);
|
|
170
|
+
if (ambiguousWrite && price.cache) {
|
|
171
|
+
// Z.ai stores cache by time; Alibaba mixes implicit and explicit writes
|
|
172
|
+
// at distinct rates, which OpenCode's aggregate counter cannot separate.
|
|
173
|
+
const { write: _unrepresentable, ...cache } = price.cache;
|
|
174
|
+
return { ...price, cache };
|
|
175
|
+
}
|
|
176
|
+
if (providerID === "xai" && price.tier?.size === 200_000) {
|
|
177
|
+
// xAI's >=200K bracket begins at 200,000; OpenCode tiers are exclusive.
|
|
178
|
+
return { ...price, tier: { type: "context", size: 199_999 } };
|
|
179
|
+
}
|
|
180
|
+
return price;
|
|
181
|
+
}
|
package/dist/performance.d.ts
CHANGED
|
@@ -17,10 +17,18 @@ export interface PerformanceEvent {
|
|
|
17
17
|
id?: string;
|
|
18
18
|
created?: number;
|
|
19
19
|
}
|
|
20
|
+
export interface PreparedPerformance {
|
|
21
|
+
tps?: number;
|
|
22
|
+
ttftSum: number;
|
|
23
|
+
ttftCount: number;
|
|
24
|
+
historical: ReadonlyMap<string, number>;
|
|
25
|
+
}
|
|
20
26
|
export type PerformanceSubscribe = (listener: (event: PerformanceEvent) => void) => () => void;
|
|
21
27
|
export type PerformanceListener = (sessionID: string) => void;
|
|
22
28
|
/** Exact completed-run TPS and the average of only measurable TTFT samples. */
|
|
23
29
|
export declare function historicalPerformance(messages: Iterable<UsageMessage>, observations?: Iterable<FirstTokenObservation>): PerformanceSummary;
|
|
30
|
+
/** The immutable part of a snapshot is scanned once, then reused for stream updates. */
|
|
31
|
+
export declare function preparePerformance(messages: Iterable<UsageMessage>): PreparedPerformance;
|
|
24
32
|
/** Captures streaming performance for every session for one plugin lifetime. */
|
|
25
33
|
export declare class PerformanceMonitor {
|
|
26
34
|
private readonly retention;
|
|
@@ -42,4 +50,5 @@ export declare class PerformanceMonitor {
|
|
|
42
50
|
handle(event: PerformanceEvent): boolean;
|
|
43
51
|
reconcile(messages: Iterable<UsageMessage>, sessionIDs: Iterable<string>): void;
|
|
44
52
|
summary(messages: Iterable<UsageMessage>, sessionIDs: Iterable<string>): PerformanceSummary;
|
|
53
|
+
summaryPrepared(prepared: PreparedPerformance, sessionIDs: Iterable<string>): PerformanceSummary;
|
|
45
54
|
}
|
package/dist/performance.js
CHANGED
|
@@ -25,20 +25,18 @@ function historicalFirst(message) {
|
|
|
25
25
|
}
|
|
26
26
|
/** Exact completed-run TPS and the average of only measurable TTFT samples. */
|
|
27
27
|
export function historicalPerformance(messages, observations = []) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
28
|
+
return performanceWithObservations(preparePerformance(messages), observations);
|
|
29
|
+
}
|
|
30
|
+
/** The immutable part of a snapshot is scanned once, then reused for stream updates. */
|
|
31
|
+
export function preparePerformance(messages) {
|
|
33
32
|
let output = 0;
|
|
34
33
|
let duration = 0;
|
|
35
|
-
let
|
|
36
|
-
let
|
|
37
|
-
const
|
|
34
|
+
let ttftSum = 0;
|
|
35
|
+
let ttftCount = 0;
|
|
36
|
+
const historical = new Map();
|
|
38
37
|
for (const message of messages) {
|
|
39
38
|
if (message.type !== "assistant")
|
|
40
39
|
continue;
|
|
41
|
-
messagesSeen.add(message.id);
|
|
42
40
|
const created = message.time?.created;
|
|
43
41
|
const streamed = message.time?.streamed;
|
|
44
42
|
const tokens = normalize(message.tokens);
|
|
@@ -47,23 +45,35 @@ export function historicalPerformance(messages, observations = []) {
|
|
|
47
45
|
output += generated;
|
|
48
46
|
duration += streamed - created;
|
|
49
47
|
}
|
|
50
|
-
const
|
|
51
|
-
const sample = runtime ? runtime.first - runtime.started : historicalFirst(message);
|
|
48
|
+
const sample = historicalFirst(message);
|
|
52
49
|
if (sample !== undefined && finite(sample) && sample >= 0) {
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
historical.set(message.id, sample);
|
|
51
|
+
ttftSum += sample;
|
|
52
|
+
ttftCount++;
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
|
-
|
|
55
|
+
return { ...(duration > 0 ? { tps: output / (duration / 1_000) } : {}), ttftSum, ttftCount, historical };
|
|
56
|
+
}
|
|
57
|
+
function performanceWithObservations(prepared, observations) {
|
|
58
|
+
const observed = new Map();
|
|
59
|
+
for (const sample of observations) {
|
|
60
|
+
if (finite(sample.started) && finite(sample.first) && sample.first >= sample.started)
|
|
61
|
+
observed.set(sample.messageID, sample);
|
|
62
|
+
}
|
|
63
|
+
let ttft = prepared.ttftSum;
|
|
64
|
+
let ttftSamples = prepared.ttftCount;
|
|
58
65
|
for (const sample of observed.values()) {
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
const old = prepared.historical.get(sample.messageID);
|
|
67
|
+
if (old !== undefined) {
|
|
68
|
+
ttft -= old;
|
|
69
|
+
ttftSamples--;
|
|
70
|
+
}
|
|
61
71
|
ttft += sample.first - sample.started;
|
|
62
72
|
ttftSamples++;
|
|
63
73
|
}
|
|
64
74
|
const result = {};
|
|
65
|
-
if (
|
|
66
|
-
result.tps =
|
|
75
|
+
if (prepared.tps !== undefined)
|
|
76
|
+
result.tps = prepared.tps;
|
|
67
77
|
if (ttftSamples > 0)
|
|
68
78
|
result.ttft = ttft / ttftSamples;
|
|
69
79
|
return result;
|
|
@@ -117,7 +127,8 @@ export class PerformanceMonitor {
|
|
|
117
127
|
const now = this.now();
|
|
118
128
|
const changed = new Set();
|
|
119
129
|
for (const [key, step] of this.active) {
|
|
120
|
-
if (step.finishedAt
|
|
130
|
+
if ((step.finishedAt ?? step.reconciledAt) !== undefined
|
|
131
|
+
&& (step.finishedAt ?? step.reconciledAt) + this.retention <= now) {
|
|
121
132
|
this.active.delete(key);
|
|
122
133
|
changed.add(step.sessionID);
|
|
123
134
|
}
|
|
@@ -136,8 +147,9 @@ export class PerformanceMonitor {
|
|
|
136
147
|
return;
|
|
137
148
|
let next = Infinity;
|
|
138
149
|
for (const step of this.active.values()) {
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
const settled = step.finishedAt ?? step.reconciledAt;
|
|
151
|
+
if (settled !== undefined)
|
|
152
|
+
next = Math.min(next, settled + this.retention);
|
|
141
153
|
}
|
|
142
154
|
for (const sample of this.observed.values()) {
|
|
143
155
|
if (sample.expiresAt !== undefined)
|
|
@@ -195,6 +207,8 @@ export class PerformanceMonitor {
|
|
|
195
207
|
const sample = this.observed.get(key);
|
|
196
208
|
if (sample)
|
|
197
209
|
sample.expiresAt = step.finishedAt + this.retention;
|
|
210
|
+
if (step.reconciledAt !== undefined)
|
|
211
|
+
this.active.delete(key);
|
|
198
212
|
this.scheduleCleanup();
|
|
199
213
|
return false;
|
|
200
214
|
}
|
|
@@ -207,6 +221,14 @@ export class PerformanceMonitor {
|
|
|
207
221
|
return false;
|
|
208
222
|
if (event.id)
|
|
209
223
|
step.events.add(event.id);
|
|
224
|
+
if (step.reconciledAt !== undefined) {
|
|
225
|
+
// A later delta means the snapshot did not represent the final stream.
|
|
226
|
+
delete step.reconciledAt;
|
|
227
|
+
const sample = this.observed.get(key);
|
|
228
|
+
if (sample)
|
|
229
|
+
delete sample.expiresAt;
|
|
230
|
+
this.scheduleCleanup();
|
|
231
|
+
}
|
|
210
232
|
step.bytes += new TextEncoder().encode(data.delta).byteLength;
|
|
211
233
|
step.last = Math.max(step.last ?? event.created, event.created);
|
|
212
234
|
if (step.first === undefined) {
|
|
@@ -225,19 +247,32 @@ export class PerformanceMonitor {
|
|
|
225
247
|
represented.add(message.id);
|
|
226
248
|
}
|
|
227
249
|
for (const [key, step] of this.active) {
|
|
228
|
-
if (sessions.has(step.sessionID) && represented.has(step.messageID)
|
|
229
|
-
|
|
230
|
-
|
|
250
|
+
if (sessions.has(step.sessionID) && represented.has(step.messageID)) {
|
|
251
|
+
if (step.finishedAt !== undefined)
|
|
252
|
+
this.active.delete(key);
|
|
253
|
+
else {
|
|
254
|
+
// A snapshot can arrive before step.ended. Retain the step to attach
|
|
255
|
+
// the real expiry later, and set a fallback if that event is lost.
|
|
256
|
+
step.reconciledAt ??= this.now();
|
|
257
|
+
const sample = this.observed.get(key);
|
|
258
|
+
if (sample)
|
|
259
|
+
sample.expiresAt ??= step.reconciledAt + this.retention;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
231
262
|
}
|
|
263
|
+
this.scheduleCleanup();
|
|
232
264
|
}
|
|
233
265
|
summary(messages, sessionIDs) {
|
|
266
|
+
return this.summaryPrepared(preparePerformance(messages), sessionIDs);
|
|
267
|
+
}
|
|
268
|
+
summaryPrepared(prepared, sessionIDs) {
|
|
234
269
|
const sessions = new Set(sessionIDs);
|
|
235
270
|
const samples = [...this.observed.values()].filter(sample => sessions.has(sample.sessionID));
|
|
236
|
-
const result =
|
|
271
|
+
const result = performanceWithObservations(prepared, samples);
|
|
237
272
|
let bytes = 0;
|
|
238
273
|
let duration = 0;
|
|
239
274
|
for (const step of this.active.values()) {
|
|
240
|
-
if (!sessions.has(step.sessionID) || step.bytes <= 0)
|
|
275
|
+
if (!sessions.has(step.sessionID) || step.reconciledAt !== undefined || step.bytes <= 0)
|
|
241
276
|
continue;
|
|
242
277
|
const last = step.streamed ?? step.last;
|
|
243
278
|
if (!finite(last) || last <= step.started)
|