@rayadesu/dsh-client-ui-billing 0.3.12 → 0.3.13

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/lib/client.js CHANGED
@@ -4108,6 +4108,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4108
4108
  "toppedUp": string()
4109
4109
  }))
4110
4110
  });
4111
+ const _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_0$schema = intersection(string(), unknown());
4112
+ const _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_1$schema = union([
4113
+ _undefined(),
4114
+ literal(false),
4115
+ literal(true)
4116
+ ]);
4117
+ const _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_result$schema = object({
4118
+ "total": number(),
4119
+ "models": array(object({
4120
+ "model": string(),
4121
+ "displayName": string(),
4122
+ "cost": number(),
4123
+ "peakCost": number(),
4124
+ "offPeakCost": number(),
4125
+ "cacheHitInputTokens": number(),
4126
+ "cacheMissInputTokens": number(),
4127
+ "outputTokens": number(),
4128
+ "cacheHitInputCost": number(),
4129
+ "cacheMissInputCost": number(),
4130
+ "outputCost": number()
4131
+ })),
4132
+ "isSubagent": boolean(),
4133
+ "crossedDay": boolean()
4134
+ });
4111
4135
  const _rayadesu_dsh_llm_billing_billing_getSessionSpend_parameter_0$schema = intersection(string(), unknown());
4112
4136
  const _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema = object({
4113
4137
  "total": number(),
@@ -4138,7 +4162,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4138
4162
  const _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema = object({ "sessions": array(object({
4139
4163
  "sessionId": intersection(string(), unknown()),
4140
4164
  "title": union([literal(null), string()]),
4141
- "total": number()
4165
+ "total": number(),
4166
+ "ownTotal": number()
4142
4167
  })) });
4143
4168
  const _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema = union([
4144
4169
  _undefined(),
@@ -4181,17 +4206,59 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4181
4206
  codec: {
4182
4207
  mode: "strict",
4183
4208
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getBalance:force",
4184
- schema: _rayadesu_dsh_llm_billing_billing_getBalance_parameter_0$schema
4209
+ schema: _rayadesu_dsh_llm_billing_billing_getBalance_parameter_0$schema,
4210
+ create: () => _rayadesu_dsh_llm_billing_billing_getBalance_parameter_0$schema
4185
4211
  }
4186
4212
  }],
4187
4213
  result: {
4188
4214
  mode: "strict",
4189
4215
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekBalance",
4190
- schema: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema
4216
+ schema: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema,
4217
+ create: () => _rayadesu_dsh_llm_billing_billing_getBalance_result$schema
4218
+ },
4219
+ sourceLocation: {
4220
+ "file": "packages/llm-billing/src/balance.ts",
4221
+ "line": 172,
4222
+ "column": 3
4223
+ }
4224
+ },
4225
+ {
4226
+ id: "@rayadesu/dsh-llm-billing#billing/getDelegatedSpend",
4227
+ service: "billing",
4228
+ namespace: "billing",
4229
+ method: "getDelegatedSpend",
4230
+ invocation: { kind: "direct" },
4231
+ parameters: [{
4232
+ name: "sessionId",
4233
+ wire: "sessionId",
4234
+ source: "json",
4235
+ codec: {
4236
+ mode: "strict",
4237
+ typeSymbol: "@rayadesu/dsh-llm-billing#billing/getDelegatedSpend:sessionId",
4238
+ schema: _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_0$schema,
4239
+ create: () => _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_0$schema
4240
+ }
4241
+ }, {
4242
+ name: "force",
4243
+ wire: "force",
4244
+ source: "json",
4245
+ acceptsUndefined: true,
4246
+ codec: {
4247
+ mode: "strict",
4248
+ typeSymbol: "@rayadesu/dsh-llm-billing#billing/getDelegatedSpend:force",
4249
+ schema: _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_1$schema,
4250
+ create: () => _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_parameter_1$schema
4251
+ }
4252
+ }],
4253
+ result: {
4254
+ mode: "strict",
4255
+ typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekDelegatedSpend",
4256
+ schema: _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_result$schema,
4257
+ create: () => _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_result$schema
4191
4258
  },
4192
4259
  sourceLocation: {
4193
4260
  "file": "packages/llm-billing/src/balance.ts",
4194
- "line": 165,
4261
+ "line": 226,
4195
4262
  "column": 3
4196
4263
  }
4197
4264
  },
@@ -4208,17 +4275,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4208
4275
  codec: {
4209
4276
  mode: "strict",
4210
4277
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getSessionSpend:sessionId",
4211
- schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_parameter_0$schema
4278
+ schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_parameter_0$schema,
4279
+ create: () => _rayadesu_dsh_llm_billing_billing_getSessionSpend_parameter_0$schema
4212
4280
  }
4213
4281
  }],
4214
4282
  result: {
4215
4283
  mode: "strict",
4216
4284
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekSessionSpend",
4217
- schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema
4285
+ schema: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema,
4286
+ create: () => _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema
4218
4287
  },
4219
4288
  sourceLocation: {
4220
4289
  "file": "packages/llm-billing/src/balance.ts",
4221
- "line": 176,
4290
+ "line": 183,
4222
4291
  "column": 3
4223
4292
  }
4224
4293
  },
@@ -4235,17 +4304,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4235
4304
  codec: {
4236
4305
  mode: "strict",
4237
4306
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getSessionTurnSpends:sessionId",
4238
- schema: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema
4307
+ schema: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema,
4308
+ create: () => _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema
4239
4309
  }
4240
4310
  }],
4241
4311
  result: {
4242
4312
  mode: "strict",
4243
4313
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekSessionTurnSpends",
4244
- schema: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_result$schema
4314
+ schema: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_result$schema,
4315
+ create: () => _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_result$schema
4245
4316
  },
4246
4317
  sourceLocation: {
4247
4318
  "file": "packages/llm-billing/src/balance.ts",
4248
- "line": 227,
4319
+ "line": 251,
4249
4320
  "column": 3
4250
4321
  }
4251
4322
  },
@@ -4263,17 +4334,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4263
4334
  codec: {
4264
4335
  mode: "strict",
4265
4336
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend:force",
4266
- schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema
4337
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema,
4338
+ create: () => _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema
4267
4339
  }
4268
4340
  }],
4269
4341
  result: {
4270
4342
  mode: "strict",
4271
4343
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend",
4272
- schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema
4344
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema,
4345
+ create: () => _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema
4273
4346
  },
4274
4347
  sourceLocation: {
4275
4348
  "file": "packages/llm-billing/src/balance.ts",
4276
- "line": 202,
4349
+ "line": 211,
4277
4350
  "column": 3
4278
4351
  }
4279
4352
  },
@@ -4291,17 +4364,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4291
4364
  codec: {
4292
4365
  mode: "strict",
4293
4366
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTodaySpend:force",
4294
- schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema
4367
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema,
4368
+ create: () => _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema
4295
4369
  }
4296
4370
  }],
4297
4371
  result: {
4298
4372
  mode: "strict",
4299
4373
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekTodaySpend",
4300
- schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema
4374
+ schema: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema,
4375
+ create: () => _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema
4301
4376
  },
4302
4377
  sourceLocation: {
4303
4378
  "file": "packages/llm-billing/src/balance.ts",
4304
- "line": 189,
4379
+ "line": 196,
4305
4380
  "column": 3
4306
4381
  }
4307
4382
  },
@@ -4318,7 +4393,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4318
4393
  codec: {
4319
4394
  mode: "strict",
4320
4395
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTurnSpend:sessionId",
4321
- schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema
4396
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema,
4397
+ create: () => _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema
4322
4398
  }
4323
4399
  }, {
4324
4400
  name: "messageId",
@@ -4327,17 +4403,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4327
4403
  codec: {
4328
4404
  mode: "strict",
4329
4405
  typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTurnSpend:messageId",
4330
- schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema
4406
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema,
4407
+ create: () => _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema
4331
4408
  }
4332
4409
  }],
4333
4410
  result: {
4334
4411
  mode: "strict",
4335
4412
  typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend",
4336
- schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema
4413
+ schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema,
4414
+ create: () => _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema
4337
4415
  },
4338
4416
  sourceLocation: {
4339
4417
  "file": "packages/llm-billing/src/balance.ts",
4340
- "line": 215,
4418
+ "line": 239,
4341
4419
  "column": 3
4342
4420
  }
4343
4421
  }
@@ -4360,11 +4438,36 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4360
4438
  total: line.total
4361
4439
  };
4362
4440
  }
4363
- /** CNY amount, up to four decimals with trailing zeros trimmed. */
4441
+ /**
4442
+ * CNY amount, up to four decimals with trailing zeros trimmed. The balance line's
4443
+ * renderer; spend figures go through {@link formatSpendSignificant} instead.
4444
+ */
4364
4445
  function formatSpend(amount) {
4365
4446
  return `¥${amount.toFixed(4).replace(/\.?0+$/, "")}`;
4366
4447
  }
4367
4448
  /**
4449
+ * CNY spend amount at a fixed number of SIGNIFICANT digits (default three),
4450
+ * trailing zeros trimmed, prefixed with `¥` — and never finer than the panel's
4451
+ * own four-decimal resolution.
4452
+ *
4453
+ * Every spend figure the plugin renders goes through this: the day's own amount
4454
+ * and its three bucket costs, this session's spend and the parenthesized today
4455
+ * share after it, the per-model rows and their bucket breakdowns, the ranking
4456
+ * amounts, and the badge's own spend line. Four decimals are noise at these
4457
+ * magnitudes — their last digits move with every request (`¥0.5068` → `¥0.507`)
4458
+ * — while the four-decimal bound keeps a microscopic amount from growing a long
4459
+ * `¥0.00002` string that would widen its column: an amount that rounds to zero
4460
+ * there reads `¥0`. Only the BALANCE line keeps {@link formatSpend}'s plain four
4461
+ * decimals, because remaining credit is a balance, not a spend measurement.
4462
+ * @param amount - the amount to render.
4463
+ * @param digits - significant digits to keep (default 3).
4464
+ * @returns the rendered amount, e.g. `¥9.58`, `¥0.507`, `¥1230`, `¥0`.
4465
+ */
4466
+ function formatSpendSignificant(amount, digits = 3) {
4467
+ if (!Number.isFinite(amount) || amount === 0) return "¥0";
4468
+ return `¥${Number(Number(amount.toPrecision(digits)).toFixed(4))}`;
4469
+ }
4470
+ /**
4368
4471
  * A token count in DSH's own compact notation: `517` / `12.2K` / `517K` / `1.2M`.
4369
4472
  *
4370
4473
  * Mirrors ui-chat's `formatTokens` (`packages/client/ui-chat/src/client/chat/
@@ -4385,6 +4488,74 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4385
4488
  if (count < 1e6) return `${scaled(count / 1e3)}K`;
4386
4489
  return `${scaled(count / 1e6)}M`;
4387
4490
  }
4491
+ /** Round a cache-read ratio to exact percentage units, with positive ties rounded up. */
4492
+ function roundedPercentUnits(cacheReadTokens, denominator, decimalPlaces) {
4493
+ const scale = (decimalPlaces === 0 ? 1 : 10) * 100;
4494
+ const doubledScale = scale * 2;
4495
+ const denominatorQuotient = Math.floor(denominator / doubledScale);
4496
+ const denominatorRemainder = denominator % doubledScale;
4497
+ let lower = 0;
4498
+ let upper = scale;
4499
+ while (lower < upper) {
4500
+ const candidate = Math.floor((lower + upper + 1) / 2);
4501
+ const factor = candidate * 2 - 1;
4502
+ if (cacheReadTokens >= factor * denominatorQuotient + Math.ceil(factor * denominatorRemainder / doubledScale)) lower = candidate;
4503
+ else upper = candidate - 1;
4504
+ }
4505
+ return lower;
4506
+ }
4507
+ /** Percentage units back to text, dropping a redundant trailing `.0`. */
4508
+ function displayPercentUnits(units, decimalPlaces) {
4509
+ if (decimalPlaces === 0) return String(units);
4510
+ const whole = Math.floor(units / 10);
4511
+ const tenths = units % 10;
4512
+ return tenths === 0 ? String(whole) : `${whole}.${tenths}`;
4513
+ }
4514
+ /**
4515
+ * The cache-hit share of one prompt, in DSH's own percentage rule.
4516
+ *
4517
+ * Mirrors ui-chat's `formatCacheHitPercent`
4518
+ * (`packages/client/ui-chat/src/client/chat/token-format.ts`) rule for rule, so
4519
+ * one shell never prints the same ratio two ways: an integer percentage by
4520
+ * default, and — because a partial hit must never round up to a full one — just
4521
+ * enough extra decimals to stay below 100 when integer rounding would reach it
4522
+ * (`99`, `99.5`, `99.95`, …). The rounding runs in integer arithmetic with
4523
+ * positive ties going up, so it does not ride on binary-float precision.
4524
+ *
4525
+ * The ratio is over PROMPT-side tokens only (cache read over cache read plus
4526
+ * uncached input, the split DSH's own `billedInputTokens` makes); output tokens
4527
+ * never enter the denominator.
4528
+ * @param cacheReadTokens - exact prompt tokens served from cache.
4529
+ * @param promptTokens - exact aggregate prompt-side tokens.
4530
+ * @param decimalPlaces - ordinary-ratio precision; a partial hit that would
4531
+ * round to 100 automatically uses enough additional precision to stay honest.
4532
+ * @returns percentage text without its `%`, or null when there was no prompt input.
4533
+ */
4534
+ function formatCacheHitPercent(cacheReadTokens, promptTokens, decimalPlaces = 0) {
4535
+ if (promptTokens === 0) return null;
4536
+ const missedInputTokens = promptTokens - cacheReadTokens;
4537
+ if (missedInputTokens === 0) return "100";
4538
+ const roundedUnits = roundedPercentUnits(cacheReadTokens, promptTokens, decimalPlaces);
4539
+ if (roundedUnits < (decimalPlaces === 0 ? 100 : 1e3)) return displayPercentUnits(roundedUnits, decimalPlaces);
4540
+ let distinguishingPlaces = 1;
4541
+ let scaledDoubleGap = missedInputTokens * 200;
4542
+ const denominatorTens = Math.floor(promptTokens / 10);
4543
+ while (scaledDoubleGap <= denominatorTens) {
4544
+ scaledDoubleGap *= 10;
4545
+ distinguishingPlaces += 1;
4546
+ }
4547
+ const denominatorOnes = promptTokens % 10;
4548
+ let roundedLoss = 5;
4549
+ for (let loss = 1; loss < 5; loss += 1) {
4550
+ const factor = loss * 2 + 1;
4551
+ const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10);
4552
+ if (scaledDoubleGap <= threshold) {
4553
+ roundedLoss = loss;
4554
+ break;
4555
+ }
4556
+ }
4557
+ return `99.${"9".repeat(distinguishingPlaces - 1)}${10 - roundedLoss}`;
4558
+ }
4388
4559
  /**
4389
4560
  * The total billed tokens of a priced row: the three buckets summed. Tokens are
4390
4561
  * already counted per bucket when they were priced, so a cache-hit input token
@@ -4397,6 +4568,100 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4397
4568
  return row.cacheHitInputTokens + row.cacheMissInputTokens + row.outputTokens;
4398
4569
  }
4399
4570
  //#endregion
4571
+ //#region lib/types/client/spendBuckets.js
4572
+ /**
4573
+ * The composer spend card's three bucket amounts, derived from the pushed
4574
+ * `billingTodaySpend` projection. The host fold already prices every sample
4575
+ * into the three disjoint billing buckets (cache-hit input, cache-miss input —
4576
+ * which includes cache writes, output including reasoning), so the card sums
4577
+ * the session's priced model rows instead of adding a Remote call or a second
4578
+ * fold. Keeping the derivation here (pure, no React) lets the sum and its
4579
+ * float reconciliation be tested on their own.
4580
+ * @module @rayadesu/dsh-client-ui-billing/spendBuckets
4581
+ */
4582
+ /** Sum one model row's three bucket costs. */
4583
+ function bucketsOf(row) {
4584
+ return [
4585
+ row.cacheMissInputCost,
4586
+ row.cacheHitInputCost,
4587
+ row.outputCost
4588
+ ];
4589
+ }
4590
+ /**
4591
+ * Fold one spend's model rows into the card's three bucket amounts.
4592
+ *
4593
+ * The rows carry float costs, so the three independently rendered values can
4594
+ * miss the displayed total by one unit in the last rendered decimal after each
4595
+ * is formatted to four decimals. The residual is absorbed by the largest
4596
+ * bucket — the only one where a 1e-4 nudge cannot be seen — so the card's rows
4597
+ * always add up to the total it shows in its header.
4598
+ * @param spend - the session bucket of the `billingTodaySpend` projection
4599
+ * (the session's own events, every Beijing day).
4600
+ * @returns the three bucket totals plus the billed total.
4601
+ */
4602
+ function spendBucketsOf(spend) {
4603
+ let uncachedInput = 0;
4604
+ let cacheRead = 0;
4605
+ let output = 0;
4606
+ for (const row of spend.models) {
4607
+ const [miss, hit, out] = bucketsOf(row);
4608
+ uncachedInput += miss;
4609
+ cacheRead += hit;
4610
+ output += out;
4611
+ }
4612
+ const total = spend.total;
4613
+ const residual = total - (uncachedInput + cacheRead + output);
4614
+ if (residual !== 0) {
4615
+ if (uncachedInput >= cacheRead && uncachedInput >= output) uncachedInput += residual;
4616
+ else if (cacheRead >= output) cacheRead += residual;
4617
+ else output += residual;
4618
+ }
4619
+ return {
4620
+ uncachedInput,
4621
+ cacheRead,
4622
+ output,
4623
+ total
4624
+ };
4625
+ }
4626
+ /**
4627
+ * Fold one spend's model rows into the three bucket TOKEN counts — the token
4628
+ * side of {@link spendBucketsOf}, so a surface that shows both (the panel's
4629
+ * today rows) reads one bucket's tokens and its cost from the same rows.
4630
+ *
4631
+ * No reconciliation is needed here: tokens are integers, so the three counts
4632
+ * always add up to the total the token line shows.
4633
+ * @param spend - the day's (or one session's) priced rows.
4634
+ * @returns the three bucket token counts.
4635
+ */
4636
+ function tokenBucketsOf(spend) {
4637
+ let uncachedInput = 0;
4638
+ let cacheRead = 0;
4639
+ let output = 0;
4640
+ for (const row of spend.models) {
4641
+ uncachedInput += row.cacheMissInputTokens;
4642
+ cacheRead += row.cacheHitInputTokens;
4643
+ output += row.outputTokens;
4644
+ }
4645
+ return {
4646
+ uncachedInput,
4647
+ cacheRead,
4648
+ output
4649
+ };
4650
+ }
4651
+ /**
4652
+ * The cache-hit share of one spend, in DSH's own percentage rule — the ratio of
4653
+ * the cache-read bucket to every PROMPT-side bucket (uncached input with cache
4654
+ * writes folded in, plus cache read), output excluded, exactly the split DSH's
4655
+ * `billedInputTokens` makes. Rendered by {@link formatCacheHitPercent}, i.e. an
4656
+ * integer percent that grows decimals only to keep a partial hit below 100.
4657
+ * @param spend - the day's priced rows.
4658
+ * @returns the percentage text without its `%`, or null when the spend billed no prompt input.
4659
+ */
4660
+ function cacheHitPercentOf(spend) {
4661
+ const tokens = tokenBucketsOf(spend);
4662
+ return formatCacheHitPercent(tokens.cacheRead, tokens.cacheRead + tokens.uncachedInput);
4663
+ }
4664
+ //#endregion
4400
4665
  //#region lib/types/client/version.js
4401
4666
  /**
4402
4667
  * The plugin's own version, reported in the spend-hint bubble so a report can
@@ -4412,10 +4677,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4412
4677
  * @module @rayadesu/dsh-client-ui-billing/version
4413
4678
  */
4414
4679
  /** The installed plugin version, e.g. `0.3.10`; `dev` when nothing stamped one. */
4415
- const PLUGIN_VERSION = "0.3.12";
4680
+ const PLUGIN_VERSION = "0.3.13";
4416
4681
  //#endregion
4417
4682
  //#region \0dsh-css:C:\Users\admin\Desktop\me\code\DeepSeek-Harness-chat-billing\packages\ui-billing\src\client\BalanceBadge.module.css.mjs
4418
- const css$1 = "._4_Wqcq_root{position:relative}._4_Wqcq_trigger{background:var(--dsw-alias-fill-l2);min-height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:8px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._4_Wqcq_trigger:hover,._4_Wqcq_trigger:focus-visible{color:var(--dsw-alias-label-primary)}._4_Wqcq_trigger svg{transition:transform .12s}._4_Wqcq_chevronOpen{transform:rotate(180deg)}._4_Wqcq_triggerLines{text-align:left;flex-direction:column;align-items:flex-start;gap:0;display:flex}._4_Wqcq_linePrimary{font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}._4_Wqcq_lineSecondary{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:15px}._4_Wqcq_unavailable{color:var(--dsw-alias-label-tertiary)}._4_Wqcq_inlineIcon{color:var(--dsw-alias-label-tertiary);flex:none}._4_Wqcq_spinning{color:var(--dsw-alias-label-tertiary);flex:none;animation:.8s linear infinite _4_Wqcq_spin}@keyframes _4_Wqcq_spin{to{transform:rotate(360deg)}}._4_Wqcq_panel{z-index:100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu);width:336px;max-width:min(360px,100vw - 32px);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:2px;padding:6px;display:flex;position:absolute;top:calc(100% + 6px);right:0}._4_Wqcq_amountRow{border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_amountLabel{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:13px;line-height:18px}._4_Wqcq_amountRow>._4_Wqcq_amountLabel{font-size:15px;line-height:22px}._4_Wqcq_spendToday{color:var(--dsw-alias-label-tertiary);font-size:12px}._4_Wqcq_spendRow{border-top:1px solid var(--dsw-alias-border-l2);border-radius:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_costRow{align-items:center;padding:2px 8px 6px;display:flex}._4_Wqcq_costBreakdown{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:16px}._4_Wqcq_amountActions{align-items:center;gap:2px;display:inline-flex}._4_Wqcq_infoButton,._4_Wqcq_refreshButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._4_Wqcq_infoButton:hover,._4_Wqcq_infoButton:focus-visible,._4_Wqcq_refreshButton:hover,._4_Wqcq_refreshButton:focus-visible{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2)}._4_Wqcq_modelRow{border-radius:8px;justify-content:space-between;align-items:baseline;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_modelName{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px;line-height:18px}._4_Wqcq_tasks{text-align:right;min-width:0;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;overflow-wrap:anywhere;font-size:12px;line-height:18px}._4_Wqcq_ranking{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:0;padding-top:2px;display:flex}._4_Wqcq_rankingTitle{color:var(--dsw-alias-label-secondary);padding:6px 8px 2px;font-size:12px;line-height:18px}._4_Wqcq_rankingRow{border-radius:8px;align-items:baseline;gap:0;min-width:0;padding:3px 8px;display:flex}._4_Wqcq_rankingRow:hover{background:var(--dsw-alias-fill-l2)}._4_Wqcq_rankingIndex{width:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;text-align:right;flex:none;font-size:11px;line-height:16px}._4_Wqcq_rankingDot{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:pre;flex:none;font-size:11px;line-height:16px}._4_Wqcq_rankingName{min-width:0;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:11px;line-height:16px;overflow:hidden}._4_Wqcq_rankingAmount{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;margin-left:8px;font-size:11px;line-height:16px}._4_Wqcq_rankingMore{color:var(--dsw-alias-label-tertiary);padding:2px 8px 4px;font-size:11px;line-height:16px}";
4683
+ const css$1 = "._4_Wqcq_root{position:relative}._4_Wqcq_trigger{background:var(--dsw-alias-fill-l2);min-height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:8px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._4_Wqcq_trigger:hover,._4_Wqcq_trigger:focus-visible{color:var(--dsw-alias-label-primary)}._4_Wqcq_trigger svg{transition:transform .12s}._4_Wqcq_chevronOpen{transform:rotate(180deg)}._4_Wqcq_triggerLines{text-align:left;flex-direction:column;align-items:flex-start;gap:0;display:flex}._4_Wqcq_linePrimary{font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}._4_Wqcq_lineSecondary{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:15px}._4_Wqcq_unavailable{color:var(--dsw-alias-label-tertiary)}._4_Wqcq_inlineIcon{color:var(--dsw-alias-label-tertiary);flex:none}._4_Wqcq_spinning{color:var(--dsw-alias-label-tertiary);flex:none;animation:.8s linear infinite _4_Wqcq_spin}@keyframes _4_Wqcq_spin{to{transform:rotate(360deg)}}._4_Wqcq_panel{--billing-type-1-size:11px;--billing-type-1-line:16px;--billing-type-1-tone:var(--dsw-alias-label-tertiary);--billing-type-2-size:12px;--billing-type-2-line:18px;--billing-type-2-tone:var(--dsw-alias-label-secondary);--billing-type-2-number-tone:var(--dsw-alias-label-tertiary);--billing-type-3-size:13px;--billing-type-3-line:18px;--billing-type-3-tone:var(--dsw-alias-label-primary);z-index:100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu);width:336px;max-width:min(360px,100vw - 32px);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:2px;padding:6px;display:flex;position:absolute;top:calc(100% + 6px);right:0}._4_Wqcq_amountRow{border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_amountLabel{color:var(--billing-type-3-tone);font-size:var(--billing-type-3-size);line-height:var(--billing-type-3-line);font-variant-numeric:tabular-nums}._4_Wqcq_amountRow>._4_Wqcq_amountLabel{font-size:15px;line-height:22px}._4_Wqcq_sessionToday,._4_Wqcq_todayHit{color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums;margin-left:5px}._4_Wqcq_spendRow{border-top:1px solid var(--dsw-alias-border-l2);border-radius:0;justify-content:space-between;align-items:center;gap:8px;padding:8px 8px 6px;display:flex}._4_Wqcq_costRow{align-items:center;padding:0 8px;display:flex}._4_Wqcq_costRow:last-child{padding-bottom:6px}._4_Wqcq_spendRow+._4_Wqcq_costRow{padding-top:2px}._4_Wqcq_dayBucketRow{align-items:center;padding:2px 8px;display:flex}._4_Wqcq_dayBucketRow+._4_Wqcq_dayBucketRow{padding-bottom:7px}._4_Wqcq_costBreakdown{color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums}._4_Wqcq_amountActions{align-items:center;gap:2px;display:inline-flex}._4_Wqcq_infoButton,._4_Wqcq_refreshButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._4_Wqcq_infoButton:hover,._4_Wqcq_infoButton:focus-visible,._4_Wqcq_refreshButton:hover,._4_Wqcq_refreshButton:focus-visible{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2)}._4_Wqcq_modelRow{border-radius:8px;justify-content:space-between;align-items:baseline;gap:8px;padding:4px 8px;display:flex}._4_Wqcq_spendRow+._4_Wqcq_modelRow{padding-top:2px}._4_Wqcq_modelName{color:var(--billing-type-2-tone);font-size:var(--billing-type-2-size);line-height:var(--billing-type-2-line);flex:none}._4_Wqcq_tasks{text-align:right;min-width:0;color:var(--billing-type-2-number-tone);font-size:var(--billing-type-2-size);line-height:var(--billing-type-2-line);font-variant-numeric:tabular-nums;overflow-wrap:anywhere}._4_Wqcq_ranking{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:0;margin-top:7px;padding-top:2px;display:flex}._4_Wqcq_rankingTitle{color:var(--billing-type-2-tone);font-size:var(--billing-type-2-size);line-height:var(--billing-type-2-line);padding:6px 8px 2px}._4_Wqcq_rankingRow{border-radius:8px;align-items:baseline;gap:0;min-width:0;padding:3px 8px;display:flex}._4_Wqcq_rankingRow:hover{background:var(--dsw-alias-fill-l2)}._4_Wqcq_rankingIndex{width:16px;color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums;text-align:right;flex:none}._4_Wqcq_rankingDot{color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums;white-space:pre;flex:none}._4_Wqcq_rankingName{min-width:0;color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._4_Wqcq_rankingAmount{color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);font-variant-numeric:tabular-nums;flex:none;margin-left:8px}._4_Wqcq_rankingMore{color:var(--billing-type-1-tone);font-size:var(--billing-type-1-size);line-height:var(--billing-type-1-line);padding:2px 8px 4px}";
4419
4684
  const tagId$1 = "@rayadesu/dsh-client-ui-billing/BalanceBadge.module.css";
4420
4685
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
4421
4686
  const tag = document.createElement("style");
@@ -4431,6 +4696,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4431
4696
  "chevronOpen": "_4_Wqcq_chevronOpen",
4432
4697
  "costBreakdown": "_4_Wqcq_costBreakdown",
4433
4698
  "costRow": "_4_Wqcq_costRow",
4699
+ "dayBucketRow": "_4_Wqcq_dayBucketRow",
4434
4700
  "infoButton": "_4_Wqcq_infoButton",
4435
4701
  "inlineIcon": "_4_Wqcq_inlineIcon",
4436
4702
  "linePrimary": "_4_Wqcq_linePrimary",
@@ -4448,29 +4714,56 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4448
4714
  "rankingTitle": "_4_Wqcq_rankingTitle",
4449
4715
  "refreshButton": "_4_Wqcq_refreshButton",
4450
4716
  "root": "_4_Wqcq_root",
4717
+ "sessionToday": "_4_Wqcq_sessionToday",
4451
4718
  "spendRow": "_4_Wqcq_spendRow",
4452
- "spendToday": "_4_Wqcq_spendToday",
4453
4719
  "spin": "_4_Wqcq_spin",
4454
4720
  "spinning": "_4_Wqcq_spinning",
4455
4721
  "tasks": "_4_Wqcq_tasks",
4722
+ "todayHit": "_4_Wqcq_todayHit",
4456
4723
  "trigger": "_4_Wqcq_trigger",
4457
4724
  "triggerLines": "_4_Wqcq_triggerLines",
4458
4725
  "unavailable": "_4_Wqcq_unavailable"
4459
4726
  };
4460
4727
  /**
4461
- * Tolerance for "the session total and its share of today are the same number".
4462
- * Both are sums of the same priced samples, so a same-day session yields
4463
- * bit-identical floats; the slack only absorbs the accumulation-order noise a
4464
- * float sum can pick up on the way (a 1e-9 CNY difference is not a real one).
4728
+ * Today's two bucket detail lines, rendered under the 今日 Token / 今日花费 row:
4729
+ * the three buckets' token counts, then their costs DSH's own bucket wording
4730
+ * and row order in both lines, so they read like the per-model breakdown line
4731
+ * further down and keep its own row rhythm. Each line stands on its own (no
4732
+ * column alignment between them): the middots keep the natural " · " spacing of
4733
+ * every other breakdown line in the panel.
4734
+ *
4735
+ * Tokens and costs come from the SAME day row set (`todaySpend.models`) the row
4736
+ * above is built from: the three token counts add up to the token figure, and
4737
+ * the three costs (with `spendBucketsOf`'s residual absorbed into the largest
4738
+ * bucket) to the cost figure. Costs render at three significant digits, like the
4739
+ * day figure above them, and never finer than four decimals — their last digits
4740
+ * move with every request and carry no meaning.
4465
4741
  */
4466
- const SPEND_SAME_EPSILON = 1e-9;
4742
+ function todayBucketLines(t, spend) {
4743
+ const tokens = tokenBucketsOf(spend);
4744
+ const costs = spendBucketsOf(spend);
4745
+ return {
4746
+ tokenLine: [
4747
+ `${t("label.bucket.input")} ${t("unit.tokens", { count: formatTokens(tokens.uncachedInput) })}`,
4748
+ `${t("label.bucket.cacheRead")} ${t("unit.tokens", { count: formatTokens(tokens.cacheRead) })}`,
4749
+ `${t("label.bucket.output")} ${t("unit.tokens", { count: formatTokens(tokens.output) })}`
4750
+ ].join(" · "),
4751
+ costLine: [
4752
+ t("label.cost.input", { amount: formatSpendSignificant(costs.uncachedInput) }),
4753
+ t("label.cost.cacheRead", { amount: formatSpendSignificant(costs.cacheRead) }),
4754
+ t("label.cost.output", { amount: formatSpendSignificant(costs.output) })
4755
+ ].join(" · ")
4756
+ };
4757
+ }
4467
4758
  /** The detail box opened from the badge trigger. */
4468
- function BalancePanel({ amount, sessionId, spend, todaySpend, sessionsSpend, refreshing, onRefresh, t }) {
4469
- const sessionToday = sessionsSpend === null ? void 0 : sessionsSpend.sessions.find((row) => row.sessionId === sessionId)?.total ?? 0;
4470
- const sessionTodayShare = spend !== null && sessionToday !== void 0 && Math.abs(spend.total - sessionToday) > SPEND_SAME_EPSILON ? sessionToday : null;
4471
- const sessionAmount = spend === null ? "—" : spend.models.length === 0 ? t("stat.none") : formatSpend(spend.total);
4759
+ function BalancePanel({ amount, sessionId, spend, todaySpend, sessionsSpend, isSubagent, crossedDay, refreshing, onRefresh, t }) {
4760
+ const row = sessionsSpend === null ? void 0 : sessionsSpend.sessions.find((entry) => entry.sessionId === sessionId);
4761
+ const sessionToday = spend !== null && !isSubagent && crossedDay && sessionsSpend !== null ? row?.total ?? 0 : null;
4762
+ const sessionAmount = spend === null ? "—" : spend.models.length === 0 ? t("stat.none") : formatSpendSignificant(spend.total);
4472
4763
  const todayTokens = todaySpend === null ? "—" : todaySpend.models.length === 0 ? t("stat.none") : t("unit.tokens", { count: formatTokens(todaySpend.models.reduce((sum, row) => sum + rowTokens(row), 0)) });
4473
- const todayAmount = todaySpend === null ? "—" : todaySpend.models.length === 0 ? t("stat.none") : formatSpend(todaySpend.total);
4764
+ const todayAmount = todaySpend === null ? "—" : todaySpend.models.length === 0 ? t("stat.none") : formatSpendSignificant(todaySpend.total);
4765
+ const todayHit = todaySpend === null || todaySpend.models.length === 0 ? null : cacheHitPercentOf(todaySpend);
4766
+ const modelCount = spend === null ? 0 : spend.models.length;
4474
4767
  return (0, react_jsx_runtime.jsxs)("div", {
4475
4768
  className: BalanceBadge_module_css_default.panel,
4476
4769
  role: "dialog",
@@ -4506,43 +4799,59 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4506
4799
  }),
4507
4800
  (0, react_jsx_runtime.jsxs)("div", {
4508
4801
  className: BalanceBadge_module_css_default.spendRow,
4509
- children: [(0, react_jsx_runtime.jsx)("span", {
4802
+ children: [(0, react_jsx_runtime.jsxs)("span", {
4510
4803
  className: BalanceBadge_module_css_default.amountLabel,
4511
- children: t("label.todayTokens", { count: todayTokens })
4804
+ children: [t("label.todayTokens", { count: todayTokens }), todayHit !== null ? (0, react_jsx_runtime.jsx)("span", {
4805
+ className: BalanceBadge_module_css_default.todayHit,
4806
+ children: t("label.todayTokens.hit", { percent: todayHit })
4807
+ }) : null]
4512
4808
  }), (0, react_jsx_runtime.jsx)("span", {
4513
4809
  className: BalanceBadge_module_css_default.amountLabel,
4514
4810
  children: t("label.todaySpend", { amount: todayAmount })
4515
4811
  })]
4516
4812
  }),
4813
+ todaySpend !== null && todaySpend.models.length > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
4814
+ className: BalanceBadge_module_css_default.dayBucketRow,
4815
+ children: (0, react_jsx_runtime.jsx)("span", {
4816
+ className: BalanceBadge_module_css_default.costBreakdown,
4817
+ children: todayBucketLines(t, todaySpend).tokenLine
4818
+ })
4819
+ }), (0, react_jsx_runtime.jsx)("div", {
4820
+ className: BalanceBadge_module_css_default.dayBucketRow,
4821
+ children: (0, react_jsx_runtime.jsx)("span", {
4822
+ className: BalanceBadge_module_css_default.costBreakdown,
4823
+ children: todayBucketLines(t, todaySpend).costLine
4824
+ })
4825
+ })] }),
4517
4826
  (0, react_jsx_runtime.jsx)("div", {
4518
4827
  className: BalanceBadge_module_css_default.spendRow,
4519
4828
  children: (0, react_jsx_runtime.jsxs)("span", {
4520
4829
  className: BalanceBadge_module_css_default.amountLabel,
4521
- children: [t("label.sessionSpend", { amount: sessionAmount }), sessionTodayShare !== null ? (0, react_jsx_runtime.jsx)("span", {
4522
- className: BalanceBadge_module_css_default.spendToday,
4523
- children: t("label.sessionSpend.today", { amount: formatSpend(sessionTodayShare) })
4830
+ children: [t("label.sessionSpend", { amount: sessionAmount }), sessionToday !== null ? (0, react_jsx_runtime.jsx)("span", {
4831
+ className: BalanceBadge_module_css_default.sessionToday,
4832
+ children: t("label.sessionSpend.today", { amount: formatSpendSignificant(sessionToday) })
4524
4833
  }) : null]
4525
4834
  })
4526
4835
  }),
4527
- spend?.models.map((model) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
4836
+ spend?.models.map((model) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [modelCount > 1 && (0, react_jsx_runtime.jsxs)("div", {
4528
4837
  className: BalanceBadge_module_css_default.modelRow,
4529
4838
  children: [(0, react_jsx_runtime.jsx)("span", {
4530
4839
  className: BalanceBadge_module_css_default.modelName,
4531
4840
  children: model.displayName
4532
4841
  }), (0, react_jsx_runtime.jsx)("span", {
4533
4842
  className: BalanceBadge_module_css_default.tasks,
4534
- children: formatSpend(model.cost)
4843
+ children: formatSpendSignificant(model.cost)
4535
4844
  })]
4536
4845
  }), (0, react_jsx_runtime.jsx)("div", {
4537
4846
  className: BalanceBadge_module_css_default.costRow,
4538
4847
  children: (0, react_jsx_runtime.jsxs)("span", {
4539
4848
  className: BalanceBadge_module_css_default.costBreakdown,
4540
4849
  children: [
4541
- t("label.cost.input", { amount: formatSpend(model.cacheMissInputCost) }),
4850
+ t("label.cost.input", { amount: formatSpendSignificant(model.cacheMissInputCost) }),
4542
4851
  " · ",
4543
- t("label.cost.cacheRead", { amount: formatSpend(model.cacheHitInputCost) }),
4852
+ t("label.cost.cacheRead", { amount: formatSpendSignificant(model.cacheHitInputCost) }),
4544
4853
  " · ",
4545
- t("label.cost.output", { amount: formatSpend(model.outputCost) })
4854
+ t("label.cost.output", { amount: formatSpendSignificant(model.outputCost) })
4546
4855
  ]
4547
4856
  })
4548
4857
  })] }, model.model)),
@@ -4572,7 +4881,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4572
4881
  }),
4573
4882
  (0, react_jsx_runtime.jsx)("span", {
4574
4883
  className: BalanceBadge_module_css_default.rankingAmount,
4575
- children: formatSpend(row.total)
4884
+ children: formatSpendSignificant(row.total)
4576
4885
  })
4577
4886
  ]
4578
4887
  }, row.sessionId)),
@@ -4589,7 +4898,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4589
4898
  //#region lib/types/client/BalanceTrigger.js
4590
4899
  /**
4591
4900
  * Session-header billing trigger: the balance plus the conversation-spend
4592
- * line. Pure view over the data hook's values — no state, no effects.
4901
+ * line. Pure view over the data hook's values — no state, no effects. The
4902
+ * balance line uses the trigger's own `trigger.balance` (the panel headline
4903
+ * without its "API" prefix, since the chip is narrow); the spend line arrives
4904
+ * precomputed from the parent carrying the PANEL's `label.sessionSpend`.
4593
4905
  */
4594
4906
  /** The badge button: balance line, spend line, and the open-state chevron. */
4595
4907
  function BalanceTrigger({ amount, spendLine, open, onToggle, t }) {
@@ -4612,9 +4924,57 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4612
4924
  });
4613
4925
  }
4614
4926
  //#endregion
4927
+ //#region lib/types/client/spends.js
4928
+ /**
4929
+ * Additive merge of a conversation's two spend halves: the session's OWN billed
4930
+ * spend (live, from the pushed `billingTodaySpend` projection or the
4931
+ * `billing/getSessionSpend` fallback) and the delegated-subagent subtotal
4932
+ * (`billing/getDelegatedSpend`). The panel shows one amount for the whole
4933
+ * conversation, so the two must be summed before rendering — and the per-model
4934
+ * rows must be summed the same way, or the breakdown would no longer add up to
4935
+ * the amount above it.
4936
+ *
4937
+ * Pure addition only: no pricing, no timezone, no fork-boundary knowledge lives
4938
+ * here (the host owns all of that), so this cannot drift from the host's own
4939
+ * sums.
4940
+ */
4941
+ /** Sum two model rows of the same wire model (display name taken from the first). */
4942
+ function addRows(left, right) {
4943
+ return {
4944
+ ...left,
4945
+ cost: left.cost + right.cost,
4946
+ peakCost: left.peakCost + right.peakCost,
4947
+ offPeakCost: left.offPeakCost + right.offPeakCost,
4948
+ cacheHitInputTokens: left.cacheHitInputTokens + right.cacheHitInputTokens,
4949
+ cacheMissInputTokens: left.cacheMissInputTokens + right.cacheMissInputTokens,
4950
+ outputTokens: left.outputTokens + right.outputTokens,
4951
+ cacheHitInputCost: left.cacheHitInputCost + right.cacheHitInputCost,
4952
+ cacheMissInputCost: left.cacheMissInputCost + right.cacheMissInputCost,
4953
+ outputCost: left.outputCost + right.outputCost
4954
+ };
4955
+ }
4956
+ /**
4957
+ * Sum one conversation's own spend and its delegated-subagent subtotal.
4958
+ * @param own - the session's own billed spend.
4959
+ * @param delegated - the delegated subtotal from the last `getDelegatedSpend` read.
4960
+ * @returns the merged spend (own model order first, then newly seen models).
4961
+ */
4962
+ function sumSpends(own, delegated) {
4963
+ const rows = /* @__PURE__ */ new Map();
4964
+ for (const row of own.models) rows.set(row.model, row);
4965
+ for (const row of delegated.models) {
4966
+ const existing = rows.get(row.model);
4967
+ rows.set(row.model, existing === void 0 ? row : addRows(existing, row));
4968
+ }
4969
+ return {
4970
+ total: own.total + delegated.total,
4971
+ models: [...rows.values()]
4972
+ };
4973
+ }
4974
+ //#endregion
4615
4975
  //#region lib/types/client/useBillingData.js
4616
4976
  /**
4617
- * Billing badge data hook: owns the balance/session/today states, the
4977
+ * Billing badge data hook: owns the balance/session/today/delegated states, the
4618
4978
  * mount+refresh fetch, the turn-settled recompute, and the click-outside
4619
4979
  * close. The trigger and the panel are pure views over the returned values,
4620
4980
  * so the concurrency/race handling lives in exactly one file.
@@ -4637,18 +4997,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4637
4997
  }
4638
4998
  /**
4639
4999
  * Start the badge's data lifecycle for one session. The spend follows the
4640
- * conversation: the host-pushed `billingTodaySpend` projection drives the
4641
- * session line live (zero Remote calls), with `getSessionSpend` as the
4642
- * bootstrap/fallback when the projection key is absent; today's spend is
4643
- * recomputed through `getTodaySpend` when a turn settles; the balance stays a
4644
- * manual-refresh snapshot and is never refetched on its own.
5000
+ * conversation: the host-pushed `billingTodaySpend` projection drives this
5001
+ * session's own part live (zero Remote calls), with `getSessionSpend` as the
5002
+ * bootstrap/fallback when the projection key is absent; the delegated-subagent
5003
+ * subtotal comes from `getDelegatedSpend` on mount, on refresh, and when a turn
5004
+ * settles; today's spend is recomputed through `getTodaySpend` on the same
5005
+ * events; the balance stays a manual-refresh snapshot and is never refetched on
5006
+ * its own.
4645
5007
  * @param props - the badge's injected face and session runtime share.
4646
5008
  */
4647
- function useBillingData({ getBalance, getCachedBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, sessionId, useSession, useProjection }) {
5009
+ function useBillingData({ getBalance, getCachedBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, getDelegatedSpend, sessionId, useSession, useProjection }) {
4648
5010
  const [balance, setBalance] = (0, react.useState)(getCachedBalance);
4649
5011
  const [spend, setSpend] = (0, react.useState)(null);
4650
5012
  const [todaySpend, setTodaySpend] = (0, react.useState)(null);
4651
5013
  const [sessionsSpend, setSessionsSpend] = (0, react.useState)(null);
5014
+ const [delegated, setDelegated] = (0, react.useState)(null);
4652
5015
  const [error, setError] = (0, react.useState)(null);
4653
5016
  const [refreshing, setRefreshing] = (0, react.useState)(false);
4654
5017
  const [open, setOpen] = (0, react.useState)(false);
@@ -4674,11 +5037,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4674
5037
  });
4675
5038
  const sessionSpendRequest = fetchLine(isCurrent, () => getSessionSpend(sessionId), setSpend);
4676
5039
  const todaySpendRequest = fetchLine(isCurrent, () => getTodaySpend(request > 0), setTodaySpend);
5040
+ const delegatedRequest = fetchLine(isCurrent, () => getDelegatedSpend(sessionId, request > 0), setDelegated);
4677
5041
  const sessionsSpendRequest = openRef.current ? fetchLine(isCurrent, () => getTodaySessionsSpend(request > 0), setSessionsSpend) : Promise.resolve();
4678
5042
  Promise.allSettled([
4679
5043
  balanceRequest,
4680
5044
  sessionSpendRequest,
4681
5045
  todaySpendRequest,
5046
+ delegatedRequest,
4682
5047
  sessionsSpendRequest
4683
5048
  ]).then(() => {
4684
5049
  if (current) setRefreshing(false);
@@ -4692,6 +5057,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4692
5057
  getSessionSpend,
4693
5058
  getTodaySpend,
4694
5059
  getTodaySessionsSpend,
5060
+ getDelegatedSpend,
4695
5061
  sessionId,
4696
5062
  request
4697
5063
  ]);
@@ -4711,6 +5077,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4711
5077
  const isCurrent = () => current;
4712
5078
  const timer = setTimeout(() => {
4713
5079
  fetchLine(isCurrent, () => getSessionSpend(sessionId), setSpend);
5080
+ fetchLine(isCurrent, () => getDelegatedSpend(sessionId), setDelegated);
4714
5081
  fetchLine(isCurrent, () => getTodaySpend(), setTodaySpend);
4715
5082
  if (openRef.current) fetchLine(isCurrent, () => getTodaySessionsSpend(), setSessionsSpend);
4716
5083
  }, TURN_SETTLE_DEBOUNCE_MS);
@@ -4720,6 +5087,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4720
5087
  };
4721
5088
  }, [
4722
5089
  getSessionSpend,
5090
+ getDelegatedSpend,
4723
5091
  getTodaySpend,
4724
5092
  getTodaySessionsSpend,
4725
5093
  sessionId,
@@ -4735,11 +5103,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4735
5103
  document.removeEventListener("pointerdown", closeOutside);
4736
5104
  };
4737
5105
  }, [open]);
5106
+ const own = projected === void 0 ? spend : projected.session;
4738
5107
  return {
4739
5108
  balance,
4740
- spend: projected === void 0 ? spend : projected.session,
5109
+ spend: own === null ? null : delegated === null ? own : sumSpends(own, delegated),
4741
5110
  todaySpend,
4742
5111
  sessionsSpend,
5112
+ isSubagent: delegated?.isSubagent ?? false,
5113
+ crossedDay: delegated?.crossedDay ?? false,
4743
5114
  error,
4744
5115
  refreshing,
4745
5116
  open,
@@ -4762,13 +5133,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4762
5133
  * @param props - Remote face, locale, and the standard session-header runtime share.
4763
5134
  * @returns the badge, or null until the first balance fetch settles.
4764
5135
  */
4765
- function BalanceBadge({ getBalance, getCachedBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, sessionId, useSession, useProjection, t }) {
4766
- const { balance, spend, todaySpend, sessionsSpend, error, refreshing, open, rootRef, refresh, toggleOpen } = useBillingData({
5136
+ function BalanceBadge({ getBalance, getCachedBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, getDelegatedSpend, sessionId, useSession, useProjection, t }) {
5137
+ const { balance, spend, todaySpend, sessionsSpend, isSubagent, crossedDay, error, refreshing, open, rootRef, refresh, toggleOpen } = useBillingData({
4767
5138
  getBalance,
4768
5139
  getCachedBalance,
4769
5140
  getSessionSpend,
4770
5141
  getTodaySpend,
4771
5142
  getTodaySessionsSpend,
5143
+ getDelegatedSpend,
4772
5144
  sessionId,
4773
5145
  useSession,
4774
5146
  useProjection
@@ -4792,7 +5164,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4792
5164
  }
4793
5165
  const line = primaryLine(balance);
4794
5166
  const amount = line === void 0 ? "—" : `${line.symbol}${line.total}`;
4795
- const spendLine = spend !== null && spend.models.length > 0 ? t("trigger.conversationSpend", { amount: formatSpend(spend.total) }) : void 0;
5167
+ const spendLine = spend !== null && spend.models.length > 0 ? t("label.sessionSpend", { amount: formatSpendSignificant(spend.total) }) : void 0;
4796
5168
  return (0, react_jsx_runtime.jsxs)("div", {
4797
5169
  ref: rootRef,
4798
5170
  className: BalanceBadge_module_css_default.root,
@@ -4808,6 +5180,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4808
5180
  spend,
4809
5181
  todaySpend,
4810
5182
  sessionsSpend,
5183
+ isSubagent,
5184
+ crossedDay,
4811
5185
  refreshing,
4812
5186
  onRefresh: refresh,
4813
5187
  t
@@ -4929,28 +5303,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4929
5303
  const NS = "billing";
4930
5304
  /** Simplified Chinese dictionary (the key-set source of truth). */
4931
5305
  const zh = {
4932
- "trigger.balance": "剩余额度:{amount}",
4933
- "trigger.conversationSpend": "本轮对话花费:{amount}",
5306
+ "trigger.balance": "剩余金额:{amount}",
4934
5307
  "label.amount": "API 剩余金额:{amount}",
4935
5308
  "label.sessionSpend": "本会话花费:{amount}",
4936
- "label.sessionSpend.today": "{amount}",
5309
+ "label.sessionSpend.today": " {amount}",
4937
5310
  "label.todaySpend": "今日花费:{amount}",
4938
5311
  "label.todayTokens": "今日 Token:{count}",
5312
+ "label.todayTokens.hit": " {percent}%",
4939
5313
  "unit.tokens": "{count} tok",
4940
5314
  "label.cost.input": "未缓存输入 {amount}",
4941
5315
  "label.cost.cacheRead": "缓存读取 {amount}",
4942
5316
  "label.cost.output": "输出 {amount}",
4943
5317
  "card.title": "花费金额",
4944
- "card.input": "未缓存输入",
4945
- "card.cacheRead": "缓存读取",
4946
- "card.output": "输出",
4947
5318
  "card.aria": "本轮对话花费:{amount}",
5319
+ "label.bucket.input": "未缓存输入",
5320
+ "label.bucket.cacheRead": "缓存读取",
5321
+ "label.bucket.output": "输出",
4948
5322
  "stat.none": "暂无消耗记录",
4949
5323
  "stat.untitled": "未命名",
4950
5324
  "state.unavailable": "额度不可用",
4951
5325
  "action.refresh": "刷新",
4952
5326
  "info.aria": "花费说明",
4953
- "info.hint": "估算:仅 DeepSeek 与 MiMo 模型,按每条消息自身时刻的峰谷官方单价计价(高峰:工作日 9:00–12:00、14:00–18:00)。\n括号内为本会话今日花费。\nv{version}",
5327
+ "info.hint": "估算:仅 DeepSeek 与 MiMo 模型,按每条消息自身时刻的峰谷官方单价计价(高峰:工作日 9:00–12:00、14:00–18:00)。\n金额含本会话委派的子代理会话。\n紧跟的数字为本会话今日花费。\nv{version}",
4954
5328
  "badge.aria": "DeepSeek 额度:{amount}",
4955
5329
  "panel.aria": "DeepSeek 额度详情",
4956
5330
  "label.sessionRanking": "今日会话花费",
@@ -4959,27 +5333,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4959
5333
  /** English dictionary, key-identical to the Chinese source of truth. */
4960
5334
  const en = {
4961
5335
  "trigger.balance": "Balance: {amount}",
4962
- "trigger.conversationSpend": "This conversation: {amount}",
4963
5336
  "label.amount": "API balance: {amount}",
4964
5337
  "label.sessionSpend": "This session: {amount}",
4965
- "label.sessionSpend.today": " ({amount})",
5338
+ "label.sessionSpend.today": " {amount}",
4966
5339
  "label.todaySpend": "Today spend: {amount}",
4967
5340
  "label.todayTokens": "Today tokens: {count}",
5341
+ "label.todayTokens.hit": " {percent}%",
4968
5342
  "unit.tokens": "{count} tok",
4969
5343
  "label.cost.input": "Uncached input {amount}",
4970
5344
  "label.cost.cacheRead": "Cached input {amount}",
4971
5345
  "label.cost.output": "Output {amount}",
4972
5346
  "card.title": "Spend",
4973
- "card.input": "Uncached input",
4974
- "card.cacheRead": "Cached input",
4975
- "card.output": "Output",
4976
5347
  "card.aria": "This conversation’s spend: {amount}",
5348
+ "label.bucket.input": "Uncached input",
5349
+ "label.bucket.cacheRead": "Cached input",
5350
+ "label.bucket.output": "Output",
4977
5351
  "stat.none": "No usage recorded",
4978
5352
  "stat.untitled": "Untitled",
4979
5353
  "state.unavailable": "Balance unavailable",
4980
5354
  "action.refresh": "Refresh",
4981
5355
  "info.aria": "About this spend",
4982
- "info.hint": "Estimate: DeepSeek and MiMo models only, each message priced at the official peak/off-peak rate of its own time (peak: weekdays 09:00–12:00, 14:00–18:00).\nThe parenthesized amount is this session's spend today.\nv{version}",
5356
+ "info.hint": "Estimate: DeepSeek and MiMo models only, each message priced at the official peak/off-peak rate of its own time (peak: weekdays 09:00–12:00, 14:00–18:00).\nThe amounts include the subagent sessions this session delegated.\nThe figure after it is this session's spend today.\nv{version}",
4983
5357
  "badge.aria": "DeepSeek balance {amount}",
4984
5358
  "panel.aria": "DeepSeek balance details",
4985
5359
  "label.sessionRanking": "Today session spend",
@@ -5022,7 +5396,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5022
5396
  getCachedBalance: () => lastBalance,
5023
5397
  getSessionSpend: async (sessionId) => unwrap("billing.getSessionSpend", await billing.getSessionSpend(sessionId)),
5024
5398
  getTodaySpend: async (force) => unwrap("billing.getTodaySpend", await billing.getTodaySpend(force)),
5025
- getTodaySessionsSpend: async (force) => unwrap("billing.getTodaySessionsSpend", await billing.getTodaySessionsSpend(force))
5399
+ getTodaySessionsSpend: async (force) => unwrap("billing.getTodaySessionsSpend", await billing.getTodaySessionsSpend(force)),
5400
+ getDelegatedSpend: async (sessionId, force) => unwrap("billing.getDelegatedSpend", await billing.getDelegatedSpend(sessionId, force))
5026
5401
  };
5027
5402
  ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
5028
5403
  name: "conversation.session.header.utilities",