@rulvar/core 1.208.0 → 1.210.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
@@ -13131,6 +13171,24 @@ interface PreflightSpawnReport {
13131
13171
  * turn grows with the prompt, so this is a floor, never a cap.
13132
13172
  */
13133
13173
  turnFloorUsd?: number;
13174
+ /**
13175
+ * The loop's input floor over its projected turns, UNCACHED
13176
+ * (RV2007): the declared prompt floor (`estInputTokens`) re-billed
13177
+ * at the full input rate on every projected provider turn. A floor
13178
+ * over the static prefix: real prompts grow. Present when the shape
13179
+ * prices and projects more than one turn.
13180
+ */
13181
+ uncachedLoopInputFloorUsd?: number;
13182
+ /**
13183
+ * The same loop under the RV2006 cache policy: one cache write of
13184
+ * the prompt floor plus a cache read on every later turn, priced by
13185
+ * the row's cache rates. Present beside the uncached figure when
13186
+ * the row carries cache rates. The parity worker shape (36k-token
13187
+ * prompt floor, a long cycle) prices the difference at roughly
13188
+ * three to four times, the gap between four seats fitting a $6
13189
+ * envelope and three seats dying against it.
13190
+ */
13191
+ cachedLoopInputFloorUsd?: number;
13134
13192
  /** Executed-call ceiling across any tool mix; null = unlimited. */
13135
13193
  executedToolCallCeiling: number | null;
13136
13194
  /**
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;
@@ -24474,6 +24526,29 @@ function preflightEstimate(input) {
24474
24526
  const toolCeilings = toolCeilingsOf(limits);
24475
24527
  const executedToolCallCeiling = overallExecutedCeiling(limits, toolCeilings);
24476
24528
  const projectedProviderTurns = projectedProviderTurnsOf(limits, executedToolCallCeiling);
24529
+ let uncachedLoopInputFloorUsd;
24530
+ let cachedLoopInputFloorUsd;
24531
+ if (pricing !== void 0 && (spec.estInputTokens ?? 0) > 0 && Number.isFinite(projectedProviderTurns) && projectedProviderTurns > 1) {
24532
+ const loopInputTokens = spec.estInputTokens ?? 0;
24533
+ uncachedLoopInputFloorUsd = projectedProviderTurns * priceUsdOf(pricing, {
24534
+ inputTokens: loopInputTokens,
24535
+ outputTokens: 0,
24536
+ cacheReadTokens: 0,
24537
+ cacheWriteTokens: 0
24538
+ });
24539
+ if (pricing.cacheReadUsdPerMTok !== void 0 && pricing.cacheWriteUsdPerMTok !== void 0) cachedLoopInputFloorUsd = priceUsdOf(pricing, {
24540
+ inputTokens: projectedProviderTurns * loopInputTokens,
24541
+ outputTokens: 0,
24542
+ cacheReadTokens: (projectedProviderTurns - 1) * loopInputTokens,
24543
+ cacheWriteTokens: loopInputTokens
24544
+ });
24545
+ }
24546
+ if (caps?.promptCaching === "explicit" && engine.defaults?.cache?.mode === "off" && uncachedLoopInputFloorUsd !== void 0 && cachedLoopInputFloorUsd !== void 0 && projectedProviderTurns >= 4) say({
24547
+ severity: "warning",
24548
+ code: "uncached-long-loop",
24549
+ message: `spawn '${label}' projects ${String(projectedProviderTurns)} provider turns on the explicit-caching '${servedBy ?? ""}' with the cache policy OFF: the loop's input floor re-bills every turn (${uncachedLoopInputFloorUsd.toFixed(4)} USD uncached against ${cachedLoopInputFloorUsd.toFixed(4)} USD under the default policy); drop defaults.cache { mode: 'off' } or scope the opt-out to the profiles that need it`,
24550
+ spawn: label
24551
+ });
24477
24552
  for (const row of toolCeilings) {
24478
24553
  if (row.tool === ANY_TOOL) continue;
24479
24554
  const cost = limits.toolUnits?.costs?.[row.tool];
@@ -24610,6 +24685,8 @@ function preflightEstimate(input) {
24610
24685
  reserveSource,
24611
24686
  ...outputBound === void 0 ? {} : { maxOutputTokensPerTurn: outputBound },
24612
24687
  ...turnFloorUsd === void 0 ? {} : { turnFloorUsd },
24688
+ ...uncachedLoopInputFloorUsd === void 0 ? {} : { uncachedLoopInputFloorUsd },
24689
+ ...cachedLoopInputFloorUsd === void 0 ? {} : { cachedLoopInputFloorUsd },
24613
24690
  executedToolCallCeiling,
24614
24691
  projectedProviderTurns,
24615
24692
  toolCeilings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.208.0",
3
+ "version": "1.210.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",