@oh-my-pi/pi-ai 18.1.13 → 18.1.14
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/CHANGELOG.md +11 -0
- package/dist/types/providers/openai-codex-responses.d.ts +1 -1
- package/package.json +6 -6
- package/src/providers/cursor.ts +23 -18
- package/src/providers/openai-codex-responses.ts +14 -6
- package/src/providers/openai-completions.ts +16 -6
- package/src/providers/openai-reasoning-fallback.ts +75 -24
- package/src/providers/openai-responses.ts +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.14] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed reasoning-off requests (e.g. GitHub Copilot `gpt-6-astra`) surfacing `400 Unsupported value: 'none' … Supported values are: …` instead of retrying at the lowest allowed effort: the reasoning-effort fallback now recognizes `Supported values` phrasing ([#11128](https://github.com/can1357/oh-my-pi/pull/11128) by [@H4vC](https://github.com/H4vC)).
|
|
10
|
+
- Fixed Cursor GPT off-tier requests sending raw `-none` sibling ids (e.g. `gpt-5.6-sol-none-fast`), which the Run endpoint rejects; they now normalize to the base model id with no reasoning parameter, matching every other effort tier ([#11128](https://github.com/can1357/oh-my-pi/pull/11128) by [@H4vC](https://github.com/H4vC)).
|
|
11
|
+
|
|
5
12
|
## [18.1.12] - 2026-09-06
|
|
6
13
|
|
|
7
14
|
### Added
|
|
8
15
|
|
|
9
16
|
- Added Muse Code subscription sign-in, credential refresh, inference, and quota reporting in `/usage`, with durable rate-limit backoff so quota refresh recovers instead of repeatedly retrying.
|
|
10
17
|
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fixed Codex compaction timeouts triggering prolonged retries instead of advancing to the next compaction method.
|
|
21
|
+
|
|
11
22
|
## [18.1.11] - 2026-09-05
|
|
12
23
|
|
|
13
24
|
### Fixed
|
|
@@ -223,7 +223,7 @@ type CodexToolPayload = {
|
|
|
223
223
|
/** @internal Exported for tests. */
|
|
224
224
|
export declare function convertOpenAICodexResponsesTools(tools: Tool[], model: Model<"openai-codex-responses">): CodexToolPayload[];
|
|
225
225
|
export declare class CodexWebSocketTransportError extends Error {
|
|
226
|
-
constructor(detail: string);
|
|
226
|
+
constructor(detail: string, options?: ErrorOptions);
|
|
227
227
|
}
|
|
228
228
|
declare class CodexProviderStreamError extends Error {
|
|
229
229
|
readonly retryable: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.1.
|
|
3
|
+
"version": "18.1.14",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
125
125
|
},
|
|
126
126
|
"dependencies": {
|
|
127
|
-
"@oh-my-pi/omptype": "18.1.
|
|
128
|
-
"@oh-my-pi/pi-catalog": "18.1.
|
|
129
|
-
"@oh-my-pi/pi-natives": "18.1.
|
|
130
|
-
"@oh-my-pi/pi-utils": "18.1.
|
|
131
|
-
"@oh-my-pi/pi-wire": "18.1.
|
|
127
|
+
"@oh-my-pi/omptype": "18.1.14",
|
|
128
|
+
"@oh-my-pi/pi-catalog": "18.1.14",
|
|
129
|
+
"@oh-my-pi/pi-natives": "18.1.14",
|
|
130
|
+
"@oh-my-pi/pi-utils": "18.1.14",
|
|
131
|
+
"@oh-my-pi/pi-wire": "18.1.14"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@types/bun": "^1.3.14"
|
package/src/providers/cursor.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import http2 from "node:http2";
|
|
4
|
-
import { classifyModel } from "@oh-my-pi/pi-catalog/compat/taxonomy";
|
|
4
|
+
import { classifyModel, collapseVariantId } from "@oh-my-pi/pi-catalog/compat/taxonomy";
|
|
5
5
|
import type {
|
|
6
6
|
ConversationStep,
|
|
7
7
|
CursorRule,
|
|
@@ -5236,8 +5236,11 @@ function extractImages(content: (TextContent | ImageContent)[]) {
|
|
|
5236
5236
|
* The Run endpoint rejects a sibling slug as the wire `model_id` with
|
|
5237
5237
|
* `resource_exhausted` (errorId 528384); the official `cursor-agent` splits the
|
|
5238
5238
|
* slug into its base model id plus a `reasoning` effort parameter. Mirror that
|
|
5239
|
-
* for OpenAI-family ids:
|
|
5240
|
-
* `
|
|
5239
|
+
* for OpenAI-family ids: split the tier with the compiled catalog policy
|
|
5240
|
+
* (`collapseVariantId`, KDL suffix/lane rules) and emit
|
|
5241
|
+
* `{ id: "reasoning", value: <effort> }`. The off tier (`-none`) is a sibling
|
|
5242
|
+
* slug too, so it normalizes to the bare (lane-preserving) base with no
|
|
5243
|
+
* reasoning parameter instead of going out raw.
|
|
5241
5244
|
*
|
|
5242
5245
|
* Non-OpenAI ids pass through unchanged — Cursor-native ids (`composer-*`,
|
|
5243
5246
|
* `cursor-grok-*`, `default`) carry no effort suffix, and Claude/other siblings
|
|
@@ -5255,21 +5258,23 @@ function resolveCursorWireModel(
|
|
|
5255
5258
|
} {
|
|
5256
5259
|
const wireModelId = requestModelId ?? model.requestModelId ?? model.id;
|
|
5257
5260
|
if (wireMode === "discovered") return { modelId: wireModelId, parameters: [] };
|
|
5258
|
-
//
|
|
5259
|
-
//
|
|
5260
|
-
const
|
|
5261
|
-
const
|
|
5262
|
-
const
|
|
5263
|
-
if (
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5261
|
+
// `collapseVariantId` keeps the lane in the logical id (`-high-fast` →
|
|
5262
|
+
// base `-fast`) and decodes the KDL effort (`-none` → `off`).
|
|
5263
|
+
const collapsed = collapseVariantId("cursor", wireModelId);
|
|
5264
|
+
const effort = collapsed.effort;
|
|
5265
|
+
const base = effort !== undefined ? collapsed.logicalId : undefined;
|
|
5266
|
+
if (effort !== undefined && base && classifyModel("cursor", base).class === "openai") {
|
|
5267
|
+
if (effort === "off") {
|
|
5268
|
+
return { modelId: base, parameters: [] };
|
|
5269
|
+
}
|
|
5270
|
+
if ((THINKING_EFFORTS as readonly string[]).includes(effort)) {
|
|
5271
|
+
return {
|
|
5272
|
+
modelId: base,
|
|
5273
|
+
parameters: [
|
|
5274
|
+
create(RequestedModel_ModelParameterbytesSchema, { id: "reasoning", value: collapsed.effort }),
|
|
5275
|
+
],
|
|
5276
|
+
};
|
|
5277
|
+
}
|
|
5273
5278
|
}
|
|
5274
5279
|
// A bare `composer-2.5` id resolves to the Fast variant server-side
|
|
5275
5280
|
// (can1357/oh-my-pi#9012). Pin the Standard tier explicitly; `-fast`
|
|
@@ -3734,12 +3734,12 @@ class CodexWebSocketConnection {
|
|
|
3734
3734
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
3735
3735
|
};
|
|
3736
3736
|
const onAbort = () => {
|
|
3737
|
-
this.close("aborted");
|
|
3738
3737
|
if (!settled) {
|
|
3739
3738
|
settled = true;
|
|
3740
3739
|
clearPending();
|
|
3741
|
-
reject(new CodexWebSocketTransportError(`request was aborted
|
|
3740
|
+
reject(new CodexWebSocketTransportError(`request was aborted`, { cause: signal?.reason }));
|
|
3742
3741
|
}
|
|
3742
|
+
this.close("aborted");
|
|
3743
3743
|
};
|
|
3744
3744
|
if (signal) {
|
|
3745
3745
|
if (signal.aborted) {
|
|
@@ -3844,7 +3844,7 @@ class CodexWebSocketConnection {
|
|
|
3844
3844
|
throw new CodexWebSocketTransportError(`websocket request already in progress`);
|
|
3845
3845
|
}
|
|
3846
3846
|
if (signal?.aborted) {
|
|
3847
|
-
throw new CodexWebSocketTransportError(`request was aborted
|
|
3847
|
+
throw new CodexWebSocketTransportError(`request was aborted`, { cause: signal.reason });
|
|
3848
3848
|
}
|
|
3849
3849
|
this.#activeRequest = true;
|
|
3850
3850
|
this.#streamObserver = onSseEvent;
|
|
@@ -3860,8 +3860,9 @@ class CodexWebSocketConnection {
|
|
|
3860
3860
|
// the death signal instead of writing into a dead socket.
|
|
3861
3861
|
this.#dropStaleFrames();
|
|
3862
3862
|
const onAbort = () => {
|
|
3863
|
+
this.#push(new CodexWebSocketTransportError(`request was aborted`, { cause: signal?.reason }));
|
|
3864
|
+
// Closing can synchronously enqueue a generic onclose error.
|
|
3863
3865
|
this.close("aborted");
|
|
3864
|
-
this.#push(new CodexWebSocketTransportError(`request was aborted`));
|
|
3865
3866
|
};
|
|
3866
3867
|
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
3867
3868
|
|
|
@@ -3882,8 +3883,15 @@ class CodexWebSocketConnection {
|
|
|
3882
3883
|
const requestPayload = JSON.stringify(request);
|
|
3883
3884
|
notifyCodexWebSocketOutbound(onSseEvent, request, requestPayload);
|
|
3884
3885
|
// Re-check liveness: the debug-session await above can outlive the socket.
|
|
3886
|
+
// Preserve the abort cause: onAbort already queued a caused error, but this
|
|
3887
|
+
// throw would otherwise mask it before #nextMessage() drains the queue.
|
|
3885
3888
|
const socket = this.#socket;
|
|
3886
3889
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
3890
|
+
if (signal?.aborted) {
|
|
3891
|
+
throw new CodexWebSocketTransportError(`websocket connection is unavailable`, {
|
|
3892
|
+
cause: signal.reason,
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3887
3895
|
throw new CodexWebSocketTransportError(`websocket connection is unavailable`);
|
|
3888
3896
|
}
|
|
3889
3897
|
try {
|
|
@@ -4752,8 +4760,8 @@ export function convertOpenAICodexResponsesTools(
|
|
|
4752
4760
|
}
|
|
4753
4761
|
|
|
4754
4762
|
export class CodexWebSocketTransportError extends Error {
|
|
4755
|
-
constructor(detail: string) {
|
|
4756
|
-
super(`${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: ${detail}
|
|
4763
|
+
constructor(detail: string, options?: ErrorOptions) {
|
|
4764
|
+
super(`${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: ${detail}`, options);
|
|
4757
4765
|
this.name = "CodexWebSocketTransportError";
|
|
4758
4766
|
}
|
|
4759
4767
|
}
|
|
@@ -816,10 +816,15 @@ const streamOpenAICompletionsOnce = (
|
|
|
816
816
|
openaiStream = await createCompletionsStream();
|
|
817
817
|
} catch (error) {
|
|
818
818
|
const capturedErrorResponse = error instanceof OpenAIHttpError ? error.captured : undefined;
|
|
819
|
+
// A caller disable with a retained effort preference is still an
|
|
820
|
+
// explicit disable: without this, a fieldless rejection of the
|
|
821
|
+
// resulting `none` resolves to a delete-effort retry that gets
|
|
822
|
+
// cached and strips later enabled turns.
|
|
823
|
+
const isExplicitDisable = options?.disableReasoning === true;
|
|
819
824
|
const reasoningEffortFallback =
|
|
820
825
|
activeReasoningEffortFallbackKey && activeRequestParams && !requestSignal.aborted
|
|
821
826
|
? resolveOpenAIReasoningEffortFallback(error, capturedErrorResponse, activeRequestParams, {
|
|
822
|
-
explicitDisable:
|
|
827
|
+
explicitDisable: isExplicitDisable,
|
|
823
828
|
})
|
|
824
829
|
: undefined;
|
|
825
830
|
if (reasoningEffortFallback !== undefined && activeReasoningEffortFallbackKey) {
|
|
@@ -828,11 +833,16 @@ const streamOpenAICompletionsOnce = (
|
|
|
828
833
|
attemptedReasoningEffortFallbacks.add(retryMarker);
|
|
829
834
|
requestReasoningEffortFallbacks.set(activeReasoningEffortFallbackKey, reasoningEffortFallback);
|
|
830
835
|
openaiStream = await createCompletionsStream();
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
+
// Explicit-disable fallbacks stay per-request so a reasoning-off
|
|
837
|
+
// side request cannot downgrade later normal turns sharing
|
|
838
|
+
// the session state.
|
|
839
|
+
if (!isExplicitDisable) {
|
|
840
|
+
rememberOpenAIReasoningEffortFallback(
|
|
841
|
+
providerSessionState,
|
|
842
|
+
activeReasoningEffortFallbackKey,
|
|
843
|
+
reasoningEffortFallback,
|
|
844
|
+
);
|
|
845
|
+
}
|
|
836
846
|
} else if (
|
|
837
847
|
model.compat.retryWithoutStrictOnGrammarError &&
|
|
838
848
|
!disableStrictTools &&
|
|
@@ -183,9 +183,11 @@ function collectMessageParts(error: unknown, captured: CapturedHttpErrorResponse
|
|
|
183
183
|
* Text that identifies a 400 as being about the reasoning-effort field.
|
|
184
184
|
* OpenAI-compatible gateways (cliproxy, …) never name the field — they reject
|
|
185
185
|
* the value alone with `level "none" not supported, valid levels: low, …` — so
|
|
186
|
-
* the allowed-level phrasing counts as a mention too.
|
|
186
|
+
* the allowed-level phrasing counts as a mention too. GitHub Copilot phrases
|
|
187
|
+
* the same rejection with `Supported values are: …`, so value lists count too.
|
|
187
188
|
*/
|
|
188
|
-
const REASONING_EFFORT_FIELD_PATTERN =
|
|
189
|
+
const REASONING_EFFORT_FIELD_PATTERN =
|
|
190
|
+
/reasoning[_. ]effort|reasoning value|(?:valid|supported|allowed) (?:levels?|values?)/i;
|
|
189
191
|
|
|
190
192
|
function mentionsReasoningEffort(error: unknown, captured: CapturedHttpErrorResponse | undefined): boolean {
|
|
191
193
|
const param = capturedStringField(captured, "param");
|
|
@@ -200,6 +202,68 @@ function mentionsReasoningEffort(error: unknown, captured: CapturedHttpErrorResp
|
|
|
200
202
|
);
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
/** Which request field a rejection attributes itself to, parsed once. */
|
|
206
|
+
type EffortRejectionField = "reasoning-effort" | "other" | "unknown";
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parsed attribution of a 400/422, in precedence order: the structured error
|
|
210
|
+
* field first, the message verdict second, bare vocabulary last. New server
|
|
211
|
+
* wordings extend these parsers; the decision in
|
|
212
|
+
* {@link isInvalidReasoningEffortError} stays fixed.
|
|
213
|
+
*/
|
|
214
|
+
interface EffortRejectionSignal {
|
|
215
|
+
/** Authoritative attribution: explicit `param`, else message content, else unknown. */
|
|
216
|
+
field: EffortRejectionField;
|
|
217
|
+
/** The message names the reasoning-effort option without a verdict. */
|
|
218
|
+
namesFieldInMessage: boolean;
|
|
219
|
+
/** The message carries a fielded rejection verdict (any word order). */
|
|
220
|
+
messageVerdict: boolean;
|
|
221
|
+
/** The message lists allowed tiers in gateway levels vocabulary. */
|
|
222
|
+
listsLevels: boolean;
|
|
223
|
+
/** The rejected effort is quoted next to a rejection verdict. */
|
|
224
|
+
rejectedMatches: boolean;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const EFFORT_FIELD_PATTERN = /reasoning[_. ]effort|reasoning value/i;
|
|
228
|
+
const ALLOWED_LEVELS_PATTERN = /(?:valid|supported|allowed) levels?/i;
|
|
229
|
+
|
|
230
|
+
/** Fielded rejection verdicts in any word order: verdict-first, field-first, or bare mention plus verdict. */
|
|
231
|
+
function messageCarriesEffortVerdict(message: string): boolean {
|
|
232
|
+
return (
|
|
233
|
+
/invalid[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(message) ||
|
|
234
|
+
/(?:reasoning[_. ]effort|reasoning value)[^\n]*(?:invalid|unsupported|not supported|not permitted|must be|expected|unknown|unexpected|unrecognized)/i.test(
|
|
235
|
+
message,
|
|
236
|
+
) ||
|
|
237
|
+
/(?:unsupported|not supported|not permitted|unknown|unexpected|unrecognized|extra)[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(
|
|
238
|
+
message,
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseEffortRejectionSignal(
|
|
244
|
+
message: string,
|
|
245
|
+
captured: CapturedHttpErrorResponse | undefined,
|
|
246
|
+
currentEffort: string,
|
|
247
|
+
): EffortRejectionSignal {
|
|
248
|
+
const namesFieldInMessage = EFFORT_FIELD_PATTERN.test(message);
|
|
249
|
+
const param = capturedStringField(captured, "param") ?? "";
|
|
250
|
+
const quoted = `["'\`]${escapeRegExp(currentEffort)}["'\`]`;
|
|
251
|
+
return {
|
|
252
|
+
field:
|
|
253
|
+
namesFieldInMessage || EFFORT_FIELD_PATTERN.test(param)
|
|
254
|
+
? "reasoning-effort"
|
|
255
|
+
: param.trim() !== ""
|
|
256
|
+
? "other"
|
|
257
|
+
: "unknown",
|
|
258
|
+
namesFieldInMessage,
|
|
259
|
+
messageVerdict: messageCarriesEffortVerdict(message),
|
|
260
|
+
listsLevels: ALLOWED_LEVELS_PATTERN.test(message),
|
|
261
|
+
rejectedMatches:
|
|
262
|
+
new RegExp(`(?:invalid|unsupported|not supported)[^\\n]*${quoted}`, "i").test(message) ||
|
|
263
|
+
new RegExp(`${quoted}[^\\n]*(?:invalid|unsupported|not supported)`, "i").test(message),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
203
267
|
function isInvalidReasoningEffortError(
|
|
204
268
|
error: unknown,
|
|
205
269
|
captured: CapturedHttpErrorResponse | undefined,
|
|
@@ -210,28 +274,15 @@ function isInvalidReasoningEffortError(
|
|
|
210
274
|
if (!mentionsReasoningEffort(error, captured)) return false;
|
|
211
275
|
const message = collectMessageParts(error, captured);
|
|
212
276
|
if (/reasoning[_ ]content/i.test(message) && !REASONING_EFFORT_FIELD_PATTERN.test(message)) return false;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
/(?:unsupported|not supported|not permitted|unknown|unexpected|unrecognized|extra)[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(
|
|
223
|
-
message,
|
|
224
|
-
)
|
|
225
|
-
) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
// Gateways put the rejected value first (`level "none" not supported`), the
|
|
229
|
-
// official API puts the verdict first (`Unsupported value: 'none'`).
|
|
230
|
-
const quoted = `["'\`]${escapeRegExp(currentEffort)}["'\`]`;
|
|
231
|
-
return (
|
|
232
|
-
new RegExp(`(?:invalid|unsupported|not supported)[^\\n]*${quoted}`, "i").test(message) ||
|
|
233
|
-
new RegExp(`${quoted}[^\\n]*(?:invalid|unsupported|not supported)`, "i").test(message)
|
|
234
|
-
);
|
|
277
|
+
const signal = parseEffortRejectionSignal(message, captured, currentEffort);
|
|
278
|
+
// Precedence is fixed: an explicit foreign field defeats the heuristic,
|
|
279
|
+
// a fielded verdict always qualifies, and fieldless values-lists are
|
|
280
|
+
// trusted only for the reasoning-off value (levels vocabulary is
|
|
281
|
+
// gateway-effort dialect, so it stays trusted for every tier).
|
|
282
|
+
if (signal.field === "other" && !signal.namesFieldInMessage) return false;
|
|
283
|
+
if (signal.messageVerdict) return true;
|
|
284
|
+
if (currentEffort.toLowerCase() !== "none" && !signal.namesFieldInMessage && !signal.listsLevels) return false;
|
|
285
|
+
return signal.rejectedMatches;
|
|
235
286
|
}
|
|
236
287
|
|
|
237
288
|
function escapeRegExp(value: string): string {
|
|
@@ -591,11 +591,21 @@ const streamOpenAIResponsesOnce = (
|
|
|
591
591
|
try {
|
|
592
592
|
openaiStream = await openResponsesStream(chained.params);
|
|
593
593
|
if (pendingReasoningEffortFallback) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
594
|
+
// Explicit-disable fallbacks (none -> lowest allowed) are
|
|
595
|
+
// per-request: persisting them under the model key would
|
|
596
|
+
// silently downgrade later normal turns sharing the
|
|
597
|
+
// session state. A retained effort preference does not
|
|
598
|
+
// make the disable less explicit. Keep them in the
|
|
599
|
+
// per-request map only.
|
|
600
|
+
const isExplicitDisable =
|
|
601
|
+
options?.forceReasoningOff === true || options?.disableReasoning === true;
|
|
602
|
+
if (!isExplicitDisable) {
|
|
603
|
+
rememberOpenAIReasoningEffortFallback(
|
|
604
|
+
providerSessionState,
|
|
605
|
+
pendingReasoningEffortFallback.key,
|
|
606
|
+
pendingReasoningEffortFallback.fallback,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
599
609
|
pendingReasoningEffortFallback = undefined;
|
|
600
610
|
}
|
|
601
611
|
break;
|
|
@@ -605,8 +615,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
605
615
|
activeReasoningEffortFallbackKey && activeRequestParams && !requestSignal.aborted
|
|
606
616
|
? resolveOpenAIReasoningEffortFallback(error, capturedErrorResponse, activeRequestParams, {
|
|
607
617
|
explicitDisable:
|
|
608
|
-
options?.forceReasoningOff === true ||
|
|
609
|
-
(options?.disableReasoning === true && options.reasoning === undefined),
|
|
618
|
+
options?.forceReasoningOff === true || options?.disableReasoning === true,
|
|
610
619
|
})
|
|
611
620
|
: undefined;
|
|
612
621
|
if (reasoningEffortFallback !== undefined && activeReasoningEffortFallbackKey) {
|