sentinelayer-cli 0.17.0 → 0.18.0
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 +16 -6
- package/package.json +3 -2
- package/src/commands/legacy-args.js +1 -0
- package/src/commands/omargate.js +1 -0
- package/src/commands/session.js +302 -25
- package/src/cost/tracker.js +3 -1
- package/src/events/schema.js +21 -0
- package/src/legacy-cli.js +24 -1
- package/src/review/investor-dd-devtestbot.js +83 -8
- package/src/review/investor-dd-file-loop.js +83 -6
- package/src/review/investor-dd-orchestrator.js +42 -1
- package/src/review/investor-dd-progress.js +351 -0
- package/src/review/investor-dd-usage.js +227 -0
- package/src/scan/generator.js +8 -1
- package/src/session/daemon.js +341 -2
- package/src/session/pricing-ledger.js +260 -0
- package/src/session/recap.js +288 -69
- package/src/session/sync.js +1 -4
- package/src/session/usage.js +72 -50
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_PRICE_BOOK_VERSION,
|
|
5
|
+
estimateModelCost,
|
|
6
|
+
} from "../cost/tracker.js";
|
|
7
|
+
|
|
8
|
+
const SESSION_USAGE_EVENT = "session_usage";
|
|
9
|
+
|
|
10
|
+
function n(value) {
|
|
11
|
+
return String(value == null ? "" : value).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function object(value) {
|
|
15
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function nonNegativeNumber(value) {
|
|
19
|
+
if (value == null || value === "") return null;
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function nonNegativeInt(value) {
|
|
25
|
+
const parsed = nonNegativeNumber(value);
|
|
26
|
+
return parsed == null ? null : Math.floor(parsed);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function money(value) {
|
|
30
|
+
const parsed = nonNegativeNumber(value);
|
|
31
|
+
return parsed == null ? null : Math.round(parsed * 1_000_000) / 1_000_000;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pick(sources, keys) {
|
|
35
|
+
for (const source of sources) {
|
|
36
|
+
const bag = object(source);
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (bag[key] != null && bag[key] !== "") return bag[key];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pickText(sources, keys) {
|
|
45
|
+
return n(pick(sources, keys));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pickInt(sources, keys) {
|
|
49
|
+
return nonNegativeInt(pick(sources, keys)) ?? 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function pickMoney(sources, keys) {
|
|
53
|
+
return money(pick(sources, keys));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function roundUsd(value) {
|
|
57
|
+
return Math.round(Number(value || 0) * 1_000_000) / 1_000_000;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createSessionUsageLedgerId({
|
|
61
|
+
sessionId = "",
|
|
62
|
+
agentId = "",
|
|
63
|
+
action = "",
|
|
64
|
+
idempotencyKey = "",
|
|
65
|
+
} = {}) {
|
|
66
|
+
const digest = createHash("sha256")
|
|
67
|
+
.update([sessionId, agentId, action, idempotencyKey].map(n).join("\x1f"))
|
|
68
|
+
.digest("hex")
|
|
69
|
+
.slice(0, 32);
|
|
70
|
+
return `bill_${digest}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fallbackIdempotencyKey({ sessionId, event, agentId, action, model, totalTokens }) {
|
|
74
|
+
const sequence = n(event.sequenceId ?? event.sequence_id);
|
|
75
|
+
if (sequence) return `seq:${sequence}`;
|
|
76
|
+
const timestamp = n(event.ts || event.timestamp);
|
|
77
|
+
const interaction = n(object(event.payload).interactionId || object(event.payload).interaction_id);
|
|
78
|
+
const source = [sessionId, timestamp, agentId, action, model, totalTokens, interaction].join("\x1f");
|
|
79
|
+
return `event:${createHash("sha256").update(source).digest("hex").slice(0, 32)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function providerCostFromPriceBook({ model, inputTokens, outputTokens, explicitProviderCost }) {
|
|
83
|
+
if (explicitProviderCost != null) {
|
|
84
|
+
return { providerCostUsd: explicitProviderCost, unpriced: false };
|
|
85
|
+
}
|
|
86
|
+
if (inputTokens <= 0 && outputTokens <= 0) {
|
|
87
|
+
return { providerCostUsd: 0, unpriced: false };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return {
|
|
91
|
+
providerCostUsd: estimateModelCost({ modelId: model, inputTokens, outputTokens }),
|
|
92
|
+
unpriced: false,
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return { providerCostUsd: 0, unpriced: true };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildUsageLedgerEntry(
|
|
100
|
+
event,
|
|
101
|
+
{ sessionId = "", priceBookVersion = DEFAULT_PRICE_BOOK_VERSION, billingTier = "unknown" } = {},
|
|
102
|
+
) {
|
|
103
|
+
const kind = n(event?.event || event?.type);
|
|
104
|
+
if (kind !== SESSION_USAGE_EVENT) return null;
|
|
105
|
+
|
|
106
|
+
const payload = object(event?.payload);
|
|
107
|
+
const usage = object(payload.usage);
|
|
108
|
+
const prompt = object(payload.prompt);
|
|
109
|
+
const response = object(payload.response);
|
|
110
|
+
const agent = object(event?.agent);
|
|
111
|
+
const sources = [payload, usage];
|
|
112
|
+
|
|
113
|
+
const agentId =
|
|
114
|
+
pickText(sources, ["agentId", "agent_id"]) ||
|
|
115
|
+
n(agent.id || event?.agentId) ||
|
|
116
|
+
"unknown";
|
|
117
|
+
const model =
|
|
118
|
+
pickText(sources, ["model", "modelId", "model_id"]) ||
|
|
119
|
+
n(agent.model || event?.agentModel) ||
|
|
120
|
+
"unknown";
|
|
121
|
+
const action =
|
|
122
|
+
pickText(sources, ["action", "operation", "kind", "billingAction", "billing_action"]) ||
|
|
123
|
+
"agent_message";
|
|
124
|
+
const resolvedPriceBook =
|
|
125
|
+
pickText(sources, ["priceBookVersion", "price_book_version", "pricingVersion", "pricing_version"]) ||
|
|
126
|
+
priceBookVersion;
|
|
127
|
+
const resolvedBillingTier =
|
|
128
|
+
pickText(sources, ["billingTier", "billing_tier", "tier"]) ||
|
|
129
|
+
n(billingTier) ||
|
|
130
|
+
"unknown";
|
|
131
|
+
|
|
132
|
+
const inputTokens =
|
|
133
|
+
pickInt(sources, ["inputTokens", "input_tokens", "tokensIn", "tokens_in", "promptTokens", "prompt_tokens"]) ||
|
|
134
|
+
pickInt([prompt], ["tokens", "tokenCount", "token_count"]);
|
|
135
|
+
const outputTokens =
|
|
136
|
+
pickInt(sources, ["outputTokens", "output_tokens", "tokensOut", "tokens_out", "completionTokens", "completion_tokens"]) ||
|
|
137
|
+
pickInt([response], ["tokens", "tokenCount", "token_count"]);
|
|
138
|
+
const explicitTotalTokens = pickInt(sources, ["totalTokens", "total_tokens", "tokens", "tokenTotal", "token_total"]);
|
|
139
|
+
const totalTokens = explicitTotalTokens || inputTokens + outputTokens;
|
|
140
|
+
const explicitProviderCost = pickMoney(sources, ["providerCostUsd", "provider_cost_usd", "costUsd", "cost_usd", "cost"]);
|
|
141
|
+
const customerCostUsd = pickMoney(sources, ["customerCostUsd", "customer_cost_usd", "billableCostUsd", "billable_cost_usd"]);
|
|
142
|
+
const { providerCostUsd, unpriced } = providerCostFromPriceBook({
|
|
143
|
+
model,
|
|
144
|
+
inputTokens,
|
|
145
|
+
outputTokens,
|
|
146
|
+
explicitProviderCost,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const idempotencyKey =
|
|
150
|
+
pickText(sources, ["idempotencyKey", "idempotency_key", "runKey", "run_key"]) ||
|
|
151
|
+
pickText(sources, ["interactionId", "interaction_id"]) ||
|
|
152
|
+
fallbackIdempotencyKey({ sessionId, event, agentId, action, model, totalTokens });
|
|
153
|
+
const ledgerEntryId =
|
|
154
|
+
pickText(sources, ["ledgerEntryId", "ledger_entry_id", "billingEventId", "billing_event_id"]) ||
|
|
155
|
+
createSessionUsageLedgerId({ sessionId, agentId, action, idempotencyKey });
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
ledgerEntryId,
|
|
159
|
+
idempotencyKey,
|
|
160
|
+
sessionId: n(sessionId),
|
|
161
|
+
agentId,
|
|
162
|
+
action,
|
|
163
|
+
model,
|
|
164
|
+
priceBookVersion: resolvedPriceBook,
|
|
165
|
+
billingTier: resolvedBillingTier,
|
|
166
|
+
provider: pickText(sources, ["provider", "providerName", "provider_name"]),
|
|
167
|
+
inputTokens,
|
|
168
|
+
outputTokens,
|
|
169
|
+
totalTokens,
|
|
170
|
+
providerCostUsd: roundUsd(providerCostUsd),
|
|
171
|
+
customerCostUsd,
|
|
172
|
+
unpriced,
|
|
173
|
+
timestamp: n(event?.ts || event?.timestamp),
|
|
174
|
+
sequenceId: nonNegativeInt(event?.sequenceId ?? event?.sequence_id),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function newRollup(label) {
|
|
179
|
+
return {
|
|
180
|
+
label,
|
|
181
|
+
entries: 0,
|
|
182
|
+
inputTokens: 0,
|
|
183
|
+
outputTokens: 0,
|
|
184
|
+
totalTokens: 0,
|
|
185
|
+
providerCostUsd: 0,
|
|
186
|
+
customerCostUsd: 0,
|
|
187
|
+
hasCustomerCost: false,
|
|
188
|
+
unpriced: 0,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function addToRollup(rollup, entry) {
|
|
193
|
+
rollup.entries += 1;
|
|
194
|
+
rollup.inputTokens += entry.inputTokens;
|
|
195
|
+
rollup.outputTokens += entry.outputTokens;
|
|
196
|
+
rollup.totalTokens += entry.totalTokens;
|
|
197
|
+
rollup.providerCostUsd += entry.providerCostUsd;
|
|
198
|
+
if (entry.customerCostUsd != null) {
|
|
199
|
+
rollup.customerCostUsd += entry.customerCostUsd;
|
|
200
|
+
rollup.hasCustomerCost = true;
|
|
201
|
+
}
|
|
202
|
+
if (entry.unpriced) rollup.unpriced += 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function finalizeRollup(rollup) {
|
|
206
|
+
rollup.providerCostUsd = roundUsd(rollup.providerCostUsd);
|
|
207
|
+
rollup.customerCostUsd = roundUsd(rollup.customerCostUsd);
|
|
208
|
+
return rollup;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function buildSessionUsageLedger(events = [], options = {}) {
|
|
212
|
+
if (!Array.isArray(events)) {
|
|
213
|
+
throw new Error("events must be an array.");
|
|
214
|
+
}
|
|
215
|
+
const entries = [];
|
|
216
|
+
const totals = newRollup("session");
|
|
217
|
+
const perAgent = new Map();
|
|
218
|
+
const perAction = new Map();
|
|
219
|
+
const priceBookVersions = new Set();
|
|
220
|
+
const seenKeys = new Set();
|
|
221
|
+
let duplicatesSkipped = 0;
|
|
222
|
+
|
|
223
|
+
for (const event of events) {
|
|
224
|
+
const entry = buildUsageLedgerEntry(event, options);
|
|
225
|
+
if (!entry) continue;
|
|
226
|
+
const dedupeKeys = [
|
|
227
|
+
entry.idempotencyKey ? `idem:${entry.idempotencyKey}` : "",
|
|
228
|
+
entry.ledgerEntryId ? `ledger:${entry.ledgerEntryId}` : "",
|
|
229
|
+
].filter(Boolean);
|
|
230
|
+
if (dedupeKeys.some((dedupeKey) => seenKeys.has(dedupeKey))) {
|
|
231
|
+
duplicatesSkipped += 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const dedupeKey of dedupeKeys) seenKeys.add(dedupeKey);
|
|
235
|
+
entries.push(entry);
|
|
236
|
+
priceBookVersions.add(entry.priceBookVersion);
|
|
237
|
+
addToRollup(totals, entry);
|
|
238
|
+
|
|
239
|
+
if (!perAgent.has(entry.agentId)) perAgent.set(entry.agentId, newRollup(entry.agentId));
|
|
240
|
+
addToRollup(perAgent.get(entry.agentId), entry);
|
|
241
|
+
|
|
242
|
+
if (!perAction.has(entry.action)) perAction.set(entry.action, newRollup(entry.action));
|
|
243
|
+
addToRollup(perAction.get(entry.action), entry);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
finalizeRollup(totals);
|
|
247
|
+
for (const rollup of perAgent.values()) finalizeRollup(rollup);
|
|
248
|
+
for (const rollup of perAction.values()) finalizeRollup(rollup);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
entries,
|
|
252
|
+
totals,
|
|
253
|
+
perAgent,
|
|
254
|
+
perAction,
|
|
255
|
+
priceBookVersions: [...priceBookVersions].sort(),
|
|
256
|
+
duplicatesSkipped,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { DEFAULT_PRICE_BOOK_VERSION };
|