@f5-sales-demo/pi-ai 21.49.3 → 21.50.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/package.json +2 -2
- package/src/codex-model-interaction.ts +164 -0
- package/src/index.ts +1 -0
- package/src/model-thinking.ts +8 -2
- package/src/models.ts +2 -1
- package/src/provider-models/special.ts +1 -0
- package/src/providers/amazon-bedrock.ts +1 -0
- package/src/providers/openai-codex/request-transformer.ts +101 -9
- package/src/providers/openai-codex-responses.ts +5 -3
- package/src/providers/openai-responses.ts +66 -11
- package/src/stream.ts +7 -1
- package/src/types.ts +18 -0
- package/src/utils/discovery/codex.ts +33 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.50.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "0.123.0",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "3.1126.0",
|
|
48
48
|
"@bufbuild/protobuf": "2.14.1",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "21.50.0",
|
|
50
50
|
"@google/genai": "2.21.0",
|
|
51
51
|
"@sinclair/typebox": "0.34.52",
|
|
52
52
|
"@smithy/node-http-handler": "4.12.1",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ReasoningEffort } from "./model-thinking";
|
|
2
|
+
import type { Api, Model, ServiceTier, ThinkingConfig } from "./types";
|
|
3
|
+
|
|
4
|
+
export type CodexContextTier = "standard" | "codex-max" | "provider-max";
|
|
5
|
+
export type ReasoningSummary = "none" | "auto" | "concise" | "detailed";
|
|
6
|
+
export type WireReasoningSummary = Exclude<ReasoningSummary, "none">;
|
|
7
|
+
|
|
8
|
+
export interface CodexModelInteractionMetadata {
|
|
9
|
+
standardContextWindow: number;
|
|
10
|
+
maxContextWindow: number;
|
|
11
|
+
internalProviderMaxContextWindow: number;
|
|
12
|
+
outputLimit: number;
|
|
13
|
+
effectiveContextWindowPercent: number;
|
|
14
|
+
autoCompactThresholdPercent: number;
|
|
15
|
+
defaultReasoningSummary: ReasoningSummary;
|
|
16
|
+
defaultVerbosity: "low" | "medium" | "high";
|
|
17
|
+
thinking: ThinkingConfig;
|
|
18
|
+
serviceTiers: ServiceTier[];
|
|
19
|
+
defaultServiceTier: ServiceTier;
|
|
20
|
+
truncationPolicy: { mode: "tokens" | "bytes"; limit: number };
|
|
21
|
+
supportsParallelToolCalls: boolean;
|
|
22
|
+
ultraReasoningEffort?: ReasoningEffort;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const descriptions: Record<ReasoningEffort, string> = {
|
|
26
|
+
none: "No reasoning",
|
|
27
|
+
minimal: "Minimal reasoning",
|
|
28
|
+
low: "Light reasoning",
|
|
29
|
+
medium: "Balanced reasoning",
|
|
30
|
+
high: "Deep reasoning",
|
|
31
|
+
xhigh: "Very deep reasoning",
|
|
32
|
+
max: "Maximum reasoning",
|
|
33
|
+
ultra: "Maximum reasoning with automatic task delegation",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function metadata(
|
|
37
|
+
defaultLevel: ReasoningEffort,
|
|
38
|
+
supported: ReasoningEffort[],
|
|
39
|
+
defaultServiceTier: ServiceTier,
|
|
40
|
+
maxContextWindow = 872_000,
|
|
41
|
+
internalProviderMaxContextWindow = 1_050_000,
|
|
42
|
+
ultraReasoningEffort: ReasoningEffort = "max",
|
|
43
|
+
): CodexModelInteractionMetadata {
|
|
44
|
+
return {
|
|
45
|
+
standardContextWindow: 272_000,
|
|
46
|
+
maxContextWindow,
|
|
47
|
+
internalProviderMaxContextWindow,
|
|
48
|
+
outputLimit: 128_000,
|
|
49
|
+
effectiveContextWindowPercent: 95,
|
|
50
|
+
autoCompactThresholdPercent: 90,
|
|
51
|
+
defaultReasoningSummary: "none",
|
|
52
|
+
defaultVerbosity: "low",
|
|
53
|
+
thinking: {
|
|
54
|
+
mode: "effort",
|
|
55
|
+
defaultLevel,
|
|
56
|
+
supportedLevels: supported.map(effort => ({ effort, description: descriptions[effort] })),
|
|
57
|
+
},
|
|
58
|
+
serviceTiers: ["default", "priority"],
|
|
59
|
+
defaultServiceTier,
|
|
60
|
+
truncationPolicy: { mode: "tokens", limit: 10_000 },
|
|
61
|
+
supportsParallelToolCalls: true,
|
|
62
|
+
...(supported.includes("ultra") ? { ultraReasoningEffort } : {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const CODEX_MODEL_INTERACTION_METADATA: Readonly<Record<string, CodexModelInteractionMetadata>> = {
|
|
67
|
+
"gpt-6-astra": metadata(
|
|
68
|
+
"low",
|
|
69
|
+
["low", "medium", "high", "xhigh", "max", "ultra"],
|
|
70
|
+
"default",
|
|
71
|
+
872_000,
|
|
72
|
+
922_000,
|
|
73
|
+
"xhigh",
|
|
74
|
+
),
|
|
75
|
+
"gpt-6-sol": metadata("medium", ["low", "medium", "high", "xhigh", "max", "ultra"], "priority", 872_000, 922_000),
|
|
76
|
+
"gpt-6-luna": metadata("medium", ["low", "medium", "high", "xhigh", "max"], "priority", 872_000, 922_000),
|
|
77
|
+
"gpt-5.6-sol": metadata("low", ["low", "medium", "high", "xhigh", "max", "ultra"], "default"),
|
|
78
|
+
"gpt-5.6-terra": metadata("medium", ["low", "medium", "high", "xhigh", "max", "ultra"], "default"),
|
|
79
|
+
"gpt-5.6-luna": metadata("medium", ["low", "medium", "high", "xhigh", "max"], "default"),
|
|
80
|
+
"gpt-5.5": metadata("medium", ["low", "medium", "high", "xhigh"], "default", 272_000),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function resolveCodexContextBudget(
|
|
84
|
+
modelId: string,
|
|
85
|
+
tier: CodexContextTier,
|
|
86
|
+
providerContextWindow?: number,
|
|
87
|
+
): { contextWindow: number; effectiveContextWindow: number; autoCompactTokenLimit: number } | undefined {
|
|
88
|
+
const interaction = CODEX_MODEL_INTERACTION_METADATA[modelId];
|
|
89
|
+
if (!interaction) return undefined;
|
|
90
|
+
const providerLimit =
|
|
91
|
+
typeof providerContextWindow === "number" && Number.isFinite(providerContextWindow) && providerContextWindow > 0
|
|
92
|
+
? Math.floor(providerContextWindow)
|
|
93
|
+
: interaction.maxContextWindow;
|
|
94
|
+
let contextWindow: number;
|
|
95
|
+
if (tier === "standard") {
|
|
96
|
+
contextWindow = Math.min(interaction.standardContextWindow, providerLimit);
|
|
97
|
+
} else if (tier === "codex-max") {
|
|
98
|
+
contextWindow = Math.min(interaction.maxContextWindow, providerLimit);
|
|
99
|
+
} else {
|
|
100
|
+
contextWindow = providerLimit;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
contextWindow,
|
|
104
|
+
effectiveContextWindow: Math.floor((contextWindow * interaction.effectiveContextWindowPercent) / 100),
|
|
105
|
+
autoCompactTokenLimit: Math.floor((contextWindow * interaction.autoCompactThresholdPercent) / 100),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resolveCodexWireReasoningEffort(modelId: string, effort: ReasoningEffort): ReasoningEffort {
|
|
110
|
+
if (effort !== "ultra") return effort;
|
|
111
|
+
return CODEX_MODEL_INTERACTION_METADATA[modelId]?.ultraReasoningEffort ?? "max";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function resolveWireReasoningSummary(summary: ReasoningSummary): WireReasoningSummary | undefined {
|
|
115
|
+
return summary === "none" ? undefined : summary;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Return the model's usable runtime context, clamped to its selected provider-facing window. */
|
|
119
|
+
export function getModelEffectiveContextWindow(model: Pick<Model, "contextWindow" | "effectiveContextWindow">): number {
|
|
120
|
+
const contextWindow =
|
|
121
|
+
typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0
|
|
122
|
+
? Math.floor(model.contextWindow)
|
|
123
|
+
: 0;
|
|
124
|
+
const effectiveContextWindow =
|
|
125
|
+
typeof model.effectiveContextWindow === "number" &&
|
|
126
|
+
Number.isFinite(model.effectiveContextWindow) &&
|
|
127
|
+
model.effectiveContextWindow > 0
|
|
128
|
+
? Math.floor(model.effectiveContextWindow)
|
|
129
|
+
: contextWindow;
|
|
130
|
+
return contextWindow > 0 ? Math.min(contextWindow, effectiveContextWindow) : effectiveContextWindow;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function applyCodexInteractionMetadata<TApi extends Api>(
|
|
134
|
+
model: Model<TApi>,
|
|
135
|
+
providerContextWindow?: number,
|
|
136
|
+
): Model<TApi> {
|
|
137
|
+
if (model.provider !== "openai-codex" && model.provider !== "litellm") return model;
|
|
138
|
+
const interaction = CODEX_MODEL_INTERACTION_METADATA[model.id];
|
|
139
|
+
if (!interaction) return model;
|
|
140
|
+
const rawProviderLimit =
|
|
141
|
+
providerContextWindow ??
|
|
142
|
+
model.providerContextWindow ??
|
|
143
|
+
(model.provider === "openai-codex" ? interaction.maxContextWindow : model.contextWindow);
|
|
144
|
+
const advertisedLimit = Math.min(rawProviderLimit, interaction.internalProviderMaxContextWindow);
|
|
145
|
+
const budget = resolveCodexContextBudget(model.id, "standard", advertisedLimit)!;
|
|
146
|
+
return {
|
|
147
|
+
...model,
|
|
148
|
+
thinking: interaction.thinking,
|
|
149
|
+
contextWindow: budget.contextWindow,
|
|
150
|
+
maxContextWindow: interaction.maxContextWindow,
|
|
151
|
+
maxTokens: interaction.outputLimit,
|
|
152
|
+
providerContextWindow: advertisedLimit,
|
|
153
|
+
effectiveContextWindowPercent: interaction.effectiveContextWindowPercent,
|
|
154
|
+
autoCompactThresholdPercent: interaction.autoCompactThresholdPercent,
|
|
155
|
+
effectiveContextWindow: budget.effectiveContextWindow,
|
|
156
|
+
autoCompactTokenLimit: budget.autoCompactTokenLimit,
|
|
157
|
+
defaultReasoningSummary: interaction.defaultReasoningSummary,
|
|
158
|
+
defaultVerbosity: interaction.defaultVerbosity,
|
|
159
|
+
serviceTiers: interaction.serviceTiers,
|
|
160
|
+
defaultServiceTier: interaction.defaultServiceTier,
|
|
161
|
+
truncationPolicy: interaction.truncationPolicy,
|
|
162
|
+
supportsParallelToolCalls: interaction.supportsParallelToolCalls,
|
|
163
|
+
};
|
|
164
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type { Static, TSchema } from "@sinclair/typebox";
|
|
|
2
2
|
export { Type } from "@sinclair/typebox";
|
|
3
3
|
export * from "./api-registry";
|
|
4
4
|
export * from "./auth-storage";
|
|
5
|
+
export * from "./codex-model-interaction";
|
|
5
6
|
export * from "./model-cache";
|
|
6
7
|
export * from "./model-manager";
|
|
7
8
|
export * from "./model-thinking";
|
package/src/model-thinking.ts
CHANGED
|
@@ -15,6 +15,7 @@ export const enum Effort {
|
|
|
15
15
|
High = "high",
|
|
16
16
|
XHigh = "xhigh",
|
|
17
17
|
Max = "max",
|
|
18
|
+
Ultra = "ultra",
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/** Public reasoning preset enum. `none` is distinct from inheriting the provider default. */
|
|
@@ -26,8 +27,9 @@ export const ReasoningEffort = {
|
|
|
26
27
|
High: Effort.High,
|
|
27
28
|
XHigh: Effort.XHigh,
|
|
28
29
|
Max: Effort.Max,
|
|
30
|
+
Ultra: Effort.Ultra,
|
|
29
31
|
} as const;
|
|
30
|
-
export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
32
|
+
export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Anthropic's `output_config.effort` enum, verbatim. Authority is the API's own
|
|
@@ -47,7 +49,7 @@ export type EffortThroughXHigh = "minimal" | "low" | "medium" | "high" | "xhigh"
|
|
|
47
49
|
* clamping is the safe direction if a caller bypasses the range check.
|
|
48
50
|
*/
|
|
49
51
|
export function clampEffortThroughXHigh(effort: Effort): EffortThroughXHigh {
|
|
50
|
-
return effort === Effort.Max ? Effort.XHigh : effort;
|
|
52
|
+
return effort === Effort.Max || effort === Effort.Ultra ? Effort.XHigh : effort;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/** Order is load-bearing: `indexOf` drives expandEffortRange/requireSupportedEffort. */
|
|
@@ -58,6 +60,7 @@ export const THINKING_EFFORTS: readonly Effort[] = [
|
|
|
58
60
|
Effort.High,
|
|
59
61
|
Effort.XHigh,
|
|
60
62
|
Effort.Max,
|
|
63
|
+
Effort.Ultra,
|
|
61
64
|
];
|
|
62
65
|
|
|
63
66
|
export const REASONING_EFFORTS: readonly ReasoningEffort[] = [ReasoningEffort.None, ...THINKING_EFFORTS];
|
|
@@ -70,6 +73,7 @@ const EFFORT_DESCRIPTIONS: Readonly<Record<ReasoningEffort, string>> = {
|
|
|
70
73
|
[ReasoningEffort.High]: "Deep reasoning",
|
|
71
74
|
[ReasoningEffort.XHigh]: "Very deep reasoning",
|
|
72
75
|
[ReasoningEffort.Max]: "Maximum reasoning",
|
|
76
|
+
[ReasoningEffort.Ultra]: "Maximum reasoning with automatic task delegation",
|
|
73
77
|
};
|
|
74
78
|
|
|
75
79
|
/** Build explicit metadata for custom models and programmatic registries. */
|
|
@@ -365,6 +369,7 @@ export function mapEffortToGoogleThinkingLevel<TApi extends Api>(
|
|
|
365
369
|
case Effort.High:
|
|
366
370
|
case Effort.XHigh:
|
|
367
371
|
case Effort.Max:
|
|
372
|
+
case Effort.Ultra:
|
|
368
373
|
return "HIGH";
|
|
369
374
|
}
|
|
370
375
|
}
|
|
@@ -391,6 +396,7 @@ export function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(
|
|
|
391
396
|
case Effort.XHigh:
|
|
392
397
|
return "xhigh";
|
|
393
398
|
case Effort.Max:
|
|
399
|
+
case Effort.Ultra:
|
|
394
400
|
return "max";
|
|
395
401
|
}
|
|
396
402
|
}
|
package/src/models.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyCodexInteractionMetadata } from "./codex-model-interaction";
|
|
1
2
|
import { enrichModelThinking } from "./model-thinking";
|
|
2
3
|
import MODELS from "./models.json" with { type: "json" };
|
|
3
4
|
import type { Api, KnownProvider, Model, Usage } from "./types";
|
|
@@ -14,7 +15,7 @@ const modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
|
|
|
14
15
|
for (const [provider, models] of Object.entries(MODELS)) {
|
|
15
16
|
const providerModels = new Map<string, Model<Api>>();
|
|
16
17
|
for (const [id, model] of Object.entries(models)) {
|
|
17
|
-
providerModels.set(id, enrichModelThinking(model as Model<Api>));
|
|
18
|
+
providerModels.set(id, applyCodexInteractionMetadata(enrichModelThinking(model as Model<Api>)));
|
|
18
19
|
}
|
|
19
20
|
modelRegistry.set(provider, providerModels);
|
|
20
21
|
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ReasoningSummary,
|
|
3
|
+
resolveCodexWireReasoningEffort,
|
|
4
|
+
resolveWireReasoningSummary,
|
|
5
|
+
type WireReasoningSummary,
|
|
6
|
+
} from "../../codex-model-interaction";
|
|
1
7
|
import type { ReasoningEffort } from "../../model-thinking";
|
|
2
8
|
import { requireSupportedReasoningEffort } from "../../model-thinking";
|
|
3
9
|
import type { Api, Model } from "../../types";
|
|
4
10
|
|
|
5
11
|
export interface ReasoningConfig {
|
|
6
|
-
effort:
|
|
7
|
-
summary
|
|
12
|
+
effort: ReasoningEffort;
|
|
13
|
+
summary?: WireReasoningSummary;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
export interface CodexRequestOptions {
|
|
11
17
|
reasoningEffort?: ReasoningConfig["effort"];
|
|
12
|
-
reasoningSummary?:
|
|
18
|
+
reasoningSummary?: ReasoningSummary | null;
|
|
13
19
|
textVerbosity?: "low" | "medium" | "high";
|
|
14
20
|
include?: string[];
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
export interface InputItem {
|
|
@@ -33,6 +40,7 @@ export interface RequestBody {
|
|
|
33
40
|
input?: InputItem[];
|
|
34
41
|
tools?: unknown;
|
|
35
42
|
tool_choice?: unknown;
|
|
43
|
+
parallel_tool_calls?: boolean;
|
|
36
44
|
temperature?: number;
|
|
37
45
|
top_p?: number;
|
|
38
46
|
top_k?: number;
|
|
@@ -46,18 +54,88 @@ export interface RequestBody {
|
|
|
46
54
|
include?: string[];
|
|
47
55
|
prompt_cache_key?: string;
|
|
48
56
|
prompt_cache_retention?: "in_memory" | "24h";
|
|
57
|
+
service_tier?: string;
|
|
58
|
+
client_metadata?: Record<string, string>;
|
|
49
59
|
max_output_tokens?: number;
|
|
50
60
|
max_completion_tokens?: number;
|
|
51
61
|
[key: string]: unknown;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
function getReasoningConfig(model: Model<Api>, options: CodexRequestOptions): ReasoningConfig {
|
|
65
|
+
const summary = resolveWireReasoningSummary(options.reasoningSummary ?? model.defaultReasoningSummary ?? "none");
|
|
55
66
|
return {
|
|
56
|
-
effort:
|
|
57
|
-
|
|
67
|
+
effort: resolveCodexWireReasoningEffort(
|
|
68
|
+
model.id,
|
|
69
|
+
requireSupportedReasoningEffort(
|
|
70
|
+
model,
|
|
71
|
+
(options.reasoningEffort ?? model.thinking?.defaultLevel) as ReasoningEffort,
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
...(summary ? { summary } : {}),
|
|
58
75
|
};
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
export function normalizeClientMetadata(
|
|
79
|
+
metadata: Record<string, unknown> | undefined,
|
|
80
|
+
): Record<string, string> | undefined {
|
|
81
|
+
if (!metadata) return undefined;
|
|
82
|
+
const entries = Object.entries(metadata).flatMap(([key, value]) => {
|
|
83
|
+
if (typeof value === "string") return [[key, value] as const];
|
|
84
|
+
if (typeof value === "number" || typeof value === "boolean") return [[key, String(value)] as const];
|
|
85
|
+
return [];
|
|
86
|
+
});
|
|
87
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function utf8Length(value: string): number {
|
|
91
|
+
return new TextEncoder().encode(value).byteLength;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function truncateMiddle(value: string, policy: NonNullable<Model<Api>["truncationPolicy"]>): string {
|
|
95
|
+
const maxBytes = policy.mode === "tokens" ? policy.limit * 4 : policy.limit;
|
|
96
|
+
const totalBytes = utf8Length(value);
|
|
97
|
+
if (value.length === 0 || (maxBytes > 0 && totalBytes <= maxBytes)) return value;
|
|
98
|
+
|
|
99
|
+
const characters = Array.from(value);
|
|
100
|
+
const leftBudget = Math.floor(Math.max(0, maxBytes) / 2);
|
|
101
|
+
const rightBudget = Math.max(0, maxBytes) - leftBudget;
|
|
102
|
+
let leftBytes = 0;
|
|
103
|
+
let leftCount = 0;
|
|
104
|
+
while (leftCount < characters.length) {
|
|
105
|
+
const nextBytes = utf8Length(characters[leftCount]!);
|
|
106
|
+
if (leftBytes + nextBytes > leftBudget) break;
|
|
107
|
+
leftBytes += nextBytes;
|
|
108
|
+
leftCount += 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let rightBytes = 0;
|
|
112
|
+
let rightStart = characters.length;
|
|
113
|
+
while (rightStart > leftCount) {
|
|
114
|
+
const nextBytes = utf8Length(characters[rightStart - 1]!);
|
|
115
|
+
if (rightBytes + nextBytes > rightBudget) break;
|
|
116
|
+
rightBytes += nextBytes;
|
|
117
|
+
rightStart -= 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const removed =
|
|
121
|
+
policy.mode === "tokens"
|
|
122
|
+
? Math.ceil(Math.max(0, totalBytes - Math.max(0, maxBytes)) / 4)
|
|
123
|
+
: Math.max(0, characters.length - leftCount - (characters.length - rightStart));
|
|
124
|
+
const unit = policy.mode === "tokens" ? "tokens" : "chars";
|
|
125
|
+
return `${characters.slice(0, leftCount).join("")}…${removed} ${unit} truncated…${characters.slice(rightStart).join("")}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function truncateFunctionCallOutputs(
|
|
129
|
+
input: InputItem[] | undefined,
|
|
130
|
+
policy: Model<Api>["truncationPolicy"],
|
|
131
|
+
): InputItem[] | undefined {
|
|
132
|
+
if (!input || !policy) return input;
|
|
133
|
+
return input.map(item => {
|
|
134
|
+
if (item.type !== "function_call_output" || typeof item.output !== "string") return item;
|
|
135
|
+
return { ...item, output: truncateMiddle(item.output, policy) };
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
61
139
|
function filterInput(input: InputItem[] | undefined): InputItem[] | undefined {
|
|
62
140
|
if (!Array.isArray(input)) return input;
|
|
63
141
|
|
|
@@ -80,6 +158,8 @@ export async function transformRequestBody(
|
|
|
80
158
|
): Promise<RequestBody> {
|
|
81
159
|
body.store = false;
|
|
82
160
|
body.stream = true;
|
|
161
|
+
body.tool_choice = "auto";
|
|
162
|
+
body.parallel_tool_calls = model.supportsParallelToolCalls ?? false;
|
|
83
163
|
// The ChatGPT Codex backend rejects sampling controls with `Unsupported parameter`.
|
|
84
164
|
// Generic stream options may supply defaults, so remove them at the provider boundary.
|
|
85
165
|
delete body.temperature;
|
|
@@ -90,7 +170,7 @@ export async function transformRequestBody(
|
|
|
90
170
|
delete body.repetition_penalty;
|
|
91
171
|
|
|
92
172
|
if (body.input && Array.isArray(body.input)) {
|
|
93
|
-
body.input = filterInput(body.input);
|
|
173
|
+
body.input = truncateFunctionCallOutputs(filterInput(body.input), model.truncationPolicy);
|
|
94
174
|
|
|
95
175
|
if (body.input) {
|
|
96
176
|
const functionCallIds = new Set(
|
|
@@ -139,10 +219,11 @@ export async function transformRequestBody(
|
|
|
139
219
|
body.input = [...developerMessages, ...body.input];
|
|
140
220
|
}
|
|
141
221
|
|
|
142
|
-
if (options.reasoningEffort !== undefined) {
|
|
222
|
+
if (model.reasoning && (model.thinking || options.reasoningEffort !== undefined)) {
|
|
143
223
|
const reasoningConfig = getReasoningConfig(model, options);
|
|
224
|
+
const { summary: _summary, ...existingReasoning } = body.reasoning ?? {};
|
|
144
225
|
body.reasoning = {
|
|
145
|
-
...
|
|
226
|
+
...existingReasoning,
|
|
146
227
|
...reasoningConfig,
|
|
147
228
|
};
|
|
148
229
|
} else {
|
|
@@ -151,9 +232,20 @@ export async function transformRequestBody(
|
|
|
151
232
|
|
|
152
233
|
body.text = {
|
|
153
234
|
...body.text,
|
|
154
|
-
verbosity: options.textVerbosity
|
|
235
|
+
verbosity: options.textVerbosity ?? model.defaultVerbosity ?? "low",
|
|
155
236
|
};
|
|
156
237
|
|
|
238
|
+
const requestedServiceTier = body.service_tier ?? model.defaultServiceTier;
|
|
239
|
+
if (requestedServiceTier) {
|
|
240
|
+
if (model.serviceTiers && !model.serviceTiers.includes(requestedServiceTier as never)) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Service tier "${requestedServiceTier}" is unavailable for ${model.provider}/${model.id}. Supported tiers: ${model.serviceTiers.join(", ")}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
body.service_tier = requestedServiceTier;
|
|
246
|
+
}
|
|
247
|
+
body.client_metadata = normalizeClientMetadata(options.metadata);
|
|
248
|
+
|
|
157
249
|
const include = Array.isArray(options.include) ? [...options.include] : [];
|
|
158
250
|
include.push("reasoning.encrypted_content");
|
|
159
251
|
body.include = Array.from(new Set(include));
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
ResponseReasoningItem,
|
|
20
20
|
} from "openai/resources/responses/responses";
|
|
21
21
|
import packageJson from "../../package.json" with { type: "json" };
|
|
22
|
+
import type { ReasoningEffort } from "../model-thinking";
|
|
22
23
|
import { calculateCost } from "../models";
|
|
23
24
|
import { isUsageLimitError } from "../rate-limit-utils";
|
|
24
25
|
import { getEnvApiKey } from "../stream";
|
|
@@ -72,8 +73,8 @@ import {
|
|
|
72
73
|
import { transformMessages } from "./transform-messages";
|
|
73
74
|
|
|
74
75
|
export interface OpenAICodexResponsesOptions extends StreamOptions {
|
|
75
|
-
reasoning?:
|
|
76
|
-
reasoningSummary?: "auto" | "concise" | "detailed" | null;
|
|
76
|
+
reasoning?: ReasoningEffort;
|
|
77
|
+
reasoningSummary?: "none" | "auto" | "concise" | "detailed" | null;
|
|
77
78
|
textVerbosity?: "low" | "medium" | "high";
|
|
78
79
|
include?: string[];
|
|
79
80
|
codexMode?: boolean;
|
|
@@ -527,9 +528,10 @@ async function buildTransformedCodexRequestBody(
|
|
|
527
528
|
|
|
528
529
|
const codexOptions: CodexRequestOptions = {
|
|
529
530
|
reasoningEffort: options?.reasoning,
|
|
530
|
-
reasoningSummary: options?.reasoningSummary
|
|
531
|
+
reasoningSummary: options?.reasoningSummary,
|
|
531
532
|
textVerbosity: options?.textVerbosity,
|
|
532
533
|
include: options?.include,
|
|
534
|
+
metadata: options?.metadata,
|
|
533
535
|
};
|
|
534
536
|
|
|
535
537
|
return transformRequestBody(params, model, codexOptions, systemPrompt);
|
|
@@ -5,6 +5,14 @@ import type {
|
|
|
5
5
|
ResponseCreateParamsStreaming,
|
|
6
6
|
ResponseInput,
|
|
7
7
|
} from "openai/resources/responses/responses";
|
|
8
|
+
import {
|
|
9
|
+
type ReasoningSummary,
|
|
10
|
+
resolveCodexWireReasoningEffort,
|
|
11
|
+
resolveWireReasoningSummary,
|
|
12
|
+
type WireReasoningSummary,
|
|
13
|
+
} from "../codex-model-interaction";
|
|
14
|
+
import type { ReasoningEffort } from "../model-thinking";
|
|
15
|
+
import { requireSupportedReasoningEffort } from "../model-thinking";
|
|
8
16
|
import { getEnvApiKey } from "../stream";
|
|
9
17
|
import {
|
|
10
18
|
type Api,
|
|
@@ -47,6 +55,7 @@ import {
|
|
|
47
55
|
hasCopilotVisionInput,
|
|
48
56
|
resolveGitHubCopilotBaseUrl,
|
|
49
57
|
} from "./github-copilot-headers";
|
|
58
|
+
import { normalizeClientMetadata, truncateFunctionCallOutputs } from "./openai-codex/request-transformer";
|
|
50
59
|
import {
|
|
51
60
|
appendResponsesToolResultMessages,
|
|
52
61
|
collectKnownCallIds,
|
|
@@ -73,8 +82,9 @@ function getPromptCacheRetention(baseUrl: string, cacheRetention: CacheRetention
|
|
|
73
82
|
|
|
74
83
|
// OpenAI Responses-specific options
|
|
75
84
|
export interface OpenAIResponsesOptions extends StreamOptions {
|
|
76
|
-
reasoning?:
|
|
77
|
-
reasoningSummary?:
|
|
85
|
+
reasoning?: ReasoningEffort;
|
|
86
|
+
reasoningSummary?: ReasoningSummary | null;
|
|
87
|
+
textVerbosity?: "low" | "medium" | "high";
|
|
78
88
|
serviceTier?: ServiceTier;
|
|
79
89
|
toolChoice?: ToolChoice;
|
|
80
90
|
/**
|
|
@@ -125,12 +135,17 @@ function canReplayOpenAIResponsesNativeHistory(
|
|
|
125
135
|
return providerSessionState?.nativeHistoryReplayWarmed ?? true;
|
|
126
136
|
}
|
|
127
137
|
|
|
128
|
-
type OpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
|
|
138
|
+
type OpenAIResponsesSamplingParams = Omit<ResponseCreateParamsStreaming, "reasoning"> & {
|
|
139
|
+
reasoning?: {
|
|
140
|
+
effort?: Exclude<ReasoningEffort, "ultra">;
|
|
141
|
+
summary?: WireReasoningSummary;
|
|
142
|
+
};
|
|
129
143
|
top_p?: number;
|
|
130
144
|
top_k?: number;
|
|
131
145
|
min_p?: number;
|
|
132
146
|
presence_penalty?: number;
|
|
133
147
|
repetition_penalty?: number;
|
|
148
|
+
client_metadata?: Record<string, string>;
|
|
134
149
|
};
|
|
135
150
|
|
|
136
151
|
/**
|
|
@@ -193,7 +208,10 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
193
208
|
url: `${baseUrl ?? "https://api.openai.com/v1"}/responses`,
|
|
194
209
|
body: params,
|
|
195
210
|
};
|
|
196
|
-
|
|
211
|
+
// The pinned SDK schema predates the provider's `max` effort value.
|
|
212
|
+
const openaiStream = await client.responses.create(params as ResponseCreateParamsStreaming, {
|
|
213
|
+
signal: requestSignal,
|
|
214
|
+
});
|
|
197
215
|
const firstEventWatchdog = createFirstEventWatchdog(
|
|
198
216
|
options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs),
|
|
199
217
|
() => abortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
@@ -325,7 +343,7 @@ function buildParams(
|
|
|
325
343
|
strictResponsesPairing,
|
|
326
344
|
providerSessionState,
|
|
327
345
|
);
|
|
328
|
-
|
|
346
|
+
let messages: ResponseInput = [...conversationMessages];
|
|
329
347
|
|
|
330
348
|
if (context.systemPrompt) {
|
|
331
349
|
const role = model.reasoning && supportsDeveloperRole(resolvedBaseUrl ?? model) ? "developer" : "system";
|
|
@@ -337,6 +355,7 @@ function buildParams(
|
|
|
337
355
|
|
|
338
356
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
339
357
|
const promptCacheKey = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
358
|
+
const hasCodexInteractionDefaults = model.defaultReasoningSummary !== undefined;
|
|
340
359
|
const params: OpenAIResponsesSamplingParams = {
|
|
341
360
|
model: model.id,
|
|
342
361
|
input: messages,
|
|
@@ -346,6 +365,14 @@ function buildParams(
|
|
|
346
365
|
store: false,
|
|
347
366
|
};
|
|
348
367
|
|
|
368
|
+
if (model.truncationPolicy) {
|
|
369
|
+
messages = truncateFunctionCallOutputs(
|
|
370
|
+
messages as unknown as import("./openai-codex/request-transformer").InputItem[],
|
|
371
|
+
model.truncationPolicy,
|
|
372
|
+
) as unknown as ResponseInput;
|
|
373
|
+
params.input = messages;
|
|
374
|
+
}
|
|
375
|
+
|
|
349
376
|
if (options?.maxTokens) {
|
|
350
377
|
params.max_output_tokens = options?.maxTokens;
|
|
351
378
|
}
|
|
@@ -369,16 +396,28 @@ function buildParams(
|
|
|
369
396
|
if (options?.repetitionPenalty !== undefined) {
|
|
370
397
|
params.repetition_penalty = options.repetitionPenalty;
|
|
371
398
|
}
|
|
372
|
-
|
|
373
|
-
|
|
399
|
+
const requestedServiceTier = options?.serviceTier ?? model.defaultServiceTier;
|
|
400
|
+
if (requestedServiceTier && model.serviceTiers) {
|
|
401
|
+
if (!model.serviceTiers.includes(requestedServiceTier)) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`Service tier "${requestedServiceTier}" is unavailable for ${model.provider}/${model.id}. Supported tiers: ${model.serviceTiers.join(", ")}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
params.service_tier = requestedServiceTier;
|
|
407
|
+
} else if (isSpecialServiceTier(requestedServiceTier)) {
|
|
408
|
+
params.service_tier = requestedServiceTier;
|
|
374
409
|
}
|
|
375
410
|
|
|
376
411
|
if (context.tools) {
|
|
377
412
|
params.tools = convertTools(context.tools, supportsStrictMode(model));
|
|
378
|
-
if (options?.toolChoice) {
|
|
413
|
+
if (!hasCodexInteractionDefaults && options?.toolChoice) {
|
|
379
414
|
params.tool_choice = mapToOpenAIResponsesToolChoice(options.toolChoice);
|
|
380
415
|
}
|
|
381
416
|
}
|
|
417
|
+
if (hasCodexInteractionDefaults) params.tool_choice = "auto";
|
|
418
|
+
if (model.supportsParallelToolCalls !== undefined) {
|
|
419
|
+
params.parallel_tool_calls = model.supportsParallelToolCalls;
|
|
420
|
+
}
|
|
382
421
|
|
|
383
422
|
if (model.reasoning) {
|
|
384
423
|
// Always request encrypted reasoning content so reasoning items can be
|
|
@@ -387,10 +426,19 @@ function buildParams(
|
|
|
387
426
|
// See: https://github.com/f5-sales-demo/xcsh/issues/41
|
|
388
427
|
params.include = ["reasoning.encrypted_content"];
|
|
389
428
|
|
|
390
|
-
if (options?.reasoning || options?.reasoningSummary) {
|
|
429
|
+
if (options?.reasoning || options?.reasoningSummary || hasCodexInteractionDefaults) {
|
|
430
|
+
const requestedEffort = (options?.reasoning ??
|
|
431
|
+
(hasCodexInteractionDefaults ? model.thinking?.defaultLevel : undefined) ??
|
|
432
|
+
"medium") as ReasoningEffort;
|
|
433
|
+
const supportedEffort = model.thinking
|
|
434
|
+
? requireSupportedReasoningEffort(model, requestedEffort)
|
|
435
|
+
: requestedEffort;
|
|
436
|
+
const summary = resolveWireReasoningSummary(
|
|
437
|
+
options?.reasoningSummary ?? model.defaultReasoningSummary ?? "auto",
|
|
438
|
+
);
|
|
391
439
|
params.reasoning = {
|
|
392
|
-
effort:
|
|
393
|
-
summary
|
|
440
|
+
effort: resolveCodexWireReasoningEffort(model.id, supportedEffort) as Exclude<ReasoningEffort, "ultra">,
|
|
441
|
+
...(summary ? { summary } : {}),
|
|
394
442
|
};
|
|
395
443
|
} else if (model.name.startsWith("gpt-5")) {
|
|
396
444
|
// Jesus Christ, see https://community.openai.com/t/need-reasoning-false-option-for-gpt-5/1351588/7
|
|
@@ -406,6 +454,13 @@ function buildParams(
|
|
|
406
454
|
}
|
|
407
455
|
}
|
|
408
456
|
|
|
457
|
+
if (options?.textVerbosity || model.defaultVerbosity) {
|
|
458
|
+
params.text = { verbosity: options?.textVerbosity ?? model.defaultVerbosity ?? "low" };
|
|
459
|
+
}
|
|
460
|
+
if (hasCodexInteractionDefaults) {
|
|
461
|
+
params.client_metadata = normalizeClientMetadata(options?.metadata);
|
|
462
|
+
}
|
|
463
|
+
|
|
409
464
|
return { conversationMessages, params };
|
|
410
465
|
}
|
|
411
466
|
|
package/src/stream.ts
CHANGED
|
@@ -293,6 +293,7 @@ export const ANTHROPIC_THINKING: Record<Effort, number> = {
|
|
|
293
293
|
high: 16384,
|
|
294
294
|
xhigh: 32768,
|
|
295
295
|
max: 65536,
|
|
296
|
+
ultra: 65536,
|
|
296
297
|
};
|
|
297
298
|
|
|
298
299
|
const GOOGLE_THINKING: Record<Effort, number> = {
|
|
@@ -303,6 +304,7 @@ const GOOGLE_THINKING: Record<Effort, number> = {
|
|
|
303
304
|
xhigh: 24575,
|
|
304
305
|
// Google caps the thinking budget here; `max` cannot exceed it.
|
|
305
306
|
max: 24575,
|
|
307
|
+
ultra: 24575,
|
|
306
308
|
};
|
|
307
309
|
|
|
308
310
|
const BEDROCK_CLAUDE_THINKING: Record<Effort, number> = {
|
|
@@ -313,6 +315,7 @@ const BEDROCK_CLAUDE_THINKING: Record<Effort, number> = {
|
|
|
313
315
|
xhigh: 16384,
|
|
314
316
|
// Bedrock Claude caps the thinking budget here; `max` cannot exceed it.
|
|
315
317
|
max: 16384,
|
|
318
|
+
ultra: 16384,
|
|
316
319
|
};
|
|
317
320
|
|
|
318
321
|
function resolveBedrockThinkingBudget(
|
|
@@ -536,7 +539,10 @@ export function mapOptionsForApi<TApi extends Api>(
|
|
|
536
539
|
case "openai-responses":
|
|
537
540
|
return castApi<"openai-responses">({
|
|
538
541
|
...base,
|
|
539
|
-
reasoning:
|
|
542
|
+
reasoning:
|
|
543
|
+
model.defaultReasoningSummary !== undefined
|
|
544
|
+
? resolveCodexReasoningEffort(model, options)
|
|
545
|
+
: resolveOpenAiReasoningEffort(model, options),
|
|
540
546
|
toolChoice: mapOpenAiToolChoice(options?.toolChoice),
|
|
541
547
|
serviceTier: options?.serviceTier,
|
|
542
548
|
});
|
package/src/types.ts
CHANGED
|
@@ -620,6 +620,24 @@ export interface Model<TApi extends Api = any> {
|
|
|
620
620
|
premiumMultiplier?: number;
|
|
621
621
|
contextWindow: number;
|
|
622
622
|
maxTokens: number;
|
|
623
|
+
/** Largest context accepted by the Codex interaction contract. */
|
|
624
|
+
maxContextWindow?: number;
|
|
625
|
+
/** Live provider-advertised input limit, retained independently from the selected tier. */
|
|
626
|
+
providerContextWindow?: number;
|
|
627
|
+
/** Percentage of the selected context window available to request input. */
|
|
628
|
+
effectiveContextWindowPercent?: number;
|
|
629
|
+
/** Resolved usable input budget for the selected context tier. */
|
|
630
|
+
effectiveContextWindow?: number;
|
|
631
|
+
/** Percentage of the selected context window that triggers automatic compaction. */
|
|
632
|
+
autoCompactThresholdPercent?: number;
|
|
633
|
+
/** Resolved automatic-compaction threshold for the selected context tier. */
|
|
634
|
+
autoCompactTokenLimit?: number;
|
|
635
|
+
defaultReasoningSummary?: "none" | "auto" | "concise" | "detailed";
|
|
636
|
+
defaultVerbosity?: "low" | "medium" | "high";
|
|
637
|
+
serviceTiers?: ServiceTier[];
|
|
638
|
+
defaultServiceTier?: ServiceTier;
|
|
639
|
+
truncationPolicy?: { mode: "tokens" | "bytes"; limit: number };
|
|
640
|
+
supportsParallelToolCalls?: boolean;
|
|
623
641
|
headers?: Record<string, string>;
|
|
624
642
|
/**
|
|
625
643
|
* Extra anthropic-beta feature flags to send for this model (anthropic-messages API only).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { applyCodexInteractionMetadata } from "../../codex-model-interaction";
|
|
2
3
|
import { REASONING_EFFORTS, type ReasoningEffort } from "../../model-thinking";
|
|
3
4
|
import { CODEX_BASE_URL, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../../providers/openai-codex/constants";
|
|
4
5
|
import type { Model, ThinkingConfig } from "../../types";
|
|
@@ -28,6 +29,15 @@ const codexModelEntrySchema = z
|
|
|
28
29
|
family: z.unknown().optional(),
|
|
29
30
|
tier: z.unknown().optional(),
|
|
30
31
|
context_window: z.unknown().optional(),
|
|
32
|
+
max_context_window: z.unknown().optional(),
|
|
33
|
+
auto_compact_token_limit: z.unknown().optional(),
|
|
34
|
+
effective_context_window_percent: z.unknown().optional(),
|
|
35
|
+
default_reasoning_summary: z.unknown().optional(),
|
|
36
|
+
default_verbosity: z.unknown().optional(),
|
|
37
|
+
service_tiers: z.unknown().optional(),
|
|
38
|
+
default_service_tier: z.unknown().optional(),
|
|
39
|
+
truncation_policy: z.unknown().optional(),
|
|
40
|
+
supports_parallel_tool_calls: z.unknown().optional(),
|
|
31
41
|
default_reasoning_level: z.unknown().optional(),
|
|
32
42
|
supported_reasoning_levels: z.unknown().optional(),
|
|
33
43
|
input_modalities: z.unknown().optional(),
|
|
@@ -266,6 +276,7 @@ function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCo
|
|
|
266
276
|
|
|
267
277
|
const name = toNonEmptyString(payload.display_name) ?? slug;
|
|
268
278
|
const contextWindow = toPositiveInt(payload.context_window) ?? DEFAULT_CONTEXT_WINDOW;
|
|
279
|
+
const providerContextWindow = toPositiveInt(payload.max_context_window) ?? contextWindow;
|
|
269
280
|
const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
|
|
270
281
|
const thinking = normalizeReasoningConfig(payload.default_reasoning_level, payload.supported_reasoning_levels);
|
|
271
282
|
const reasoning = Boolean(thinking?.supportedLevels.some(level => level.effort !== "none"));
|
|
@@ -277,24 +288,28 @@ function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCo
|
|
|
277
288
|
|
|
278
289
|
return {
|
|
279
290
|
priority,
|
|
280
|
-
model:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
model: applyCodexInteractionMetadata(
|
|
292
|
+
{
|
|
293
|
+
id: slug,
|
|
294
|
+
name,
|
|
295
|
+
api: "openai-codex-responses",
|
|
296
|
+
provider: "openai-codex",
|
|
297
|
+
baseUrl,
|
|
298
|
+
reasoning,
|
|
299
|
+
input,
|
|
300
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
301
|
+
contextWindow,
|
|
302
|
+
maxTokens,
|
|
303
|
+
providerContextWindow,
|
|
304
|
+
...(toNonEmptyString(payload.description) ? { description: toNonEmptyString(payload.description)! } : {}),
|
|
305
|
+
...(visibility ? { visibility } : {}),
|
|
306
|
+
...presentation,
|
|
307
|
+
...(thinking ? { thinking } : {}),
|
|
308
|
+
...(preferWebsockets ? { preferWebsockets: true } : {}),
|
|
309
|
+
...(priority !== Number.MAX_SAFE_INTEGER ? { priority } : {}),
|
|
310
|
+
},
|
|
311
|
+
providerContextWindow,
|
|
312
|
+
),
|
|
298
313
|
};
|
|
299
314
|
}
|
|
300
315
|
|