@rulvar/openai 1.125.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 +25 -3
- package/dist/index.js +43 -3
- package/package.json +3 -3
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
|
-
/**
|
|
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),
|
|
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
|
@@ -1084,17 +1084,40 @@ const defaultModelOf = (servedBy) => {
|
|
|
1084
1084
|
return colon === -1 ? servedBy : servedBy.slice(colon + 1);
|
|
1085
1085
|
};
|
|
1086
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
|
+
/**
|
|
1087
1105
|
* Reconciles the invoice against a normalized provider export. Pure and
|
|
1088
1106
|
* journal-free; see the module doc for the contract. Throws a typed
|
|
1089
1107
|
* ConfigError on inputs that cannot be evidence: an empty statement (a
|
|
1090
1108
|
* headline total with no rows), a request row without a response id, a
|
|
1091
|
-
* duplicate response id (an ambiguous join),
|
|
1092
|
-
* 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).
|
|
1093
1114
|
*/
|
|
1094
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`);
|
|
1095
1117
|
const componentToleranceUsd = options.componentToleranceUsd ?? .005;
|
|
1096
1118
|
const totalToleranceUsd = options.totalToleranceUsd ?? .01;
|
|
1097
1119
|
const modelOf = options.modelOf ?? defaultModelOf;
|
|
1120
|
+
const tokenComparison = options.tokenComparison ?? "verdict";
|
|
1098
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");
|
|
1099
1122
|
const billable = [];
|
|
1100
1123
|
let usageUnknownRows = 0;
|
|
@@ -1122,6 +1145,21 @@ function reconcileStatement(invoice, statement, options) {
|
|
|
1122
1145
|
for (const row of statement.rows) {
|
|
1123
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");
|
|
1124
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
|
+
}
|
|
1125
1163
|
byId.set(row.responseId, row);
|
|
1126
1164
|
if (row.usd !== void 0 || row.componentsUsd !== void 0 || row.usage !== void 0) carriesAnything = true;
|
|
1127
1165
|
}
|
|
@@ -1202,6 +1240,7 @@ function reconcileStatement(invoice, statement, options) {
|
|
|
1202
1240
|
statementComponents = /* @__PURE__ */ new Map();
|
|
1203
1241
|
let total = 0;
|
|
1204
1242
|
for (const row of statement.rows) {
|
|
1243
|
+
assertStatementUsd(`category row '${row.model}' ${row.component}`, "usd", row.usd);
|
|
1205
1244
|
const sums = statementComponents.get(row.model) ?? {};
|
|
1206
1245
|
sums[row.component] = (sums[row.component] ?? 0) + row.usd;
|
|
1207
1246
|
statementComponents.set(row.model, sums);
|
|
@@ -1279,8 +1318,9 @@ function reconcileStatement(invoice, statement, options) {
|
|
|
1279
1318
|
const totalsDelta = statementTotalUsd === void 0 ? void 0 : statementTotalUsd - ourUsd;
|
|
1280
1319
|
const totalsDivergent = statementComponents === void 0 && totalsDelta !== void 0 && Math.abs(totalsDelta) > totalToleranceUsd;
|
|
1281
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;
|
|
1282
1322
|
let verdict;
|
|
1283
|
-
if (divergent.length > 0 || totalsDivergent) verdict = "divergence";
|
|
1323
|
+
if (divergent.length > 0 || totalsDivergent || tokensDivergent) verdict = "divergence";
|
|
1284
1324
|
else if (matchedRows === 0) verdict = "no-overlap";
|
|
1285
1325
|
else if (!coverageComplete) verdict = "partial-coverage";
|
|
1286
1326
|
else verdict = "match";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/openai",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
32
|
+
"@rulvar/testing": "1.126.0"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|