@oh-my-pi/pi-ai 17.2.13 → 17.2.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 +6 -0
- package/dist/types/providers/azure-openai-responses.d.ts +1 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -0
- package/dist/types/providers/openai-responses.d.ts +1 -0
- package/dist/types/providers/openai-shared.d.ts +10 -0
- package/dist/types/types.d.ts +5 -0
- package/package.json +5 -5
- package/src/providers/azure-openai-responses.ts +1 -0
- package/src/providers/openai-codex/request-transformer.ts +9 -4
- package/src/providers/openai-codex-responses.ts +1 -0
- package/src/providers/openai-reasoning-fallback.ts +21 -8
- package/src/providers/openai-responses.ts +73 -12
- package/src/providers/openai-shared.ts +26 -0
- package/src/stream.ts +4 -0
- package/src/types.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,8 @@ export interface ReasoningConfig {
|
|
|
13
13
|
export interface CodexRequestOptions {
|
|
14
14
|
/** User-facing effort; maps 1:1 onto the wire tier of the same name. */
|
|
15
15
|
reasoningEffort?: CodexCallerEffort | "none";
|
|
16
|
+
/** Suppress native reasoning by sending `reasoning.effort: "none"`. */
|
|
17
|
+
reasoningOff?: boolean;
|
|
16
18
|
reasoningSummary?: ReasoningConfig["summary"] | null;
|
|
17
19
|
/** Explicit `reasoning.context` override. Omitted by default; Responses Lite forces `all_turns` as required by that transport. */
|
|
18
20
|
reasoningContext?: CodexReasoningContext;
|
|
@@ -114,6 +114,7 @@ type OpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
|
|
|
114
114
|
export declare const streamOpenAIResponses: StreamFunction<"openai-responses">;
|
|
115
115
|
export declare function buildParams(model: Model<"openai-responses">, context: Context, options: OpenAIResponsesOptions | undefined, providerSessionState: OpenAIResponsesProviderSessionState | undefined, strictToolsScope?: OpenAIStrictToolsScope, disableStrictToolsOverride?: boolean, statefulCacheBaseline?: ResponseInput): {
|
|
116
116
|
params: OpenAIResponsesSamplingParams;
|
|
117
|
+
trailingScaffoldingItems: number;
|
|
117
118
|
strictToolsApplied: boolean;
|
|
118
119
|
};
|
|
119
120
|
/**
|
|
@@ -505,6 +505,8 @@ export declare function appendMessageContentPart(item: ResponseOutputMessage, pa
|
|
|
505
505
|
export declare function appendMessageTextDelta(item: ResponseOutputMessage, block: TextContent, delta: string, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number, partType: "output_text" | "refusal"): void;
|
|
506
506
|
/** Chooses final message text while treating non-empty terminal content as authoritative. */
|
|
507
507
|
export declare function finalizeMessageText(item: ResponseOutputMessage, streamedText: string): string;
|
|
508
|
+
export declare const JUICE_EFFORT_MAP: Record<string, number>;
|
|
509
|
+
export declare function getJuiceValue(effort?: string): number;
|
|
508
510
|
export declare function accumulateToolCallArgumentsDelta(block: ResponsesToolCallBlock, delta: string, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number): void;
|
|
509
511
|
/**
|
|
510
512
|
* Finalize streamed function-call arguments from the authoritative `.done`
|
|
@@ -591,6 +593,14 @@ type ReasoningOptions = {
|
|
|
591
593
|
export interface ApplyResponsesCompatPolicyOptions {
|
|
592
594
|
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
593
595
|
mapEffort?: (effort: string) => string;
|
|
596
|
+
/**
|
|
597
|
+
* Suppress native reasoning by sending `reasoning.effort: "none"` — the only
|
|
598
|
+
* disable level the Responses API defines (`"off"` is not a wire value and
|
|
599
|
+
* 400s everywhere). Gateways that reject `none` for a given model are
|
|
600
|
+
* handled by the reasoning-effort fallback retry, which clamps to the
|
|
601
|
+
* lowest level the error reports as allowed.
|
|
602
|
+
*/
|
|
603
|
+
forceReasoningOff?: boolean;
|
|
594
604
|
}
|
|
595
605
|
export declare function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreaming>(params: P, policy: OpenAICompatPolicy, options: ApplyResponsesCompatPolicyOptions | undefined): void;
|
|
596
606
|
/**
|
package/dist/types/types.d.ts
CHANGED
|
@@ -294,6 +294,11 @@ export interface StreamOptions {
|
|
|
294
294
|
* `false` so `previous_response_id` cannot explain a result.
|
|
295
295
|
*/
|
|
296
296
|
statefulResponses?: boolean;
|
|
297
|
+
/**
|
|
298
|
+
* Emit `reasoning: { effort: "none" }` for OpenAI Responses and Codex requests.
|
|
299
|
+
* Used when a caller supplies an external reasoning scratchpad; other transports ignore it.
|
|
300
|
+
*/
|
|
301
|
+
forceReasoningOff?: boolean;
|
|
297
302
|
/**
|
|
298
303
|
* Provider-scoped mutable state store for this agent session.
|
|
299
304
|
* Providers can use this to persist transport/session state between turns.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "17.2.
|
|
4
|
+
"version": "17.2.14",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/omptype": "17.2.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
43
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
44
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
41
|
+
"@oh-my-pi/omptype": "17.2.14",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "17.2.14",
|
|
43
|
+
"@oh-my-pi/pi-utils": "17.2.14",
|
|
44
|
+
"@oh-my-pi/pi-wire": "17.2.14"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
|
@@ -65,6 +65,7 @@ export interface AzureOpenAIResponsesOptions extends StreamOptions {
|
|
|
65
65
|
azureDeploymentName?: string;
|
|
66
66
|
toolChoice?: ToolChoice;
|
|
67
67
|
serviceTier?: ServiceTier;
|
|
68
|
+
disableReasoning?: boolean;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
type AzureOpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
|
|
@@ -32,6 +32,8 @@ export interface ReasoningConfig {
|
|
|
32
32
|
export interface CodexRequestOptions {
|
|
33
33
|
/** User-facing effort; maps 1:1 onto the wire tier of the same name. */
|
|
34
34
|
reasoningEffort?: CodexCallerEffort | "none";
|
|
35
|
+
/** Suppress native reasoning by sending `reasoning.effort: "none"`. */
|
|
36
|
+
reasoningOff?: boolean;
|
|
35
37
|
reasoningSummary?: ReasoningConfig["summary"] | null;
|
|
36
38
|
/** Explicit `reasoning.context` override. Omitted by default; Responses Lite forces `all_turns` as required by that transport. */
|
|
37
39
|
reasoningContext?: CodexReasoningContext;
|
|
@@ -454,9 +456,12 @@ export async function transformRequestBody(
|
|
|
454
456
|
applyCodexResponsesLiteShape(body);
|
|
455
457
|
}
|
|
456
458
|
|
|
457
|
-
if (options.reasoningEffort !== undefined || responsesLite) {
|
|
458
|
-
const reasoningConfig =
|
|
459
|
-
|
|
459
|
+
if (options.reasoningOff || options.reasoningEffort !== undefined || responsesLite) {
|
|
460
|
+
const reasoningConfig: Partial<ReasoningConfig> = options.reasoningOff
|
|
461
|
+
? { effort: "none" }
|
|
462
|
+
: options.reasoningEffort !== undefined
|
|
463
|
+
? getReasoningConfig(model, options.reasoningEffort, options)
|
|
464
|
+
: {};
|
|
460
465
|
body.reasoning = {
|
|
461
466
|
...body.reasoning,
|
|
462
467
|
...reasoningConfig,
|
|
@@ -478,7 +483,7 @@ export async function transformRequestBody(
|
|
|
478
483
|
// Catalog pro aliases (`gpt-5.6-*-pro`): applied after the effort branch so
|
|
479
484
|
// the mode is sent even when no effort is set (the branch above deletes
|
|
480
485
|
// `body.reasoning` in that case) — mode and effort are independent fields.
|
|
481
|
-
if (model.reasoningMode) {
|
|
486
|
+
if (model.reasoningMode && !options.reasoningOff) {
|
|
482
487
|
body.reasoning = { ...body.reasoning, mode: model.reasoningMode };
|
|
483
488
|
}
|
|
484
489
|
|
|
@@ -1529,6 +1529,7 @@ export async function buildTransformedCodexRequestBody(
|
|
|
1529
1529
|
}
|
|
1530
1530
|
const codexOptions: CodexRequestOptions = {
|
|
1531
1531
|
reasoningEffort: options?.reasoning,
|
|
1532
|
+
reasoningOff: options?.forceReasoningOff,
|
|
1532
1533
|
reasoningSummary: options?.reasoningSummary,
|
|
1533
1534
|
reasoningContext: options?.reasoningContext,
|
|
1534
1535
|
textVerbosity: options?.textVerbosity,
|
|
@@ -132,7 +132,13 @@ function collectMessageParts(error: unknown, captured: CapturedHttpErrorResponse
|
|
|
132
132
|
return parts.join("\n");
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Text that identifies a 400 as being about the reasoning-effort field.
|
|
137
|
+
* OpenAI-compatible gateways (cliproxy, …) never name the field — they reject
|
|
138
|
+
* the value alone with `level "none" not supported, valid levels: low, …` — so
|
|
139
|
+
* the allowed-level phrasing counts as a mention too.
|
|
140
|
+
*/
|
|
141
|
+
const REASONING_EFFORT_FIELD_PATTERN = /reasoning[_. ]effort|reasoning value|(?:valid|supported|allowed) levels?/i;
|
|
136
142
|
|
|
137
143
|
function mentionsReasoningEffort(error: unknown, captured: CapturedHttpErrorResponse | undefined): boolean {
|
|
138
144
|
const param = capturedStringField(captured, "param");
|
|
@@ -168,10 +174,13 @@ function isInvalidReasoningEffortError(
|
|
|
168
174
|
if (/(?:unsupported|not supported)[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(message)) {
|
|
169
175
|
return true;
|
|
170
176
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
// Gateways put the rejected value first (`level "none" not supported`), the
|
|
178
|
+
// official API puts the verdict first (`Unsupported value: 'none'`).
|
|
179
|
+
const quoted = `["'\`]${escapeRegExp(currentEffort)}["'\`]`;
|
|
180
|
+
return (
|
|
181
|
+
new RegExp(`(?:invalid|unsupported|not supported)[^\\n]*${quoted}`, "i").test(message) ||
|
|
182
|
+
new RegExp(`${quoted}[^\\n]*(?:invalid|unsupported|not supported)`, "i").test(message)
|
|
183
|
+
);
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
function escapeRegExp(value: string): string {
|
|
@@ -186,9 +195,12 @@ function parseKnownReasoningValues(text: string): Set<string> {
|
|
|
186
195
|
values.add(quotedMatch[1]!.toLowerCase());
|
|
187
196
|
quotedMatch = quotedPattern.exec(text);
|
|
188
197
|
}
|
|
189
|
-
const allowedMatch =
|
|
198
|
+
const allowedMatch =
|
|
199
|
+
/(?:must be|one of|allowed values?|supported values?(?: are)?|expected|(?:valid|supported|allowed) levels?(?: are)?)[^.\n]+/i.exec(
|
|
200
|
+
text,
|
|
201
|
+
);
|
|
190
202
|
if (allowedMatch) {
|
|
191
|
-
const allowedText = allowedMatch[
|
|
203
|
+
const allowedText = allowedMatch[0]!;
|
|
192
204
|
const barePattern = /\b(none|minimal|low|medium|high|xhigh|max)\b/gi;
|
|
193
205
|
let bareMatch = barePattern.exec(allowedText);
|
|
194
206
|
while (bareMatch !== null) {
|
|
@@ -201,7 +213,8 @@ function parseKnownReasoningValues(text: string): Set<string> {
|
|
|
201
213
|
|
|
202
214
|
function parseAllowedReasoningValues(message: string, currentEffort: string): Set<string> | undefined {
|
|
203
215
|
const values = parseKnownReasoningValues(message);
|
|
204
|
-
const hasAllowedCue =
|
|
216
|
+
const hasAllowedCue =
|
|
217
|
+
/must be|one of|allowed values?|supported values?|expected|(?:valid|supported|allowed) levels?/i.test(message);
|
|
205
218
|
values.delete(currentEffort.toLowerCase());
|
|
206
219
|
if (!hasAllowedCue && values.size === 0) return undefined;
|
|
207
220
|
return values;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { scheduler } from "node:timers/promises";
|
|
2
2
|
import { hostMatchesUrl } from "@oh-my-pi/pi-catalog/hosts";
|
|
3
|
+
import { bareModelId, parseOpenAIModel, semverGte } from "@oh-my-pi/pi-catalog/identity";
|
|
3
4
|
import { $flag, logger, structuredCloneJSON } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import * as AIError from "../error";
|
|
5
6
|
import { getEnvApiKey } from "../stream";
|
|
@@ -80,6 +81,7 @@ import {
|
|
|
80
81
|
createInitialResponsesAssistantMessage,
|
|
81
82
|
createOpenAIStrictToolsState,
|
|
82
83
|
disableStrictToolsForScope,
|
|
84
|
+
getJuiceValue,
|
|
83
85
|
getOpenAIPromptCacheKey,
|
|
84
86
|
getOpenAIResponsesRoutingSessionId,
|
|
85
87
|
getOpenAIStrictToolsScope,
|
|
@@ -298,14 +300,23 @@ interface OpenAIResponsesChainedParams {
|
|
|
298
300
|
*/
|
|
299
301
|
function buildOpenAIResponsesChainedParams(
|
|
300
302
|
params: OpenAIResponsesSamplingParams,
|
|
303
|
+
trailingScaffoldingItems: number,
|
|
301
304
|
chain: OpenAIResponsesChainState,
|
|
302
305
|
): OpenAIResponsesChainedParams {
|
|
306
|
+
const historyParams =
|
|
307
|
+
trailingScaffoldingItems > 0 && Array.isArray(params.input)
|
|
308
|
+
? { ...params, input: params.input.slice(0, params.input.length - trailingScaffoldingItems) }
|
|
309
|
+
: params;
|
|
303
310
|
const deltaInput = chain.canAppend
|
|
304
|
-
? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems,
|
|
311
|
+
? buildResponsesDeltaInput(chain.lastParams, chain.lastResponseItems, historyParams)
|
|
305
312
|
: null;
|
|
306
313
|
if (deltaInput && deltaInput.length > 0 && chain.lastResponseId) {
|
|
314
|
+
const scaffolding =
|
|
315
|
+
historyParams !== params && Array.isArray(params.input)
|
|
316
|
+
? params.input.slice(params.input.length - trailingScaffoldingItems)
|
|
317
|
+
: [];
|
|
307
318
|
return {
|
|
308
|
-
params: { ...params, previous_response_id: chain.lastResponseId, input: deltaInput },
|
|
319
|
+
params: { ...params, previous_response_id: chain.lastResponseId, input: [...deltaInput, ...scaffolding] },
|
|
309
320
|
previousResponseId: chain.lastResponseId,
|
|
310
321
|
};
|
|
311
322
|
}
|
|
@@ -462,8 +473,9 @@ const streamOpenAIResponsesOnce = (
|
|
|
462
473
|
false,
|
|
463
474
|
chainState?.canAppend ? chainState.lastParams?.input : undefined,
|
|
464
475
|
);
|
|
465
|
-
const params = builtParams
|
|
476
|
+
const { params, trailingScaffoldingItems } = builtParams;
|
|
466
477
|
let activeParams = params;
|
|
478
|
+
let activeTrailingScaffoldingItems = trailingScaffoldingItems;
|
|
467
479
|
const resolvedBaseUrl = (baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
468
480
|
const requestReasoningEffortFallbacks = new Map<string, OpenAIReasoningEffortFallback>();
|
|
469
481
|
const attemptedReasoningEffortFallbacks = new Set<string>();
|
|
@@ -490,7 +502,9 @@ const streamOpenAIResponsesOnce = (
|
|
|
490
502
|
}
|
|
491
503
|
applyReasoningEffortFallbackForRequest(params);
|
|
492
504
|
let chained: OpenAIResponsesChainedParams =
|
|
493
|
-
chainState && !chainState.disabled
|
|
505
|
+
chainState && !chainState.disabled
|
|
506
|
+
? buildOpenAIResponsesChainedParams(params, trailingScaffoldingItems, chainState)
|
|
507
|
+
: { params };
|
|
494
508
|
sentPreviousResponseId = chained.previousResponseId;
|
|
495
509
|
const idleTimeoutMs =
|
|
496
510
|
options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs(model.compat.streamIdleTimeoutMs);
|
|
@@ -586,7 +600,9 @@ const streamOpenAIResponsesOnce = (
|
|
|
586
600
|
const reasoningEffortFallback =
|
|
587
601
|
activeReasoningEffortFallbackKey && activeRequestParams && !requestSignal.aborted
|
|
588
602
|
? resolveOpenAIReasoningEffortFallback(error, capturedErrorResponse, activeRequestParams, {
|
|
589
|
-
explicitDisable:
|
|
603
|
+
explicitDisable:
|
|
604
|
+
options?.forceReasoningOff === true ||
|
|
605
|
+
(options?.disableReasoning === true && options.reasoning === undefined),
|
|
590
606
|
})
|
|
591
607
|
: undefined;
|
|
592
608
|
if (reasoningEffortFallback !== undefined && activeReasoningEffortFallbackKey) {
|
|
@@ -632,7 +648,11 @@ const streamOpenAIResponsesOnce = (
|
|
|
632
648
|
if (chainState && !chainState.disabled) fallbackParams.store = true;
|
|
633
649
|
let fallbackChained: OpenAIResponsesChainedParams =
|
|
634
650
|
chainState && !chainState.disabled
|
|
635
|
-
? buildOpenAIResponsesChainedParams(
|
|
651
|
+
? buildOpenAIResponsesChainedParams(
|
|
652
|
+
fallbackParams,
|
|
653
|
+
fallbackBuilt.trailingScaffoldingItems,
|
|
654
|
+
chainState,
|
|
655
|
+
)
|
|
636
656
|
: { params: fallbackParams };
|
|
637
657
|
sentPreviousResponseId = fallbackChained.previousResponseId;
|
|
638
658
|
fallbackChained = {
|
|
@@ -642,7 +662,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
642
662
|
chained = fallbackChained;
|
|
643
663
|
activeRawRequestDump.body = chained.params;
|
|
644
664
|
activeParams = fallbackParams;
|
|
645
|
-
|
|
665
|
+
activeTrailingScaffoldingItems = fallbackBuilt.trailingScaffoldingItems;
|
|
646
666
|
continue;
|
|
647
667
|
}
|
|
648
668
|
if (!chainState || !sentPreviousResponseId || requestSignal.aborted) {
|
|
@@ -688,6 +708,7 @@ const streamOpenAIResponsesOnce = (
|
|
|
688
708
|
chained = { params: retryParams };
|
|
689
709
|
activeRawRequestDump.body = retryParams;
|
|
690
710
|
activeParams = currentParams;
|
|
711
|
+
activeTrailingScaffoldingItems = currentBuilt.trailingScaffoldingItems;
|
|
691
712
|
activeStrictToolsApplied = currentBuilt.strictToolsApplied;
|
|
692
713
|
}
|
|
693
714
|
}
|
|
@@ -824,7 +845,17 @@ const streamOpenAIResponsesOnce = (
|
|
|
824
845
|
if (replayableResponseItems) {
|
|
825
846
|
if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
|
|
826
847
|
if (chainState) {
|
|
827
|
-
chainState.lastParams = structuredCloneJSON(
|
|
848
|
+
chainState.lastParams = structuredCloneJSON(
|
|
849
|
+
activeTrailingScaffoldingItems > 0 && Array.isArray(activeParams.input)
|
|
850
|
+
? {
|
|
851
|
+
...activeParams,
|
|
852
|
+
input: activeParams.input.slice(
|
|
853
|
+
0,
|
|
854
|
+
activeParams.input.length - activeTrailingScaffoldingItems,
|
|
855
|
+
),
|
|
856
|
+
}
|
|
857
|
+
: activeParams,
|
|
858
|
+
);
|
|
828
859
|
chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
|
|
829
860
|
if (output.responseId) {
|
|
830
861
|
chainState.lastResponseId = output.responseId;
|
|
@@ -843,7 +874,14 @@ const streamOpenAIResponsesOnce = (
|
|
|
843
874
|
// baseline, but `lastParams` still records the successful wire controls
|
|
844
875
|
// without re-enabling `previous_response_id` chaining.
|
|
845
876
|
chainState.canAppend = false;
|
|
846
|
-
chainState.lastParams = structuredCloneJSON(
|
|
877
|
+
chainState.lastParams = structuredCloneJSON(
|
|
878
|
+
activeTrailingScaffoldingItems > 0 && Array.isArray(activeParams.input)
|
|
879
|
+
? {
|
|
880
|
+
...activeParams,
|
|
881
|
+
input: activeParams.input.slice(0, activeParams.input.length - activeTrailingScaffoldingItems),
|
|
882
|
+
}
|
|
883
|
+
: activeParams,
|
|
884
|
+
);
|
|
847
885
|
chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
|
|
848
886
|
chainState.lastResponseId = undefined;
|
|
849
887
|
chainState.lastResponseItems = undefined;
|
|
@@ -899,6 +937,17 @@ function isOfficialOpenAIResponsesEndpoint(model: Model<"openai-responses">): bo
|
|
|
899
937
|
}
|
|
900
938
|
}
|
|
901
939
|
|
|
940
|
+
/**
|
|
941
|
+
* GPT-5.6+ family check for Responses routes. The model id classifies the
|
|
942
|
+
* reasoning family regardless of the provider/host serving it — a cliproxy or
|
|
943
|
+
* other OpenAI-compatible gateway carrying `gpt-5.6-sol` gets the same
|
|
944
|
+
* scaffolding as the official endpoint.
|
|
945
|
+
*/
|
|
946
|
+
function isGpt56PlusResponsesModel(model: Model<"openai-responses">): boolean {
|
|
947
|
+
const parsed = parseOpenAIModel(bareModelId(model.requestModelId ?? model.id));
|
|
948
|
+
return parsed !== null && semverGte(parsed.version, "5.6");
|
|
949
|
+
}
|
|
950
|
+
|
|
902
951
|
function isResponsesPromptCacheableContentBlock(block: unknown): block is ResponseInputContent {
|
|
903
952
|
if (typeof block !== "object" || block === null || !("type" in block)) return false;
|
|
904
953
|
return block.type === "input_text" || block.type === "input_image" || block.type === "input_file";
|
|
@@ -1090,7 +1139,7 @@ export function buildParams(
|
|
|
1090
1139
|
strictToolsScope?: OpenAIStrictToolsScope,
|
|
1091
1140
|
disableStrictToolsOverride = false,
|
|
1092
1141
|
statefulCacheBaseline?: ResponseInput,
|
|
1093
|
-
): { params: OpenAIResponsesSamplingParams; strictToolsApplied: boolean } {
|
|
1142
|
+
): { params: OpenAIResponsesSamplingParams; trailingScaffoldingItems: number; strictToolsApplied: boolean } {
|
|
1094
1143
|
const policy = resolveOpenAICompatPolicy(model, {
|
|
1095
1144
|
endpoint: "responses",
|
|
1096
1145
|
reasoning: options?.reasoning,
|
|
@@ -1244,6 +1293,7 @@ export function buildParams(
|
|
|
1244
1293
|
: options?.reasoningSummary;
|
|
1245
1294
|
applyResponsesCompatPolicy(params, reasoningPolicy, {
|
|
1246
1295
|
reasoningSummary,
|
|
1296
|
+
forceReasoningOff: options?.forceReasoningOff,
|
|
1247
1297
|
mapEffort: effort =>
|
|
1248
1298
|
model.compat.reasoningEffortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
|
|
1249
1299
|
model.thinking?.effortMap?.[effort as NonNullable<OpenAIResponsesOptions["reasoning"]>] ??
|
|
@@ -1253,7 +1303,7 @@ export function buildParams(
|
|
|
1253
1303
|
// mode survives every policy branch (disabled/omitted effort included) while
|
|
1254
1304
|
// keeping whatever effort/summary the policy produced — mode and effort are
|
|
1255
1305
|
// independent wire fields.
|
|
1256
|
-
if (model.reasoningMode) {
|
|
1306
|
+
if (model.reasoningMode && !options?.forceReasoningOff) {
|
|
1257
1307
|
params.reasoning = { ...params.reasoning, mode: model.reasoningMode };
|
|
1258
1308
|
}
|
|
1259
1309
|
|
|
@@ -1266,7 +1316,18 @@ export function buildParams(
|
|
|
1266
1316
|
applyOpenAIExtraBody(params, options?.extraBody);
|
|
1267
1317
|
applyOpenAIResponsesPromptCachePolicy(params, model, options, statefulCacheBaseline);
|
|
1268
1318
|
|
|
1269
|
-
|
|
1319
|
+
let trailingScaffoldingItems = 0;
|
|
1320
|
+
if (options?.forceReasoningOff && isGpt56PlusResponsesModel(model)) {
|
|
1321
|
+
const effort = options.reasoning ?? "medium";
|
|
1322
|
+
const juice = getJuiceValue(effort);
|
|
1323
|
+
messages.push({
|
|
1324
|
+
role: "developer",
|
|
1325
|
+
content: [{ type: "input_text", text: `# Juice: ${juice} !important` }],
|
|
1326
|
+
});
|
|
1327
|
+
trailingScaffoldingItems = 1;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
return { params, trailingScaffoldingItems, strictToolsApplied };
|
|
1270
1331
|
}
|
|
1271
1332
|
|
|
1272
1333
|
/**
|
|
@@ -2428,6 +2428,20 @@ export function finalizeMessageText(item: ResponseOutputMessage, streamedText: s
|
|
|
2428
2428
|
if (!item.content?.length) return streamedText || "";
|
|
2429
2429
|
return item.content.map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? ""))).join("");
|
|
2430
2430
|
}
|
|
2431
|
+
export const JUICE_EFFORT_MAP: Record<string, number> = {
|
|
2432
|
+
none: 0,
|
|
2433
|
+
minimal: 2,
|
|
2434
|
+
low: 4,
|
|
2435
|
+
medium: 8,
|
|
2436
|
+
high: 48,
|
|
2437
|
+
xhigh: 112,
|
|
2438
|
+
max: 960,
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
export function getJuiceValue(effort?: string): number {
|
|
2442
|
+
if (!effort) return 8;
|
|
2443
|
+
return JUICE_EFFORT_MAP[effort] ?? 8;
|
|
2444
|
+
}
|
|
2431
2445
|
|
|
2432
2446
|
export function accumulateToolCallArgumentsDelta(
|
|
2433
2447
|
block: ResponsesToolCallBlock,
|
|
@@ -3308,6 +3322,14 @@ type ReasoningOptions = {
|
|
|
3308
3322
|
export interface ApplyResponsesCompatPolicyOptions {
|
|
3309
3323
|
reasoningSummary?: "auto" | "detailed" | "concise" | null;
|
|
3310
3324
|
mapEffort?: (effort: string) => string;
|
|
3325
|
+
/**
|
|
3326
|
+
* Suppress native reasoning by sending `reasoning.effort: "none"` — the only
|
|
3327
|
+
* disable level the Responses API defines (`"off"` is not a wire value and
|
|
3328
|
+
* 400s everywhere). Gateways that reject `none` for a given model are
|
|
3329
|
+
* handled by the reasoning-effort fallback retry, which clamps to the
|
|
3330
|
+
* lowest level the error reports as allowed.
|
|
3331
|
+
*/
|
|
3332
|
+
forceReasoningOff?: boolean;
|
|
3311
3333
|
}
|
|
3312
3334
|
|
|
3313
3335
|
export function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreaming>(
|
|
@@ -3316,6 +3338,10 @@ export function applyResponsesCompatPolicy<P extends ResponseCreateParamsStreami
|
|
|
3316
3338
|
options: ApplyResponsesCompatPolicyOptions | undefined,
|
|
3317
3339
|
): void {
|
|
3318
3340
|
const reasoning = policy.reasoning;
|
|
3341
|
+
if (options?.forceReasoningOff) {
|
|
3342
|
+
params.reasoning = { effort: "none" } as P["reasoning"];
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3319
3345
|
if (!reasoning.modelSupported) return;
|
|
3320
3346
|
if (reasoning.includeEncryptedReasoning) {
|
|
3321
3347
|
const include = params.include ?? [];
|
package/src/stream.ts
CHANGED
|
@@ -1692,6 +1692,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1692
1692
|
openrouterVariant: options?.openrouterVariant,
|
|
1693
1693
|
maxTokensExplicit: rawOptions?.maxTokens !== undefined,
|
|
1694
1694
|
disableReasoning: options?.disableReasoning,
|
|
1695
|
+
forceReasoningOff: options?.forceReasoningOff,
|
|
1695
1696
|
textVerbosity: options?.textVerbosity,
|
|
1696
1697
|
promptCache: options?.promptCache,
|
|
1697
1698
|
statefulResponses: options?.statefulResponses,
|
|
@@ -1706,6 +1707,8 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1706
1707
|
reasoningSummary: options?.hideThinkingSummary ? null : undefined,
|
|
1707
1708
|
promptCache: options?.promptCache,
|
|
1708
1709
|
statefulResponses: options?.statefulResponses,
|
|
1710
|
+
disableReasoning: options?.disableReasoning || options?.forceReasoningOff,
|
|
1711
|
+
forceReasoningOff: options?.forceReasoningOff,
|
|
1709
1712
|
});
|
|
1710
1713
|
|
|
1711
1714
|
case "openai-codex-responses":
|
|
@@ -1718,6 +1721,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1718
1721
|
codexCompaction: options?.codexCompaction,
|
|
1719
1722
|
reasoningSummary: options?.hideThinkingSummary ? null : undefined,
|
|
1720
1723
|
textVerbosity: options?.textVerbosity,
|
|
1724
|
+
forceReasoningOff: options?.forceReasoningOff,
|
|
1721
1725
|
});
|
|
1722
1726
|
|
|
1723
1727
|
case "google-generative-ai": {
|
package/src/types.ts
CHANGED
|
@@ -478,6 +478,11 @@ export interface StreamOptions {
|
|
|
478
478
|
* `false` so `previous_response_id` cannot explain a result.
|
|
479
479
|
*/
|
|
480
480
|
statefulResponses?: boolean;
|
|
481
|
+
/**
|
|
482
|
+
* Emit `reasoning: { effort: "none" }` for OpenAI Responses and Codex requests.
|
|
483
|
+
* Used when a caller supplies an external reasoning scratchpad; other transports ignore it.
|
|
484
|
+
*/
|
|
485
|
+
forceReasoningOff?: boolean;
|
|
481
486
|
/**
|
|
482
487
|
* Provider-scoped mutable state store for this agent session.
|
|
483
488
|
* Providers can use this to persist transport/session state between turns.
|