@rulvar/openai 1.17.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 +30 -7
- package/dist/index.js +124 -57
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ interface OpenAiModelInfo {
|
|
|
7
7
|
api: "responses" | "chat";
|
|
8
8
|
/** Reasoning models reject non-default sampling parameters. */
|
|
9
9
|
reasoning: boolean;
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
wireMaxEffort: boolean;
|
|
10
18
|
}
|
|
11
19
|
/** Static seed table of the current model set. */
|
|
12
20
|
declare const OPENAI_MODELS: Record<string, OpenAiModelInfo>;
|
|
@@ -111,12 +119,15 @@ declare class OpenAiIdMap {
|
|
|
111
119
|
wireFor(canonicalId: CanonicalId): string;
|
|
112
120
|
}
|
|
113
121
|
/**
|
|
114
|
-
* Canonical-to-wire effort: low through
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
122
|
+
* Canonical-to-wire effort: low through xhigh pass through. Canonical
|
|
123
|
+
* 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.
|
|
118
127
|
*/
|
|
119
|
-
declare function mapOpenAiEffort(effort: Effort
|
|
128
|
+
declare function mapOpenAiEffort(effort: Effort, options?: {
|
|
129
|
+
wireMaxEffort?: boolean;
|
|
130
|
+
}): {
|
|
120
131
|
wire: string;
|
|
121
132
|
downmapped: boolean;
|
|
122
133
|
};
|
|
@@ -127,7 +138,9 @@ declare function mapOpenAiEffort(effort: Effort): {
|
|
|
127
138
|
* are REJECTED as a typed ConfigError. Role
|
|
128
139
|
* 'system' messages project into top-level instructions on every request.
|
|
129
140
|
*/
|
|
130
|
-
declare function buildResponsesParams(req: ChatRequest, ids: OpenAiIdMap
|
|
141
|
+
declare function buildResponsesParams(req: ChatRequest, ids: OpenAiIdMap, options?: {
|
|
142
|
+
wireMaxEffort?: boolean;
|
|
143
|
+
}): {
|
|
131
144
|
params: Record<string, unknown>;
|
|
132
145
|
effortDownmapped: boolean;
|
|
133
146
|
};
|
|
@@ -135,7 +148,17 @@ declare function buildResponsesParams(req: ChatRequest, ids: OpenAiIdMap): {
|
|
|
135
148
|
type ResponsesStreamEvent = Record<string, unknown> & {
|
|
136
149
|
type: string;
|
|
137
150
|
};
|
|
138
|
-
/**
|
|
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
|
+
*/
|
|
139
162
|
declare function normalizeOpenAiUsage(raw: Record<string, unknown> | undefined): Usage;
|
|
140
163
|
/**
|
|
141
164
|
* Maps the typed Responses SSE stream to ChatEvents, yielding each
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const REASONING_EFFORTS = [
|
|
|
7
7
|
"high",
|
|
8
8
|
"xhigh"
|
|
9
9
|
];
|
|
10
|
-
function responses(contextWindow, maxOutputTokens, pricing) {
|
|
10
|
+
function responses(contextWindow, maxOutputTokens, pricing, options) {
|
|
11
11
|
return {
|
|
12
12
|
caps: {
|
|
13
13
|
structuredOutput: "native",
|
|
@@ -19,48 +19,66 @@ function responses(contextWindow, maxOutputTokens, pricing) {
|
|
|
19
19
|
...pricing === void 0 ? {} : { pricing }
|
|
20
20
|
},
|
|
21
21
|
api: "responses",
|
|
22
|
-
reasoning: true
|
|
22
|
+
reasoning: true,
|
|
23
|
+
wireMaxEffort: options?.wireMaxEffort === true
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
const GPT_56_TIERS = [{
|
|
27
|
+
aboveInputTokens: 272e3,
|
|
28
|
+
inputMultiplier: 2,
|
|
29
|
+
outputMultiplier: 1.5
|
|
30
|
+
}];
|
|
25
31
|
/**
|
|
26
|
-
* GPT-5.6 Sol
|
|
27
|
-
*
|
|
28
|
-
*
|
|
32
|
+
* GPT-5.6 Sol, Terra, and Luna are three sibling models, not snapshots
|
|
33
|
+
* of one model (developers.openai.com/api/docs/models/gpt-5.6-sol,
|
|
34
|
+
* .../gpt-5.6-terra, .../gpt-5.6-luna; rates verified 2026-07-18). All
|
|
35
|
+
* three: prompts strictly above 272K input tokens price the FULL
|
|
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`.
|
|
29
38
|
*/
|
|
30
39
|
const GPT_56_SOL = responses(105e4, 128e3, {
|
|
31
40
|
inputUsdPerMTok: 5,
|
|
32
41
|
outputUsdPerMTok: 30,
|
|
33
42
|
cacheReadUsdPerMTok: .5,
|
|
34
43
|
cacheWriteUsdPerMTok: 6.25,
|
|
35
|
-
tiers:
|
|
36
|
-
|
|
37
|
-
inputMultiplier: 2,
|
|
38
|
-
outputMultiplier: 1.5
|
|
39
|
-
}]
|
|
40
|
-
});
|
|
44
|
+
tiers: GPT_56_TIERS
|
|
45
|
+
}, { wireMaxEffort: true });
|
|
41
46
|
/** Static seed table of the current model set. */
|
|
42
47
|
const OPENAI_MODELS = {
|
|
43
48
|
"gpt-5.6-sol": GPT_56_SOL,
|
|
49
|
+
"gpt-5.6-terra": responses(105e4, 128e3, {
|
|
50
|
+
inputUsdPerMTok: 2.5,
|
|
51
|
+
outputUsdPerMTok: 15,
|
|
52
|
+
cacheReadUsdPerMTok: .25,
|
|
53
|
+
cacheWriteUsdPerMTok: 3.125,
|
|
54
|
+
tiers: GPT_56_TIERS
|
|
55
|
+
}),
|
|
56
|
+
"gpt-5.6-luna": responses(105e4, 128e3, {
|
|
57
|
+
inputUsdPerMTok: 1,
|
|
58
|
+
outputUsdPerMTok: 6,
|
|
59
|
+
cacheReadUsdPerMTok: .1,
|
|
60
|
+
cacheWriteUsdPerMTok: 1.25,
|
|
61
|
+
tiers: GPT_56_TIERS
|
|
62
|
+
}),
|
|
44
63
|
"gpt-5.6": GPT_56_SOL,
|
|
45
64
|
"gpt-5.5": responses(4e5, 128e3, {
|
|
46
|
-
inputUsdPerMTok:
|
|
47
|
-
outputUsdPerMTok:
|
|
48
|
-
cacheReadUsdPerMTok:
|
|
65
|
+
inputUsdPerMTok: 5,
|
|
66
|
+
outputUsdPerMTok: 30,
|
|
67
|
+
cacheReadUsdPerMTok: .5
|
|
49
68
|
}),
|
|
50
69
|
"gpt-5.5-pro": responses(4e5, 128e3, {
|
|
51
|
-
inputUsdPerMTok:
|
|
52
|
-
outputUsdPerMTok:
|
|
53
|
-
cacheReadUsdPerMTok: 4
|
|
70
|
+
inputUsdPerMTok: 30,
|
|
71
|
+
outputUsdPerMTok: 180
|
|
54
72
|
}),
|
|
55
73
|
"gpt-5.4": responses(272e3, 1e5, {
|
|
56
|
-
inputUsdPerMTok:
|
|
57
|
-
outputUsdPerMTok:
|
|
58
|
-
cacheReadUsdPerMTok: .
|
|
74
|
+
inputUsdPerMTok: 2.5,
|
|
75
|
+
outputUsdPerMTok: 15,
|
|
76
|
+
cacheReadUsdPerMTok: .25
|
|
59
77
|
}),
|
|
60
78
|
"gpt-5.4-mini": responses(272e3, 1e5, {
|
|
61
|
-
inputUsdPerMTok:
|
|
62
|
-
outputUsdPerMTok: 4.
|
|
63
|
-
cacheReadUsdPerMTok: .
|
|
79
|
+
inputUsdPerMTok: .75,
|
|
80
|
+
outputUsdPerMTok: 4.5,
|
|
81
|
+
cacheReadUsdPerMTok: .075
|
|
64
82
|
})
|
|
65
83
|
};
|
|
66
84
|
/**
|
|
@@ -83,22 +101,30 @@ const OPENAI_MODELS = {
|
|
|
83
101
|
* silent reinterpretation.
|
|
84
102
|
*/
|
|
85
103
|
const OPENAI_PRICING = {
|
|
86
|
-
pricingVersion: "openai-2026-07-
|
|
104
|
+
pricingVersion: "openai-2026-07-18-r2",
|
|
87
105
|
models: (() => {
|
|
88
106
|
const models = {};
|
|
89
107
|
for (const [name, info] of Object.entries(OPENAI_MODELS)) if (info.caps.pricing !== void 0) models[`openai:${name}`] = info.caps.pricing;
|
|
90
108
|
return models;
|
|
91
109
|
})()
|
|
92
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* The documented snapshot grammar: `<exact model>-YYYY-MM-DD`. Nothing
|
|
113
|
+
* else inherits a table row (v1.17.0 review P1-1): a general prefix
|
|
114
|
+
* matcher let the 'gpt-5.6' family alias capture the SIBLING models
|
|
115
|
+
* 'gpt-5.6-terra' and 'gpt-5.6-luna' and price them as Sol, which is
|
|
116
|
+
* worse than no price at all. An unknown sibling or preview suffix now
|
|
117
|
+
* falls through to conservative unpriced caps.
|
|
118
|
+
*/
|
|
119
|
+
const DATED_SNAPSHOT = /^(?<base>.+)-\d{4}-\d{2}-\d{2}$/u;
|
|
93
120
|
function openAiModelInfo(model) {
|
|
94
121
|
const exact = OPENAI_MODELS[model];
|
|
95
122
|
if (exact !== void 0) return exact;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (best !== void 0) return best.info;
|
|
123
|
+
const snapshot = DATED_SNAPSHOT.exec(model)?.groups?.base;
|
|
124
|
+
if (snapshot !== void 0) {
|
|
125
|
+
const base = OPENAI_MODELS[snapshot];
|
|
126
|
+
if (base !== void 0) return base;
|
|
127
|
+
}
|
|
102
128
|
return responses(272e3, 1e5);
|
|
103
129
|
}
|
|
104
130
|
//#endregion
|
|
@@ -140,13 +166,14 @@ var OpenAiIdMap = class {
|
|
|
140
166
|
}
|
|
141
167
|
};
|
|
142
168
|
/**
|
|
143
|
-
* Canonical-to-wire effort: low through
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
169
|
+
* Canonical-to-wire effort: low through xhigh pass through. Canonical
|
|
170
|
+
* 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.
|
|
147
174
|
*/
|
|
148
|
-
function mapOpenAiEffort(effort) {
|
|
149
|
-
if (effort === "max") return {
|
|
175
|
+
function mapOpenAiEffort(effort, options) {
|
|
176
|
+
if (effort === "max" && options?.wireMaxEffort !== true) return {
|
|
150
177
|
wire: "xhigh",
|
|
151
178
|
downmapped: true
|
|
152
179
|
};
|
|
@@ -162,7 +189,7 @@ function mapOpenAiEffort(effort) {
|
|
|
162
189
|
* are REJECTED as a typed ConfigError. Role
|
|
163
190
|
* 'system' messages project into top-level instructions on every request.
|
|
164
191
|
*/
|
|
165
|
-
function buildResponsesParams(req, ids) {
|
|
192
|
+
function buildResponsesParams(req, ids, options) {
|
|
166
193
|
const openaiOptions = req.providerOptions?.openai ?? {};
|
|
167
194
|
for (const forbidden of [
|
|
168
195
|
"previous_response_id",
|
|
@@ -260,7 +287,7 @@ function buildResponsesParams(req, ids) {
|
|
|
260
287
|
const explicitEffort = openaiOptions.reasoningEffort;
|
|
261
288
|
if (typeof explicitEffort === "string") params.reasoning = { effort: explicitEffort };
|
|
262
289
|
else if (req.effort !== void 0) {
|
|
263
|
-
const mapped = mapOpenAiEffort(req.effort);
|
|
290
|
+
const mapped = mapOpenAiEffort(req.effort, options);
|
|
264
291
|
effortDownmapped = mapped.downmapped;
|
|
265
292
|
params.reasoning = { effort: mapped.wire };
|
|
266
293
|
}
|
|
@@ -270,15 +297,26 @@ function buildResponsesParams(req, ids) {
|
|
|
270
297
|
effortDownmapped
|
|
271
298
|
};
|
|
272
299
|
}
|
|
273
|
-
/**
|
|
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
|
+
*/
|
|
274
311
|
function normalizeOpenAiUsage(raw) {
|
|
275
312
|
const inputDetails = raw?.input_tokens_details;
|
|
276
313
|
const outputDetails = raw?.output_tokens_details;
|
|
314
|
+
const cacheWrite = typeof inputDetails?.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : 0;
|
|
277
315
|
const usage = {
|
|
278
|
-
inputTokens: typeof raw?.input_tokens === "number" ? raw.input_tokens : 0,
|
|
316
|
+
inputTokens: (typeof raw?.input_tokens === "number" ? raw.input_tokens : 0) + cacheWrite,
|
|
279
317
|
outputTokens: typeof raw?.output_tokens === "number" ? raw.output_tokens : 0,
|
|
280
318
|
cacheReadTokens: typeof inputDetails?.cached_tokens === "number" ? inputDetails.cached_tokens : 0,
|
|
281
|
-
cacheWriteTokens:
|
|
319
|
+
cacheWriteTokens: cacheWrite
|
|
282
320
|
};
|
|
283
321
|
const reasoning = outputDetails?.reasoning_tokens;
|
|
284
322
|
if (typeof reasoning === "number" && reasoning > 0) usage.reasoningTokens = reasoning;
|
|
@@ -387,31 +425,59 @@ async function* mapResponsesStream(stream, ids, options) {
|
|
|
387
425
|
};
|
|
388
426
|
return;
|
|
389
427
|
}
|
|
390
|
-
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;
|
|
391
436
|
yield {
|
|
392
437
|
type: "error",
|
|
393
|
-
error:
|
|
394
|
-
code: "agent",
|
|
395
|
-
message: (event.response?.error)?.message ?? "response.failed",
|
|
396
|
-
retryable: false,
|
|
397
|
-
data: { kind: "transport" }
|
|
398
|
-
}
|
|
438
|
+
error: failedResponseError(typeof error?.code === "string" ? error.code : void 0, error?.message ?? "response.failed")
|
|
399
439
|
};
|
|
400
440
|
return;
|
|
441
|
+
}
|
|
401
442
|
case "error":
|
|
402
443
|
yield {
|
|
403
444
|
type: "error",
|
|
404
|
-
error:
|
|
405
|
-
code: "agent",
|
|
406
|
-
message: event.message ?? "stream error",
|
|
407
|
-
retryable: false,
|
|
408
|
-
data: { kind: "transport" }
|
|
409
|
-
}
|
|
445
|
+
error: failedResponseError(typeof event.code === "string" ? event.code : void 0, event.message ?? "stream error")
|
|
410
446
|
};
|
|
411
447
|
return;
|
|
412
448
|
default: break;
|
|
413
449
|
}
|
|
414
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
|
+
}
|
|
415
481
|
/** Projects SDK/API errors into the retryable WireError vocabulary. */
|
|
416
482
|
function openAiErrorToWire(error) {
|
|
417
483
|
const record = error;
|
|
@@ -578,11 +644,12 @@ async function* mapChatCompletionsStream(stream, ids) {
|
|
|
578
644
|
const chunkUsage = chunk.usage;
|
|
579
645
|
if (chunkUsage !== void 0 && chunkUsage !== null) {
|
|
580
646
|
const promptDetails = chunkUsage.prompt_tokens_details;
|
|
647
|
+
const cacheWrite = typeof promptDetails?.cache_write_tokens === "number" ? promptDetails.cache_write_tokens : 0;
|
|
581
648
|
usage = {
|
|
582
|
-
inputTokens: typeof chunkUsage.prompt_tokens === "number" ? chunkUsage.prompt_tokens : 0,
|
|
649
|
+
inputTokens: (typeof chunkUsage.prompt_tokens === "number" ? chunkUsage.prompt_tokens : 0) + cacheWrite,
|
|
583
650
|
outputTokens: typeof chunkUsage.completion_tokens === "number" ? chunkUsage.completion_tokens : 0,
|
|
584
651
|
cacheReadTokens: typeof promptDetails?.cached_tokens === "number" ? promptDetails.cached_tokens : 0,
|
|
585
|
-
cacheWriteTokens:
|
|
652
|
+
cacheWriteTokens: cacheWrite
|
|
586
653
|
};
|
|
587
654
|
}
|
|
588
655
|
}
|
|
@@ -655,7 +722,7 @@ function openai(options = {}) {
|
|
|
655
722
|
const info = openAiModelInfo(req.model);
|
|
656
723
|
try {
|
|
657
724
|
if (info.api === "responses") {
|
|
658
|
-
const { params, effortDownmapped } = buildResponsesParams(req, ids);
|
|
725
|
+
const { params, effortDownmapped } = buildResponsesParams(req, ids, { wireMaxEffort: info.wireMaxEffort });
|
|
659
726
|
yield* mapResponsesStream(await client.responses.create({
|
|
660
727
|
...params,
|
|
661
728
|
stream: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/openai",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
32
|
+
"@rulvar/testing": "1.19.0"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|