@rulvar/core 1.19.0 → 1.20.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
@@ -602,10 +602,20 @@ type AbandonPayload = {
602
602
  retainCheckpoint?: boolean; /** Default false; counts against the pin cap (DEF-5). */
603
603
  retainWorktree?: boolean;
604
604
  };
605
- /** One serving model's slice of a multi-model agent call's usage. */
605
+ /**
606
+ * One (invocation role, serving model) slice of an agent call's usage.
607
+ * `role` is the phase that PAID the slice (v1.19.0 review P1-2: the
608
+ * loop, extract, finalize, and summarize phases of one agent call must
609
+ * land in their own CostReport.byRole buckets even when a single model
610
+ * serves several of them). Absent on slices written before roles
611
+ * shipped: readers fall back to the entry's primary
612
+ * `costAttribution.role`, exactly like the other documented fallbacks.
613
+ * Policy, never identity.
614
+ */
606
615
  interface UsageSlice {
607
616
  servedBy: ModelRef;
608
617
  usage: Usage;
618
+ role?: InvocationRole;
609
619
  }
610
620
  /**
611
621
  * Cost-attribution facts a live run knows at settlement and a pure
@@ -2760,11 +2770,13 @@ interface AgentResult<T> {
2760
2770
  */
2761
2771
  servedBy: ModelRef;
2762
2772
  /**
2763
- * Present only when the call spanned MORE THAN ONE serving model (the
2764
- * loop, extract, finalize, and summarize roles resolve independently):
2765
- * usage split per model, so `costUsd` and every cost bucket price each
2766
- * slice at its own rate. Absent for a single-model call, which
2767
- * (usage, servedBy) already describes exactly.
2773
+ * Present only when the call spanned MORE THAN ONE (invocation role,
2774
+ * serving model) pair (the loop, extract, finalize, and summarize
2775
+ * roles resolve independently): usage split per (role, model), so
2776
+ * `costUsd` and every cost bucket price each slice at its own rate
2777
+ * and `CostReport.byRole` attributes each phase to its own bucket
2778
+ * (v1.19.0 review P1-2). Absent for a single-phase single-model call,
2779
+ * which (usage, servedBy) already describes exactly.
2768
2780
  */
2769
2781
  usageByModel?: UsageSlice[];
2770
2782
  transcriptRef: string;
package/dist/index.js CHANGED
@@ -6386,7 +6386,8 @@ function costReportFromJournal(entries, priceUsd) {
6386
6386
  byPhase[phase] = (byPhase[phase] ?? 0) + priced.usd;
6387
6387
  const agentType = facts?.agentType ?? "unknown";
6388
6388
  byAgentType[agentType] = (byAgentType[agentType] ?? 0) + priced.usd;
6389
- byRole[facts?.role ?? "loop"] += priced.usd;
6389
+ const primaryRole = facts?.role ?? "loop";
6390
+ for (const slice of priced.priced) byRole[slice.role ?? primaryRole] += slice.usd;
6390
6391
  if (facts?.budgetAccount !== void 0 && isOrchestratorAccount(facts.budgetAccount)) {
6391
6392
  orchestratorSpentUsd += priced.usd;
6392
6393
  if (facts.finalizeReserve === true) reserveUsedUsd += priced.usd;
@@ -8058,7 +8059,17 @@ async function runAgent(options) {
8058
8059
  }]
8059
8060
  }];
8060
8061
  let totalUsage = ZERO_USAGE$1;
8061
- const usageByModel = /* @__PURE__ */ new Map();
8062
+ const primaryRole = options.role ?? "loop";
8063
+ const usageByPhaseModel = /* @__PURE__ */ new Map();
8064
+ const addPhaseUsage = (role, ref, usage) => {
8065
+ const key = `${role}${ref}`;
8066
+ const prior = usageByPhaseModel.get(key);
8067
+ usageByPhaseModel.set(key, {
8068
+ role,
8069
+ servedBy: ref,
8070
+ usage: addUsage(prior?.usage ?? ZERO_USAGE$1, usage)
8071
+ });
8072
+ };
8062
8073
  let turns = 0;
8063
8074
  let schemaAttempts = 0;
8064
8075
  let output = null;
@@ -8093,13 +8104,14 @@ async function runAgent(options) {
8093
8104
  usage: restored.usage
8094
8105
  }];
8095
8106
  for (const slice of restoredSlices) {
8096
- usageByModel.set(slice.servedBy, addUsage(usageByModel.get(slice.servedBy) ?? ZERO_USAGE$1, slice.usage));
8107
+ addPhaseUsage(slice.role ?? primaryRole, slice.servedBy, slice.usage);
8097
8108
  options.budget?.onUsage(slice.usage, slice.servedBy);
8098
8109
  }
8099
8110
  }
8100
- const usageSlices = () => [...usageByModel].map(([sliceServedBy, usage]) => ({
8111
+ const usageSlices = () => [...usageByPhaseModel.values()].map(({ role, servedBy: sliceServedBy, usage }) => ({
8101
8112
  servedBy: sliceServedBy,
8102
- usage
8113
+ usage,
8114
+ role
8103
8115
  }));
8104
8116
  /**
8105
8117
  * Every slice priced at ITS OWN model's rate. An unpriced model
@@ -8110,7 +8122,7 @@ async function runAgent(options) {
8110
8122
  const price = options.priceUsd;
8111
8123
  if (price === void 0) return 0;
8112
8124
  let usd = 0;
8113
- for (const [sliceServedBy, usage] of usageByModel) usd += price(sliceServedBy, usage) ?? 0;
8125
+ for (const slice of usageByPhaseModel.values()) usd += price(slice.servedBy, slice.usage) ?? 0;
8114
8126
  return usd;
8115
8127
  };
8116
8128
  const saveBoundary = async (pending) => {
@@ -8343,17 +8355,17 @@ async function runAgent(options) {
8343
8355
  agentType,
8344
8356
  label: options.label,
8345
8357
  model: servedBy,
8346
- role: options.role ?? "loop"
8358
+ role: primaryRole
8347
8359
  });
8348
8360
  let invariantViolation;
8349
- const recordUsage = (usage, reported, adapterId, ref) => {
8361
+ const recordUsage = (usage, reported, adapterId, ref, role) => {
8350
8362
  try {
8351
8363
  assertUsageInvariant(usage, adapterId);
8352
8364
  } catch (thrown) {
8353
8365
  invariantViolation = thrown instanceof Error ? thrown.message : String(thrown);
8354
8366
  }
8355
8367
  totalUsage = addUsage(totalUsage, usage);
8356
- usageByModel.set(ref, addUsage(usageByModel.get(ref) ?? ZERO_USAGE$1, usage));
8368
+ addPhaseUsage(role, ref, usage);
8357
8369
  const remainder = {
8358
8370
  inputTokens: Math.max(0, usage.inputTokens - reported.inputTokens),
8359
8371
  outputTokens: Math.max(0, usage.outputTokens - reported.outputTokens),
@@ -8375,7 +8387,7 @@ async function runAgent(options) {
8375
8387
  inner: for (;;) {
8376
8388
  const dispatch = () => streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target));
8377
8389
  const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch));
8378
- recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref);
8390
+ recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role);
8379
8391
  tries += 1;
8380
8392
  const retryClass = outcome.aborted === "idle" ? "transport" : outcome.wireError === void 0 ? void 0 : retryClassOf(outcome.wireError);
8381
8393
  if (retryClass === void 0) return {
@@ -8452,6 +8464,7 @@ async function runAgent(options) {
8452
8464
  let loopDispatch;
8453
8465
  try {
8454
8466
  loopDispatch = await dispatchPhase({
8467
+ role: primaryRole,
8455
8468
  chain: loopChain,
8456
8469
  cursor: loopCursor,
8457
8470
  requestFor: (target) => {
@@ -8643,6 +8656,7 @@ async function runAgent(options) {
8643
8656
  let summaryDispatch;
8644
8657
  try {
8645
8658
  summaryDispatch = await dispatchPhase({
8659
+ role: "summarize",
8646
8660
  chain: [{
8647
8661
  adapter: options.summarize.adapter,
8648
8662
  resolved: options.summarize.resolved
@@ -8822,6 +8836,7 @@ async function runAgent(options) {
8822
8836
  let finalizeDispatch;
8823
8837
  try {
8824
8838
  finalizeDispatch = await dispatchPhase({
8839
+ role: "finalize",
8825
8840
  chain: [{
8826
8841
  adapter: options.finalize.adapter,
8827
8842
  resolved: options.finalize.resolved
@@ -8944,6 +8959,7 @@ async function runAgent(options) {
8944
8959
  let extractDispatch;
8945
8960
  try {
8946
8961
  extractDispatch = await dispatchPhase({
8962
+ role: "extract",
8947
8963
  chain: extractChain,
8948
8964
  cursor: extractCursor,
8949
8965
  requestFor: (target) => {
@@ -9047,7 +9063,7 @@ async function runAgent(options) {
9047
9063
  servedBy,
9048
9064
  transcriptRef
9049
9065
  };
9050
- if (usageByModel.size > 1) result.usageByModel = usageSlices();
9066
+ if (usageByPhaseModel.size > 1) result.usageByModel = usageSlices();
9051
9067
  if (agentError !== void 0) result.error = agentError;
9052
9068
  if (escalationRequest !== void 0) result.escalationRequest = escalationRequest;
9053
9069
  if (abortClass !== void 0) result.abortClass = abortClass;
@@ -11062,10 +11078,11 @@ function createCtx(internals, rootWorkflow) {
11062
11078
  });
11063
11079
  }
11064
11080
  const usd = result.costUsd;
11065
- for (const slice of result.usageByModel ?? [{
11081
+ const attributionSlices = result.usageByModel ?? [{
11066
11082
  servedBy: result.servedBy,
11067
11083
  usage: result.usage
11068
- }]) {
11084
+ }];
11085
+ for (const slice of attributionSlices) {
11069
11086
  const priced = internals.priceUsd(slice.servedBy, slice.usage);
11070
11087
  if (priced === void 0) {
11071
11088
  internals.cost.unpriced.push({
@@ -11075,10 +11092,11 @@ function createCtx(internals, rootWorkflow) {
11075
11092
  continue;
11076
11093
  }
11077
11094
  bump(internals.cost.byModel, slice.servedBy, priced);
11095
+ const sliceRole = slice.role ?? primaryRole;
11096
+ internals.cost.byRole.set(sliceRole, (internals.cost.byRole.get(sliceRole) ?? 0) + priced);
11078
11097
  }
11079
11098
  bump(internals.cost.byPhase, state.phase ?? "", usd);
11080
11099
  bump(internals.cost.byAgentType, agentType, usd);
11081
- internals.cost.byRole.set(primaryRole, (internals.cost.byRole.get(primaryRole) ?? 0) + usd);
11082
11100
  if (result.error?.kind === "budget" || internals.budget.exhausted && result.status !== "ok") {
11083
11101
  const diagnostics = internals.budget.exhaustionDiagnostics(state.budgetScope ?? "run");
11084
11102
  const crossed = diagnostics.crossed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.19.0",
3
+ "version": "1.20.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",