sentinelayer-cli 0.30.10 → 0.30.11
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/package.json +2 -2
- package/src/session/pricing-ledger.js +4 -1
- package/src/session/transcript.js +154 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sentinelayer-cli",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.11",
|
|
4
4
|
"description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"docs:build": "node scripts/docs-build.mjs",
|
|
10
10
|
"test": "npm run test:unit && npm run test:e2e",
|
|
11
11
|
"test:unit": "node --import ./tests/setup-env.mjs --test tests/unit*.test.mjs",
|
|
12
|
-
"test:e2e": "node --import ./tests/setup-env.mjs --test tests/e2e
|
|
12
|
+
"test:e2e": "node --import ./tests/setup-env.mjs --test tests/e2e*.test.mjs",
|
|
13
13
|
"test:coverage": "c8 node --import ./tests/setup-env.mjs --test tests/unit*.test.mjs",
|
|
14
14
|
"verify": "npm run check && npm run docs:build && npm run test:e2e && npm run test:coverage && npm pack --dry-run",
|
|
15
15
|
"release:publish": "node scripts/release-publish.mjs"
|
|
@@ -138,7 +138,10 @@ export function buildUsageLedgerEntry(
|
|
|
138
138
|
{ sessionId = "", priceBookVersion = DEFAULT_PRICE_BOOK_VERSION, billingTier = "unknown" } = {},
|
|
139
139
|
) {
|
|
140
140
|
const kind = n(event?.event || event?.type);
|
|
141
|
-
const
|
|
141
|
+
const rawPayload = event?.payload;
|
|
142
|
+
const payload = object(rawPayload);
|
|
143
|
+
const payloadWasObject = rawPayload && typeof rawPayload === "object" && !Array.isArray(rawPayload);
|
|
144
|
+
if (kind === SESSION_USAGE_EVENT && !payloadWasObject) return null;
|
|
142
145
|
const schema = n(payload.schema);
|
|
143
146
|
const usage = object(payload.usage);
|
|
144
147
|
const legacyUsageEvent = kind !== SESSION_USAGE_EVENT && hasUsageHints(usage);
|
|
@@ -237,7 +237,12 @@ function eventBody(event) {
|
|
|
237
237
|
* - totals: { tokenTotal, costTotalUsd } summed through the pricing ledger
|
|
238
238
|
* - sentiActions: count of orchestrator events
|
|
239
239
|
*/
|
|
240
|
-
export function computeTranscriptStats({
|
|
240
|
+
export function computeTranscriptStats({
|
|
241
|
+
sessionMeta = {},
|
|
242
|
+
events = [],
|
|
243
|
+
speakerProfiles = new Map(),
|
|
244
|
+
usageLedger = null,
|
|
245
|
+
} = {}) {
|
|
241
246
|
const perAgent = new Map();
|
|
242
247
|
let firstEventTs = null;
|
|
243
248
|
let lastEventTs = null;
|
|
@@ -305,14 +310,14 @@ export function computeTranscriptStats({ sessionMeta = {}, events = [], speakerP
|
|
|
305
310
|
if (epoch > record.lastSeenMs) record.lastSeenMs = epoch;
|
|
306
311
|
}
|
|
307
312
|
|
|
308
|
-
const
|
|
313
|
+
const resolvedUsageLedger = usageLedger || buildSessionUsageLedger(events, {
|
|
309
314
|
sessionId: normalize(sessionMeta.sessionId),
|
|
310
315
|
});
|
|
311
316
|
const fallbackUsageEpoch =
|
|
312
317
|
lastEventTs ??
|
|
313
318
|
firstEventTs ??
|
|
314
319
|
(Number.isFinite(Date.parse(sessionMeta?.createdAt)) ? Date.parse(sessionMeta.createdAt) : 0);
|
|
315
|
-
for (const entry of
|
|
320
|
+
for (const entry of resolvedUsageLedger.entries) {
|
|
316
321
|
const entryEpoch = Number.isFinite(Date.parse(entry.timestamp))
|
|
317
322
|
? Date.parse(entry.timestamp)
|
|
318
323
|
: fallbackUsageEpoch;
|
|
@@ -364,17 +369,17 @@ export function computeTranscriptStats({ sessionMeta = {}, events = [], speakerP
|
|
|
364
369
|
sessionLiveSeconds,
|
|
365
370
|
agents,
|
|
366
371
|
totals: {
|
|
367
|
-
tokenTotal:
|
|
368
|
-
inputTokens:
|
|
369
|
-
outputTokens:
|
|
370
|
-
costTotalUsd:
|
|
371
|
-
customerCostTotalUsd:
|
|
372
|
-
?
|
|
372
|
+
tokenTotal: resolvedUsageLedger.totals.totalTokens,
|
|
373
|
+
inputTokens: resolvedUsageLedger.totals.inputTokens,
|
|
374
|
+
outputTokens: resolvedUsageLedger.totals.outputTokens,
|
|
375
|
+
costTotalUsd: resolvedUsageLedger.totals.providerCostUsd,
|
|
376
|
+
customerCostTotalUsd: resolvedUsageLedger.totals.hasCustomerCost
|
|
377
|
+
? resolvedUsageLedger.totals.customerCostUsd
|
|
373
378
|
: null,
|
|
374
|
-
usageEntries:
|
|
375
|
-
duplicatesSkipped:
|
|
376
|
-
unpriced:
|
|
377
|
-
priceBookVersions:
|
|
379
|
+
usageEntries: resolvedUsageLedger.entries.length,
|
|
380
|
+
duplicatesSkipped: resolvedUsageLedger.duplicatesSkipped,
|
|
381
|
+
unpriced: resolvedUsageLedger.totals.unpriced,
|
|
382
|
+
priceBookVersions: resolvedUsageLedger.priceBookVersions,
|
|
378
383
|
},
|
|
379
384
|
sentiActions,
|
|
380
385
|
};
|
|
@@ -408,6 +413,134 @@ function avatarMd(identity) {
|
|
|
408
413
|
return identity.avatar || letterTile(identity.displayName || identity.agentId);
|
|
409
414
|
}
|
|
410
415
|
|
|
416
|
+
function intCell(value) {
|
|
417
|
+
return Math.max(0, Math.floor(Number(value || 0))).toLocaleString("en-US");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function usdCell(value) {
|
|
421
|
+
return `$${Number(value || 0).toFixed(6)}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function tableText(value, { maxLength = 160 } = {}) {
|
|
425
|
+
const escaped = normalize(value)
|
|
426
|
+
.replace(/\r?\n/g, " ")
|
|
427
|
+
.replace(/\|/g, "\\|")
|
|
428
|
+
.replace(/`/g, "'");
|
|
429
|
+
if (!maxLength || escaped.length <= maxLength) return escaped;
|
|
430
|
+
const head = Math.max(24, Math.floor((maxLength - 3) * 0.7));
|
|
431
|
+
const tail = Math.max(12, maxLength - 3 - head);
|
|
432
|
+
return `${escaped.slice(0, head)}...${escaped.slice(-tail)}`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function codeCell(value) {
|
|
436
|
+
const text = tableText(value);
|
|
437
|
+
return text ? `\`${text}\`` : "—";
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function optionalUsdCell(value, hasValue) {
|
|
441
|
+
return hasValue ? usdCell(value) : "—";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function shortenIdempotencyKey(value, maxLength = 36) {
|
|
445
|
+
const text = tableText(value, { maxLength: 0 });
|
|
446
|
+
if (!text || text.length <= maxLength) return text;
|
|
447
|
+
const head = Math.max(8, Math.floor((maxLength - 3) * 0.58));
|
|
448
|
+
const tail = Math.max(6, maxLength - 3 - head);
|
|
449
|
+
return `${text.slice(0, head)}...${text.slice(-tail)}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function rollupsByCostAndTokens(map) {
|
|
453
|
+
return [...map.values()].sort((a, b) => (
|
|
454
|
+
b.providerCostUsd - a.providerCostUsd ||
|
|
455
|
+
b.totalTokens - a.totalTokens ||
|
|
456
|
+
b.entries - a.entries ||
|
|
457
|
+
tableText(a.label).localeCompare(tableText(b.label))
|
|
458
|
+
));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function recentUsageEntries(entries, limit = 10) {
|
|
462
|
+
return entries
|
|
463
|
+
.map((entry, index) => ({ entry, index }))
|
|
464
|
+
.sort((a, b) => {
|
|
465
|
+
const aSequence = Number.isFinite(a.entry.sequenceId) ? a.entry.sequenceId : null;
|
|
466
|
+
const bSequence = Number.isFinite(b.entry.sequenceId) ? b.entry.sequenceId : null;
|
|
467
|
+
if (aSequence != null && bSequence != null && aSequence !== bSequence) {
|
|
468
|
+
return bSequence - aSequence;
|
|
469
|
+
}
|
|
470
|
+
const aTime = Date.parse(a.entry.timestamp);
|
|
471
|
+
const bTime = Date.parse(b.entry.timestamp);
|
|
472
|
+
if (Number.isFinite(aTime) && Number.isFinite(bTime) && aTime !== bTime) {
|
|
473
|
+
return bTime - aTime;
|
|
474
|
+
}
|
|
475
|
+
return b.index - a.index;
|
|
476
|
+
})
|
|
477
|
+
.slice(0, limit)
|
|
478
|
+
.map(({ entry }) => entry);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function appendUsageRollupTable(lines, { title, labelHeader, rollups }) {
|
|
482
|
+
lines.push(`### ${title}`);
|
|
483
|
+
lines.push("");
|
|
484
|
+
lines.push(`| ${labelHeader} | Entries | Input | Output | Total | Provider cost | Customer cost | Unpriced |`);
|
|
485
|
+
lines.push("|---|---:|---:|---:|---:|---:|---:|---:|");
|
|
486
|
+
if (rollups.length === 0) {
|
|
487
|
+
lines.push(`| — | 0 | 0 | 0 | 0 | ${usdCell(0)} | — | 0 |`);
|
|
488
|
+
} else {
|
|
489
|
+
for (const rollup of rollups) {
|
|
490
|
+
lines.push(
|
|
491
|
+
`| ${codeCell(rollup.label)} | ${intCell(rollup.entries)} | ${intCell(rollup.inputTokens)} | ${intCell(rollup.outputTokens)} | ${intCell(rollup.totalTokens)} | ${usdCell(rollup.providerCostUsd)} | ${optionalUsdCell(rollup.customerCostUsd, rollup.hasCustomerCost)} | ${intCell(rollup.unpriced)} |`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
lines.push("");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function appendUsageLedgerSection(lines, usageLedger) {
|
|
499
|
+
const totals = usageLedger.totals;
|
|
500
|
+
lines.push("## Usage Ledger");
|
|
501
|
+
lines.push("");
|
|
502
|
+
|
|
503
|
+
if (usageLedger.entries.length === 0 && usageLedger.duplicatesSkipped === 0) {
|
|
504
|
+
lines.push("_No usage telemetry recorded._");
|
|
505
|
+
lines.push("");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const customerCostText = totals.hasCustomerCost
|
|
510
|
+
? `Customer cost: ${usdCell(totals.customerCostUsd)}`
|
|
511
|
+
: "Customer cost: —";
|
|
512
|
+
lines.push(`Accepted entries: ${intCell(usageLedger.entries.length)}`);
|
|
513
|
+
lines.push(
|
|
514
|
+
`Tokens: ${intCell(totals.totalTokens)} (input ${intCell(totals.inputTokens)} / output ${intCell(totals.outputTokens)})`,
|
|
515
|
+
);
|
|
516
|
+
lines.push(`Provider cost: ${usdCell(totals.providerCostUsd)} · ${customerCostText}`);
|
|
517
|
+
lines.push(`Price books: ${usageLedger.priceBookVersions.map(tableText).filter(Boolean).join(", ") || "—"}`);
|
|
518
|
+
lines.push(`Duplicates skipped: ${intCell(usageLedger.duplicatesSkipped)} · Unpriced entries: ${intCell(totals.unpriced)}`);
|
|
519
|
+
lines.push("");
|
|
520
|
+
|
|
521
|
+
appendUsageRollupTable(lines, {
|
|
522
|
+
title: "Per Agent",
|
|
523
|
+
labelHeader: "Agent",
|
|
524
|
+
rollups: rollupsByCostAndTokens(usageLedger.perAgent),
|
|
525
|
+
});
|
|
526
|
+
appendUsageRollupTable(lines, {
|
|
527
|
+
title: "Per Action",
|
|
528
|
+
labelHeader: "Action",
|
|
529
|
+
rollups: rollupsByCostAndTokens(usageLedger.perAction),
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
lines.push("### Recent Entries");
|
|
533
|
+
lines.push("");
|
|
534
|
+
lines.push("| Time | Agent | Action | Model | Tokens | Provider cost | Customer cost | Idempotency key |");
|
|
535
|
+
lines.push("|---|---|---|---|---:|---:|---:|---|");
|
|
536
|
+
for (const entry of recentUsageEntries(usageLedger.entries)) {
|
|
537
|
+
lines.push(
|
|
538
|
+
`| ${tableText(timestampOnly(entry.timestamp)) || "—"} | ${codeCell(entry.agentId)} | ${codeCell(entry.action)} | ${codeCell(entry.model)} | ${intCell(entry.totalTokens)} | ${usdCell(entry.providerCostUsd)} | ${optionalUsdCell(entry.customerCostUsd, entry.customerCostUsd != null)} | ${codeCell(shortenIdempotencyKey(entry.idempotencyKey))} |`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
lines.push("");
|
|
542
|
+
}
|
|
543
|
+
|
|
411
544
|
/**
|
|
412
545
|
* Build the iMessage-style markdown transcript.
|
|
413
546
|
*
|
|
@@ -429,8 +562,11 @@ export function buildTranscriptMarkdown({
|
|
|
429
562
|
speakerProfiles = new Map(),
|
|
430
563
|
options = {},
|
|
431
564
|
} = {}) {
|
|
565
|
+
const usageLedger = buildSessionUsageLedger(events, {
|
|
566
|
+
sessionId: normalize(sessionMeta.sessionId),
|
|
567
|
+
});
|
|
432
568
|
const includeSystemEvents = options.includeSystemEvents !== false;
|
|
433
|
-
const stats = computeTranscriptStats({ sessionMeta, events, speakerProfiles });
|
|
569
|
+
const stats = computeTranscriptStats({ sessionMeta, events, speakerProfiles, usageLedger });
|
|
434
570
|
|
|
435
571
|
const lines = [];
|
|
436
572
|
const sessionId = normalize(sessionMeta.sessionId) || "unknown";
|
|
@@ -496,15 +632,17 @@ export function buildTranscriptMarkdown({
|
|
|
496
632
|
stats.participantCount = stats.agents.length + silentRegisteredAgents.length;
|
|
497
633
|
lines.push("");
|
|
498
634
|
|
|
635
|
+
appendUsageLedgerSection(lines, usageLedger);
|
|
636
|
+
|
|
499
637
|
// Conversation
|
|
500
638
|
lines.push("## Conversation");
|
|
501
639
|
lines.push("");
|
|
502
640
|
for (const event of events) {
|
|
503
|
-
const kind = normalize(event
|
|
641
|
+
const kind = normalize(event?.event || event?.type);
|
|
504
642
|
if (!kind || !TRANSCRIPT_EVENT_KINDS.has(kind)) continue;
|
|
505
643
|
if (!includeSystemEvents && SYSTEM_EVENT_KINDS.has(kind)) continue;
|
|
506
644
|
|
|
507
|
-
const agentId = normalize(event
|
|
645
|
+
const agentId = normalize(event?.agent?.id || event?.agentId);
|
|
508
646
|
const profile = speakerProfiles.get(agentId) || null;
|
|
509
647
|
const identity = resolveSpeakerIdentity({
|
|
510
648
|
agentId,
|