negotium 0.2.15 → 0.2.16
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-helpers.js +178 -26
- package/dist/agent-helpers.js.map +8 -8
- package/dist/{chunk-5g5a63vv.js → chunk-5cpdbbeh.js} +56 -6
- package/dist/{chunk-5g5a63vv.js.map → chunk-5cpdbbeh.js.map} +4 -4
- package/dist/hosted-agent.js +118 -9
- package/dist/hosted-agent.js.map +6 -6
- package/dist/main.js +389 -50
- package/dist/main.js.map +15 -15
- package/dist/mcp-factories.js +247 -36
- package/dist/mcp-factories.js.map +9 -9
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/codex-provider.ts +53 -2
- package/dist/runtime/src/agents/rollout/codex.ts +62 -0
- package/dist/runtime/src/mcp/factories/token-stats.ts +33 -11
- package/dist/runtime/src/mcp/runtime-spec.ts +58 -1
- package/dist/runtime/src/node-host.ts +1 -0
- package/dist/runtime/src/runtime/turn-event-stream.ts +17 -2
- package/dist/runtime/src/storage/token-stats.ts +200 -19
- package/dist/runtime/src/version.ts +1 -1
- package/dist/storage.js +112 -11
- package/dist/storage.js.map +3 -3
- package/dist/types/packages/core/src/agents/codex-provider.d.ts +13 -0
- package/dist/types/packages/core/src/agents/rollout/codex.d.ts +9 -0
- package/dist/types/packages/core/src/mcp/factories/token-stats.d.ts +4 -2
- package/dist/types/packages/core/src/storage/token-stats.d.ts +35 -6
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -4,14 +4,24 @@ import { join } from "node:path";
|
|
|
4
4
|
import { appendJsonlEntry, readJsonlLines } from "#platform/jsonl";
|
|
5
5
|
import { logger } from "#platform/logger";
|
|
6
6
|
import { resolveStorageLogDir } from "#storage/storage-host";
|
|
7
|
+
import type { AgentKind, TokenUsage } from "#types";
|
|
7
8
|
|
|
8
9
|
export interface QueryRecord {
|
|
10
|
+
schemaVersion: 2;
|
|
9
11
|
timestamp: string; // ISO 8601 UTC
|
|
10
|
-
session: string; // topic name
|
|
12
|
+
session: string; // topic title (legacy output name)
|
|
13
|
+
topicId: string;
|
|
14
|
+
providerSessionId?: string;
|
|
15
|
+
agent: AgentKind;
|
|
16
|
+
model: string;
|
|
17
|
+
/** Cache-miss input only. */
|
|
11
18
|
inputTokens: number;
|
|
12
19
|
outputTokens: number;
|
|
13
20
|
cacheCreationInputTokens: number;
|
|
14
21
|
cacheReadInputTokens: number;
|
|
22
|
+
contextTokens?: number;
|
|
23
|
+
contextWindow?: number;
|
|
24
|
+
estimatedCostUsd: number;
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
export interface Bucket {
|
|
@@ -20,6 +30,23 @@ export interface Bucket {
|
|
|
20
30
|
cacheCreationInputTokens: number;
|
|
21
31
|
cacheReadInputTokens: number;
|
|
22
32
|
queries: number;
|
|
33
|
+
estimatedCostUsd: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CurrentSessionUsage {
|
|
37
|
+
timestamp: string;
|
|
38
|
+
topicId: string;
|
|
39
|
+
topicTitle: string;
|
|
40
|
+
providerSessionId?: string;
|
|
41
|
+
agent: AgentKind;
|
|
42
|
+
model: string;
|
|
43
|
+
contextTokens: number;
|
|
44
|
+
contextWindow: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TopicUsageSummary extends Bucket {
|
|
48
|
+
topicId: string;
|
|
49
|
+
currentSession?: CurrentSessionUsage;
|
|
23
50
|
}
|
|
24
51
|
|
|
25
52
|
function emptyBucket(): Bucket {
|
|
@@ -29,6 +56,7 @@ function emptyBucket(): Bucket {
|
|
|
29
56
|
cacheCreationInputTokens: 0,
|
|
30
57
|
cacheReadInputTokens: 0,
|
|
31
58
|
queries: 0,
|
|
59
|
+
estimatedCostUsd: 0,
|
|
32
60
|
};
|
|
33
61
|
}
|
|
34
62
|
|
|
@@ -48,11 +76,11 @@ function queriesPath(userId: number | string): string {
|
|
|
48
76
|
return join(logDir, `token-queries-${fileId}.jsonl`);
|
|
49
77
|
}
|
|
50
78
|
|
|
51
|
-
function loadRecords(userId: number | string):
|
|
79
|
+
function loadRecords(userId: number | string): unknown[] {
|
|
52
80
|
try {
|
|
53
81
|
return readJsonlLines(queriesPath(userId)).flatMap((line) => {
|
|
54
82
|
try {
|
|
55
|
-
return [JSON.parse(line) as
|
|
83
|
+
return [JSON.parse(line) as unknown];
|
|
56
84
|
} catch {
|
|
57
85
|
return [];
|
|
58
86
|
}
|
|
@@ -65,14 +93,72 @@ function loadRecords(userId: number | string): QueryRecord[] {
|
|
|
65
93
|
export function calcCost(
|
|
66
94
|
b: Pick<
|
|
67
95
|
Bucket,
|
|
96
|
+
| "inputTokens"
|
|
97
|
+
| "outputTokens"
|
|
98
|
+
| "cacheCreationInputTokens"
|
|
99
|
+
| "cacheReadInputTokens"
|
|
100
|
+
| "estimatedCostUsd"
|
|
101
|
+
>,
|
|
102
|
+
): number {
|
|
103
|
+
return b.estimatedCostUsd;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type TokenPrices = {
|
|
107
|
+
input: number;
|
|
108
|
+
output: number;
|
|
109
|
+
cacheWrite?: number;
|
|
110
|
+
cacheRead: number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const TOKEN_PRICES: Record<string, TokenPrices> = {
|
|
114
|
+
"codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
|
|
115
|
+
"codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
|
|
116
|
+
"codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
|
|
117
|
+
"claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
|
|
118
|
+
"claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
|
|
119
|
+
"claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
|
|
120
|
+
"maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
|
|
121
|
+
"maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
|
|
122
|
+
"maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
|
|
123
|
+
"maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 },
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function estimateUsageCost(
|
|
127
|
+
agent: AgentKind,
|
|
128
|
+
model: string,
|
|
129
|
+
usage: Pick<
|
|
130
|
+
QueryRecord,
|
|
68
131
|
"inputTokens" | "outputTokens" | "cacheCreationInputTokens" | "cacheReadInputTokens"
|
|
69
132
|
>,
|
|
70
133
|
): number {
|
|
134
|
+
const prices = TOKEN_PRICES[`${agent}:${model}`];
|
|
135
|
+
if (!prices) return 0;
|
|
71
136
|
return (
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
137
|
+
(usage.inputTokens * prices.input +
|
|
138
|
+
usage.outputTokens * prices.output +
|
|
139
|
+
(agent === "claude"
|
|
140
|
+
? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input)
|
|
141
|
+
: 0) +
|
|
142
|
+
usage.cacheReadInputTokens * prices.cacheRead) /
|
|
143
|
+
1_000_000
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isQueryRecord(value: unknown): value is QueryRecord {
|
|
148
|
+
if (!value || typeof value !== "object") return false;
|
|
149
|
+
const record = value as Partial<QueryRecord>;
|
|
150
|
+
return (
|
|
151
|
+
record.schemaVersion === 2 &&
|
|
152
|
+
typeof record.timestamp === "string" &&
|
|
153
|
+
typeof record.session === "string" &&
|
|
154
|
+
typeof record.topicId === "string" &&
|
|
155
|
+
typeof record.agent === "string" &&
|
|
156
|
+
typeof record.model === "string" &&
|
|
157
|
+
typeof record.inputTokens === "number" &&
|
|
158
|
+
typeof record.outputTokens === "number" &&
|
|
159
|
+
typeof record.cacheCreationInputTokens === "number" &&
|
|
160
|
+
typeof record.cacheReadInputTokens === "number" &&
|
|
161
|
+
typeof record.estimatedCostUsd === "number"
|
|
76
162
|
);
|
|
77
163
|
}
|
|
78
164
|
|
|
@@ -80,20 +166,39 @@ export function calcCost(
|
|
|
80
166
|
export function recordUsage(
|
|
81
167
|
userId: number | string,
|
|
82
168
|
session: string,
|
|
83
|
-
usage:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
169
|
+
usage: TokenUsage,
|
|
170
|
+
context: {
|
|
171
|
+
topicId: string;
|
|
172
|
+
providerSessionId?: string;
|
|
173
|
+
agent: AgentKind;
|
|
174
|
+
model: string;
|
|
88
175
|
},
|
|
89
176
|
) {
|
|
177
|
+
const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
|
|
178
|
+
// Claude reports cache buckets separately; Codex and Maestro include cache
|
|
179
|
+
// hits in their provider input total.
|
|
180
|
+
const inputTokens =
|
|
181
|
+
context.agent === "claude"
|
|
182
|
+
? usage.inputTokens
|
|
183
|
+
: Math.max(0, usage.inputTokens - cacheReadInputTokens);
|
|
184
|
+
const normalized = {
|
|
185
|
+
inputTokens,
|
|
186
|
+
outputTokens: usage.outputTokens,
|
|
187
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
|
|
188
|
+
cacheReadInputTokens,
|
|
189
|
+
};
|
|
90
190
|
const record: QueryRecord = {
|
|
191
|
+
schemaVersion: 2,
|
|
91
192
|
timestamp: new Date().toISOString(),
|
|
92
193
|
session,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
194
|
+
topicId: context.topicId,
|
|
195
|
+
...(context.providerSessionId ? { providerSessionId: context.providerSessionId } : {}),
|
|
196
|
+
agent: context.agent,
|
|
197
|
+
model: context.model,
|
|
198
|
+
...normalized,
|
|
199
|
+
...(usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {}),
|
|
200
|
+
...(usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {}),
|
|
201
|
+
estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized),
|
|
97
202
|
};
|
|
98
203
|
try {
|
|
99
204
|
appendJsonlEntry(queriesPath(userId), record);
|
|
@@ -110,6 +215,8 @@ export function getStats(
|
|
|
110
215
|
total: Bucket;
|
|
111
216
|
byHour: Record<string, Bucket>;
|
|
112
217
|
bySession: Record<string, Bucket>;
|
|
218
|
+
currentSessions: CurrentSessionUsage[];
|
|
219
|
+
ignoredLegacyRecords: number;
|
|
113
220
|
estimatedCostUsd: number;
|
|
114
221
|
} {
|
|
115
222
|
const records = loadRecords(userId);
|
|
@@ -119,14 +226,28 @@ export function getStats(
|
|
|
119
226
|
|
|
120
227
|
if ((from && Number.isNaN(fromTs)) || (to && Number.isNaN(toTs))) {
|
|
121
228
|
logger.warn({ from, to }, "token-stats: Invalid date range, returning empty");
|
|
122
|
-
return {
|
|
229
|
+
return {
|
|
230
|
+
total: emptyBucket(),
|
|
231
|
+
byHour: {},
|
|
232
|
+
bySession: {},
|
|
233
|
+
currentSessions: [],
|
|
234
|
+
ignoredLegacyRecords: 0,
|
|
235
|
+
estimatedCostUsd: 0,
|
|
236
|
+
};
|
|
123
237
|
}
|
|
124
238
|
|
|
125
239
|
const total = emptyBucket();
|
|
126
240
|
const byHour: Record<string, Bucket> = {};
|
|
127
241
|
const bySession: Record<string, Bucket> = {};
|
|
242
|
+
const currentSessions = new Map<string, CurrentSessionUsage>();
|
|
243
|
+
let ignoredLegacyRecords = 0;
|
|
128
244
|
|
|
129
|
-
for (const
|
|
245
|
+
for (const raw of records) {
|
|
246
|
+
if (!isQueryRecord(raw)) {
|
|
247
|
+
ignoredLegacyRecords += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const r = raw;
|
|
130
251
|
const ts = new Date(r.timestamp).getTime();
|
|
131
252
|
if (ts < fromTs || ts > toTs) continue;
|
|
132
253
|
|
|
@@ -141,8 +262,68 @@ export function getStats(
|
|
|
141
262
|
bucket.cacheCreationInputTokens += r.cacheCreationInputTokens;
|
|
142
263
|
bucket.cacheReadInputTokens += r.cacheReadInputTokens;
|
|
143
264
|
bucket.queries += 1;
|
|
265
|
+
bucket.estimatedCostUsd += r.estimatedCostUsd;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (r.contextTokens !== undefined && r.contextWindow !== undefined && r.contextWindow > 0) {
|
|
269
|
+
currentSessions.set(r.topicId, {
|
|
270
|
+
timestamp: r.timestamp,
|
|
271
|
+
topicId: r.topicId,
|
|
272
|
+
topicTitle: r.session,
|
|
273
|
+
...(r.providerSessionId ? { providerSessionId: r.providerSessionId } : {}),
|
|
274
|
+
agent: r.agent,
|
|
275
|
+
model: r.model,
|
|
276
|
+
contextTokens: r.contextTokens,
|
|
277
|
+
contextWindow: r.contextWindow,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
total,
|
|
284
|
+
byHour,
|
|
285
|
+
bySession,
|
|
286
|
+
currentSessions: [...currentSessions.values()].sort((a, b) =>
|
|
287
|
+
b.timestamp.localeCompare(a.timestamp),
|
|
288
|
+
),
|
|
289
|
+
ignoredLegacyRecords,
|
|
290
|
+
estimatedCostUsd: calcCost(total),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Exact all-time usage for one topic, independent of transcript pagination. */
|
|
295
|
+
export function getTopicStats(userId: number | string, topicId: string): TopicUsageSummary {
|
|
296
|
+
const total = emptyBucket();
|
|
297
|
+
let currentSession: CurrentSessionUsage | undefined;
|
|
298
|
+
|
|
299
|
+
for (const raw of loadRecords(userId)) {
|
|
300
|
+
if (!isQueryRecord(raw) || raw.topicId !== topicId) continue;
|
|
301
|
+
|
|
302
|
+
total.inputTokens += raw.inputTokens;
|
|
303
|
+
total.outputTokens += raw.outputTokens;
|
|
304
|
+
total.cacheCreationInputTokens += raw.cacheCreationInputTokens;
|
|
305
|
+
total.cacheReadInputTokens += raw.cacheReadInputTokens;
|
|
306
|
+
total.queries += 1;
|
|
307
|
+
total.estimatedCostUsd += raw.estimatedCostUsd;
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
raw.contextTokens !== undefined &&
|
|
311
|
+
raw.contextWindow !== undefined &&
|
|
312
|
+
raw.contextWindow > 0 &&
|
|
313
|
+
(!currentSession || raw.timestamp > currentSession.timestamp)
|
|
314
|
+
) {
|
|
315
|
+
currentSession = {
|
|
316
|
+
timestamp: raw.timestamp,
|
|
317
|
+
topicId: raw.topicId,
|
|
318
|
+
topicTitle: raw.session,
|
|
319
|
+
...(raw.providerSessionId ? { providerSessionId: raw.providerSessionId } : {}),
|
|
320
|
+
agent: raw.agent,
|
|
321
|
+
model: raw.model,
|
|
322
|
+
contextTokens: raw.contextTokens,
|
|
323
|
+
contextWindow: raw.contextWindow,
|
|
324
|
+
};
|
|
144
325
|
}
|
|
145
326
|
}
|
|
146
327
|
|
|
147
|
-
return {
|
|
328
|
+
return { topicId, ...total, ...(currentSession ? { currentSession } : {}) };
|
|
148
329
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.2.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.2.16";
|
package/dist/storage.js
CHANGED
|
@@ -3361,6 +3361,7 @@ var exports_token_stats = {};
|
|
|
3361
3361
|
__export(exports_token_stats, {
|
|
3362
3362
|
tokenStatsFileId: () => tokenStatsFileId,
|
|
3363
3363
|
recordUsage: () => recordUsage,
|
|
3364
|
+
getTopicStats: () => getTopicStats,
|
|
3364
3365
|
getStats: () => getStats,
|
|
3365
3366
|
calcCost: () => calcCost
|
|
3366
3367
|
});
|
|
@@ -3373,7 +3374,8 @@ function emptyBucket() {
|
|
|
3373
3374
|
outputTokens: 0,
|
|
3374
3375
|
cacheCreationInputTokens: 0,
|
|
3375
3376
|
cacheReadInputTokens: 0,
|
|
3376
|
-
queries: 0
|
|
3377
|
+
queries: 0,
|
|
3378
|
+
estimatedCostUsd: 0
|
|
3377
3379
|
};
|
|
3378
3380
|
}
|
|
3379
3381
|
function tokenStatsFileId(userId) {
|
|
@@ -3400,16 +3402,53 @@ function loadRecords(userId) {
|
|
|
3400
3402
|
}
|
|
3401
3403
|
}
|
|
3402
3404
|
function calcCost(b) {
|
|
3403
|
-
return b.
|
|
3405
|
+
return b.estimatedCostUsd;
|
|
3406
|
+
}
|
|
3407
|
+
var TOKEN_PRICES = {
|
|
3408
|
+
"codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
|
|
3409
|
+
"codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
|
|
3410
|
+
"codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
|
|
3411
|
+
"claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
|
|
3412
|
+
"claude:opus": { input: 5, cacheWrite: 6.25, cacheRead: 0.5, output: 25 },
|
|
3413
|
+
"claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
|
|
3414
|
+
"maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
|
|
3415
|
+
"maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
|
|
3416
|
+
"maestro:deepseek-pro": { input: 0.435, cacheRead: 0.003625, output: 0.87 },
|
|
3417
|
+
"maestro:deepseek-flash": { input: 0.14, cacheRead: 0.0028, output: 0.28 }
|
|
3418
|
+
};
|
|
3419
|
+
function estimateUsageCost(agent, model, usage) {
|
|
3420
|
+
const prices = TOKEN_PRICES[`${agent}:${model}`];
|
|
3421
|
+
if (!prices)
|
|
3422
|
+
return 0;
|
|
3423
|
+
return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + (agent === "claude" ? usage.cacheCreationInputTokens * (prices.cacheWrite ?? prices.input) : 0) + usage.cacheReadInputTokens * prices.cacheRead) / 1e6;
|
|
3404
3424
|
}
|
|
3405
|
-
function
|
|
3425
|
+
function isQueryRecord(value) {
|
|
3426
|
+
if (!value || typeof value !== "object")
|
|
3427
|
+
return false;
|
|
3428
|
+
const record = value;
|
|
3429
|
+
return record.schemaVersion === 2 && typeof record.timestamp === "string" && typeof record.session === "string" && typeof record.topicId === "string" && typeof record.agent === "string" && typeof record.model === "string" && typeof record.inputTokens === "number" && typeof record.outputTokens === "number" && typeof record.cacheCreationInputTokens === "number" && typeof record.cacheReadInputTokens === "number" && typeof record.estimatedCostUsd === "number";
|
|
3430
|
+
}
|
|
3431
|
+
function recordUsage(userId, session, usage, context) {
|
|
3432
|
+
const cacheReadInputTokens = usage.cacheReadInputTokens ?? 0;
|
|
3433
|
+
const inputTokens = context.agent === "claude" ? usage.inputTokens : Math.max(0, usage.inputTokens - cacheReadInputTokens);
|
|
3434
|
+
const normalized = {
|
|
3435
|
+
inputTokens,
|
|
3436
|
+
outputTokens: usage.outputTokens,
|
|
3437
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens ?? 0,
|
|
3438
|
+
cacheReadInputTokens
|
|
3439
|
+
};
|
|
3406
3440
|
const record = {
|
|
3441
|
+
schemaVersion: 2,
|
|
3407
3442
|
timestamp: new Date().toISOString(),
|
|
3408
3443
|
session,
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3444
|
+
topicId: context.topicId,
|
|
3445
|
+
...context.providerSessionId ? { providerSessionId: context.providerSessionId } : {},
|
|
3446
|
+
agent: context.agent,
|
|
3447
|
+
model: context.model,
|
|
3448
|
+
...normalized,
|
|
3449
|
+
...usage.contextTokens !== undefined ? { contextTokens: usage.contextTokens } : {},
|
|
3450
|
+
...usage.contextWindow !== undefined ? { contextWindow: usage.contextWindow } : {},
|
|
3451
|
+
estimatedCostUsd: usage.costUsd ?? estimateUsageCost(context.agent, context.model, normalized)
|
|
3413
3452
|
};
|
|
3414
3453
|
try {
|
|
3415
3454
|
appendJsonlEntry(queriesPath(userId), record);
|
|
@@ -3423,12 +3462,26 @@ function getStats(userId, from, to) {
|
|
|
3423
3462
|
const toTs = to ? new Date(to).getTime() : Infinity;
|
|
3424
3463
|
if (from && Number.isNaN(fromTs) || to && Number.isNaN(toTs)) {
|
|
3425
3464
|
logger.warn({ from, to }, "token-stats: Invalid date range, returning empty");
|
|
3426
|
-
return {
|
|
3465
|
+
return {
|
|
3466
|
+
total: emptyBucket(),
|
|
3467
|
+
byHour: {},
|
|
3468
|
+
bySession: {},
|
|
3469
|
+
currentSessions: [],
|
|
3470
|
+
ignoredLegacyRecords: 0,
|
|
3471
|
+
estimatedCostUsd: 0
|
|
3472
|
+
};
|
|
3427
3473
|
}
|
|
3428
3474
|
const total = emptyBucket();
|
|
3429
3475
|
const byHour = {};
|
|
3430
3476
|
const bySession = {};
|
|
3431
|
-
|
|
3477
|
+
const currentSessions = new Map;
|
|
3478
|
+
let ignoredLegacyRecords = 0;
|
|
3479
|
+
for (const raw of records) {
|
|
3480
|
+
if (!isQueryRecord(raw)) {
|
|
3481
|
+
ignoredLegacyRecords += 1;
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
const r = raw;
|
|
3432
3485
|
const ts = new Date(r.timestamp).getTime();
|
|
3433
3486
|
if (ts < fromTs || ts > toTs)
|
|
3434
3487
|
continue;
|
|
@@ -3443,9 +3496,56 @@ function getStats(userId, from, to) {
|
|
|
3443
3496
|
bucket.cacheCreationInputTokens += r.cacheCreationInputTokens;
|
|
3444
3497
|
bucket.cacheReadInputTokens += r.cacheReadInputTokens;
|
|
3445
3498
|
bucket.queries += 1;
|
|
3499
|
+
bucket.estimatedCostUsd += r.estimatedCostUsd;
|
|
3500
|
+
}
|
|
3501
|
+
if (r.contextTokens !== undefined && r.contextWindow !== undefined && r.contextWindow > 0) {
|
|
3502
|
+
currentSessions.set(r.topicId, {
|
|
3503
|
+
timestamp: r.timestamp,
|
|
3504
|
+
topicId: r.topicId,
|
|
3505
|
+
topicTitle: r.session,
|
|
3506
|
+
...r.providerSessionId ? { providerSessionId: r.providerSessionId } : {},
|
|
3507
|
+
agent: r.agent,
|
|
3508
|
+
model: r.model,
|
|
3509
|
+
contextTokens: r.contextTokens,
|
|
3510
|
+
contextWindow: r.contextWindow
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
return {
|
|
3515
|
+
total,
|
|
3516
|
+
byHour,
|
|
3517
|
+
bySession,
|
|
3518
|
+
currentSessions: [...currentSessions.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp)),
|
|
3519
|
+
ignoredLegacyRecords,
|
|
3520
|
+
estimatedCostUsd: calcCost(total)
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
function getTopicStats(userId, topicId) {
|
|
3524
|
+
const total = emptyBucket();
|
|
3525
|
+
let currentSession;
|
|
3526
|
+
for (const raw of loadRecords(userId)) {
|
|
3527
|
+
if (!isQueryRecord(raw) || raw.topicId !== topicId)
|
|
3528
|
+
continue;
|
|
3529
|
+
total.inputTokens += raw.inputTokens;
|
|
3530
|
+
total.outputTokens += raw.outputTokens;
|
|
3531
|
+
total.cacheCreationInputTokens += raw.cacheCreationInputTokens;
|
|
3532
|
+
total.cacheReadInputTokens += raw.cacheReadInputTokens;
|
|
3533
|
+
total.queries += 1;
|
|
3534
|
+
total.estimatedCostUsd += raw.estimatedCostUsd;
|
|
3535
|
+
if (raw.contextTokens !== undefined && raw.contextWindow !== undefined && raw.contextWindow > 0 && (!currentSession || raw.timestamp > currentSession.timestamp)) {
|
|
3536
|
+
currentSession = {
|
|
3537
|
+
timestamp: raw.timestamp,
|
|
3538
|
+
topicId: raw.topicId,
|
|
3539
|
+
topicTitle: raw.session,
|
|
3540
|
+
...raw.providerSessionId ? { providerSessionId: raw.providerSessionId } : {},
|
|
3541
|
+
agent: raw.agent,
|
|
3542
|
+
model: raw.model,
|
|
3543
|
+
contextTokens: raw.contextTokens,
|
|
3544
|
+
contextWindow: raw.contextWindow
|
|
3545
|
+
};
|
|
3446
3546
|
}
|
|
3447
3547
|
}
|
|
3448
|
-
return {
|
|
3548
|
+
return { topicId, ...total, ...currentSession ? { currentSession } : {} };
|
|
3449
3549
|
}
|
|
3450
3550
|
// ../../packages/core/src/storage/topic-archive.ts
|
|
3451
3551
|
var exports_topic_archive = {};
|
|
@@ -3971,6 +4071,7 @@ export {
|
|
|
3971
4071
|
grantSubagentTellTarget,
|
|
3972
4072
|
getWikiDir,
|
|
3973
4073
|
getUserConfig,
|
|
4074
|
+
getTopicStats,
|
|
3974
4075
|
getTopicSessionId,
|
|
3975
4076
|
getTopicNames,
|
|
3976
4077
|
getTopicMemoryOrigin,
|
|
@@ -4065,4 +4166,4 @@ export {
|
|
|
4065
4166
|
ConversationLogDivergedError
|
|
4066
4167
|
};
|
|
4067
4168
|
|
|
4068
|
-
//# debugId=
|
|
4169
|
+
//# debugId=0E9898BA8C097A7864756E2164756E21
|