sentinelayer-cli 0.27.0 → 0.28.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 +1 -0
- package/package.json +1 -1
- package/src/ai/client.js +29 -2
- package/src/billing/ledger-entry.js +3 -0
- package/src/commands/review.js +5 -0
- package/src/review/ai-review.js +10 -1
package/README.md
CHANGED
|
@@ -259,6 +259,7 @@ The `review` command can now add budget-governed AI reasoning on top of determin
|
|
|
259
259
|
|
|
260
260
|
- `sl review --ai --provider openai --model gpt-5.3-codex`
|
|
261
261
|
- `sl review --ai --ai-dry-run` (no provider call; deterministic synthetic output)
|
|
262
|
+
- `sl review --ai --require-usage-ledger` (fail closed if billing-grade usage cannot be recorded)
|
|
262
263
|
- `sl review --ai --max-cost 1.0 --max-tokens 0 --max-runtime-ms 0 --max-tool-calls 0`
|
|
263
264
|
|
|
264
265
|
AI artifacts are persisted in the same run folder:
|
package/package.json
CHANGED
package/src/ai/client.js
CHANGED
|
@@ -413,10 +413,18 @@ export class MultiProviderApiClient {
|
|
|
413
413
|
* prompt?: string,
|
|
414
414
|
* stream?: boolean,
|
|
415
415
|
* apiKey?: string,
|
|
416
|
+
* apiUrl?: string,
|
|
416
417
|
* env?: NodeJS.ProcessEnv,
|
|
417
|
-
* onChunk?: (chunk: string) => void
|
|
418
|
+
* onChunk?: (chunk: string) => void,
|
|
419
|
+
* sessionId?: string,
|
|
420
|
+
* agentId?: string,
|
|
421
|
+
* action?: string,
|
|
422
|
+
* usageIdempotencyKey?: string,
|
|
423
|
+
* billingTier?: string,
|
|
424
|
+
* customerPricingPolicy?: string,
|
|
425
|
+
* metadata?: object
|
|
418
426
|
* }} [options]
|
|
419
|
-
* @returns {Promise<{ provider: string, model: string, text: string }>}
|
|
427
|
+
* @returns {Promise<{ provider: string, model: string, text: string, usage?: object, usageLedger?: object | null }>}
|
|
420
428
|
*/
|
|
421
429
|
async invoke({
|
|
422
430
|
provider,
|
|
@@ -424,8 +432,16 @@ export class MultiProviderApiClient {
|
|
|
424
432
|
prompt,
|
|
425
433
|
stream = false,
|
|
426
434
|
apiKey,
|
|
435
|
+
apiUrl = "",
|
|
427
436
|
env = process.env,
|
|
428
437
|
onChunk,
|
|
438
|
+
sessionId = "",
|
|
439
|
+
agentId = "",
|
|
440
|
+
action = "",
|
|
441
|
+
usageIdempotencyKey = "",
|
|
442
|
+
billingTier = "",
|
|
443
|
+
customerPricingPolicy = "",
|
|
444
|
+
metadata = null,
|
|
429
445
|
} = {}) {
|
|
430
446
|
const resolvedProvider = resolveProvider({ provider, env });
|
|
431
447
|
const resolvedModel = resolveModel({ provider: resolvedProvider, model });
|
|
@@ -437,10 +453,21 @@ export class MultiProviderApiClient {
|
|
|
437
453
|
systemPrompt: "",
|
|
438
454
|
model: resolvedModel,
|
|
439
455
|
maxTokens: 4096,
|
|
456
|
+
apiUrl,
|
|
457
|
+
token: apiKey,
|
|
458
|
+
sessionId,
|
|
459
|
+
agentId,
|
|
460
|
+
action,
|
|
461
|
+
usageIdempotencyKey,
|
|
462
|
+
billingTier,
|
|
463
|
+
customerPricingPolicy,
|
|
464
|
+
metadata,
|
|
465
|
+
fetchImpl: this.fetchImpl,
|
|
440
466
|
});
|
|
441
467
|
return {
|
|
442
468
|
text: result.text,
|
|
443
469
|
usage: result.usage,
|
|
470
|
+
usageLedger: result.usageLedger,
|
|
444
471
|
provider: "sentinelayer",
|
|
445
472
|
model: resolvedModel,
|
|
446
473
|
};
|
package/src/commands/review.js
CHANGED
|
@@ -120,6 +120,7 @@ async function executeReviewRun({
|
|
|
120
120
|
maxNoProgress: aiConfig.maxNoProgress,
|
|
121
121
|
warningThresholdPercent: aiConfig.warnAtPercent,
|
|
122
122
|
dryRun: Boolean(aiConfig.aiDryRun),
|
|
123
|
+
requireUsageLedger: Boolean(aiConfig.requireUsageLedger),
|
|
123
124
|
env: process.env,
|
|
124
125
|
});
|
|
125
126
|
}
|
|
@@ -146,6 +147,7 @@ async function executeReviewRun({
|
|
|
146
147
|
invocation: {
|
|
147
148
|
aiEnabled: Boolean(aiConfig.enable),
|
|
148
149
|
aiDryRun: Boolean(aiConfig.aiDryRun),
|
|
150
|
+
requireUsageLedger: Boolean(aiConfig.requireUsageLedger),
|
|
149
151
|
provider: aiConfig.provider,
|
|
150
152
|
model: aiConfig.model,
|
|
151
153
|
sessionId: aiConfig.sessionId,
|
|
@@ -209,6 +211,7 @@ async function executeReviewRun({
|
|
|
209
211
|
budget: aiLayer.budget,
|
|
210
212
|
cost: aiLayer.cost,
|
|
211
213
|
telemetry: aiLayer.telemetry,
|
|
214
|
+
billing: aiLayer.billing,
|
|
212
215
|
}
|
|
213
216
|
: {
|
|
214
217
|
enabled: false,
|
|
@@ -283,6 +286,7 @@ export function registerReviewCommand(program) {
|
|
|
283
286
|
.option("--staged", "Alias for --mode staged")
|
|
284
287
|
.option("--ai", "Enable AI reasoning layer over deterministic findings")
|
|
285
288
|
.option("--ai-dry-run", "Run AI layer in dry-run mode (no provider call)")
|
|
289
|
+
.option("--require-usage-ledger", "Fail --ai review if billing-grade session_usage cannot be recorded")
|
|
286
290
|
.option("--provider <name>", "AI provider override (openai|anthropic|google)")
|
|
287
291
|
.option("--model <id>", "AI model override")
|
|
288
292
|
.option("--api-key <key>", "Optional explicit API key override")
|
|
@@ -314,6 +318,7 @@ export function registerReviewCommand(program) {
|
|
|
314
318
|
aiConfig: {
|
|
315
319
|
enable: Boolean(options.ai),
|
|
316
320
|
aiDryRun: Boolean(options.aiDryRun),
|
|
321
|
+
requireUsageLedger: Boolean(options.requireUsageLedger),
|
|
317
322
|
provider: options.provider,
|
|
318
323
|
model: options.model,
|
|
319
324
|
apiKey: options.apiKey,
|
package/src/review/ai-review.js
CHANGED
|
@@ -486,6 +486,8 @@ export async function runAiReviewLayer({
|
|
|
486
486
|
warningThresholdPercent = 80,
|
|
487
487
|
systemPrompt = "",
|
|
488
488
|
dryRun = false,
|
|
489
|
+
requireUsageLedger = false,
|
|
490
|
+
usageRecorder = recordSessionUsage,
|
|
489
491
|
env = process.env,
|
|
490
492
|
} = {}) {
|
|
491
493
|
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
@@ -496,6 +498,7 @@ export async function runAiReviewLayer({
|
|
|
496
498
|
Math.floor(Number(maxFindings || DEFAULT_AI_MAX_FINDINGS))
|
|
497
499
|
);
|
|
498
500
|
const normalizedRunId = normalizeString(runId) || "review-ai";
|
|
501
|
+
const normalizedRequireUsageLedger = Boolean(requireUsageLedger);
|
|
499
502
|
|
|
500
503
|
const config = await loadConfig({ cwd: normalizedTargetPath, env });
|
|
501
504
|
let resolvedProvider = resolveProvider({
|
|
@@ -663,7 +666,7 @@ export async function runAiReviewLayer({
|
|
|
663
666
|
dryRun: Boolean(dryRun),
|
|
664
667
|
}),
|
|
665
668
|
});
|
|
666
|
-
sessionUsageLedger = await
|
|
669
|
+
sessionUsageLedger = await usageRecorder(
|
|
667
670
|
normalizedSessionId,
|
|
668
671
|
{
|
|
669
672
|
agentId: "audit-orchestrator",
|
|
@@ -693,6 +696,11 @@ export async function runAiReviewLayer({
|
|
|
693
696
|
ok: false,
|
|
694
697
|
reason: error instanceof Error ? error.message : String(error || "session_usage_failed"),
|
|
695
698
|
};
|
|
699
|
+
if (normalizedRequireUsageLedger) {
|
|
700
|
+
throw new Error(
|
|
701
|
+
`Review AI usage ledger recording failed: ${sessionUsageLedger.reason || "unknown"}.`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
696
704
|
}
|
|
697
705
|
|
|
698
706
|
let stopTelemetry = null;
|
|
@@ -754,6 +762,7 @@ export async function runAiReviewLayer({
|
|
|
754
762
|
provider: resolvedProvider,
|
|
755
763
|
model: resolvedModel,
|
|
756
764
|
dryRun: Boolean(dryRun),
|
|
765
|
+
requireUsageLedger: normalizedRequireUsageLedger,
|
|
757
766
|
usage,
|
|
758
767
|
pricingFound: modelCost.pricingFound,
|
|
759
768
|
sessionUsageLedger,
|