negotium 0.2.14 → 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/public-helpers.ts +8 -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/runtime-helpers.js +443 -30
- package/dist/runtime-helpers.js.map +9 -3
- 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/runtime/bashrs-completions.d.ts +50 -0
- package/dist/types/packages/core/src/runtime/public-helpers.d.ts +2 -0
- 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";
|