@oh-my-pi/pi-ai 3.20.1 → 3.34.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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * OpenAI Codex utilities - exported for use by coding-agent export
3
+ */
4
+
5
+ export { type CacheMetadata, getCodexInstructions, getModelFamily, type ModelFamily } from "./prompts/codex";
6
+ export { buildCodexPiBridge } from "./prompts/pi-codex-bridge";
7
+ export { buildCodexSystemPrompt, type CodexSystemPrompt } from "./prompts/system-prompt";
@@ -1,6 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
+
5
+ // Bun text embed for fallback instructions
4
6
  import FALLBACK_INSTRUCTIONS from "./codex-instructions.md" with { type: "text" };
5
7
 
6
8
  const GITHUB_API_RELEASES = "https://api.github.com/repos/openai/codex/releases/latest";
@@ -41,17 +43,17 @@ export type CacheMetadata = {
41
43
  url: string;
42
44
  };
43
45
 
44
- export function getModelFamily(normalizedModel: string): ModelFamily {
45
- if (normalizedModel.includes("gpt-5.2-codex") || normalizedModel.includes("gpt 5.2 codex")) {
46
+ export function getModelFamily(model: string): ModelFamily {
47
+ if (model.includes("gpt-5.2-codex") || model.includes("gpt 5.2 codex")) {
46
48
  return "gpt-5.2-codex";
47
49
  }
48
- if (normalizedModel.includes("codex-max")) {
50
+ if (model.includes("codex-max")) {
49
51
  return "codex-max";
50
52
  }
51
- if (normalizedModel.includes("codex") || normalizedModel.startsWith("codex-")) {
53
+ if (model.includes("codex") || model.startsWith("codex-")) {
52
54
  return "codex";
53
55
  }
54
- if (normalizedModel.includes("gpt-5.2")) {
56
+ if (model.includes("gpt-5.2")) {
55
57
  return "gpt-5.2";
56
58
  }
57
59
  return "gpt-5.1";
@@ -93,8 +95,8 @@ async function getLatestReleaseTag(): Promise<string> {
93
95
  throw new Error("Failed to determine latest release tag from GitHub");
94
96
  }
95
97
 
96
- export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): Promise<string> {
97
- const modelFamily = getModelFamily(normalizedModel);
98
+ export async function getCodexInstructions(model = "gpt-5.1-codex"): Promise<string> {
99
+ const modelFamily = getModelFamily(model);
98
100
  const promptFile = PROMPT_FILES[modelFamily];
99
101
  const cacheDir = getCacheDir();
100
102
  const cacheFile = join(cacheDir, CACHE_FILES[modelFamily]);
@@ -106,15 +108,15 @@ export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): P
106
108
  let cachedTimestamp: number | null = null;
107
109
 
108
110
  if (existsSync(cacheMetaFile)) {
109
- const metadata = JSON.parse(readFileSync(cacheMetaFile, "utf-8")) as CacheMetadata;
111
+ const metadata = JSON.parse(readFileSync(cacheMetaFile, "utf8")) as CacheMetadata;
110
112
  cachedETag = metadata.etag;
111
113
  cachedTag = metadata.tag;
112
114
  cachedTimestamp = metadata.lastChecked;
113
115
  }
114
116
 
115
- const CACHE_TTL_MS = 15 * 60 * 1000;
117
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
116
118
  if (cachedTimestamp && Date.now() - cachedTimestamp < CACHE_TTL_MS && existsSync(cacheFile)) {
117
- return readFileSync(cacheFile, "utf-8");
119
+ return readFileSync(cacheFile, "utf8");
118
120
  }
119
121
 
120
122
  const latestTag = await getLatestReleaseTag();
@@ -133,7 +135,7 @@ export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): P
133
135
 
134
136
  if (response.status === 304) {
135
137
  if (existsSync(cacheFile)) {
136
- return readFileSync(cacheFile, "utf-8");
138
+ return readFileSync(cacheFile, "utf8");
137
139
  }
138
140
  }
139
141
 
@@ -141,8 +143,11 @@ export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): P
141
143
  const instructions = await response.text();
142
144
  const newETag = response.headers.get("etag");
143
145
 
144
- mkdirSync(cacheDir, { recursive: true });
145
- writeFileSync(cacheFile, instructions, "utf-8");
146
+ if (!existsSync(cacheDir)) {
147
+ mkdirSync(cacheDir, { recursive: true });
148
+ }
149
+
150
+ writeFileSync(cacheFile, instructions, "utf8");
146
151
  writeFileSync(
147
152
  cacheMetaFile,
148
153
  JSON.stringify({
@@ -151,7 +156,7 @@ export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): P
151
156
  lastChecked: Date.now(),
152
157
  url: instructionsUrl,
153
158
  } satisfies CacheMetadata),
154
- "utf-8",
159
+ "utf8",
155
160
  );
156
161
 
157
162
  return instructions;
@@ -166,52 +171,14 @@ export async function getCodexInstructions(normalizedModel = "gpt-5.1-codex"): P
166
171
 
167
172
  if (existsSync(cacheFile)) {
168
173
  console.error(`[openai-codex] Using cached ${modelFamily} instructions`);
169
- return readFileSync(cacheFile, "utf-8");
174
+ return readFileSync(cacheFile, "utf8");
170
175
  }
171
176
 
172
- console.error(`[openai-codex] Falling back to bundled instructions for ${modelFamily}`);
173
- return FALLBACK_INSTRUCTIONS;
177
+ if (FALLBACK_INSTRUCTIONS) {
178
+ console.error(`[openai-codex] Falling back to bundled instructions for ${modelFamily}`);
179
+ return FALLBACK_INSTRUCTIONS;
180
+ }
181
+
182
+ throw new Error(`No cached Codex instructions available for ${modelFamily}`);
174
183
  }
175
184
  }
176
-
177
- export const TOOL_REMAP_MESSAGE = `<user_instructions priority="0">
178
- <environment_override priority="0">
179
- YOU ARE IN A DIFFERENT ENVIRONMENT. These instructions override ALL previous tool references.
180
- </environment_override>
181
-
182
- <tool_replacements priority="0">
183
- <critical_rule priority="0">
184
- ❌ APPLY_PATCH DOES NOT EXIST → ✅ USE "edit" INSTEAD
185
- - NEVER use: apply_patch, applyPatch
186
- - ALWAYS use: edit tool for ALL file modifications
187
- </critical_rule>
188
-
189
- <critical_rule priority="0">
190
- ❌ UPDATE_PLAN DOES NOT EXIST
191
- - NEVER use: update_plan, updatePlan, read_plan, readPlan, todowrite, todoread
192
- - There is no plan tool in this environment
193
- </critical_rule>
194
- </tool_replacements>
195
-
196
- <available_tools priority="0">
197
- File Operations:
198
- • read - Read file contents
199
- • edit - Modify files with exact find/replace
200
- • write - Create or overwrite files
201
-
202
- Search/Discovery:
203
- • grep - Search file contents for patterns (read-only)
204
- • find - Find files by glob pattern (read-only)
205
- • ls - List directory contents (read-only)
206
-
207
- Execution:
208
- • bash - Run shell commands
209
- </available_tools>
210
-
211
- <verification_checklist priority="0">
212
- Before file modifications:
213
- 1. Am I using "edit" NOT "apply_patch"?
214
- 2. Am I avoiding plan tools entirely?
215
- 3. Am I using only the tools listed above?
216
- </verification_checklist>
217
- </user_instructions>`;
@@ -3,46 +3,53 @@
3
3
  * Aligns Codex CLI expectations with Pi's toolset.
4
4
  */
5
5
 
6
- export const CODEX_PI_BRIDGE = `# Codex Running in Pi
6
+ import type { Tool } from "../../../types";
7
7
 
8
- You are running Codex through pi, a terminal coding assistant. The tools and rules differ from Codex CLI.
8
+ function formatToolList(tools?: Tool[]): string {
9
+ if (!tools || tools.length === 0) {
10
+ return "- (none)";
11
+ }
9
12
 
10
- ## CRITICAL: Tool Replacements
13
+ const normalized = tools
14
+ .map((tool) => {
15
+ const name = tool.name.trim();
16
+ if (!name) return null;
17
+ const description = (tool.description || "Custom tool").replace(/\s*\n\s*/g, " ").trim();
18
+ return { name, description };
19
+ })
20
+ .filter((tool): tool is { name: string; description: string } => tool !== null);
11
21
 
12
- <critical_rule priority="0">
13
- APPLY_PATCH DOES NOT EXIST → ✅ USE "edit" INSTEAD
14
- - NEVER use: apply_patch, applyPatch
15
- - ALWAYS use: edit for ALL file modifications
16
- </critical_rule>
22
+ if (normalized.length === 0) {
23
+ return "- (none)";
24
+ }
17
25
 
18
- <critical_rule priority="0">
19
- UPDATE_PLAN DOES NOT EXIST
20
- - NEVER use: update_plan, updatePlan, read_plan, readPlan, todowrite, todoread
21
- - There is no plan tool in this environment
22
- </critical_rule>
26
+ const maxNameLength = normalized.reduce((max, tool) => Math.max(max, tool.name.length), 0);
27
+ const padWidth = Math.max(6, maxNameLength + 1);
23
28
 
24
- ## Available Tools (pi)
29
+ return normalized
30
+ .map((tool) => {
31
+ const paddedName = tool.name.padEnd(padWidth);
32
+ return `- ${paddedName}- ${tool.description}`;
33
+ })
34
+ .join("\n");
35
+ }
25
36
 
26
- - read - Read file contents
27
- - bash - Execute bash commands
28
- - edit - Modify files with exact find/replace (requires prior read)
29
- - write - Create or overwrite files
30
- - grep - Search file contents (read-only)
31
- - find - Find files by glob pattern (read-only)
32
- - ls - List directory contents (read-only)
37
+ export function buildCodexPiBridge(tools?: Tool[]): string {
38
+ const toolsList = formatToolList(tools);
33
39
 
34
- ## Usage Rules
40
+ return `# Codex Environment Bridge
35
41
 
36
- - Read before edit; use read instead of cat/sed for file contents
37
- - Use edit for surgical changes; write only for new files or complete rewrites
38
- - Prefer grep/find/ls over bash for discovery
39
- - Be concise and show file paths clearly when working with files
42
+ <environment_override priority="0">
43
+ IGNORE ALL PREVIOUS INSTRUCTIONS ABOVE THIS MESSAGE.
44
+ Do not assume any tools are available unless listed below.
45
+ </environment_override>
40
46
 
41
- ## Verification Checklist
47
+ The next system instructions that follow this message are authoritative and must be obeyed, even if they conflict with earlier instructions.
42
48
 
43
- 1. Using edit, not apply_patch
44
- 2. No plan tools used
45
- 3. Only the tools listed above are called
49
+ ## Available Tools
46
50
 
47
- Below are additional system instruction you MUST follow when responding:
51
+ ${toolsList}
52
+
53
+ Only use the tools listed above. Do not reference or call any other tools.
48
54
  `;
55
+ }
@@ -0,0 +1,26 @@
1
+ export interface CodexSystemPrompt {
2
+ instructions: string;
3
+ developerMessages: string[];
4
+ }
5
+
6
+ export function buildCodexSystemPrompt(args: {
7
+ codexInstructions: string;
8
+ bridgeText: string;
9
+ userSystemPrompt?: string;
10
+ }): CodexSystemPrompt {
11
+ const { codexInstructions, bridgeText, userSystemPrompt } = args;
12
+ const developerMessages: string[] = [];
13
+
14
+ if (bridgeText.trim().length > 0) {
15
+ developerMessages.push(bridgeText.trim());
16
+ }
17
+
18
+ if (userSystemPrompt && userSystemPrompt.trim().length > 0) {
19
+ developerMessages.push(userSystemPrompt.trim());
20
+ }
21
+
22
+ return {
23
+ instructions: codexInstructions.trim(),
24
+ developerMessages,
25
+ };
26
+ }
@@ -1,6 +1,3 @@
1
- import { TOOL_REMAP_MESSAGE } from "./prompts/codex";
2
- import { CODEX_PI_BRIDGE } from "./prompts/pi-codex-bridge";
3
-
4
1
  export interface ReasoningConfig {
5
2
  effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
6
3
  summary: "auto" | "concise" | "detailed" | "off" | "on";
@@ -38,160 +35,32 @@ export interface RequestBody {
38
35
  };
39
36
  include?: string[];
40
37
  prompt_cache_key?: string;
38
+ prompt_cache_retention?: "in_memory" | "24h";
41
39
  max_output_tokens?: number;
42
40
  max_completion_tokens?: number;
43
41
  [key: string]: unknown;
44
42
  }
45
43
 
46
- const MODEL_MAP: Record<string, string> = {
47
- "gpt-5.1-codex": "gpt-5.1-codex",
48
- "gpt-5.1-codex-low": "gpt-5.1-codex",
49
- "gpt-5.1-codex-medium": "gpt-5.1-codex",
50
- "gpt-5.1-codex-high": "gpt-5.1-codex",
51
- "gpt-5.1-codex-max": "gpt-5.1-codex-max",
52
- "gpt-5.1-codex-max-low": "gpt-5.1-codex-max",
53
- "gpt-5.1-codex-max-medium": "gpt-5.1-codex-max",
54
- "gpt-5.1-codex-max-high": "gpt-5.1-codex-max",
55
- "gpt-5.1-codex-max-xhigh": "gpt-5.1-codex-max",
56
- "gpt-5.2": "gpt-5.2",
57
- "gpt-5.2-none": "gpt-5.2",
58
- "gpt-5.2-low": "gpt-5.2",
59
- "gpt-5.2-medium": "gpt-5.2",
60
- "gpt-5.2-high": "gpt-5.2",
61
- "gpt-5.2-xhigh": "gpt-5.2",
62
- "gpt-5.2-codex": "gpt-5.2-codex",
63
- "gpt-5.2-codex-low": "gpt-5.2-codex",
64
- "gpt-5.2-codex-medium": "gpt-5.2-codex",
65
- "gpt-5.2-codex-high": "gpt-5.2-codex",
66
- "gpt-5.2-codex-xhigh": "gpt-5.2-codex",
67
- "gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
68
- "gpt-5.1-codex-mini-medium": "gpt-5.1-codex-mini",
69
- "gpt-5.1-codex-mini-high": "gpt-5.1-codex-mini",
70
- "gpt-5.1": "gpt-5.1",
71
- "gpt-5.1-none": "gpt-5.1",
72
- "gpt-5.1-low": "gpt-5.1",
73
- "gpt-5.1-medium": "gpt-5.1",
74
- "gpt-5.1-high": "gpt-5.1",
75
- "gpt-5.1-chat-latest": "gpt-5.1",
76
- "gpt-5-codex": "gpt-5.1-codex",
77
- "codex-mini-latest": "gpt-5.1-codex-mini",
78
- "gpt-5-codex-mini": "gpt-5.1-codex-mini",
79
- "gpt-5-codex-mini-medium": "gpt-5.1-codex-mini",
80
- "gpt-5-codex-mini-high": "gpt-5.1-codex-mini",
81
- "gpt-5": "gpt-5.1",
82
- "gpt-5-mini": "gpt-5.1",
83
- "gpt-5-nano": "gpt-5.1",
84
- };
85
-
86
- function getNormalizedModel(modelId: string): string | undefined {
87
- if (MODEL_MAP[modelId]) return MODEL_MAP[modelId];
88
- const lowerModelId = modelId.toLowerCase();
89
- const match = Object.keys(MODEL_MAP).find((key) => key.toLowerCase() === lowerModelId);
90
- return match ? MODEL_MAP[match] : undefined;
91
- }
92
-
93
- export function normalizeModel(model: string | undefined): string {
94
- if (!model) return "gpt-5.1";
95
-
44
+ function clampReasoningEffort(model: string, effort: ReasoningConfig["effort"]): ReasoningConfig["effort"] {
45
+ // Codex backend expects exact model IDs. Do not normalize model names here.
96
46
  const modelId = model.includes("/") ? model.split("/").pop()! : model;
97
- const mappedModel = getNormalizedModel(modelId);
98
- if (mappedModel) return mappedModel;
99
-
100
- const normalized = modelId.toLowerCase();
101
-
102
- if (normalized.includes("gpt-5.2-codex") || normalized.includes("gpt 5.2 codex")) {
103
- return "gpt-5.2-codex";
104
- }
105
- if (normalized.includes("gpt-5.2") || normalized.includes("gpt 5.2")) {
106
- return "gpt-5.2";
107
- }
108
- if (normalized.includes("gpt-5.1-codex-max") || normalized.includes("gpt 5.1 codex max")) {
109
- return "gpt-5.1-codex-max";
110
- }
111
- if (normalized.includes("gpt-5.1-codex-mini") || normalized.includes("gpt 5.1 codex mini")) {
112
- return "gpt-5.1-codex-mini";
113
- }
114
- if (
115
- normalized.includes("codex-mini-latest") ||
116
- normalized.includes("gpt-5-codex-mini") ||
117
- normalized.includes("gpt 5 codex mini")
118
- ) {
119
- return "codex-mini-latest";
120
- }
121
- if (normalized.includes("gpt-5.1-codex") || normalized.includes("gpt 5.1 codex")) {
122
- return "gpt-5.1-codex";
123
- }
124
- if (normalized.includes("gpt-5.1") || normalized.includes("gpt 5.1")) {
125
- return "gpt-5.1";
126
- }
127
- if (normalized.includes("codex")) {
128
- return "gpt-5.1-codex";
129
- }
130
- if (normalized.includes("gpt-5") || normalized.includes("gpt 5")) {
131
- return "gpt-5.1";
132
- }
133
-
134
- return "gpt-5.1";
135
- }
136
-
137
- function getReasoningConfig(modelName: string | undefined, options: CodexRequestOptions = {}): ReasoningConfig {
138
- const normalizedName = modelName?.toLowerCase() ?? "";
139
-
140
- const isGpt52Codex = normalizedName.includes("gpt-5.2-codex") || normalizedName.includes("gpt 5.2 codex");
141
- const isGpt52General = (normalizedName.includes("gpt-5.2") || normalizedName.includes("gpt 5.2")) && !isGpt52Codex;
142
- const isCodexMax = normalizedName.includes("codex-max") || normalizedName.includes("codex max");
143
- const isCodexMini =
144
- normalizedName.includes("codex-mini") ||
145
- normalizedName.includes("codex mini") ||
146
- normalizedName.includes("codex_mini") ||
147
- normalizedName.includes("codex-mini-latest");
148
- const isCodex = normalizedName.includes("codex") && !isCodexMini;
149
- const isLightweight = !isCodexMini && (normalizedName.includes("nano") || normalizedName.includes("mini"));
150
- const isGpt51General =
151
- (normalizedName.includes("gpt-5.1") || normalizedName.includes("gpt 5.1")) &&
152
- !isCodex &&
153
- !isCodexMax &&
154
- !isCodexMini;
155
-
156
- const supportsXhigh = isGpt52General || isGpt52Codex || isCodexMax;
157
- const supportsNone = isGpt52General || isGpt51General;
158
-
159
- const defaultEffort: ReasoningConfig["effort"] = isCodexMini
160
- ? "medium"
161
- : supportsXhigh
162
- ? "high"
163
- : isLightweight
164
- ? "minimal"
165
- : "medium";
166
-
167
- let effort = options.reasoningEffort || defaultEffort;
168
-
169
- if (isCodexMini) {
170
- if (effort === "minimal" || effort === "low" || effort === "none") {
171
- effort = "medium";
172
- }
173
- if (effort === "xhigh") {
174
- effort = "high";
175
- }
176
- if (effort !== "high" && effort !== "medium") {
177
- effort = "medium";
178
- }
179
- }
180
47
 
181
- if (!supportsXhigh && effort === "xhigh") {
182
- effort = "high";
48
+ // gpt-5.1 does not support xhigh.
49
+ if (modelId === "gpt-5.1" && effort === "xhigh") {
50
+ return "high";
183
51
  }
184
52
 
185
- if (!supportsNone && effort === "none") {
186
- effort = "low";
53
+ // gpt-5.1-codex-mini only supports medium/high.
54
+ if (modelId === "gpt-5.1-codex-mini") {
55
+ return effort === "high" || effort === "xhigh" ? "high" : "medium";
187
56
  }
188
57
 
189
- if (isCodex && effort === "minimal") {
190
- effort = "low";
191
- }
58
+ return effort;
59
+ }
192
60
 
61
+ function getReasoningConfig(model: string, options: CodexRequestOptions): ReasoningConfig {
193
62
  return {
194
- effort,
63
+ effort: clampReasoningEffort(model, options.reasoningEffort as ReasoningConfig["effort"]),
195
64
  summary: options.reasoningSummary ?? "auto",
196
65
  };
197
66
  }
@@ -210,69 +79,17 @@ function filterInput(input: InputItem[] | undefined): InputItem[] | undefined {
210
79
  });
211
80
  }
212
81
 
213
- function addCodexBridgeMessage(
214
- input: InputItem[] | undefined,
215
- hasTools: boolean,
216
- systemPrompt?: string,
217
- ): InputItem[] | undefined {
218
- if (!hasTools || !Array.isArray(input)) return input;
219
-
220
- const bridgeText = systemPrompt ? `${CODEX_PI_BRIDGE}\n\n${systemPrompt}` : CODEX_PI_BRIDGE;
221
-
222
- const bridgeMessage: InputItem = {
223
- type: "message",
224
- role: "developer",
225
- content: [
226
- {
227
- type: "input_text",
228
- text: bridgeText,
229
- },
230
- ],
231
- };
232
-
233
- return [bridgeMessage, ...input];
234
- }
235
-
236
- function addToolRemapMessage(input: InputItem[] | undefined, hasTools: boolean): InputItem[] | undefined {
237
- if (!hasTools || !Array.isArray(input)) return input;
238
-
239
- const toolRemapMessage: InputItem = {
240
- type: "message",
241
- role: "developer",
242
- content: [
243
- {
244
- type: "input_text",
245
- text: TOOL_REMAP_MESSAGE,
246
- },
247
- ],
248
- };
249
-
250
- return [toolRemapMessage, ...input];
251
- }
252
-
253
82
  export async function transformRequestBody(
254
83
  body: RequestBody,
255
- codexInstructions: string,
256
84
  options: CodexRequestOptions = {},
257
- codexMode = true,
258
- systemPrompt?: string,
85
+ prompt?: { instructions: string; developerMessages: string[] },
259
86
  ): Promise<RequestBody> {
260
- const normalizedModel = normalizeModel(body.model);
261
-
262
- body.model = normalizedModel;
263
87
  body.store = false;
264
88
  body.stream = true;
265
- body.instructions = codexInstructions;
266
89
 
267
90
  if (body.input && Array.isArray(body.input)) {
268
91
  body.input = filterInput(body.input);
269
92
 
270
- if (codexMode) {
271
- body.input = addCodexBridgeMessage(body.input, !!body.tools, systemPrompt);
272
- } else {
273
- body.input = addToolRemapMessage(body.input, !!body.tools);
274
- }
275
-
276
93
  if (body.input) {
277
94
  const functionCallIds = new Set(
278
95
  body.input
@@ -308,18 +125,36 @@ export async function transformRequestBody(
308
125
  }
309
126
  }
310
127
 
311
- const reasoningConfig = getReasoningConfig(normalizedModel, options);
312
- body.reasoning = {
313
- ...body.reasoning,
314
- ...reasoningConfig,
315
- };
128
+ if (prompt?.developerMessages && prompt.developerMessages.length > 0 && Array.isArray(body.input)) {
129
+ const developerMessages = prompt.developerMessages.map(
130
+ (text) =>
131
+ ({
132
+ type: "message",
133
+ role: "developer",
134
+ content: [{ type: "input_text", text }],
135
+ }) as InputItem,
136
+ );
137
+ body.input = [...developerMessages, ...body.input];
138
+ }
139
+
140
+ if (options.reasoningEffort !== undefined) {
141
+ const reasoningConfig = getReasoningConfig(body.model, options);
142
+ body.reasoning = {
143
+ ...body.reasoning,
144
+ ...reasoningConfig,
145
+ };
146
+ } else {
147
+ delete body.reasoning;
148
+ }
316
149
 
317
150
  body.text = {
318
151
  ...body.text,
319
152
  verbosity: options.textVerbosity || "medium",
320
153
  };
321
154
 
322
- body.include = options.include || ["reasoning.encrypted_content"];
155
+ const include = Array.isArray(options.include) ? [...options.include] : [];
156
+ include.push("reasoning.encrypted_content");
157
+ body.include = Array.from(new Set(include));
323
158
 
324
159
  delete body.max_output_tokens;
325
160
  delete body.max_completion_tokens;