@vincemakes/kiso-core 0.36.0 → 0.38.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.
@@ -416,6 +416,50 @@ export interface Usage {
416
416
  readonly cacheRead: number | null;
417
417
  readonly cacheWrite: number | null;
418
418
  readonly known: boolean;
419
+ /**
420
+ * RSN-1: how many of `outputTokens` the model spent THINKING, when the
421
+ * provider says. It is a SPLIT of the output, not a fifth quantity —
422
+ * the bill has always counted it, because completion tokens include
423
+ * reasoning ones (measured: 39 reasoning inside 76 completion, with the
424
+ * remaining 37 accounting for the answer exactly).
425
+ *
426
+ * What was missing is the SPLIT, and the split is what the effort knob
427
+ * controls. Without it, an arm that costs more cannot be told from an
428
+ * arm that thought longer — and a cost regression traced to reasoning
429
+ * length once already had to be found by other means.
430
+ *
431
+ * ABSENT means the provider did not report one: the field is missing
432
+ * entirely from the vendor's response when thinking is off, and missing
433
+ * on every route whose usage carries no breakdown at all. Absent is not
434
+ * zero, and a zero here is a provider that measured no thinking.
435
+ */
436
+ readonly reasoningTokens?: number;
437
+ /**
438
+ * TRACE-F1: the model id the SERVER says it served, when it says one.
439
+ *
440
+ * Every adapter until now spoke only of `options.model` — the id we
441
+ * ASKED for. A vendor is free to serve something else: a retired name
442
+ * that is now an alias, a migration id, a silently upgraded tier. The
443
+ * bench read `deepseek-v4-flash` off its own config for four days while
444
+ * the server served `deepseek-flash`, and the specified-vs-observed
445
+ * reconciliation built exactly to catch that never fired, because
446
+ * nothing produced the observed half.
447
+ *
448
+ * Optional because it is the SERVER's statement, not ours: a provider
449
+ * that reports no model leaves this undefined, and undefined means
450
+ * "not stated", never "same as requested" (Area 6 — unknown is not a
451
+ * default).
452
+ *
453
+ * It rides the usage event because that is where the adapter holds the
454
+ * response. NOT because usage arrives once: the contract is AT LEAST
455
+ * once, and the openai-compat adapter has three exits that can emit it
456
+ * — believing otherwise is what W22-R1 was, where a second report for
457
+ * one call was added to the first and put a wrong number on screen. A
458
+ * consumer of this field must therefore expect repeats, and they are
459
+ * the same statement rather than two: the adapters capture the served
460
+ * id ONCE per call and every exit reports that one value.
461
+ */
462
+ readonly servedModel?: string;
419
463
  }
420
464
  /** MG-1 (ADR-0051 Amendment 5): the continuation envelope's scope — WHO
421
465
  * may replay it. Kernel-stamped at the Turn Commit append from the run's
@@ -173,6 +173,18 @@ function isContinuation(v) {
173
173
  function isUsage(v) {
174
174
  if (typeof v.known !== "boolean")
175
175
  return false;
176
+ // TRACE-F1: a stated served model is a NON-EMPTY string. An empty one is
177
+ // a provider field we failed to read, and it would reconcile as a
178
+ // contradiction against every requested id — absent says "not stated".
179
+ if (v.servedModel !== undefined && (typeof v.servedModel !== "string" || v.servedModel === ""))
180
+ return false;
181
+ // RSN-1: a reported split is a non-negative integer, and it CANNOT ride
182
+ // a `known: false` event — that says the provider reported nothing, and
183
+ // a number here would be reporting something.
184
+ if (v.reasoningTokens !== undefined) {
185
+ if (!isNonNegativeInt(v.reasoningTokens) || v.known === false)
186
+ return false;
187
+ }
176
188
  const tokens = [v.inputTokens, v.outputTokens, v.cacheRead, v.cacheWrite];
177
189
  if (v.known === false)
178
190
  return tokens.every((t) => t === null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "kiso core \u2014 protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "openai"
34
34
  ],
35
35
  "devDependencies": {
36
- "@vincemakes/kiso-evals": "0.36.0",
36
+ "@vincemakes/kiso-evals": "0.38.0",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"