@rulvar/openai 1.20.0 → 1.22.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
@@ -1,5 +1,5 @@
1
1
  import OpenAI, { ClientOptions } from "openai";
2
- import { CanonicalId, ChatEvent, ChatRequest, Effort, ModelCaps, PriceTable, ProviderAdapter, Usage, WireError } from "@rulvar/core";
2
+ import { CanonicalId, ChatEvent, ChatRequest, Effort, JournalEntry, ModelCaps, ModelRef, PriceTable, ProviderAdapter, Usage, WireError } from "@rulvar/core";
3
3
 
4
4
  //#region src/caps.d.ts
5
5
  interface OpenAiModelInfo {
@@ -8,11 +8,13 @@ interface OpenAiModelInfo {
8
8
  /** Reasoning models reject non-default sampling parameters. */
9
9
  reasoning: boolean;
10
10
  /**
11
- * The model accepts wire `reasoning.effort: "max"` (GPT-5.6 Sol per
12
- * the official model docs). When false, canonical max downmaps to
13
- * wire xhigh; the downmap is recorded in providerMetadata and the
14
- * journal identity keeps max, so caps accept the full canonical set
15
- * either way.
11
+ * The model accepts wire `reasoning.effort: "max"` (the whole GPT-5.6
12
+ * family per the official model guidance, each sibling verified live
13
+ * 2026-07-18). When false, canonical max downmaps to wire xhigh; the
14
+ * downmap is recorded in providerMetadata and the journal identity
15
+ * keeps max, so caps accept the full canonical set either way. Flip
16
+ * this to true ONLY on a per-model live verification, never from the
17
+ * family page alone.
16
18
  */
17
19
  wireMaxEffort: boolean;
18
20
  }
@@ -108,6 +110,41 @@ interface OpenAiCompatibleConfig {
108
110
  /** Creates a Chat Completions dialect adapter for a compatible endpoint. */
109
111
  declare function openaiCompatible(cfg: OpenAiCompatibleConfig): ProviderAdapter;
110
112
  //#endregion
113
+ //#region src/audit.d.ts
114
+ /**
115
+ * The exact inverse of the v1.19.0 double count for one usage:
116
+ * subtracts `cacheWriteTokens` back out of `inputTokens` and leaves
117
+ * every other field untouched. A usage without cache writes is returned
118
+ * unchanged (v1.19.0 recorded those correctly). Throws a typed
119
+ * ConfigError when the arithmetic cannot be the v1.19.0 shape (the
120
+ * recorded input has no room for the subtraction), which means the
121
+ * usage was NOT recorded by the affected adapter; do not guess.
122
+ */
123
+ declare function undoV1190CacheDoubleCount(usage: Usage): Usage;
124
+ /** One journal's sidecar reconciliation; see auditV1190CacheJournal. */
125
+ interface V1190CacheAudit {
126
+ /** Entries whose usage carried the affected shape and were inverted. */
127
+ affectedEntries: number;
128
+ /** The fold as recorded (what reports and budgets saw). */
129
+ recordedUsd: number;
130
+ /** The fold with every affected usage inverted to the true wire shape. */
131
+ correctedUsd: number;
132
+ }
133
+ /**
134
+ * Folds a journal twice with the SAME price function: once as recorded
135
+ * and once with every affected OpenAI usage passed through
136
+ * `undoV1190CacheDoubleCount`, returning both totals and the affected
137
+ * entry count. An entry (or per-model slice) counts as affected when it
138
+ * was served by the `openai` adapter, carries cache writes, and has no
139
+ * `usageSemantics` stamp; stamped entries are already correct and fold
140
+ * identically in both totals. The journal itself is never touched.
141
+ * `recordedUsd - correctedUsd` is the exact overcharge IF the journal
142
+ * was recorded by v1.19.0; for a v1.20.0 journal the same shape folds
143
+ * to a smaller `correctedUsd` that does NOT correspond to any real
144
+ * charge, so version provenance stays the caller's responsibility.
145
+ */
146
+ declare function auditV1190CacheJournal(entries: readonly JournalEntry[], priceUsd: (servedBy: ModelRef, usage: Usage) => number | undefined): V1190CacheAudit;
147
+ //#endregion
111
148
  //#region src/wire.d.ts
112
149
  /** Bijective canonical-to-wire (call_*) id map. */
113
150
  declare class OpenAiIdMap {
@@ -121,9 +158,10 @@ declare class OpenAiIdMap {
121
158
  /**
122
159
  * Canonical-to-wire effort: low through xhigh pass through. Canonical
123
160
  * max passes through unchanged on models whose caps declare wire max
124
- * support (GPT-5.6 Sol); elsewhere it downmaps to xhigh (documented
125
- * lossy; recorded in providerMetadata). Provider 'none' is reachable
126
- * only via providerOptions.openai.reasoningEffort.
161
+ * support (the whole GPT-5.6 family, each sibling verified live
162
+ * 2026-07-18; v1.20.0 review P2-3); elsewhere it downmaps to xhigh
163
+ * (documented lossy; recorded in providerMetadata). Provider 'none' is
164
+ * reachable only via providerOptions.openai.reasoningEffort.
127
165
  */
128
166
  declare function mapOpenAiEffort(effort: Effort, options?: {
129
167
  wireMaxEffort?: boolean;
@@ -164,6 +202,15 @@ type ResponsesStreamEvent = Record<string, unknown> & {
164
202
  * wire genuinely EXCLUDES both cache counts from `input_tokens`, so
165
203
  * that adapter adds them; the two wires differ, the canonical Usage
166
204
  * invariant does not.
205
+ *
206
+ * Numeric hygiene is deliberately NOT this function's job: any `number`
207
+ * the wire (or an injected client) reports passes through, and the core
208
+ * enforces the full telemetry invariant at the adapter boundary for
209
+ * every adapter uniformly, failing the call loud on non-finite,
210
+ * negative, or fractional counts while accounting only sanitized values
211
+ * (`usageViolations`/`sanitizeUsage` in @rulvar/core; v1.20.0 review
212
+ * P1-1). Real wires report whole nonnegative integers; a violation here
213
+ * means a broken transport, never plausible provider data.
167
214
  */
168
215
  declare function normalizeOpenAiUsage(raw: Record<string, unknown> | undefined): Usage;
169
216
  /**
@@ -195,4 +242,4 @@ declare function buildChatCompletionsParams(req: ChatRequest, ids: OpenAiIdMap):
195
242
  */
196
243
  declare function mapChatCompletionsStream(stream: AsyncIterable<Record<string, unknown>>, ids: OpenAiIdMap): AsyncGenerator<ChatEvent, void>;
197
244
  //#endregion
198
- export { CONSERVATIVE_COMPATIBLE_CAPS, OPENAI_MODELS, OPENAI_PRICING, type OpenAiAdapterOptions, type OpenAiClientLike, type OpenAiCompatibleConfig, OpenAiIdMap, type OpenAiModelInfo, type OpenAiSdkOptions, type ResponsesStreamEvent, buildChatCompletionsParams, buildResponsesParams, mapChatCompletionsStream, mapOpenAiEffort, mapResponsesStream, normalizeOpenAiUsage, openAiErrorToWire, openAiModelInfo, openai, openaiCompatible };
245
+ export { CONSERVATIVE_COMPATIBLE_CAPS, OPENAI_MODELS, OPENAI_PRICING, type OpenAiAdapterOptions, type OpenAiClientLike, type OpenAiCompatibleConfig, OpenAiIdMap, type OpenAiModelInfo, type OpenAiSdkOptions, type ResponsesStreamEvent, type V1190CacheAudit, auditV1190CacheJournal, buildChatCompletionsParams, buildResponsesParams, mapChatCompletionsStream, mapOpenAiEffort, mapResponsesStream, normalizeOpenAiUsage, openAiErrorToWire, openAiModelInfo, openai, openaiCompatible, undoV1190CacheDoubleCount };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import { ConfigError, createCanonicalIdMinter, isStrictCompatibleSchema } from "@rulvar/core";
2
+ import { ConfigError, createCanonicalIdMinter, entryUsageSlices, isStrictCompatibleSchema } from "@rulvar/core";
3
3
  //#region src/caps.ts
4
4
  const REASONING_EFFORTS = [
5
5
  "low",
@@ -34,7 +34,13 @@ const GPT_56_TIERS = [{
34
34
  * .../gpt-5.6-terra, .../gpt-5.6-luna; rates verified 2026-07-18). All
35
35
  * three: prompts strictly above 272K input tokens price the FULL
36
36
  * request at 2x input and 1.5x output; cache writes bill at 1.25x
37
- * uncached input. Only Sol accepts wire reasoning effort `max`.
37
+ * uncached input. All three accept wire reasoning effort `max`
38
+ * (v1.20.0 review P2-3; verified live per sibling 2026-07-18: a
39
+ * Responses call with `reasoning.effort: "max"` returns HTTP 200 with
40
+ * the effort echoed on Terra and Luna alike, and the API's own 400
41
+ * validator for an invalid effort enumerates `max` among the supported
42
+ * values, so acceptance is enforcement, not silence). Earlier families
43
+ * keep the conservative downmap until verified the same way.
38
44
  */
39
45
  const GPT_56_SOL = responses(105e4, 128e3, {
40
46
  inputUsdPerMTok: 5,
@@ -52,14 +58,14 @@ const OPENAI_MODELS = {
52
58
  cacheReadUsdPerMTok: .25,
53
59
  cacheWriteUsdPerMTok: 3.125,
54
60
  tiers: GPT_56_TIERS
55
- }),
61
+ }, { wireMaxEffort: true }),
56
62
  "gpt-5.6-luna": responses(105e4, 128e3, {
57
63
  inputUsdPerMTok: 1,
58
64
  outputUsdPerMTok: 6,
59
65
  cacheReadUsdPerMTok: .1,
60
66
  cacheWriteUsdPerMTok: 1.25,
61
67
  tiers: GPT_56_TIERS
62
- }),
68
+ }, { wireMaxEffort: true }),
63
69
  "gpt-5.6": GPT_56_SOL,
64
70
  "gpt-5.5": responses(4e5, 128e3, {
65
71
  inputUsdPerMTok: 5,
@@ -168,9 +174,10 @@ var OpenAiIdMap = class {
168
174
  /**
169
175
  * Canonical-to-wire effort: low through xhigh pass through. Canonical
170
176
  * max passes through unchanged on models whose caps declare wire max
171
- * support (GPT-5.6 Sol); elsewhere it downmaps to xhigh (documented
172
- * lossy; recorded in providerMetadata). Provider 'none' is reachable
173
- * only via providerOptions.openai.reasoningEffort.
177
+ * support (the whole GPT-5.6 family, each sibling verified live
178
+ * 2026-07-18; v1.20.0 review P2-3); elsewhere it downmaps to xhigh
179
+ * (documented lossy; recorded in providerMetadata). Provider 'none' is
180
+ * reachable only via providerOptions.openai.reasoningEffort.
174
181
  */
175
182
  function mapOpenAiEffort(effort, options) {
176
183
  if (effort === "max" && options?.wireMaxEffort !== true) return {
@@ -330,6 +337,15 @@ function clampCacheSubsets(inputTokens, rawRead, rawWrite) {
330
337
  * wire genuinely EXCLUDES both cache counts from `input_tokens`, so
331
338
  * that adapter adds them; the two wires differ, the canonical Usage
332
339
  * invariant does not.
340
+ *
341
+ * Numeric hygiene is deliberately NOT this function's job: any `number`
342
+ * the wire (or an injected client) reports passes through, and the core
343
+ * enforces the full telemetry invariant at the adapter boundary for
344
+ * every adapter uniformly, failing the call loud on non-finite,
345
+ * negative, or fractional counts while accounting only sanitized values
346
+ * (`usageViolations`/`sanitizeUsage` in @rulvar/core; v1.20.0 review
347
+ * P1-1). Real wires report whole nonnegative integers; a violation here
348
+ * means a broken transport, never plausible provider data.
333
349
  */
334
350
  function normalizeOpenAiUsage(raw) {
335
351
  const inputDetails = raw?.input_tokens_details;
@@ -740,6 +756,7 @@ function openai(options = {}) {
740
756
  return {
741
757
  id: "openai",
742
758
  provider: "openai",
759
+ usageSemantics: "openai-cache-subsets-v2",
743
760
  caps(model) {
744
761
  return openAiModelInfo(model).caps;
745
762
  },
@@ -810,6 +827,7 @@ function openaiCompatible(cfg) {
810
827
  return {
811
828
  id: cfg.id,
812
829
  provider: "openai",
830
+ usageSemantics: "openai-cache-subsets-v2",
813
831
  caps(model) {
814
832
  const overrides = cfg.caps?.(model);
815
833
  return overrides === void 0 ? CONSERVATIVE_COMPATIBLE_CAPS : {
@@ -835,4 +853,69 @@ function openaiCompatible(cfg) {
835
853
  };
836
854
  }
837
855
  //#endregion
838
- export { CONSERVATIVE_COMPATIBLE_CAPS, OPENAI_MODELS, OPENAI_PRICING, OpenAiIdMap, buildChatCompletionsParams, buildResponsesParams, mapChatCompletionsStream, mapOpenAiEffort, mapResponsesStream, normalizeOpenAiUsage, openAiErrorToWire, openAiModelInfo, openai, openaiCompatible };
856
+ //#region src/audit.ts
857
+ /**
858
+ * The exact inverse of the v1.19.0 double count for one usage:
859
+ * subtracts `cacheWriteTokens` back out of `inputTokens` and leaves
860
+ * every other field untouched. A usage without cache writes is returned
861
+ * unchanged (v1.19.0 recorded those correctly). Throws a typed
862
+ * ConfigError when the arithmetic cannot be the v1.19.0 shape (the
863
+ * recorded input has no room for the subtraction), which means the
864
+ * usage was NOT recorded by the affected adapter; do not guess.
865
+ */
866
+ function undoV1190CacheDoubleCount(usage) {
867
+ if (usage.cacheWriteTokens <= 0) return usage;
868
+ const trueInput = usage.inputTokens - usage.cacheWriteTokens;
869
+ if (trueInput < usage.cacheReadTokens + usage.cacheWriteTokens) throw new ConfigError(`not a v1.19.0-inflated usage: subtracting cacheWriteTokens (${String(usage.cacheWriteTokens)}) out of inputTokens (${String(usage.inputTokens)}) leaves less than the cache subsets; this usage was not recorded by the affected adapter`);
870
+ return {
871
+ ...usage,
872
+ inputTokens: trueInput
873
+ };
874
+ }
875
+ /**
876
+ * Folds a journal twice with the SAME price function: once as recorded
877
+ * and once with every affected OpenAI usage passed through
878
+ * `undoV1190CacheDoubleCount`, returning both totals and the affected
879
+ * entry count. An entry (or per-model slice) counts as affected when it
880
+ * was served by the `openai` adapter, carries cache writes, and has no
881
+ * `usageSemantics` stamp; stamped entries are already correct and fold
882
+ * identically in both totals. The journal itself is never touched.
883
+ * `recordedUsd - correctedUsd` is the exact overcharge IF the journal
884
+ * was recorded by v1.19.0; for a v1.20.0 journal the same shape folds
885
+ * to a smaller `correctedUsd` that does NOT correspond to any real
886
+ * charge, so version provenance stays the caller's responsibility.
887
+ */
888
+ function auditV1190CacheJournal(entries, priceUsd) {
889
+ let affectedEntries = 0;
890
+ let recordedUsd = 0;
891
+ let correctedUsd = 0;
892
+ for (const entry of entries) {
893
+ let entryAffected = false;
894
+ for (const slice of entryUsageSlices(entry)) {
895
+ const recorded = priceUsd(slice.servedBy, slice.usage);
896
+ if (recorded === void 0) continue;
897
+ recordedUsd += recorded;
898
+ if (entry.usageSemantics === void 0 && slice.servedBy.startsWith("openai:") && slice.usage.cacheWriteTokens > 0) {
899
+ let corrected;
900
+ try {
901
+ corrected = undoV1190CacheDoubleCount(slice.usage);
902
+ } catch {
903
+ corrected = void 0;
904
+ }
905
+ if (corrected === void 0) correctedUsd += recorded;
906
+ else {
907
+ entryAffected = true;
908
+ correctedUsd += priceUsd(slice.servedBy, corrected) ?? 0;
909
+ }
910
+ } else correctedUsd += recorded;
911
+ }
912
+ if (entryAffected) affectedEntries += 1;
913
+ }
914
+ return {
915
+ affectedEntries,
916
+ recordedUsd,
917
+ correctedUsd
918
+ };
919
+ }
920
+ //#endregion
921
+ export { CONSERVATIVE_COMPATIBLE_CAPS, OPENAI_MODELS, OPENAI_PRICING, OpenAiIdMap, auditV1190CacheJournal, buildChatCompletionsParams, buildResponsesParams, mapChatCompletionsStream, mapOpenAiEffort, mapResponsesStream, normalizeOpenAiUsage, openAiErrorToWire, openAiModelInfo, openai, openaiCompatible, undoV1190CacheDoubleCount };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/openai",
3
- "version": "1.20.0",
3
+ "version": "1.22.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.45.0",
26
- "@rulvar/core": "1.20.0"
26
+ "@rulvar/core": "1.22.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.0",
30
30
  "tsdown": "^0.22.3",
31
31
  "typescript": "~6.0.3",
32
- "@rulvar/testing": "1.20.0"
32
+ "@rulvar/testing": "1.22.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",