@rulvar/openai 1.18.0 → 1.19.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
@@ -148,7 +148,17 @@ declare function buildResponsesParams(req: ChatRequest, ids: OpenAiIdMap, option
148
148
  type ResponsesStreamEvent = Record<string, unknown> & {
149
149
  type: string;
150
150
  };
151
- /** Normalizes Responses usage: input_tokens already includes cached reads. */
151
+ /**
152
+ * Normalizes Responses usage into the canonical Usage invariant, where
153
+ * `inputTokens` is the FULL prompt: wire `input_tokens` already includes
154
+ * cached READS, while cache WRITE tokens arrive SEPARATELY in
155
+ * `input_tokens_details.cache_write_tokens` (GPT-5.6 and later families;
156
+ * they bill at the 1.25x write premium, and earlier families report no
157
+ * field and pay no premium). Dropping the field lost the whole write
158
+ * charge and weakened the budget guard (v1.18.0 review P1-2), so writes
159
+ * are added into `inputTokens` and surfaced as `cacheWriteTokens` for
160
+ * the premium rate, exactly mirroring the Anthropic adapter's mapping.
161
+ */
152
162
  declare function normalizeOpenAiUsage(raw: Record<string, unknown> | undefined): Usage;
153
163
  /**
154
164
  * Maps the typed Responses SSE stream to ChatEvents, yielding each
package/dist/index.js CHANGED
@@ -62,24 +62,23 @@ const OPENAI_MODELS = {
62
62
  }),
63
63
  "gpt-5.6": GPT_56_SOL,
64
64
  "gpt-5.5": responses(4e5, 128e3, {
65
- inputUsdPerMTok: 10,
66
- outputUsdPerMTok: 40,
67
- cacheReadUsdPerMTok: 1
65
+ inputUsdPerMTok: 5,
66
+ outputUsdPerMTok: 30,
67
+ cacheReadUsdPerMTok: .5
68
68
  }),
69
69
  "gpt-5.5-pro": responses(4e5, 128e3, {
70
- inputUsdPerMTok: 40,
71
- outputUsdPerMTok: 160,
72
- cacheReadUsdPerMTok: 4
70
+ inputUsdPerMTok: 30,
71
+ outputUsdPerMTok: 180
73
72
  }),
74
73
  "gpt-5.4": responses(272e3, 1e5, {
75
- inputUsdPerMTok: 6,
76
- outputUsdPerMTok: 24,
77
- cacheReadUsdPerMTok: .6
74
+ inputUsdPerMTok: 2.5,
75
+ outputUsdPerMTok: 15,
76
+ cacheReadUsdPerMTok: .25
78
77
  }),
79
78
  "gpt-5.4-mini": responses(272e3, 1e5, {
80
- inputUsdPerMTok: 1.2,
81
- outputUsdPerMTok: 4.8,
82
- cacheReadUsdPerMTok: .12
79
+ inputUsdPerMTok: .75,
80
+ outputUsdPerMTok: 4.5,
81
+ cacheReadUsdPerMTok: .075
83
82
  })
84
83
  };
85
84
  /**
@@ -102,7 +101,7 @@ const OPENAI_MODELS = {
102
101
  * silent reinterpretation.
103
102
  */
104
103
  const OPENAI_PRICING = {
105
- pricingVersion: "openai-2026-07-18",
104
+ pricingVersion: "openai-2026-07-18-r2",
106
105
  models: (() => {
107
106
  const models = {};
108
107
  for (const [name, info] of Object.entries(OPENAI_MODELS)) if (info.caps.pricing !== void 0) models[`openai:${name}`] = info.caps.pricing;
@@ -298,15 +297,26 @@ function buildResponsesParams(req, ids, options) {
298
297
  effortDownmapped
299
298
  };
300
299
  }
301
- /** Normalizes Responses usage: input_tokens already includes cached reads. */
300
+ /**
301
+ * Normalizes Responses usage into the canonical Usage invariant, where
302
+ * `inputTokens` is the FULL prompt: wire `input_tokens` already includes
303
+ * cached READS, while cache WRITE tokens arrive SEPARATELY in
304
+ * `input_tokens_details.cache_write_tokens` (GPT-5.6 and later families;
305
+ * they bill at the 1.25x write premium, and earlier families report no
306
+ * field and pay no premium). Dropping the field lost the whole write
307
+ * charge and weakened the budget guard (v1.18.0 review P1-2), so writes
308
+ * are added into `inputTokens` and surfaced as `cacheWriteTokens` for
309
+ * the premium rate, exactly mirroring the Anthropic adapter's mapping.
310
+ */
302
311
  function normalizeOpenAiUsage(raw) {
303
312
  const inputDetails = raw?.input_tokens_details;
304
313
  const outputDetails = raw?.output_tokens_details;
314
+ const cacheWrite = typeof inputDetails?.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : 0;
305
315
  const usage = {
306
- inputTokens: typeof raw?.input_tokens === "number" ? raw.input_tokens : 0,
316
+ inputTokens: (typeof raw?.input_tokens === "number" ? raw.input_tokens : 0) + cacheWrite,
307
317
  outputTokens: typeof raw?.output_tokens === "number" ? raw.output_tokens : 0,
308
318
  cacheReadTokens: typeof inputDetails?.cached_tokens === "number" ? inputDetails.cached_tokens : 0,
309
- cacheWriteTokens: 0
319
+ cacheWriteTokens: cacheWrite
310
320
  };
311
321
  const reasoning = outputDetails?.reasoning_tokens;
312
322
  if (typeof reasoning === "number" && reasoning > 0) usage.reasoningTokens = reasoning;
@@ -415,31 +425,59 @@ async function* mapResponsesStream(stream, ids, options) {
415
425
  };
416
426
  return;
417
427
  }
418
- case "response.failed":
428
+ case "response.failed": {
429
+ const response = event.response;
430
+ const rawUsage = response?.usage;
431
+ if (rawUsage !== void 0) yield {
432
+ type: "usage",
433
+ usage: normalizeOpenAiUsage(rawUsage)
434
+ };
435
+ const error = response?.error;
419
436
  yield {
420
437
  type: "error",
421
- error: {
422
- code: "agent",
423
- message: (event.response?.error)?.message ?? "response.failed",
424
- retryable: false,
425
- data: { kind: "transport" }
426
- }
438
+ error: failedResponseError(typeof error?.code === "string" ? error.code : void 0, error?.message ?? "response.failed")
427
439
  };
428
440
  return;
441
+ }
429
442
  case "error":
430
443
  yield {
431
444
  type: "error",
432
- error: {
433
- code: "agent",
434
- message: event.message ?? "stream error",
435
- retryable: false,
436
- data: { kind: "transport" }
437
- }
445
+ error: failedResponseError(typeof event.code === "string" ? event.code : void 0, event.message ?? "stream error")
438
446
  };
439
447
  return;
440
448
  default: break;
441
449
  }
442
450
  }
451
+ /**
452
+ * Classifies a terminal stream failure (`response.failed` /
453
+ * SSE `error`) into the retryable WireError vocabulary by the
454
+ * documented `response.error.code` enum (Responses API reference):
455
+ * `rate_limit_exceeded` retries as a rate limit, `server_error` and
456
+ * timeout-class codes retry as transport faults, and everything else
457
+ * (validation such as `invalid_prompt`, policy, auth) stays
458
+ * non-retryable. Unknown codes fail closed as non-retryable: retrying a
459
+ * permanent failure would loop-bill it (v1.18.0 review P1-3).
460
+ */
461
+ function failedResponseError(code, message) {
462
+ if (code === "rate_limit_exceeded") return {
463
+ code: "agent",
464
+ message,
465
+ retryable: true,
466
+ data: {
467
+ kind: "rate-limit",
468
+ providerCode: code
469
+ }
470
+ };
471
+ return {
472
+ code: "agent",
473
+ message,
474
+ retryable: code === "server_error" || code !== void 0 && code.endsWith("_timeout"),
475
+ data: {
476
+ kind: "transport",
477
+ ...code === void 0 ? {} : { providerCode: code }
478
+ }
479
+ };
480
+ }
443
481
  /** Projects SDK/API errors into the retryable WireError vocabulary. */
444
482
  function openAiErrorToWire(error) {
445
483
  const record = error;
@@ -606,11 +644,12 @@ async function* mapChatCompletionsStream(stream, ids) {
606
644
  const chunkUsage = chunk.usage;
607
645
  if (chunkUsage !== void 0 && chunkUsage !== null) {
608
646
  const promptDetails = chunkUsage.prompt_tokens_details;
647
+ const cacheWrite = typeof promptDetails?.cache_write_tokens === "number" ? promptDetails.cache_write_tokens : 0;
609
648
  usage = {
610
- inputTokens: typeof chunkUsage.prompt_tokens === "number" ? chunkUsage.prompt_tokens : 0,
649
+ inputTokens: (typeof chunkUsage.prompt_tokens === "number" ? chunkUsage.prompt_tokens : 0) + cacheWrite,
611
650
  outputTokens: typeof chunkUsage.completion_tokens === "number" ? chunkUsage.completion_tokens : 0,
612
651
  cacheReadTokens: typeof promptDetails?.cached_tokens === "number" ? promptDetails.cached_tokens : 0,
613
- cacheWriteTokens: 0
652
+ cacheWriteTokens: cacheWrite
614
653
  };
615
654
  }
616
655
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/openai",
3
- "version": "1.18.0",
3
+ "version": "1.19.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.18.0"
26
+ "@rulvar/core": "1.19.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.18.0"
32
+ "@rulvar/testing": "1.19.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",