@rulvar/core 1.65.0 → 1.67.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/dist/index.d.ts +59 -4
- package/dist/index.js +77 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5577,7 +5577,31 @@ interface CostReport {
|
|
|
5577
5577
|
type RunOutcome<R> = {
|
|
5578
5578
|
status: "ok" | "error" | "cancelled" | "exhausted" | "suspended";
|
|
5579
5579
|
value?: R;
|
|
5580
|
-
error?: WireError;
|
|
5580
|
+
error?: WireError;
|
|
5581
|
+
/**
|
|
5582
|
+
* The semantic completion lift, mirrored from `run:end` (RV-207 tail;
|
|
5583
|
+
* the 1.65.0 experiment review, P0.5): present when the workflow
|
|
5584
|
+
* reported semantic completion through the completion envelope
|
|
5585
|
+
* contract, an `ok`/`exhausted` run whose result value is an object
|
|
5586
|
+
* carrying a valid `completion` literal, or an `error` run whose typed
|
|
5587
|
+
* error data carries one (the orchestrator acceptance path emits
|
|
5588
|
+
* both). Transport status says whether the run ran; completion says
|
|
5589
|
+
* whether the work is COMPLETE: an accepted degraded run is `status:
|
|
5590
|
+
* 'ok'` with `completion: 'partial'`. The engine computes the lift
|
|
5591
|
+
* ONCE and both surfaces spread the same object, so the outcome and
|
|
5592
|
+
* the event can never disagree; a host reads completeness here
|
|
5593
|
+
* without parsing workflow-specific value shapes on the accepted path
|
|
5594
|
+
* or digging typed error data on the rejected one. Absent when the
|
|
5595
|
+
* workflow makes no completion claim.
|
|
5596
|
+
*/
|
|
5597
|
+
completion?: "complete" | "partial" | "rejected";
|
|
5598
|
+
/**
|
|
5599
|
+
* Settled child statuses by status name, lifted from the same
|
|
5600
|
+
* envelope (or typed error data) when it carries a valid record of
|
|
5601
|
+
* nonnegative integers; the mirror of the `run:end` field. Absent
|
|
5602
|
+
* otherwise.
|
|
5603
|
+
*/
|
|
5604
|
+
childStatusCounts?: Record<string, number>; /** Pipeline drops and onError:'null' losses; silent losses are forbidden. */
|
|
5581
5605
|
dropped: DroppedItem[]; /** Suspensions open at settle time (M2). */
|
|
5582
5606
|
pending: PendingExternal[];
|
|
5583
5607
|
usage: Usage;
|
|
@@ -8392,8 +8416,17 @@ declare class FileTranscriptStore implements TranscriptStore {
|
|
|
8392
8416
|
}
|
|
8393
8417
|
//#endregion
|
|
8394
8418
|
//#region src/engine/invoice.d.ts
|
|
8395
|
-
/**
|
|
8396
|
-
|
|
8419
|
+
/**
|
|
8420
|
+
* How far a row's identity goes toward provider-side reconciliation.
|
|
8421
|
+
* `provider-id-present` asserts exactly what it names: the adapter
|
|
8422
|
+
* surfaced the provider's response id for this call, the join key a
|
|
8423
|
+
* host needs to line the row up against a provider statement. It does
|
|
8424
|
+
* NOT assert any statement, amount, or usage match: the library never
|
|
8425
|
+
* sees provider billing data, so those deeper reconciliation tiers are
|
|
8426
|
+
* host-side joins keyed on `responseId`, not verdicts this export can
|
|
8427
|
+
* make.
|
|
8428
|
+
*/
|
|
8429
|
+
type InvoiceReconciliation = "provider-id-present" | "missing-provider-id" | "unconfirmed" | "unattributed";
|
|
8397
8430
|
/** One billable provider call (or an unattributed usage remainder). */
|
|
8398
8431
|
interface InvoiceRow {
|
|
8399
8432
|
/** The terminal journal entry the row folds from. */
|
|
@@ -8412,6 +8445,15 @@ interface InvoiceRow {
|
|
|
8412
8445
|
usageApprox?: boolean;
|
|
8413
8446
|
/** This row priced at its own model's rate; absent when no price row covers it. */
|
|
8414
8447
|
usd?: number;
|
|
8448
|
+
/**
|
|
8449
|
+
* The additive FinOps column: this row's share of `totalUsd`, always
|
|
8450
|
+
* present (zero for rows on unpriced models). Shares are computed
|
|
8451
|
+
* within the row's own (entry, serving model) slice of the same
|
|
8452
|
+
* gross fold the totals run, proportional to per-row `usd`, and one
|
|
8453
|
+
* row absorbs the IEEE rounding dust, so summing `allocatedUsd` over
|
|
8454
|
+
* `rows` reproduces `totalUsd` exactly where summing `usd` does not.
|
|
8455
|
+
*/
|
|
8456
|
+
allocatedUsd: number;
|
|
8415
8457
|
/** The row lies under an abandoned subtree: in grossUsd, not in netUsd. */
|
|
8416
8458
|
abandoned?: true;
|
|
8417
8459
|
reconciliation: InvoiceReconciliation;
|
|
@@ -8425,12 +8467,25 @@ interface InvoiceExport {
|
|
|
8425
8467
|
netUsd: number;
|
|
8426
8468
|
/** The abandoned share: totalUsd - netUsd, equals CostReport.abandoned.usd. */
|
|
8427
8469
|
abandonedUsd: number;
|
|
8470
|
+
/**
|
|
8471
|
+
* How per-row `usd` was computed: each call priced individually at
|
|
8472
|
+
* the current table's rates. Always `'per-call'` today; declared so
|
|
8473
|
+
* finance tooling never has to guess the basis.
|
|
8474
|
+
*/
|
|
8475
|
+
pricingBasis: "per-call";
|
|
8476
|
+
/**
|
|
8477
|
+
* Always true: per-call `usd` values need not sum to `totalUsd`,
|
|
8478
|
+
* because a nonlinear price table prices an aggregate differently
|
|
8479
|
+
* from the sum of its parts. Sum `allocatedUsd` instead; it exists
|
|
8480
|
+
* precisely so a column sums to the total.
|
|
8481
|
+
*/
|
|
8482
|
+
rowUsdNonAdditive: true;
|
|
8428
8483
|
/** Usage on models absent from pricing, net and abandoned alike; never a silent zero. */
|
|
8429
8484
|
unpriced: Array<{
|
|
8430
8485
|
model: string;
|
|
8431
8486
|
usage: Usage;
|
|
8432
8487
|
}>;
|
|
8433
|
-
/** Rows whose reconciliation is not '
|
|
8488
|
+
/** Rows whose reconciliation is not 'provider-id-present'. */
|
|
8434
8489
|
reconciliationFailures: number;
|
|
8435
8490
|
/** Present and true when any contributing entry carried approximate usage. */
|
|
8436
8491
|
usageApprox?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -8233,9 +8233,12 @@ function costReportFromJournal(entries, priceUsd) {
|
|
|
8233
8233
|
* against the provider's invoice. The totals are the SAME slice fold
|
|
8234
8234
|
* `costReportFromJournal` runs, so `totalUsd` here equals
|
|
8235
8235
|
* `CostReport.grossUsd` (and `netUsd` equals `CostReport.totalUsd`)
|
|
8236
|
-
* exactly, never approximately
|
|
8237
|
-
*
|
|
8238
|
-
*
|
|
8236
|
+
* exactly, never approximately. The export is self-describing about
|
|
8237
|
+
* its pricing: `pricingBasis` says per-row `usd` prices each call
|
|
8238
|
+
* individually, `rowUsdNonAdditive` says those values need not sum to
|
|
8239
|
+
* `totalUsd` (a nonlinear price table, long-context tiers, prices a
|
|
8240
|
+
* split differently from its sum), and per-row `allocatedUsd` is the
|
|
8241
|
+
* additive column whose flat sum reproduces `totalUsd` exactly.
|
|
8239
8242
|
*
|
|
8240
8243
|
* Coverage is loss-free by construction: an entry whose records do not
|
|
8241
8244
|
* cover its usage total (a resume restored from a checkpoint written
|
|
@@ -8274,6 +8277,64 @@ function usageRemainder(total, records) {
|
|
|
8274
8277
|
if (reasoning > 0) remainder.reasoningTokens = reasoning;
|
|
8275
8278
|
return USAGE_FIELDS.some((field) => remainder[field] > 0) || (remainder.reasoningTokens ?? 0) > 0 ? remainder : void 0;
|
|
8276
8279
|
}
|
|
8280
|
+
/** One allocation pool per (entry, serving model) slice of the gross fold. */
|
|
8281
|
+
function allocationKey(entrySeq, servedBy) {
|
|
8282
|
+
return `${String(entrySeq)} ${servedBy}`;
|
|
8283
|
+
}
|
|
8284
|
+
/** The token-count fallback weight when every row of a pool priced to zero. */
|
|
8285
|
+
function totalTokens(usage) {
|
|
8286
|
+
return usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + (usage.reasoningTokens ?? 0);
|
|
8287
|
+
}
|
|
8288
|
+
/**
|
|
8289
|
+
* The additive allocation pass: distributes each (entry, model) slice
|
|
8290
|
+
* total of the SAME gross fold the invoice totals run across that
|
|
8291
|
+
* slice's rows, proportional to per-row `usd` (token counts when every
|
|
8292
|
+
* row priced to zero, equal shares when even those are zero), then
|
|
8293
|
+
* lets the largest row absorb the IEEE rounding dust of the fold's own
|
|
8294
|
+
* association so the flat sum over `rows` reproduces `totalUsd`
|
|
8295
|
+
* exactly. Rows on unpriced models keep zero: their spend is in
|
|
8296
|
+
* `unpriced`, not in `totalUsd`.
|
|
8297
|
+
*/
|
|
8298
|
+
function allocateRows(rows, entries, priceUsd, totalUsd) {
|
|
8299
|
+
if (rows.length === 0) return;
|
|
8300
|
+
const targets = /* @__PURE__ */ new Map();
|
|
8301
|
+
for (const entry of entries) {
|
|
8302
|
+
if (entry.status === "running" || entry.usage === void 0) continue;
|
|
8303
|
+
for (const slice of priceEntryUsage(entry, priceUsd).priced) {
|
|
8304
|
+
const key = allocationKey(entry.seq, slice.servedBy);
|
|
8305
|
+
targets.set(key, (targets.get(key) ?? 0) + slice.usd);
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
const pools = /* @__PURE__ */ new Map();
|
|
8309
|
+
for (const row of rows) {
|
|
8310
|
+
const key = allocationKey(row.entrySeq, row.servedBy);
|
|
8311
|
+
const pool = pools.get(key);
|
|
8312
|
+
if (pool === void 0) pools.set(key, [row]);
|
|
8313
|
+
else pool.push(row);
|
|
8314
|
+
}
|
|
8315
|
+
for (const [key, members] of pools) {
|
|
8316
|
+
const target = targets.get(key) ?? 0;
|
|
8317
|
+
if (target === 0) continue;
|
|
8318
|
+
let weights = members.map((row) => row.usd ?? 0);
|
|
8319
|
+
let sum = weights.reduce((acc, weight) => acc + weight, 0);
|
|
8320
|
+
if (sum === 0) {
|
|
8321
|
+
weights = members.map((row) => totalTokens(row.usage));
|
|
8322
|
+
sum = weights.reduce((acc, weight) => acc + weight, 0);
|
|
8323
|
+
}
|
|
8324
|
+
members.forEach((row, index) => {
|
|
8325
|
+
const weight = weights[index] ?? 0;
|
|
8326
|
+
row.allocatedUsd = sum === 0 ? target / members.length : target * (weight / sum);
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
let absorber;
|
|
8330
|
+
for (const row of rows) if (absorber === void 0 || row.allocatedUsd > absorber.allocatedUsd) absorber = row;
|
|
8331
|
+
if (absorber === void 0) return;
|
|
8332
|
+
for (let pass = 0; pass < 8; pass += 1) {
|
|
8333
|
+
const flat = rows.reduce((acc, row) => acc + row.allocatedUsd, 0);
|
|
8334
|
+
if (flat === totalUsd) break;
|
|
8335
|
+
absorber.allocatedUsd += totalUsd - flat;
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8277
8338
|
/** A single row priced at its own model's rate; broken rates fold as unpriced. */
|
|
8278
8339
|
function rowUsd(priceUsd, servedBy, usage) {
|
|
8279
8340
|
const usd = priceUsd(servedBy, usage);
|
|
@@ -8311,8 +8372,9 @@ function invoiceFromJournal(entries, priceUsd) {
|
|
|
8311
8372
|
usage: record.usage,
|
|
8312
8373
|
...record.usageApprox === true ? { usageApprox: true } : {},
|
|
8313
8374
|
...usd === void 0 ? {} : { usd },
|
|
8375
|
+
allocatedUsd: 0,
|
|
8314
8376
|
...mark,
|
|
8315
|
-
reconciliation: record.responseId !== void 0 ? "
|
|
8377
|
+
reconciliation: record.responseId !== void 0 ? "provider-id-present" : record.outcome === "ok" ? "missing-provider-id" : "unconfirmed"
|
|
8316
8378
|
});
|
|
8317
8379
|
}
|
|
8318
8380
|
if (records.length === 0) {
|
|
@@ -8327,6 +8389,7 @@ function invoiceFromJournal(entries, priceUsd) {
|
|
|
8327
8389
|
usage: slice.usage,
|
|
8328
8390
|
...entry.usageApprox === true ? { usageApprox: true } : {},
|
|
8329
8391
|
...usd === void 0 ? {} : { usd },
|
|
8392
|
+
allocatedUsd: 0,
|
|
8330
8393
|
...mark,
|
|
8331
8394
|
reconciliation: "unattributed"
|
|
8332
8395
|
});
|
|
@@ -8344,19 +8407,23 @@ function invoiceFromJournal(entries, priceUsd) {
|
|
|
8344
8407
|
usage: remainder,
|
|
8345
8408
|
...entry.usageApprox === true ? { usageApprox: true } : {},
|
|
8346
8409
|
...usd === void 0 ? {} : { usd },
|
|
8410
|
+
allocatedUsd: 0,
|
|
8347
8411
|
...mark,
|
|
8348
8412
|
reconciliation: "unattributed"
|
|
8349
8413
|
});
|
|
8350
8414
|
}
|
|
8351
8415
|
}
|
|
8416
|
+
allocateRows(rows, entries, priceUsd, report.grossUsd);
|
|
8352
8417
|
const usageApprox = report.usageApprox === true || report.abandoned.usageApprox === true;
|
|
8353
8418
|
return {
|
|
8354
8419
|
rows,
|
|
8355
8420
|
totalUsd: report.grossUsd,
|
|
8356
8421
|
netUsd: report.totalUsd,
|
|
8357
8422
|
abandonedUsd: report.abandoned.usd,
|
|
8423
|
+
pricingBasis: "per-call",
|
|
8424
|
+
rowUsdNonAdditive: true,
|
|
8358
8425
|
unpriced: [...report.unpriced, ...report.abandoned.unpriced],
|
|
8359
|
-
reconciliationFailures: rows.filter((row) => row.reconciliation !== "
|
|
8426
|
+
reconciliationFailures: rows.filter((row) => row.reconciliation !== "provider-id-present").length,
|
|
8360
8427
|
...usageApprox ? { usageApprox: true } : {}
|
|
8361
8428
|
};
|
|
8362
8429
|
}
|
|
@@ -18488,6 +18555,11 @@ function createEngine(options) {
|
|
|
18488
18555
|
};
|
|
18489
18556
|
if (value !== void 0 && (status === "ok" || status === "exhausted")) outcome.value = value;
|
|
18490
18557
|
if (wireError !== void 0) outcome.error = wireError;
|
|
18558
|
+
const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcome.value : status === "error" ? wireError?.data : void 0);
|
|
18559
|
+
if (lifted !== void 0) {
|
|
18560
|
+
outcome.completion = lifted.completion;
|
|
18561
|
+
if (lifted.childStatusCounts !== void 0) outcome.childStatusCounts = lifted.childStatusCounts;
|
|
18562
|
+
}
|
|
18491
18563
|
let settlementFailure;
|
|
18492
18564
|
if (resumeCtx?.strict !== true) {
|
|
18493
18565
|
const priorCount = resumeCtx?.priorEntries.length ?? 0;
|
|
@@ -18532,7 +18604,6 @@ function createEngine(options) {
|
|
|
18532
18604
|
level: "warn",
|
|
18533
18605
|
msg: `settlement write failed (${settlementFailure.stage}); handle.result rejects with SettlementError; resume re-settles by replay without a provider call`
|
|
18534
18606
|
}, rootSpanId);
|
|
18535
|
-
const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcome.value : status === "error" ? wireError?.data : void 0);
|
|
18536
18607
|
bus.emit({
|
|
18537
18608
|
type: "run:end",
|
|
18538
18609
|
status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.0",
|
|
4
4
|
"description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|