@rulvar/core 1.209.0 → 1.211.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 CHANGED
@@ -5551,6 +5551,20 @@ interface RunAgentOptions<S extends SchemaSpec = JsonSchema> {
5551
5551
  * else. See applyCachePolicy for the exact shape.
5552
5552
  */
5553
5553
  cache?: CachePolicy;
5554
+ /**
5555
+ * The incremental billing seam (RV2008): called with every
5556
+ * ProviderCallRecord the moment the wire call settles and the record
5557
+ * is minted, so the caller can journal it while the invocation is
5558
+ * still running. The parity rerun lost ~$0.99 of root dispatches
5559
+ * because records rode ONLY the terminal entry and the process died
5560
+ * before one existed; with the seam the crash window shrinks to the
5561
+ * single in-flight turn. Restored records (a checkpoint reboot)
5562
+ * never re-emit: they were journaled by the segment that minted
5563
+ * them.
5564
+ */
5565
+ billing?: {
5566
+ onProviderCall: (record: ProviderCallRecord) => void;
5567
+ };
5554
5568
  events?: RuntimeEventSink;
5555
5569
  transcript?: {
5556
5570
  mintRef(): string;
@@ -12630,6 +12644,32 @@ interface InvoiceExport {
12630
12644
  usageApprox?: boolean;
12631
12645
  /** The rates provenance (RV407); present when the caller declared it. */
12632
12646
  pricing?: InvoicePricingProvenance;
12647
+ /**
12648
+ * The unsettled lane (RV2008): dispatches whose agent is still
12649
+ * RUNNING at the journal's edge, recovered from the incremental
12650
+ * provider-call rows the loop journals as each wire call settles.
12651
+ * Deliberately OUTSIDE the settled totals above: run_settle stays
12652
+ * the billing boundary, and this section prices what the crash
12653
+ * window preserved anyway, the ~$0.99 of parity root dispatches
12654
+ * that used to live only in process memory. Present only when such
12655
+ * rows exist; a journal whose roster is closed never carries it.
12656
+ */
12657
+ unsettled?: {
12658
+ usd: number;
12659
+ wireRequests: number;
12660
+ rows: Array<{
12661
+ agentRef: number;
12662
+ scope: string;
12663
+ ordinal: number;
12664
+ servedBy: ModelRef;
12665
+ role: string;
12666
+ attempt: number;
12667
+ outcome: string;
12668
+ usage: Usage;
12669
+ usd?: number;
12670
+ responseId?: string;
12671
+ }>;
12672
+ };
12633
12673
  }
12634
12674
  /**
12635
12675
  * The pure invoice fold. Pass the same entries and price table you
package/dist/index.js CHANGED
@@ -12511,6 +12511,7 @@ async function runAgent(options) {
12511
12511
  if (outcome.aborted !== void 0) record.aborted = outcome.aborted;
12512
12512
  else if (outcome.wireError !== void 0) record.errorCode = outcome.wireError.code;
12513
12513
  providerCalls.push(record);
12514
+ options.billing?.onProviderCall(record);
12514
12515
  addCallUsd(site.role, target.resolved.ref, accounted);
12515
12516
  const limited = outcome.wireError?.data;
12516
12517
  if (limited?.kind === "rate-limit" && typeof limited.reportedLimits === "object" && limited.reportedLimits !== null) rateLimitObservations.set(`${target.adapter.id}:${target.resolved.model}`, {
@@ -14809,6 +14810,35 @@ function invoiceFromJournal(entries, priceUsd, options) {
14809
14810
  }
14810
14811
  }
14811
14812
  const unallocatedUsd = allocateRows(rows, entries, priceUsd, report.grossUsd);
14813
+ const terminalRefs = new Set(entries.filter((entry) => entry.kind === "agent" && entry.status !== "running").map((entry) => entry.ref));
14814
+ const runningBySeq = new Map(entries.filter((entry) => entry.kind === "agent" && entry.status === "running").map((entry) => [entry.seq, entry]));
14815
+ const unsettledRows = [];
14816
+ for (const entry of entries) {
14817
+ if (entry.kind !== "decision") continue;
14818
+ const value = entry.value;
14819
+ if (value?.decisionType !== "provider-call" || typeof value.agentRef !== "number" || terminalRefs.has(value.agentRef)) continue;
14820
+ const running = runningBySeq.get(value.agentRef);
14821
+ const record = value.record;
14822
+ if (running === void 0 || record?.usage === void 0 || typeof record.ordinal !== "number" || typeof record.servedBy !== "string") continue;
14823
+ const usd = rowUsd(priceUsd, record.servedBy, record.usage, entry.seq);
14824
+ unsettledRows.push({
14825
+ agentRef: value.agentRef,
14826
+ scope: running.scope,
14827
+ ordinal: record.ordinal,
14828
+ servedBy: record.servedBy,
14829
+ role: typeof record.role === "string" ? record.role : "loop",
14830
+ attempt: typeof record.attempt === "number" ? record.attempt : 1,
14831
+ outcome: typeof record.outcome === "string" ? record.outcome : "ok",
14832
+ usage: record.usage,
14833
+ ...usd === void 0 ? {} : { usd },
14834
+ ...typeof record.responseId === "string" ? { responseId: record.responseId } : {}
14835
+ });
14836
+ }
14837
+ const unsettled = unsettledRows.length === 0 ? void 0 : {
14838
+ usd: unsettledRows.reduce((sum, row) => sum + (row.usd ?? 0), 0),
14839
+ wireRequests: unsettledRows.length,
14840
+ rows: unsettledRows
14841
+ };
14812
14842
  const usageApprox = report.usageApprox === true || report.abandoned.usageApprox === true;
14813
14843
  const invoice = {
14814
14844
  rows,
@@ -14821,6 +14851,7 @@ function invoiceFromJournal(entries, priceUsd, options) {
14821
14851
  unpriced: [...report.unpriced, ...report.abandoned.unpriced],
14822
14852
  reconciliationFailures: rows.filter((row) => row.reconciliation !== "provider-id-present").length,
14823
14853
  cardinality: cardinalityOf(rows),
14854
+ ...unsettled === void 0 ? {} : { unsettled },
14824
14855
  ...(() => {
14825
14856
  const count = rows.filter((row) => row.usageUnknown === true).length;
14826
14857
  return count === 0 ? {} : { usageUnknownRows: count };
@@ -18003,6 +18034,27 @@ function createCtx(internals, rootWorkflow) {
18003
18034
  const cachePolicy = opts.cache ?? profile?.cache ?? internals.defaults.cache;
18004
18035
  if (cachePolicy !== void 0) runAgentOptions.cache = cachePolicy;
18005
18036
  }
18037
+ runAgentOptions.billing = { onProviderCall: (record) => {
18038
+ internals.replayer.appendSinglePhase({
18039
+ scope: state.scope,
18040
+ key: `pc:${String(running.seq)}:${String(record.ordinal)}`,
18041
+ kind: "decision",
18042
+ status: "ok",
18043
+ spanId,
18044
+ site: "provider-call",
18045
+ value: {
18046
+ decisionType: "provider-call",
18047
+ agentRef: running.seq,
18048
+ record
18049
+ }
18050
+ }).catch((thrown) => {
18051
+ internals.events.emit({
18052
+ type: "log",
18053
+ level: "warn",
18054
+ msg: `incremental billing row failed to append; the terminal entry remains the canonical record (${thrown instanceof Error ? thrown.message : String(thrown)})`
18055
+ }, spanId);
18056
+ });
18057
+ } };
18006
18058
  runAgentOptions.summarize = summarize;
18007
18059
  if (profile?.compaction !== void 0) runAgentOptions.compaction = profile.compaction;
18008
18060
  if (profile?.evidenceContract !== void 0) runAgentOptions.evidenceContract = profile.evidenceContract;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.209.0",
3
+ "version": "1.211.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",