@rulvar/openai 1.124.0 → 1.126.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
@@ -200,6 +200,20 @@ interface ReconcileStatementOptions {
200
200
  totalToleranceUsd?: number;
201
201
  /** Provider-side model name of a served ref; default strips the adapter prefix. */
202
202
  modelOf?: (servedBy: ModelRef) => string;
203
+ /**
204
+ * How provider-reported token counts weigh on the verdict (RV903).
205
+ * 'verdict' (default): any token disagreement between the export and
206
+ * our recorded usage is a divergence, because our counts ARE the
207
+ * provider's own wire-reported numbers, so an export that disagrees
208
+ * with them describes a different request than the wire served, and
209
+ * dollars derived from either cannot be trusted to mean the same
210
+ * thing. 'informational' preserves the pre-v1.126 dollar-only
211
+ * verdict for exports whose token semantics legitimately differ from
212
+ * the wire's (a different cache accounting, rounded aggregates):
213
+ * mismatches are still counted and sampled, but only dollar deltas
214
+ * decide.
215
+ */
216
+ tokenComparison?: "verdict" | "informational";
203
217
  }
204
218
  /** One (model, component) line of the reconciliation. */
205
219
  interface ComponentDelta {
@@ -244,7 +258,12 @@ interface StatementReconciliation {
244
258
  components: ComponentDelta[];
245
259
  /** The lines beyond tolerance, largest |delta| first: the named divergences. */
246
260
  divergent: ComponentDelta[];
247
- /** Sample of token disagreements between the export and our recorded usage (requests mode). */
261
+ /**
262
+ * Token disagreements between the export and our recorded usage
263
+ * (requests mode). Under the default tokenComparison 'verdict' any
264
+ * mismatch makes the verdict 'divergence'; under 'informational' the
265
+ * count and sample still report, advisory only (RV903).
266
+ */
248
267
  tokenMismatches: number;
249
268
  tokenMismatchSample: Array<{
250
269
  responseId: string;
@@ -264,8 +283,11 @@ interface StatementReconciliation {
264
283
  * journal-free; see the module doc for the contract. Throws a typed
265
284
  * ConfigError on inputs that cannot be evidence: an empty statement (a
266
285
  * headline total with no rows), a request row without a response id, a
267
- * duplicate response id (an ambiguous join), or a request export whose
268
- * rows carry neither dollars, components, nor usage.
286
+ * duplicate response id (an ambiguous join), a request export whose
287
+ * rows carry neither dollars, components, nor usage, any non-finite or
288
+ * negative dollar amount, any non-integer or negative token count, or
289
+ * a non-finite or negative tolerance (RV903: a statement that cannot
290
+ * be summed must refuse loudly, never verdict 'match' on NaN totals).
269
291
  */
270
292
  declare function reconcileStatement(invoice: {
271
293
  rows: readonly InvoiceRow[];
package/dist/index.js CHANGED
@@ -32,13 +32,19 @@ const GPT_56_TIERS = [{
32
32
  /**
33
33
  * GPT-5.6 Sol, Terra, and Luna are three sibling models, not snapshots
34
34
  * of one model (developers.openai.com/api/docs/models/gpt-5.6-sol,
35
- * .../gpt-5.6-terra, .../gpt-5.6-luna; rates verified 2026-07-18
36
- * against those pages, and CONFIRMED against the provider's own
37
- * billing categories on 2026-07-30, when the twelfth-experiment
38
- * statement reconciliation matched all eight per-model per-component
39
- * dashboard categories to the cent, which is why the family rows carry
40
- * ratesVerifiedAt '2026-07-30' (RV814); the weekly rates audit
41
- * re-verifies the three pages). All
35
+ * .../gpt-5.6-terra, .../gpt-5.6-luna; the weekly rates audit
36
+ * re-verifies the three pages). The family rows carry ratesVerifiedAt
37
+ * '2026-07-31' (RV814): on that date every row was re-verified against
38
+ * its documented model page, picking up the provider's 2026-07-30
39
+ * price cut on Terra (0.8x across input, cached input, cache write,
40
+ * and output) and Luna (0.2x across the same four) that the
41
+ * thirteenth-experiment live audit caught as drift (RV911). Sol's
42
+ * rates are unchanged and additionally remain billing-CONFIRMED by the
43
+ * 2026-07-30 twelfth-experiment statement reconciliation, which
44
+ * matched all eight per-model per-component dashboard categories to
45
+ * the cent; the new Terra and Luna rates are docs-verified only, and
46
+ * their billing truth is established by the next statement
47
+ * reconciliation over saved exports, never assumed. All
42
48
  * three: prompts strictly above 272K input tokens price the FULL
43
49
  * request at 2x input and 1.5x output; cache writes bill at 1.25x
44
50
  * uncached input. All three accept wire reasoning effort `max`
@@ -55,26 +61,26 @@ const GPT_56_SOL = responses(105e4, 128e3, {
55
61
  cacheReadUsdPerMTok: .5,
56
62
  cacheWriteUsdPerMTok: 6.25,
57
63
  tiers: GPT_56_TIERS,
58
- ratesVerifiedAt: "2026-07-30"
64
+ ratesVerifiedAt: "2026-07-31"
59
65
  }, { wireMaxEffort: true });
60
66
  /** Static seed table of the current model set. */
61
67
  const OPENAI_MODELS = {
62
68
  "gpt-5.6-sol": GPT_56_SOL,
63
69
  "gpt-5.6-terra": responses(105e4, 128e3, {
64
- inputUsdPerMTok: 2.5,
65
- outputUsdPerMTok: 15,
66
- cacheReadUsdPerMTok: .25,
67
- cacheWriteUsdPerMTok: 3.125,
70
+ inputUsdPerMTok: 2,
71
+ outputUsdPerMTok: 12,
72
+ cacheReadUsdPerMTok: .2,
73
+ cacheWriteUsdPerMTok: 2.5,
68
74
  tiers: GPT_56_TIERS,
69
- ratesVerifiedAt: "2026-07-30"
75
+ ratesVerifiedAt: "2026-07-31"
70
76
  }, { wireMaxEffort: true }),
71
77
  "gpt-5.6-luna": responses(105e4, 128e3, {
72
- inputUsdPerMTok: 1,
73
- outputUsdPerMTok: 6,
74
- cacheReadUsdPerMTok: .1,
75
- cacheWriteUsdPerMTok: 1.25,
78
+ inputUsdPerMTok: .2,
79
+ outputUsdPerMTok: 1.2,
80
+ cacheReadUsdPerMTok: .02,
81
+ cacheWriteUsdPerMTok: .25,
76
82
  tiers: GPT_56_TIERS,
77
- ratesVerifiedAt: "2026-07-30"
83
+ ratesVerifiedAt: "2026-07-31"
78
84
  }, { wireMaxEffort: true }),
79
85
  "gpt-5.6": GPT_56_SOL,
80
86
  "gpt-5.5": responses(4e5, 128e3, {
@@ -121,7 +127,7 @@ const OPENAI_MODELS = {
121
127
  * silent reinterpretation.
122
128
  */
123
129
  const OPENAI_PRICING = {
124
- pricingVersion: "openai-2026-07-18-r2",
130
+ pricingVersion: "openai-2026-07-31",
125
131
  models: (() => {
126
132
  const models = {};
127
133
  for (const [name, info] of Object.entries(OPENAI_MODELS)) if (info.caps.pricing !== void 0) models[`openai:${name}`] = info.caps.pricing;
@@ -1078,17 +1084,40 @@ const defaultModelOf = (servedBy) => {
1078
1084
  return colon === -1 ? servedBy : servedBy.slice(colon + 1);
1079
1085
  };
1080
1086
  /**
1087
+ * A statement dollar amount must be a finite nonnegative number
1088
+ * (RV903). The thirteenth experiment's probe fed `usd: NaN` and got
1089
+ * verdict 'match' with NaN totals: NaN flowed through the sums and
1090
+ * `Math.abs(NaN) > tolerance` is false, so the divergence check
1091
+ * silently disarmed. Negative amounts are refused too: provider
1092
+ * credits and adjustments are real, but they are not per-request or
1093
+ * per-component BILLING evidence, and folding them into the join would
1094
+ * let an adjustment mask a rate divergence of the same size.
1095
+ */
1096
+ function assertStatementUsd(where, field, value) {
1097
+ if (!Number.isFinite(value)) throw new ConfigError(`statement reconciliation refused: ${where} carries ${field} ${String(value)}, which cannot be summed; a statement whose dollars are not finite is not evidence`);
1098
+ if (value < 0) throw new ConfigError(`statement reconciliation refused: ${where} carries negative ${field} ${String(value)}; credits and adjustments reconcile separately, never as negative statement rows`);
1099
+ }
1100
+ /** A provider-reported token count must be a nonnegative integer (RV903). */
1101
+ function assertTokenCount(where, field, value) {
1102
+ if (!Number.isInteger(value) || value < 0) throw new ConfigError(`statement reconciliation refused: ${where} carries ${field} ${String(value)}; provider-reported token counts are nonnegative integers`);
1103
+ }
1104
+ /**
1081
1105
  * Reconciles the invoice against a normalized provider export. Pure and
1082
1106
  * journal-free; see the module doc for the contract. Throws a typed
1083
1107
  * ConfigError on inputs that cannot be evidence: an empty statement (a
1084
1108
  * headline total with no rows), a request row without a response id, a
1085
- * duplicate response id (an ambiguous join), or a request export whose
1086
- * rows carry neither dollars, components, nor usage.
1109
+ * duplicate response id (an ambiguous join), a request export whose
1110
+ * rows carry neither dollars, components, nor usage, any non-finite or
1111
+ * negative dollar amount, any non-integer or negative token count, or
1112
+ * a non-finite or negative tolerance (RV903: a statement that cannot
1113
+ * be summed must refuse loudly, never verdict 'match' on NaN totals).
1087
1114
  */
1088
1115
  function reconcileStatement(invoice, statement, options) {
1116
+ for (const [name, value] of [["componentToleranceUsd", options.componentToleranceUsd], ["totalToleranceUsd", options.totalToleranceUsd]]) if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new ConfigError(`statement reconciliation refused: ${name} ${String(value)} is not a finite nonnegative dollar tolerance`);
1089
1117
  const componentToleranceUsd = options.componentToleranceUsd ?? .005;
1090
1118
  const totalToleranceUsd = options.totalToleranceUsd ?? .01;
1091
1119
  const modelOf = options.modelOf ?? defaultModelOf;
1120
+ const tokenComparison = options.tokenComparison ?? "verdict";
1092
1121
  if (statement.rows.length === 0) throw new ConfigError("statement reconciliation refused: the statement carries no rows. A headline total is not evidence (dashboard aggregates are eventually consistent); export per-request rows or per-component categories and reconcile those");
1093
1122
  const billable = [];
1094
1123
  let usageUnknownRows = 0;
@@ -1116,6 +1145,21 @@ function reconcileStatement(invoice, statement, options) {
1116
1145
  for (const row of statement.rows) {
1117
1146
  if (row.responseId === "") throw new ConfigError("statement reconciliation refused: a per-request export row has no response id, the join key; normalize the export or reconcile per-component categories instead");
1118
1147
  if (byId.has(row.responseId)) throw new ConfigError(`statement reconciliation refused: duplicate response id '${row.responseId}' in the export makes the join ambiguous`);
1148
+ const where = `row '${row.responseId}'`;
1149
+ if (row.usd !== void 0) assertStatementUsd(where, "usd", row.usd);
1150
+ if (row.componentsUsd !== void 0) for (const component of COMPONENTS) {
1151
+ const usd = row.componentsUsd[component];
1152
+ if (usd !== void 0) assertStatementUsd(where, `componentsUsd.${component}`, usd);
1153
+ }
1154
+ if (row.usage !== void 0) for (const field of [
1155
+ "inputTokens",
1156
+ "cachedInputTokens",
1157
+ "cacheWriteTokens",
1158
+ "outputTokens"
1159
+ ]) {
1160
+ const count = row.usage[field];
1161
+ if (count !== void 0) assertTokenCount(where, `usage.${field}`, count);
1162
+ }
1119
1163
  byId.set(row.responseId, row);
1120
1164
  if (row.usd !== void 0 || row.componentsUsd !== void 0 || row.usage !== void 0) carriesAnything = true;
1121
1165
  }
@@ -1196,6 +1240,7 @@ function reconcileStatement(invoice, statement, options) {
1196
1240
  statementComponents = /* @__PURE__ */ new Map();
1197
1241
  let total = 0;
1198
1242
  for (const row of statement.rows) {
1243
+ assertStatementUsd(`category row '${row.model}' ${row.component}`, "usd", row.usd);
1199
1244
  const sums = statementComponents.get(row.model) ?? {};
1200
1245
  sums[row.component] = (sums[row.component] ?? 0) + row.usd;
1201
1246
  statementComponents.set(row.model, sums);
@@ -1273,8 +1318,9 @@ function reconcileStatement(invoice, statement, options) {
1273
1318
  const totalsDelta = statementTotalUsd === void 0 ? void 0 : statementTotalUsd - ourUsd;
1274
1319
  const totalsDivergent = statementComponents === void 0 && totalsDelta !== void 0 && Math.abs(totalsDelta) > totalToleranceUsd;
1275
1320
  const coverageComplete = unmatchedRows === 0 && statementOnlyRows === 0 && unpricedModels.size === 0 && (statement.kind === "categories" || matchedRows === rowsWithResponseId && rowsWithResponseId === billable.length) && (statement.kind === "requests" || components.every((line) => line.statementUsd !== void 0));
1321
+ const tokensDivergent = tokenComparison === "verdict" && tokenMismatches > 0;
1276
1322
  let verdict;
1277
- if (divergent.length > 0 || totalsDivergent) verdict = "divergence";
1323
+ if (divergent.length > 0 || totalsDivergent || tokensDivergent) verdict = "divergence";
1278
1324
  else if (matchedRows === 0) verdict = "no-overlap";
1279
1325
  else if (!coverageComplete) verdict = "partial-coverage";
1280
1326
  else verdict = "match";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/openai",
3
- "version": "1.124.0",
3
+ "version": "1.126.0",
4
4
  "description": "Rulvar first-class provider adapter for the OpenAI Responses API, plus the openaiCompatible factory.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -23,13 +23,13 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "openai": "^6.49.0",
26
- "@rulvar/core": "1.124.0"
26
+ "@rulvar/core": "1.126.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.1",
30
30
  "tsdown": "^0.22.14",
31
31
  "typescript": "~6.0.3",
32
- "@rulvar/testing": "1.124.0"
32
+ "@rulvar/testing": "1.126.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",