@zhivex-ai/openai 0.16.2 → 0.17.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.
Files changed (42) hide show
  1. package/README.md +4 -2
  2. package/dist/image-generation.d.ts.map +1 -1
  3. package/dist/image-generation.js +2 -1
  4. package/dist/image-generation.js.map +1 -1
  5. package/dist/index.d.ts +26 -5
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +212 -30
  8. package/dist/index.js.map +1 -1
  9. package/dist/live-audio.d.ts +3 -2
  10. package/dist/live-audio.d.ts.map +1 -1
  11. package/dist/live-audio.js.map +1 -1
  12. package/dist/live-session.d.ts +7 -6
  13. package/dist/live-session.d.ts.map +1 -1
  14. package/dist/live-session.js +5 -3
  15. package/dist/live-session.js.map +1 -1
  16. package/dist/live-webrtc-server.d.ts +5 -3
  17. package/dist/live-webrtc-server.d.ts.map +1 -1
  18. package/dist/live-webrtc-server.js +2 -1
  19. package/dist/live-webrtc-server.js.map +1 -1
  20. package/dist/live.d.ts +3 -2
  21. package/dist/live.d.ts.map +1 -1
  22. package/dist/live.js.map +1 -1
  23. package/dist/manual-redirect.d.ts +9 -0
  24. package/dist/manual-redirect.d.ts.map +1 -0
  25. package/dist/manual-redirect.js +28 -0
  26. package/dist/manual-redirect.js.map +1 -0
  27. package/dist/model-profiles.d.ts.map +1 -1
  28. package/dist/model-profiles.js +5 -2
  29. package/dist/model-profiles.js.map +1 -1
  30. package/dist/realtime.d.ts +4 -3
  31. package/dist/realtime.d.ts.map +1 -1
  32. package/dist/realtime.js +13 -2
  33. package/dist/realtime.js.map +1 -1
  34. package/dist/resource-models.d.ts +2 -1
  35. package/dist/resource-models.d.ts.map +1 -1
  36. package/dist/resource-models.js +2 -1
  37. package/dist/resource-models.js.map +1 -1
  38. package/dist/voice.d.ts +34 -0
  39. package/dist/voice.d.ts.map +1 -0
  40. package/dist/voice.js +29 -0
  41. package/dist/voice.js.map +1 -0
  42. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,17 +1,90 @@
1
+ import { withManualRedirect } from "./manual-redirect.js";
1
2
  import { createOpenAIDecisionModel } from "./decision.js";
2
3
  import { computerInputSchema, computerScreenshotSchema } from "./computer.js";
3
4
  export { openAIComputerTool, OpenAIComputerExecutionError } from "./computer.js";
4
5
  import { jsonHeaders, parseJson, RESERVED_REQUEST_HEADERS } from "./http.js";
5
6
  import { OpenAIRealtimeModel, openAIRealtimeMcpMetadataKey, resolveOpenAIRealtimeHeaders } from "./realtime.js";
6
- export { openAIRealtimeMcpMetadataKey } from "./realtime.js";
7
+ export { OpenAIRealtimeModel, openAIRealtimeMcpMetadataKey } from "./realtime.js";
7
8
  import { OpenAIEmbeddingModel, OpenAITranscriptionModel, OpenAISpeechModel, toUint8Array } from "./resource-models.js";
9
+ export { OpenAISpeechModel } from "./resource-models.js";
8
10
  import { OpenAIAgentsClient } from "./agents.js";
9
11
  export { OpenAIAgentsClient } from "./agents.js";
10
12
  import { toJSONSchema, z } from "zod";
11
13
  import { OpenAILiveModel, isOpenAILiveModel } from "./live.js";
14
+ export { OpenAILiveModel } from "./live.js";
12
15
  import { createOpenAIImageGenerationModel, normalizeOpenAIImageGenerationCall, normalizeOpenAIImageGenerationPartialImage } from "./image-generation.js";
13
16
  import { groundedCapabilities, supportsOpenAIModernResponses, assertOpenAIModelRequestSupported, resolveOpenAIModelProfile, modelCapabilities } from "./capabilities.js";
14
- import { imageInputToDataUrl, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, assertTrustedEndpoint, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, streamChatCompletions, toolResultPayload, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
17
+ import { imageInputToDataUrl, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, ValidationError, assertTrustedEndpoint, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, streamChatCompletions, toolResultPayload, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
18
+ const TOOL_INPUT_JSON_SCHEMA = Symbol.for("@zhivex-ai/core/json-schema");
19
+ /** Zod inputs stay on toJSONSchema so this package still loads against the oldest core in its peer range. */
20
+ const toolInputSchema = (schema) => {
21
+ if (schema !== null && typeof schema === "object" && schema[TOOL_INPUT_JSON_SCHEMA] === true) {
22
+ return { ...schema.jsonSchema };
23
+ }
24
+ return toJSONSchema(schema);
25
+ };
26
+ const openAISamplingWarnings = new Set();
27
+ const warnOpenAISampling = (option, detail) => {
28
+ const key = `openai:${option}:${detail}`;
29
+ if (openAISamplingWarnings.has(key))
30
+ return;
31
+ openAISamplingWarnings.add(key);
32
+ console.warn(`Provider "openai" does not support ${option}. ${detail} The option was ignored.`);
33
+ };
34
+ const openAIChatCacheTtlWarnings = new Set();
35
+ const warnOpenAIChatPromptCacheTtl = () => {
36
+ const key = 'openai:prompt_cache_options.ttl:12h:chat';
37
+ if (openAIChatCacheTtlWarnings.has(key))
38
+ return;
39
+ openAIChatCacheTtlWarnings.add(key);
40
+ console.warn('Provider "openai" does not support prompt_cache_options.ttl "12h" on Chat Completions. The option was ignored.');
41
+ };
42
+ /** Chat Completions support for a 12-hour cache TTL is unconfirmed, so that value is ignored. */
43
+ const openAIChatBodyOptions = (options) => {
44
+ const cache = options.prompt_cache_options;
45
+ if (cache?.ttl !== "12h")
46
+ return options;
47
+ warnOpenAIChatPromptCacheTtl();
48
+ const rest = { ...cache };
49
+ delete rest.ttl;
50
+ const next = { ...options };
51
+ if (Object.keys(rest).length === 0)
52
+ delete next.prompt_cache_options;
53
+ else
54
+ next.prompt_cache_options = rest;
55
+ return next;
56
+ };
57
+ /** Model profiles that already reject sampling. `topP` still throws in assertOpenAIModelRequestSupported. */
58
+ const openAISamplingRejected = (modelId, input, bodyOptions) => {
59
+ const profile = resolveOpenAIModelProfile(modelId);
60
+ if (profile.sampling === "allowed")
61
+ return null;
62
+ const effort = input.reasoning?.effort ?? bodyOptions.reasoning?.effort ?? bodyOptions.reasoning_effort;
63
+ return profile.sampling === "unsupported" || effort !== "none" ? profile.label : null;
64
+ };
65
+ const openAIChatSampling = (modelId, input, bodyOptions) => {
66
+ const rejected = openAISamplingRejected(modelId, input, bodyOptions);
67
+ if (rejected) {
68
+ if (input.seed !== undefined)
69
+ warnOpenAISampling("seed", `${rejected} does not support seed.`);
70
+ if (input.stopSequences !== undefined) {
71
+ warnOpenAISampling("stopSequences", `${rejected} does not support stop sequences.`);
72
+ }
73
+ }
74
+ return {
75
+ ...(input.topP !== undefined ? { top_p: input.topP } : {}),
76
+ ...(!rejected && input.seed !== undefined ? { seed: input.seed } : {}),
77
+ ...(!rejected && input.stopSequences !== undefined ? { stop: [...input.stopSequences] } : {})
78
+ };
79
+ };
80
+ const openAIResponsesSampling = (input) => {
81
+ if (input.seed !== undefined)
82
+ warnOpenAISampling("seed", "The Responses API does not accept seed.");
83
+ if (input.stopSequences !== undefined) {
84
+ warnOpenAISampling("stopSequences", "The Responses API does not accept stop sequences.");
85
+ }
86
+ return input.topP !== undefined ? { top_p: input.topP } : {};
87
+ };
15
88
  const MIB = 1024 * 1024;
16
89
  const DEFAULT_HOSTED_IMAGE_EVENT_BYTES = 32 * MIB;
17
90
  const DEFAULT_HOSTED_IMAGE_TOTAL_BYTES = 128 * MIB;
@@ -58,13 +131,53 @@ const assertOpenAIToolPathInsideRoot = async (rootDir, targetPath, label) => {
58
131
  return targetPath;
59
132
  }
60
133
  const path = await import("node:path");
134
+ const fs = await import("node:fs/promises");
61
135
  const root = path.resolve(rootDir);
62
- const target = path.resolve(root, targetPath);
63
- const relative = path.relative(root, target);
64
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
136
+ const lexical = path.resolve(root, targetPath);
137
+ const lexicalRelative = path.relative(root, lexical);
138
+ const escapes = (relative) => path.isAbsolute(relative) || relative.split(path.sep).includes("..");
139
+ if (escapes(lexicalRelative)) {
65
140
  throw new Error(`OpenAI ${label} path escapes rootDir.`);
66
141
  }
67
- return target;
142
+ let rootReal;
143
+ try {
144
+ const rootStat = await fs.lstat(root);
145
+ if (rootStat.isSymbolicLink()) {
146
+ throw new Error(`OpenAI ${label} path escapes rootDir.`);
147
+ }
148
+ rootReal = await fs.realpath(root);
149
+ }
150
+ catch (error) {
151
+ if (error instanceof Error && error.message.includes("escapes rootDir"))
152
+ throw error;
153
+ throw new Error(`OpenAI ${label} rootDir is not an accessible directory.`);
154
+ }
155
+ const missing = [];
156
+ let current = lexical;
157
+ let resolvedExisting = rootReal;
158
+ while (true) {
159
+ try {
160
+ resolvedExisting = await fs.realpath(current);
161
+ break;
162
+ }
163
+ catch (error) {
164
+ const code = error.code;
165
+ if (code !== "ENOENT")
166
+ throw new Error(`OpenAI ${label} path escapes rootDir.`);
167
+ const parent = path.dirname(current);
168
+ if (parent === current)
169
+ throw new Error(`OpenAI ${label} path escapes rootDir.`);
170
+ missing.push(path.basename(current));
171
+ current = parent;
172
+ }
173
+ }
174
+ const resolved = missing.length === 0 ? resolvedExisting : path.resolve(resolvedExisting, ...missing.reverse());
175
+ const relative = path.relative(rootReal, resolved);
176
+ if (escapes(relative) ||
177
+ (resolved !== rootReal && !resolved.startsWith(`${rootReal}${path.sep}`))) {
178
+ throw new Error(`OpenAI ${label} path escapes rootDir.`);
179
+ }
180
+ return resolved;
68
181
  };
69
182
  const resolveOpenAILanguageRequestOptions = (providerOptions, apiKey) => {
70
183
  const bodyOptions = { ...(providerOptions ?? {}) };
@@ -141,6 +254,14 @@ const isOpenAIPromptCacheBreakpointPart = (part) => part.type === "provider-data
141
254
  part.data !== null &&
142
255
  typeof part.data === "object" &&
143
256
  part.data.type === "prompt_cache_breakpoint";
257
+ const explicitPromptCacheBreakpoint = (breakpoint) => {
258
+ const record = breakpoint;
259
+ const ttl = record.ttl;
260
+ if (ttl !== undefined && ttl !== "30m" && ttl !== "12h") {
261
+ throw new ValidationError('OpenAI prompt cache breakpoint ttl must be "30m" or "12h".');
262
+ }
263
+ return ttl === "30m" || ttl === "12h" ? { mode: "explicit", ttl } : { mode: "explicit" };
264
+ };
144
265
  const promptCacheBreakpointForContentPart = (part) => {
145
266
  const metadata = part
146
267
  .providerMetadata;
@@ -149,7 +270,7 @@ const promptCacheBreakpointForContentPart = (part) => {
149
270
  : metadata;
150
271
  const breakpoint = openAIMetadata?.prompt_cache_breakpoint;
151
272
  return breakpoint && typeof breakpoint === "object" && !Array.isArray(breakpoint)
152
- ? { prompt_cache_breakpoint: { mode: "explicit" } }
273
+ ? { prompt_cache_breakpoint: explicitPromptCacheBreakpoint(breakpoint) }
153
274
  : {};
154
275
  };
155
276
  const imageDetailForPart = (part) => {
@@ -160,12 +281,12 @@ const imageDetailForPart = (part) => {
160
281
  : undefined);
161
282
  return detail === "auto" || detail === "low" || detail === "high" || detail === "original" ? detail : undefined;
162
283
  };
163
- const markLastContentBlockForPromptCaching = (content) => {
284
+ const markLastContentBlockForPromptCaching = (content, breakpoint) => {
164
285
  const last = content.at(-1);
165
286
  if (!last) {
166
287
  throw new ConfigurationError("OpenAI prompt cache breakpoints must follow a cacheable content part.");
167
288
  }
168
- last.prompt_cache_breakpoint = { mode: "explicit" };
289
+ last.prompt_cache_breakpoint = explicitPromptCacheBreakpoint(breakpoint);
169
290
  };
170
291
  const mapContentParts = (message) => {
171
292
  const hasRichContent = message.parts.some((part) => part.type === "image" ||
@@ -210,7 +331,7 @@ const mapContentParts = (message) => {
210
331
  });
211
332
  }
212
333
  else if (isOpenAIPromptCacheBreakpointPart(part)) {
213
- markLastContentBlockForPromptCaching(content);
334
+ markLastContentBlockForPromptCaching(content, part.data);
214
335
  }
215
336
  }
216
337
  return content;
@@ -317,7 +438,7 @@ const mapTools = (input) => input
317
438
  function: {
318
439
  name: tool.name,
319
440
  description: tool.description,
320
- parameters: toJSONSchema(tool.schema)
441
+ parameters: toolInputSchema(tool.schema)
321
442
  }
322
443
  };
323
444
  }
@@ -344,7 +465,7 @@ const mapResponsesTools = (input) => input
344
465
  type: "function",
345
466
  name: tool.name,
346
467
  description: tool.description,
347
- parameters: toJSONSchema(tool.schema),
468
+ parameters: toolInputSchema(tool.schema),
348
469
  ...openAIResponsesFunctionConfig(tool)
349
470
  };
350
471
  }
@@ -757,7 +878,7 @@ const toResponsesInput = (messages, format, history = messages) => {
757
878
  break;
758
879
  case "provider-data":
759
880
  if (isOpenAIPromptCacheBreakpointPart(part)) {
760
- markLastContentBlockForPromptCaching(content);
881
+ markLastContentBlockForPromptCaching(content, part.data);
761
882
  }
762
883
  break;
763
884
  }
@@ -925,36 +1046,90 @@ const attachTerminalUsage = (error, raw) => new ProviderToolCallError({
925
1046
  cause: error.cause,
926
1047
  usage: mapResponsesUsage(raw)
927
1048
  });
928
- const mapResponsesUsage = (usage) => usage
929
- ? {
1049
+ const mapOpenAICacheWriteTokensDetails = (details) => {
1050
+ if (!details || typeof details !== "object" || Array.isArray(details))
1051
+ return undefined;
1052
+ const record = details;
1053
+ const read = (key) => typeof record[key] === "number" ? record[key] : undefined;
1054
+ const ttl30mTokens = read("ttl_30m_tokens");
1055
+ const ttl12hTokens = read("ttl_12h_tokens");
1056
+ if (ttl30mTokens === undefined && ttl12hTokens === undefined)
1057
+ return undefined;
1058
+ return {
1059
+ ...(ttl30mTokens !== undefined ? { ttl30mTokens } : {}),
1060
+ ...(ttl12hTokens !== undefined ? { ttl12hTokens } : {})
1061
+ };
1062
+ };
1063
+ const mapResponsesUsage = (usage) => {
1064
+ if (!usage)
1065
+ return undefined;
1066
+ const cacheWriteTokensDetails = mapOpenAICacheWriteTokensDetails(usage.input_tokens_details?.cache_write_tokens_details);
1067
+ return {
930
1068
  inputTokens: usage.input_tokens,
931
1069
  cachedInputTokens: usage.input_tokens_details?.cached_tokens,
932
1070
  cacheWriteTokens: usage.input_tokens_details?.cache_write_tokens,
1071
+ ...(cacheWriteTokensDetails ? { cacheWriteTokensDetails } : {}),
933
1072
  outputTokens: usage.output_tokens,
934
1073
  reasoningTokens: usage.output_tokens_details?.reasoning_tokens,
935
1074
  totalTokens: usage.total_tokens
936
- }
937
- : undefined;
938
- const mapChatUsage = (usage) => usage
939
- ? {
1075
+ };
1076
+ };
1077
+ const mapChatUsage = (usage) => {
1078
+ if (!usage)
1079
+ return undefined;
1080
+ const cacheWriteTokensDetails = mapOpenAICacheWriteTokensDetails(usage.prompt_tokens_details?.cache_write_tokens_details);
1081
+ return {
940
1082
  inputTokens: usage.prompt_tokens,
941
1083
  cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
942
1084
  cacheWriteTokens: usage.prompt_tokens_details?.cache_write_tokens,
1085
+ ...(cacheWriteTokensDetails ? { cacheWriteTokensDetails } : {}),
943
1086
  outputTokens: usage.completion_tokens,
944
1087
  reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
945
1088
  totalTokens: usage.total_tokens
946
- }
947
- : undefined;
1089
+ };
1090
+ };
1091
+ const openAIUsageCounters = (usage) => [
1092
+ usage.inputTokens,
1093
+ usage.outputTokens,
1094
+ usage.totalTokens,
1095
+ usage.cachedInputTokens,
1096
+ usage.cacheWriteTokens,
1097
+ usage.reasoningTokens,
1098
+ usage.cacheWriteTokensDetails?.ttl30mTokens,
1099
+ usage.cacheWriteTokensDetails?.ttl12hTokens
1100
+ ];
1101
+ const openAIUsageCounterInvalid = (usage) => openAIUsageCounters(usage ?? {}).some((value) => value !== undefined && (!Number.isSafeInteger(value) || value < 0));
948
1102
  const addTokenUsage = (left, right) => {
949
1103
  if (!left)
950
1104
  return right;
951
1105
  if (!right)
952
1106
  return left;
953
1107
  const sum = (a, b) => a === undefined || b === undefined ? undefined : a + b;
1108
+ const consistentDetails = (writes, details) => {
1109
+ if (!details)
1110
+ return (writes ?? 0) === 0;
1111
+ return writes !== undefined && (details.ttl30mTokens ?? 0) + (details.ttl12hTokens ?? 0) === writes;
1112
+ };
1113
+ const cacheWriteTokensDetails = consistentDetails(left.cacheWriteTokens, left.cacheWriteTokensDetails) &&
1114
+ consistentDetails(right.cacheWriteTokens, right.cacheWriteTokensDetails) &&
1115
+ (left.cacheWriteTokensDetails || right.cacheWriteTokensDetails)
1116
+ ? {
1117
+ ...(left.cacheWriteTokensDetails?.ttl30mTokens !== undefined || right.cacheWriteTokensDetails?.ttl30mTokens !== undefined
1118
+ ? { ttl30mTokens: (left.cacheWriteTokensDetails?.ttl30mTokens ?? 0) + (right.cacheWriteTokensDetails?.ttl30mTokens ?? 0) }
1119
+ : {}),
1120
+ ...(left.cacheWriteTokensDetails?.ttl12hTokens !== undefined || right.cacheWriteTokensDetails?.ttl12hTokens !== undefined
1121
+ ? { ttl12hTokens: (left.cacheWriteTokensDetails?.ttl12hTokens ?? 0) + (right.cacheWriteTokensDetails?.ttl12hTokens ?? 0) }
1122
+ : {})
1123
+ }
1124
+ : undefined;
954
1125
  return {
955
1126
  inputTokens: sum(left.inputTokens, right.inputTokens),
956
1127
  cachedInputTokens: sum(left.cachedInputTokens, right.cachedInputTokens),
957
1128
  cacheWriteTokens: sum(left.cacheWriteTokens, right.cacheWriteTokens),
1129
+ ...(cacheWriteTokensDetails &&
1130
+ (cacheWriteTokensDetails.ttl30mTokens !== undefined || cacheWriteTokensDetails.ttl12hTokens !== undefined)
1131
+ ? { cacheWriteTokensDetails }
1132
+ : {}),
958
1133
  outputTokens: sum(left.outputTokens, right.outputTokens),
959
1134
  reasoningTokens: sum(left.reasoningTokens, right.reasoningTokens),
960
1135
  totalTokens: sum(left.totalTokens, right.totalTokens)
@@ -1528,17 +1703,18 @@ class OpenAILanguageModel {
1528
1703
  ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1529
1704
  text: mapResponsesStructuredOutput(input) ?? responseBodyOptions.text,
1530
1705
  temperature: input.temperature,
1706
+ ...openAIResponsesSampling(input),
1531
1707
  max_output_tokens: ptc ? remainingOutputTokens : input.maxTokens,
1532
1708
  ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1533
1709
  })
1534
1710
  }), { ...input, abortSignal: signal, ...(ptc ? { maxRetries: 0 } : {}) }, "OpenAI");
1535
1711
  const json = await parseJson(response);
1536
1712
  const reportedUsage = mapResponsesUsage(json.usage);
1537
- if (ptc && (!reportedUsage || ![reportedUsage.inputTokens, reportedUsage.outputTokens, reportedUsage.totalTokens].every(value => typeof value === "number" && Number.isSafeInteger(value) && value >= 0) || reportedUsage.totalTokens < reportedUsage.inputTokens + reportedUsage.outputTokens || Object.values(reportedUsage).some(value => value !== undefined && (!Number.isSafeInteger(value) || value < 0)))) {
1713
+ if (ptc && (!reportedUsage || ![reportedUsage.inputTokens, reportedUsage.outputTokens, reportedUsage.totalTokens].every(value => typeof value === "number" && Number.isSafeInteger(value) && value >= 0) || reportedUsage.totalTokens < reportedUsage.inputTokens + reportedUsage.outputTokens || openAIUsageCounterInvalid(reportedUsage))) {
1538
1714
  throw failure("OPENAI_PTC_USAGE_UNKNOWN");
1539
1715
  }
1540
1716
  const nextUsage = addTokenUsage(accumulatedUsage, reportedUsage);
1541
- if (ptc && Object.values(nextUsage ?? {}).some(value => value !== undefined && !Number.isSafeInteger(value)))
1717
+ if (ptc && openAIUsageCounterInvalid(nextUsage))
1542
1718
  throw failure("OPENAI_PTC_USAGE_UNKNOWN");
1543
1719
  accumulatedUsage = nextUsage;
1544
1720
  if (ptc)
@@ -1617,13 +1793,14 @@ class OpenAILanguageModel {
1617
1793
  headers: options.headers,
1618
1794
  signal,
1619
1795
  body: JSON.stringify({
1620
- ...options.bodyOptions,
1796
+ ...openAIChatBodyOptions(options.bodyOptions),
1621
1797
  model: this.modelId,
1622
1798
  messages: mapMessages(input.messages, input.toolResultFormat),
1623
1799
  tools: mapTools(input.tools),
1624
1800
  ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1625
1801
  response_format: mapStructuredOutput(input) ?? options.bodyOptions.response_format,
1626
1802
  temperature: input.temperature,
1803
+ ...openAIChatSampling(this.modelId, input, options.bodyOptions),
1627
1804
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1628
1805
  ...mapChatReasoning(input),
1629
1806
  stream: false
@@ -1681,6 +1858,7 @@ class OpenAILanguageModel {
1681
1858
  ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1682
1859
  text: mapResponsesStructuredOutput(input) ?? responseBodyOptions.text,
1683
1860
  temperature: input.temperature,
1861
+ ...openAIResponsesSampling(input),
1684
1862
  max_output_tokens: input.maxTokens,
1685
1863
  ...mapResponsesReasoning(input, responseBodyOptions.reasoning),
1686
1864
  stream: true
@@ -1703,13 +1881,14 @@ class OpenAILanguageModel {
1703
1881
  headers: options.headers,
1704
1882
  signal,
1705
1883
  body: JSON.stringify({
1706
- ...options.bodyOptions,
1884
+ ...openAIChatBodyOptions(options.bodyOptions),
1707
1885
  model: this.modelId,
1708
1886
  messages: mapMessages(input.messages, input.toolResultFormat),
1709
1887
  tools: mapTools(input.tools),
1710
1888
  ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1711
1889
  response_format: mapStructuredOutput(input) ?? options.bodyOptions.response_format,
1712
1890
  temperature: input.temperature,
1891
+ ...openAIChatSampling(this.modelId, input, options.bodyOptions),
1713
1892
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1714
1893
  ...mapChatReasoning(input),
1715
1894
  stream: true,
@@ -1800,7 +1979,8 @@ export const createOpenAI = (options = {}) => {
1800
1979
  allowUnsafe: options.allowUnsafeEndpoints
1801
1980
  }).toString()
1802
1981
  : undefined;
1803
- const fetcher = options.fetch ?? globalThis.fetch;
1982
+ const rawFetch = options.fetch ?? globalThis.fetch;
1983
+ const fetcher = withManualRedirect(rawFetch);
1804
1984
  const responseLimits = resolveOpenAIResponseLimits(options.responseLimits);
1805
1985
  return createProviderAdapter({
1806
1986
  name: "openai",
@@ -1827,7 +2007,7 @@ export const createOpenAI = (options = {}) => {
1827
2007
  return isOpenAILiveModel(modelId) ? new OpenAILiveModel(modelId, baseURL, (providerOptions) => resolveOpenAIRealtimeHeaders(apiKey, providerOptions), options.realtimeConnectionFactory ?? openWebSocketConnection, realtimeURL, options.allowUnsafeEndpoints) : new OpenAIRealtimeModel(modelId, apiKey, baseURL, fetcher, options.realtimeConnectionFactory, realtimeURL, browserTokenURL, options.allowUnsafeEndpoints);
1828
2008
  },
1829
2009
  groundedLanguageModel: (modelId) => new OpenAIGroundedLanguageModel(modelId, apiKey, baseURL, fetcher),
1830
- rawFetch: fetcher,
2010
+ rawFetch,
1831
2011
  agents: new OpenAIAgentsClient(baseURL, apiKey, fetcher, options.allowUnsafeEndpoints)
1832
2012
  });
1833
2013
  };
@@ -1907,9 +2087,10 @@ export const openAIRealtimeMcpApprovalResult = (response) => ({
1907
2087
  }
1908
2088
  }
1909
2089
  });
1910
- export const openAIPromptCacheBreakpoint = () => providerDataPart("openai", {
2090
+ export const openAIPromptCacheBreakpoint = (breakpoint) => providerDataPart("openai", {
1911
2091
  type: "prompt_cache_breakpoint",
1912
- mode: "explicit"
2092
+ mode: "explicit",
2093
+ ...(breakpoint?.ttl ? { ttl: breakpoint.ttl } : {})
1913
2094
  });
1914
2095
  export const openAIComputerUseTool = (config) => hostedTool({
1915
2096
  name: "computer",
@@ -2011,7 +2192,8 @@ export const openAIShellTool = (config = {}) => ({
2011
2192
  });
2012
2193
  const applyOpenAIPatchOperation = async (operation, config) => {
2013
2194
  if (config.rootDir) {
2014
- await assertOpenAIToolPathInsideRoot(config.rootDir, operation.path, "apply_patch");
2195
+ const safePath = await assertOpenAIToolPathInsideRoot(config.rootDir, operation.path, "apply_patch");
2196
+ return config.applyOperation({ ...operation, path: safePath });
2015
2197
  }
2016
2198
  return config.applyOperation(operation);
2017
2199
  };