openai 6.44.0 → 6.45.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 (295) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +85 -102
  3. package/_vendor/zod-to-json-schema/parseDef.d.mts.map +1 -1
  4. package/_vendor/zod-to-json-schema/parseDef.d.ts.map +1 -1
  5. package/_vendor/zod-to-json-schema/parseDef.js +41 -15
  6. package/_vendor/zod-to-json-schema/parseDef.js.map +1 -1
  7. package/_vendor/zod-to-json-schema/parseDef.mjs +41 -15
  8. package/_vendor/zod-to-json-schema/parseDef.mjs.map +1 -1
  9. package/_vendor/zod-to-json-schema/parsers/branded.d.mts +1 -1
  10. package/_vendor/zod-to-json-schema/parsers/branded.d.mts.map +1 -1
  11. package/_vendor/zod-to-json-schema/parsers/branded.d.ts +1 -1
  12. package/_vendor/zod-to-json-schema/parsers/branded.d.ts.map +1 -1
  13. package/_vendor/zod-to-json-schema/parsers/branded.js +2 -2
  14. package/_vendor/zod-to-json-schema/parsers/branded.js.map +1 -1
  15. package/_vendor/zod-to-json-schema/parsers/branded.mjs +2 -2
  16. package/_vendor/zod-to-json-schema/parsers/branded.mjs.map +1 -1
  17. package/_vendor/zod-to-json-schema/parsers/catch.d.mts +1 -1
  18. package/_vendor/zod-to-json-schema/parsers/catch.d.mts.map +1 -1
  19. package/_vendor/zod-to-json-schema/parsers/catch.d.ts +1 -1
  20. package/_vendor/zod-to-json-schema/parsers/catch.d.ts.map +1 -1
  21. package/_vendor/zod-to-json-schema/parsers/catch.js +2 -2
  22. package/_vendor/zod-to-json-schema/parsers/catch.js.map +1 -1
  23. package/_vendor/zod-to-json-schema/parsers/catch.mjs +2 -2
  24. package/_vendor/zod-to-json-schema/parsers/catch.mjs.map +1 -1
  25. package/_vendor/zod-to-json-schema/parsers/default.d.mts +1 -1
  26. package/_vendor/zod-to-json-schema/parsers/default.d.mts.map +1 -1
  27. package/_vendor/zod-to-json-schema/parsers/default.d.ts +1 -1
  28. package/_vendor/zod-to-json-schema/parsers/default.d.ts.map +1 -1
  29. package/_vendor/zod-to-json-schema/parsers/default.js +2 -2
  30. package/_vendor/zod-to-json-schema/parsers/default.js.map +1 -1
  31. package/_vendor/zod-to-json-schema/parsers/default.mjs +2 -2
  32. package/_vendor/zod-to-json-schema/parsers/default.mjs.map +1 -1
  33. package/_vendor/zod-to-json-schema/parsers/nullable.d.mts +1 -1
  34. package/_vendor/zod-to-json-schema/parsers/nullable.d.mts.map +1 -1
  35. package/_vendor/zod-to-json-schema/parsers/nullable.d.ts +1 -1
  36. package/_vendor/zod-to-json-schema/parsers/nullable.d.ts.map +1 -1
  37. package/_vendor/zod-to-json-schema/parsers/nullable.js +2 -2
  38. package/_vendor/zod-to-json-schema/parsers/nullable.js.map +1 -1
  39. package/_vendor/zod-to-json-schema/parsers/nullable.mjs +2 -2
  40. package/_vendor/zod-to-json-schema/parsers/nullable.mjs.map +1 -1
  41. package/_vendor/zod-to-json-schema/parsers/optional.d.mts +1 -1
  42. package/_vendor/zod-to-json-schema/parsers/optional.d.mts.map +1 -1
  43. package/_vendor/zod-to-json-schema/parsers/optional.d.ts +1 -1
  44. package/_vendor/zod-to-json-schema/parsers/optional.d.ts.map +1 -1
  45. package/_vendor/zod-to-json-schema/parsers/optional.js +3 -3
  46. package/_vendor/zod-to-json-schema/parsers/optional.js.map +1 -1
  47. package/_vendor/zod-to-json-schema/parsers/optional.mjs +3 -3
  48. package/_vendor/zod-to-json-schema/parsers/optional.mjs.map +1 -1
  49. package/_vendor/zod-to-json-schema/parsers/pipeline.d.mts +1 -1
  50. package/_vendor/zod-to-json-schema/parsers/pipeline.d.mts.map +1 -1
  51. package/_vendor/zod-to-json-schema/parsers/pipeline.d.ts +1 -1
  52. package/_vendor/zod-to-json-schema/parsers/pipeline.d.ts.map +1 -1
  53. package/_vendor/zod-to-json-schema/parsers/pipeline.js +3 -3
  54. package/_vendor/zod-to-json-schema/parsers/pipeline.js.map +1 -1
  55. package/_vendor/zod-to-json-schema/parsers/pipeline.mjs +3 -3
  56. package/_vendor/zod-to-json-schema/parsers/pipeline.mjs.map +1 -1
  57. package/_vendor/zod-to-json-schema/parsers/promise.d.mts +1 -1
  58. package/_vendor/zod-to-json-schema/parsers/promise.d.mts.map +1 -1
  59. package/_vendor/zod-to-json-schema/parsers/promise.d.ts +1 -1
  60. package/_vendor/zod-to-json-schema/parsers/promise.d.ts.map +1 -1
  61. package/_vendor/zod-to-json-schema/parsers/promise.js +2 -2
  62. package/_vendor/zod-to-json-schema/parsers/promise.js.map +1 -1
  63. package/_vendor/zod-to-json-schema/parsers/promise.mjs +2 -2
  64. package/_vendor/zod-to-json-schema/parsers/promise.mjs.map +1 -1
  65. package/_vendor/zod-to-json-schema/parsers/readonly.d.mts +1 -1
  66. package/_vendor/zod-to-json-schema/parsers/readonly.d.mts.map +1 -1
  67. package/_vendor/zod-to-json-schema/parsers/readonly.d.ts +1 -1
  68. package/_vendor/zod-to-json-schema/parsers/readonly.d.ts.map +1 -1
  69. package/_vendor/zod-to-json-schema/parsers/readonly.js +2 -2
  70. package/_vendor/zod-to-json-schema/parsers/readonly.js.map +1 -1
  71. package/_vendor/zod-to-json-schema/parsers/readonly.mjs +2 -2
  72. package/_vendor/zod-to-json-schema/parsers/readonly.mjs.map +1 -1
  73. package/auth/workload-identity-auth.d.mts.map +1 -1
  74. package/auth/workload-identity-auth.d.ts.map +1 -1
  75. package/auth/workload-identity-auth.js +12 -4
  76. package/auth/workload-identity-auth.js.map +1 -1
  77. package/auth/workload-identity-auth.mjs +13 -5
  78. package/auth/workload-identity-auth.mjs.map +1 -1
  79. package/azure.d.mts +3 -1
  80. package/azure.d.mts.map +1 -1
  81. package/azure.d.ts +3 -1
  82. package/azure.d.ts.map +1 -1
  83. package/azure.js.map +1 -1
  84. package/azure.mjs.map +1 -1
  85. package/beta/realtime/internal-base.d.mts +14 -1
  86. package/beta/realtime/internal-base.d.mts.map +1 -1
  87. package/beta/realtime/internal-base.d.ts +14 -1
  88. package/beta/realtime/internal-base.d.ts.map +1 -1
  89. package/beta/realtime/internal-base.js +21 -5
  90. package/beta/realtime/internal-base.js.map +1 -1
  91. package/beta/realtime/internal-base.mjs +21 -5
  92. package/beta/realtime/internal-base.mjs.map +1 -1
  93. package/beta/realtime/websocket.d.mts +3 -5
  94. package/beta/realtime/websocket.d.mts.map +1 -1
  95. package/beta/realtime/websocket.d.ts +3 -5
  96. package/beta/realtime/websocket.d.ts.map +1 -1
  97. package/beta/realtime/websocket.js +2 -3
  98. package/beta/realtime/websocket.js.map +1 -1
  99. package/beta/realtime/websocket.mjs +3 -4
  100. package/beta/realtime/websocket.mjs.map +1 -1
  101. package/beta/realtime/ws.d.mts +3 -5
  102. package/beta/realtime/ws.d.mts.map +1 -1
  103. package/beta/realtime/ws.d.ts +3 -5
  104. package/beta/realtime/ws.d.ts.map +1 -1
  105. package/beta/realtime/ws.js +2 -3
  106. package/beta/realtime/ws.js.map +1 -1
  107. package/beta/realtime/ws.mjs +3 -4
  108. package/beta/realtime/ws.mjs.map +1 -1
  109. package/client.d.mts +9 -1
  110. package/client.d.mts.map +1 -1
  111. package/client.d.ts +9 -1
  112. package/client.d.ts.map +1 -1
  113. package/client.js +56 -9
  114. package/client.js.map +1 -1
  115. package/client.mjs +56 -9
  116. package/client.mjs.map +1 -1
  117. package/helpers/zod.d.mts.map +1 -1
  118. package/helpers/zod.d.ts.map +1 -1
  119. package/helpers/zod.js +12 -0
  120. package/helpers/zod.js.map +1 -1
  121. package/helpers/zod.mjs +12 -0
  122. package/helpers/zod.mjs.map +1 -1
  123. package/internal/bedrock.d.mts +34 -0
  124. package/internal/bedrock.d.mts.map +1 -0
  125. package/internal/bedrock.d.ts +34 -0
  126. package/internal/bedrock.d.ts.map +1 -0
  127. package/internal/bedrock.js +107 -0
  128. package/internal/bedrock.js.map +1 -0
  129. package/internal/bedrock.mjs +99 -0
  130. package/internal/bedrock.mjs.map +1 -0
  131. package/internal/provider.d.mts +23 -0
  132. package/internal/provider.d.mts.map +1 -0
  133. package/internal/provider.d.ts +23 -0
  134. package/internal/provider.d.ts.map +1 -0
  135. package/internal/provider.js +36 -0
  136. package/internal/provider.js.map +1 -0
  137. package/internal/provider.mjs +32 -0
  138. package/internal/provider.mjs.map +1 -0
  139. package/internal/utils/log.d.mts.map +1 -1
  140. package/internal/utils/log.d.ts.map +1 -1
  141. package/internal/utils/log.js +1 -0
  142. package/internal/utils/log.js.map +1 -1
  143. package/internal/utils/log.mjs +1 -0
  144. package/internal/utils/log.mjs.map +1 -1
  145. package/lib/AbstractChatCompletionRunner.d.mts +15 -4
  146. package/lib/AbstractChatCompletionRunner.d.mts.map +1 -1
  147. package/lib/AbstractChatCompletionRunner.d.ts +15 -4
  148. package/lib/AbstractChatCompletionRunner.d.ts.map +1 -1
  149. package/lib/AbstractChatCompletionRunner.js +63 -40
  150. package/lib/AbstractChatCompletionRunner.js.map +1 -1
  151. package/lib/AbstractChatCompletionRunner.mjs +63 -40
  152. package/lib/AbstractChatCompletionRunner.mjs.map +1 -1
  153. package/lib/AssistantStream.d.mts.map +1 -1
  154. package/lib/AssistantStream.d.ts.map +1 -1
  155. package/lib/AssistantStream.js +4 -24
  156. package/lib/AssistantStream.js.map +1 -1
  157. package/lib/AssistantStream.mjs +4 -24
  158. package/lib/AssistantStream.mjs.map +1 -1
  159. package/lib/ChatCompletionRunner.js +1 -1
  160. package/lib/ChatCompletionRunner.js.map +1 -1
  161. package/lib/ChatCompletionRunner.mjs +1 -1
  162. package/lib/ChatCompletionRunner.mjs.map +1 -1
  163. package/lib/ChatCompletionStream.d.mts.map +1 -1
  164. package/lib/ChatCompletionStream.d.ts.map +1 -1
  165. package/lib/ChatCompletionStream.js +4 -13
  166. package/lib/ChatCompletionStream.js.map +1 -1
  167. package/lib/ChatCompletionStream.mjs +4 -13
  168. package/lib/ChatCompletionStream.mjs.map +1 -1
  169. package/lib/ChatCompletionStreamingRunner.js +1 -1
  170. package/lib/ChatCompletionStreamingRunner.js.map +1 -1
  171. package/lib/ChatCompletionStreamingRunner.mjs +1 -1
  172. package/lib/ChatCompletionStreamingRunner.mjs.map +1 -1
  173. package/lib/EventEmitter.d.mts.map +1 -1
  174. package/lib/EventEmitter.d.ts.map +1 -1
  175. package/lib/EventEmitter.js +12 -2
  176. package/lib/EventEmitter.js.map +1 -1
  177. package/lib/EventEmitter.mjs +12 -2
  178. package/lib/EventEmitter.mjs.map +1 -1
  179. package/lib/EventStream.d.mts +1 -0
  180. package/lib/EventStream.d.mts.map +1 -1
  181. package/lib/EventStream.d.ts +1 -0
  182. package/lib/EventStream.d.ts.map +1 -1
  183. package/lib/EventStream.js +19 -2
  184. package/lib/EventStream.js.map +1 -1
  185. package/lib/EventStream.mjs +19 -2
  186. package/lib/EventStream.mjs.map +1 -1
  187. package/lib/ResponsesParser.d.mts.map +1 -1
  188. package/lib/ResponsesParser.d.ts.map +1 -1
  189. package/lib/ResponsesParser.js +12 -4
  190. package/lib/ResponsesParser.js.map +1 -1
  191. package/lib/ResponsesParser.mjs +12 -4
  192. package/lib/ResponsesParser.mjs.map +1 -1
  193. package/lib/responses/ResponseAccumulator.d.mts +10 -0
  194. package/lib/responses/ResponseAccumulator.d.mts.map +1 -0
  195. package/lib/responses/ResponseAccumulator.d.ts +10 -0
  196. package/lib/responses/ResponseAccumulator.d.ts.map +1 -0
  197. package/lib/responses/ResponseAccumulator.js +402 -0
  198. package/lib/responses/ResponseAccumulator.js.map +1 -0
  199. package/lib/responses/ResponseAccumulator.mjs +399 -0
  200. package/lib/responses/ResponseAccumulator.mjs.map +1 -0
  201. package/lib/responses/ResponseInputItems.d.mts +19 -0
  202. package/lib/responses/ResponseInputItems.d.mts.map +1 -0
  203. package/lib/responses/ResponseInputItems.d.ts +19 -0
  204. package/lib/responses/ResponseInputItems.d.ts.map +1 -0
  205. package/lib/responses/ResponseInputItems.js +102 -0
  206. package/lib/responses/ResponseInputItems.js.map +1 -0
  207. package/lib/responses/ResponseInputItems.mjs +98 -0
  208. package/lib/responses/ResponseInputItems.mjs.map +1 -0
  209. package/lib/responses/ResponseStream.d.mts.map +1 -1
  210. package/lib/responses/ResponseStream.d.ts.map +1 -1
  211. package/lib/responses/ResponseStream.js +5 -90
  212. package/lib/responses/ResponseStream.js.map +1 -1
  213. package/lib/responses/ResponseStream.mjs +5 -90
  214. package/lib/responses/ResponseStream.mjs.map +1 -1
  215. package/package.json +24 -1
  216. package/providers/bedrock/aws.d.mts +24 -0
  217. package/providers/bedrock/aws.d.mts.map +1 -0
  218. package/providers/bedrock/aws.d.ts +24 -0
  219. package/providers/bedrock/aws.d.ts.map +1 -0
  220. package/providers/bedrock/aws.js +170 -0
  221. package/providers/bedrock/aws.js.map +1 -0
  222. package/providers/bedrock/aws.mjs +166 -0
  223. package/providers/bedrock/aws.mjs.map +1 -0
  224. package/providers/bedrock.d.mts +7 -0
  225. package/providers/bedrock.d.mts.map +1 -0
  226. package/providers/bedrock.d.ts +7 -0
  227. package/providers/bedrock.d.ts.map +1 -0
  228. package/providers/bedrock.js +26 -0
  229. package/providers/bedrock.js.map +1 -0
  230. package/providers/bedrock.mjs +22 -0
  231. package/providers/bedrock.mjs.map +1 -0
  232. package/realtime/internal-base.d.mts +28 -1
  233. package/realtime/internal-base.d.mts.map +1 -1
  234. package/realtime/internal-base.d.ts +28 -1
  235. package/realtime/internal-base.d.ts.map +1 -1
  236. package/realtime/internal-base.js +50 -7
  237. package/realtime/internal-base.js.map +1 -1
  238. package/realtime/internal-base.mjs +49 -7
  239. package/realtime/internal-base.mjs.map +1 -1
  240. package/realtime/websocket.d.mts +4 -7
  241. package/realtime/websocket.d.mts.map +1 -1
  242. package/realtime/websocket.d.ts +4 -7
  243. package/realtime/websocket.d.ts.map +1 -1
  244. package/realtime/websocket.js +4 -8
  245. package/realtime/websocket.js.map +1 -1
  246. package/realtime/websocket.mjs +5 -9
  247. package/realtime/websocket.mjs.map +1 -1
  248. package/realtime/ws.d.mts +4 -7
  249. package/realtime/ws.d.mts.map +1 -1
  250. package/realtime/ws.d.ts +4 -7
  251. package/realtime/ws.d.ts.map +1 -1
  252. package/realtime/ws.js +4 -8
  253. package/realtime/ws.js.map +1 -1
  254. package/realtime/ws.mjs +5 -9
  255. package/realtime/ws.mjs.map +1 -1
  256. package/src/_vendor/zod-to-json-schema/parseDef.ts +44 -16
  257. package/src/_vendor/zod-to-json-schema/parsers/branded.ts +2 -2
  258. package/src/_vendor/zod-to-json-schema/parsers/catch.ts +2 -2
  259. package/src/_vendor/zod-to-json-schema/parsers/default.ts +6 -2
  260. package/src/_vendor/zod-to-json-schema/parsers/nullable.ts +13 -5
  261. package/src/_vendor/zod-to-json-schema/parsers/optional.ts +14 -6
  262. package/src/_vendor/zod-to-json-schema/parsers/pipeline.ts +3 -2
  263. package/src/_vendor/zod-to-json-schema/parsers/promise.ts +6 -2
  264. package/src/_vendor/zod-to-json-schema/parsers/readonly.ts +2 -2
  265. package/src/auth/workload-identity-auth.ts +16 -5
  266. package/src/azure.ts +4 -1
  267. package/src/beta/realtime/internal-base.ts +43 -5
  268. package/src/beta/realtime/websocket.ts +10 -7
  269. package/src/beta/realtime/ws.ts +10 -7
  270. package/src/client.ts +80 -18
  271. package/src/helpers/zod.ts +16 -0
  272. package/src/internal/bedrock.ts +153 -0
  273. package/src/internal/provider.ts +60 -0
  274. package/src/internal/utils/log.ts +1 -0
  275. package/src/lib/AbstractChatCompletionRunner.ts +96 -45
  276. package/src/lib/AssistantStream.ts +4 -20
  277. package/src/lib/ChatCompletionRunner.ts +1 -1
  278. package/src/lib/ChatCompletionStream.ts +4 -11
  279. package/src/lib/ChatCompletionStreamingRunner.ts +1 -1
  280. package/src/lib/EventEmitter.ts +11 -2
  281. package/src/lib/EventStream.ts +20 -0
  282. package/src/lib/ResponsesParser.ts +18 -4
  283. package/src/lib/responses/ResponseAccumulator.ts +412 -0
  284. package/src/lib/responses/ResponseInputItems.ts +127 -0
  285. package/src/lib/responses/ResponseStream.ts +4 -93
  286. package/src/providers/bedrock/aws.ts +255 -0
  287. package/src/providers/bedrock.ts +33 -0
  288. package/src/realtime/internal-base.ts +90 -7
  289. package/src/realtime/websocket.ts +15 -13
  290. package/src/realtime/ws.ts +15 -13
  291. package/src/version.ts +1 -1
  292. package/version.d.mts +1 -1
  293. package/version.d.ts +1 -1
  294. package/version.js +1 -1
  295. package/version.mjs +1 -1
@@ -0,0 +1,153 @@
1
+ import * as Errors from '../error';
2
+ import type { ApiKeySetter } from '../client';
3
+ import type { FinalizedRequestInit } from './types';
4
+ import type { ProviderRequestContext } from './provider';
5
+ import { readEnv } from './utils';
6
+
7
+ export interface BedrockEndpointOptions {
8
+ /** AWS region used to derive the default Mantle endpoint. */
9
+ region?: string | undefined;
10
+
11
+ /** Bedrock API root. Defaults to AWS_BEDROCK_BASE_URL or the regional Mantle endpoint. */
12
+ baseURL?: string | null | undefined;
13
+ }
14
+
15
+ export interface BedrockBearerOptions {
16
+ /** Explicit Bedrock bearer credential. Set to null to skip the environment bearer fallback. */
17
+ apiKey?: string | null | undefined;
18
+
19
+ /** A function that resolves a Bedrock bearer credential before every request attempt. */
20
+ tokenProvider?: ApiKeySetter | undefined;
21
+ }
22
+
23
+ export interface BedrockRequestAuth {
24
+ prepareRequest(request: FinalizedRequestInit, context: ProviderRequestContext): void | Promise<void>;
25
+ }
26
+
27
+ export type BedrockAuthFactory = () => BedrockRequestAuth;
28
+
29
+ export function errorWithCause(message: string, cause: unknown): Errors.OpenAIError {
30
+ const error = new Errors.OpenAIError(message) as Errors.OpenAIError & { cause?: unknown };
31
+ error.cause = cause;
32
+ return error;
33
+ }
34
+
35
+ export function normalizeOptionalString(value: string | null | undefined): string | undefined {
36
+ const normalized = typeof value === 'string' ? value.trim() : undefined;
37
+ return normalized ? normalized : undefined;
38
+ }
39
+
40
+ function normalizeBaseURL(baseURL: string): string {
41
+ const url = new URL(baseURL);
42
+ const responsesMatch = url.pathname.match(/\/responses(?:\/.*)?$/);
43
+ if (responsesMatch?.index !== undefined) {
44
+ url.pathname = url.pathname.slice(0, responsesMatch.index) || '/';
45
+ }
46
+ return url.toString().replace(/\/$/, '');
47
+ }
48
+
49
+ export function resolveBedrockEndpoint(options: BedrockEndpointOptions): {
50
+ region: string | undefined;
51
+ baseURL: string;
52
+ } {
53
+ if (options.region !== undefined && !normalizeOptionalString(options.region)) {
54
+ throw new Errors.OpenAIError('The Bedrock AWS `region` must not be empty.');
55
+ }
56
+ if (
57
+ options.baseURL !== undefined &&
58
+ options.baseURL !== null &&
59
+ !normalizeOptionalString(options.baseURL)
60
+ ) {
61
+ throw new Errors.OpenAIError('The Bedrock `baseURL` must not be empty.');
62
+ }
63
+
64
+ const region =
65
+ normalizeOptionalString(options.region) ??
66
+ normalizeOptionalString(readEnv('AWS_REGION')) ??
67
+ normalizeOptionalString(readEnv('AWS_DEFAULT_REGION'));
68
+ const configuredBaseURL =
69
+ options.baseURL === undefined ? normalizeOptionalString(readEnv('AWS_BEDROCK_BASE_URL'))
70
+ : options.baseURL === null ? undefined
71
+ : normalizeOptionalString(options.baseURL);
72
+
73
+ if (configuredBaseURL) return { region, baseURL: normalizeBaseURL(configuredBaseURL) };
74
+ if (!region) {
75
+ throw new Errors.OpenAIError(
76
+ 'Bedrock requires an AWS region. Pass `region` to `bedrock(...)`, or set `AWS_REGION` or `AWS_DEFAULT_REGION`.',
77
+ );
78
+ }
79
+ return { region, baseURL: `https://bedrock-mantle.${region}.api.aws/openai/v1` };
80
+ }
81
+
82
+ export function assertProviderOwnsAuthorization(headers: Headers): void {
83
+ if (headers.has('authorization')) {
84
+ throw new Errors.OpenAIError(
85
+ 'Bedrock provider authentication cannot be combined with a custom `Authorization` header.',
86
+ );
87
+ }
88
+ }
89
+
90
+ class BedrockBearerAuth implements BedrockRequestAuth {
91
+ constructor(private readonly tokenProvider: ApiKeySetter) {}
92
+
93
+ async prepareRequest(request: FinalizedRequestInit, _context: ProviderRequestContext): Promise<void> {
94
+ const headers = new Headers(request.headers);
95
+ assertProviderOwnsAuthorization(headers);
96
+
97
+ let token: unknown;
98
+ try {
99
+ token = await this.tokenProvider();
100
+ } catch (cause) {
101
+ throw errorWithCause('Failed to resolve a bearer credential for Bedrock.', cause);
102
+ }
103
+ if (typeof token !== 'string' || !token.trim()) {
104
+ throw new Errors.OpenAIError('The Bedrock bearer credential provider must return a non-empty string.');
105
+ }
106
+ headers.set('authorization', `Bearer ${token}`);
107
+ request.headers = headers;
108
+ }
109
+ }
110
+
111
+ export function resolveBedrockBearerAuth(
112
+ options: BedrockBearerOptions,
113
+ { allowEnvironment = true }: { allowEnvironment?: boolean } = {},
114
+ ): { factory: BedrockAuthFactory | undefined; explicit: boolean } {
115
+ if (
116
+ options.apiKey !== undefined &&
117
+ options.apiKey !== null &&
118
+ (typeof options.apiKey !== 'string' || !options.apiKey.trim())
119
+ ) {
120
+ throw new Errors.OpenAIError('The Bedrock bearer credential must not be empty.');
121
+ }
122
+ if (options.apiKey != null && options.tokenProvider) {
123
+ throw new Errors.OpenAIError(
124
+ 'The `apiKey` and `tokenProvider` options are mutually exclusive. Configure only one.',
125
+ );
126
+ }
127
+
128
+ if (options.tokenProvider) {
129
+ const tokenProvider = options.tokenProvider;
130
+ return { factory: () => new BedrockBearerAuth(tokenProvider), explicit: true };
131
+ }
132
+ if (options.apiKey != null) {
133
+ const apiKey = options.apiKey;
134
+ return { factory: () => new BedrockBearerAuth(async () => apiKey), explicit: true };
135
+ }
136
+ if (allowEnvironment && options.apiKey !== null && readEnv('AWS_BEARER_TOKEN_BEDROCK')) {
137
+ return {
138
+ explicit: false,
139
+ factory: () =>
140
+ new BedrockBearerAuth(async () => {
141
+ const token = readEnv('AWS_BEARER_TOKEN_BEDROCK');
142
+ if (!token) {
143
+ throw new Errors.OpenAIError(
144
+ 'Could not find credentials for Bedrock. Set `AWS_BEARER_TOKEN_BEDROCK` or configure AWS credential authentication.',
145
+ );
146
+ }
147
+ return token;
148
+ }),
149
+ };
150
+ }
151
+
152
+ return { factory: undefined, explicit: false };
153
+ }
@@ -0,0 +1,60 @@
1
+ import type { FinalRequestOptions } from './request-options';
2
+ import type { FinalizedRequestInit } from './types';
3
+
4
+ declare const providerBrand: unique symbol;
5
+
6
+ /** An opaque provider configuration created by {@link createProvider}. */
7
+ export interface Provider {
8
+ readonly [providerBrand]: true;
9
+ }
10
+
11
+ export interface ProviderRequestContext {
12
+ url: string;
13
+ options: FinalRequestOptions;
14
+ }
15
+
16
+ export interface ProviderRuntime {
17
+ name: string;
18
+ baseURL: string;
19
+ prepareRequest?(request: FinalizedRequestInit, context: ProviderRequestContext): void | Promise<void>;
20
+ }
21
+
22
+ export interface ProviderDefinition {
23
+ configure(): ProviderRuntime;
24
+ }
25
+
26
+ /**
27
+ * A provider factory such as `bedrock(options)` captures configuration in a
28
+ * definition, while every OpenAI client receives a fresh runtime from
29
+ * `definition.configure()`. Keeping definitions out of the provider object
30
+ * makes providers opaque and prevents arbitrary objects from imitating one.
31
+ * It also leaves provider-specific dependencies outside the core SDK.
32
+ *
33
+ * The registry lives on `globalThis` under a global symbol so a provider made
34
+ * by one copy of the package still works with another copy, including mixed
35
+ * CommonJS and ESM installations. The WeakMap avoids retaining discarded
36
+ * provider configurations.
37
+ */
38
+ const providerDefinitionsKey = Symbol.for('openai.node.providerDefinitions.v1');
39
+ const providerGlobal = globalThis as any;
40
+ const existingProviderDefinitions = providerGlobal[providerDefinitionsKey] as
41
+ | WeakMap<Provider, ProviderDefinition>
42
+ | undefined;
43
+ const providerDefinitions = existingProviderDefinitions ?? new WeakMap<Provider, ProviderDefinition>();
44
+ if (!existingProviderDefinitions) {
45
+ Object.defineProperty(providerGlobal, providerDefinitionsKey, { value: providerDefinitions });
46
+ }
47
+
48
+ export function createProvider(definition: ProviderDefinition): Provider {
49
+ const provider = Object.freeze({}) as Provider;
50
+ providerDefinitions.set(provider, definition);
51
+ return provider;
52
+ }
53
+
54
+ export function configureProvider(provider: Provider): ProviderRuntime {
55
+ const definition = providerDefinitions.get(provider);
56
+ if (!definition) {
57
+ throw new Error('Invalid provider. Providers must be created with createProvider().');
58
+ }
59
+ return definition.configure();
60
+ }
@@ -109,6 +109,7 @@ export const formatRequestDetails = (details: {
109
109
  name.toLowerCase() === 'authorization' ||
110
110
  name.toLowerCase() === 'api-key' ||
111
111
  name.toLowerCase() === 'x-api-key' ||
112
+ name.toLowerCase() === 'x-amz-security-token' ||
112
113
  name.toLowerCase() === 'cookie' ||
113
114
  name.toLowerCase() === 'set-cookie'
114
115
  ) ?
@@ -8,12 +8,17 @@ import type {
8
8
  ChatCompletionMessage,
9
9
  ChatCompletionMessageFunctionToolCall,
10
10
  ChatCompletionMessageParam,
11
+ ChatCompletionMessageToolCall,
11
12
  ChatCompletionTool,
13
+ ChatCompletionToolMessageParam,
12
14
  ParsedChatCompletion,
13
15
  } from '../resources/chat/completions';
14
16
  import type { CompletionUsage } from '../resources/completions';
15
- import type { ChatCompletionToolRunnerParams } from './ChatCompletionRunner';
16
- import type { ChatCompletionStreamingToolRunnerParams } from './ChatCompletionStreamingRunner';
17
+ import type { ChatCompletionRunner, ChatCompletionToolRunnerParams } from './ChatCompletionRunner';
18
+ import type {
19
+ ChatCompletionStreamingRunner,
20
+ ChatCompletionStreamingToolRunnerParams,
21
+ } from './ChatCompletionStreamingRunner';
17
22
  import { isAssistantMessage, isToolMessage } from './chatCompletionUtils';
18
23
  import { BaseEvents, EventStream } from './EventStream';
19
24
  import {
@@ -24,9 +29,22 @@ import {
24
29
  } from './RunnableFunction';
25
30
 
26
31
  const DEFAULT_MAX_CHAT_COMPLETIONS = 10;
32
+
33
+ export interface ChatCompletionRunnerContext {
34
+ messages: ChatCompletionMessageParam[];
35
+ abort(): void;
36
+ }
37
+
27
38
  export interface RunnerOptions extends RequestOptions {
28
39
  /** How many requests to make before canceling. Default 10. */
29
40
  maxChatCompletions?: number;
41
+ /**
42
+ * A callback that runs after each chat completion and after any tool calls from
43
+ * that completion have finished. The callback is awaited before the next
44
+ * request starts or before the runner ends. The runner's mutable `messages`
45
+ * array can be used to add context for the next request.
46
+ */
47
+ afterCompletion?: (completion: ChatCompletion, runner: ChatCompletionRunnerContext) => void | Promise<void>;
30
48
  }
31
49
 
32
50
  export class AbstractChatCompletionRunner<
@@ -113,7 +131,7 @@ export class AbstractChatCompletionRunner<
113
131
  }
114
132
 
115
133
  /**
116
- * @returns a promise that resolves with the the final assistant ChatCompletionMessage response,
134
+ * @returns a promise that resolves with the final assistant ChatCompletionMessage response,
117
135
  * or rejects if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
118
136
  */
119
137
  async finalMessage(): Promise<ChatCompletionMessage> {
@@ -230,11 +248,7 @@ export class AbstractChatCompletionRunner<
230
248
  params: ChatCompletionCreateParams,
231
249
  options?: RequestOptions,
232
250
  ): Promise<ParsedChatCompletion<ParsedT>> {
233
- const signal = options?.signal;
234
- if (signal) {
235
- if (signal.aborted) this.controller.abort();
236
- signal.addEventListener('abort', () => this.controller.abort());
237
- }
251
+ this._listenForAbort(options?.signal);
238
252
  this.#validateParams(params);
239
253
 
240
254
  const chatCompletion = await client.chat.completions.create(
@@ -261,13 +275,14 @@ export class AbstractChatCompletionRunner<
261
275
  params:
262
276
  | ChatCompletionToolRunnerParams<FunctionsArgs>
263
277
  | ChatCompletionStreamingToolRunnerParams<FunctionsArgs>,
278
+ runner: ChatCompletionRunner<any> | ChatCompletionStreamingRunner<any>,
264
279
  options?: RunnerOptions,
265
280
  ) {
266
281
  const role = 'tool' as const;
267
282
  const { tool_choice = 'auto', stream, ...restParams } = params;
268
283
  const singleFunctionToCall =
269
284
  typeof tool_choice !== 'string' && tool_choice.type === 'function' && tool_choice?.function?.name;
270
- const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {};
285
+ const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS, afterCompletion } = options || {};
271
286
 
272
287
  // TODO(someday): clean this logic up
273
288
  const inputTools = params.tools.map((tool): RunnableToolFunction<any> => {
@@ -320,6 +335,54 @@ export class AbstractChatCompletionRunner<
320
335
  this._addMessage(message, false);
321
336
  }
322
337
 
338
+ type ToolCallResult = {
339
+ message: ChatCompletionToolMessageParam | undefined;
340
+ functionCalled: boolean;
341
+ };
342
+
343
+ const runToolCall = async (toolCall: ChatCompletionMessageToolCall): Promise<ToolCallResult> => {
344
+ if (toolCall.type !== 'function') return { message: undefined, functionCalled: false };
345
+
346
+ const tool_call_id = toolCall.id;
347
+ const { name, arguments: args } = toolCall.function;
348
+ const fn = functionsByName[name];
349
+
350
+ if (!fn) {
351
+ const content = `Invalid tool_call: ${JSON.stringify(name)}. Available options are: ${Object.keys(
352
+ functionsByName,
353
+ )
354
+ .map((name) => JSON.stringify(name))
355
+ .join(', ')}. Please try again`;
356
+
357
+ return { message: { role, tool_call_id, content }, functionCalled: false };
358
+ }
359
+
360
+ if (singleFunctionToCall && singleFunctionToCall !== name) {
361
+ const content = `Invalid tool_call: ${JSON.stringify(name)}. ${JSON.stringify(
362
+ singleFunctionToCall,
363
+ )} requested. Please try again`;
364
+
365
+ return { message: { role, tool_call_id, content }, functionCalled: false };
366
+ }
367
+
368
+ let rawContent: unknown;
369
+ if (isRunnableFunctionWithParse(fn)) {
370
+ let parsed;
371
+ try {
372
+ parsed = await fn.parse(args);
373
+ } catch (error) {
374
+ const content = error instanceof Error ? error.message : String(error);
375
+ return { message: { role, tool_call_id, content }, functionCalled: false };
376
+ }
377
+ rawContent = await fn.function(parsed, runner);
378
+ } else {
379
+ rawContent = await fn.function(args, runner);
380
+ }
381
+
382
+ const content = this.#stringifyFunctionCallResult(rawContent);
383
+ return { message: { role, tool_call_id, content }, functionCalled: true };
384
+ };
385
+
323
386
  for (let i = 0; i < maxChatCompletions; ++i) {
324
387
  const chatCompletion: ChatCompletion = await this._createChatCompletion(
325
388
  client,
@@ -336,51 +399,39 @@ export class AbstractChatCompletionRunner<
336
399
  throw new OpenAIError(`missing message in ChatCompletion response`);
337
400
  }
338
401
  if (!message.tool_calls?.length) {
402
+ await afterCompletion?.(chatCompletion, runner);
339
403
  return;
340
404
  }
341
405
 
342
- for (const tool_call of message.tool_calls) {
343
- if (tool_call.type !== 'function') continue;
344
- const tool_call_id = tool_call.id;
345
- const { name, arguments: args } = tool_call.function;
346
- const fn = functionsByName[name];
347
-
348
- if (!fn) {
349
- const content = `Invalid tool_call: ${JSON.stringify(name)}. Available options are: ${Object.keys(
350
- functionsByName,
351
- )
352
- .map((name) => JSON.stringify(name))
353
- .join(', ')}. Please try again`;
354
-
355
- this._addMessage({ role, tool_call_id, content });
356
- continue;
357
- } else if (singleFunctionToCall && singleFunctionToCall !== name) {
358
- const content = `Invalid tool_call: ${JSON.stringify(name)}. ${JSON.stringify(
359
- singleFunctionToCall,
360
- )} requested. Please try again`;
361
-
362
- this._addMessage({ role, tool_call_id, content });
363
- continue;
364
- }
406
+ if (singleFunctionToCall || params.parallel_tool_calls === false) {
407
+ for (const toolCall of message.tool_calls) {
408
+ const result = await runToolCall(toolCall);
409
+ if (result.message) this._addMessage(result.message);
365
410
 
366
- let parsed;
367
- try {
368
- parsed = isRunnableFunctionWithParse(fn) ? await fn.parse(args) : args;
369
- } catch (error) {
370
- const content = error instanceof Error ? error.message : String(error);
371
- this._addMessage({ role, tool_call_id, content });
372
- continue;
411
+ if (singleFunctionToCall && result.functionCalled) {
412
+ await afterCompletion?.(chatCompletion, runner);
413
+ return;
414
+ }
373
415
  }
416
+ } else {
417
+ const results = await Promise.allSettled(message.tool_calls.map(runToolCall));
374
418
 
375
- // @ts-expect-error it can't rule out `never` type.
376
- const rawContent = await fn.function(parsed, this);
377
- const content = this.#stringifyFunctionCallResult(rawContent);
378
- this._addMessage({ role, tool_call_id, content });
419
+ // Wait for every concurrently running tool to settle before surfacing an
420
+ // error so tool side effects cannot continue after the runner has ended.
421
+ for (const result of results) {
422
+ if (result.status === 'rejected') throw result.reason;
423
+ }
379
424
 
380
- if (singleFunctionToCall) {
381
- return;
425
+ // Promise.allSettled preserves input order, so the next request receives
426
+ // tool result messages in the same order as the assistant's tool calls.
427
+ for (const result of results) {
428
+ if (result.status === 'fulfilled' && result.value.message) {
429
+ this._addMessage(result.value.message);
430
+ }
382
431
  }
383
432
  }
433
+
434
+ await afterCompletion?.(chatCompletion, runner);
384
435
  }
385
436
 
386
437
  return;
@@ -165,11 +165,7 @@ export class AssistantStream
165
165
  readableStream: ReadableStream,
166
166
  options?: RequestOptions,
167
167
  ): Promise<Run> {
168
- const signal = options?.signal;
169
- if (signal) {
170
- if (signal.aborted) this.controller.abort();
171
- signal.addEventListener('abort', () => this.controller.abort());
172
- }
168
+ this._listenForAbort(options?.signal);
173
169
  this._connected();
174
170
  const stream = Stream.fromReadableStream<AssistantStreamEvent>(readableStream, this.controller);
175
171
  for await (const event of stream) {
@@ -208,11 +204,7 @@ export class AssistantStream
208
204
  params: RunSubmitToolOutputsParamsStream,
209
205
  options?: RequestOptions,
210
206
  ): Promise<Run> {
211
- const signal = options?.signal;
212
- if (signal) {
213
- if (signal.aborted) this.controller.abort();
214
- signal.addEventListener('abort', () => this.controller.abort());
215
- }
207
+ this._listenForAbort(options?.signal);
216
208
 
217
209
  const body: RunSubmitToolOutputsParamsStreaming = { ...params, stream: true };
218
210
  const stream = await run.submitToolOutputs(runId, body, {
@@ -303,11 +295,7 @@ export class AssistantStream
303
295
  params: ThreadCreateAndRunParamsBase,
304
296
  options?: RequestOptions,
305
297
  ): Promise<Run> {
306
- const signal = options?.signal;
307
- if (signal) {
308
- if (signal.aborted) this.controller.abort();
309
- signal.addEventListener('abort', () => this.controller.abort());
310
- }
298
+ this._listenForAbort(options?.signal);
311
299
 
312
300
  const body: RunCreateParamsStreaming = { ...params, stream: true };
313
301
  const stream = await thread.createAndRun(body, { ...options, signal: this.controller.signal });
@@ -330,11 +318,7 @@ export class AssistantStream
330
318
  params: RunCreateParamsBase,
331
319
  options?: RequestOptions,
332
320
  ): Promise<Run> {
333
- const signal = options?.signal;
334
- if (signal) {
335
- if (signal.aborted) this.controller.abort();
336
- signal.addEventListener('abort', () => this.controller.abort());
337
- }
321
+ this._listenForAbort(options?.signal);
338
322
 
339
323
  const body: RunCreateParamsStreaming = { ...params, stream: true };
340
324
  const stream = await run.create(threadId, body, { ...options, signal: this.controller.signal });
@@ -37,7 +37,7 @@ export class ChatCompletionRunner<ParsedT = null> extends AbstractChatCompletion
37
37
  ...options,
38
38
  headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'runTools' },
39
39
  };
40
- runner._run(() => runner._runTools(client, params, opts));
40
+ runner._run(() => runner._runTools(client, params, runner, opts));
41
41
  return runner;
42
42
  }
43
43
 
@@ -373,11 +373,7 @@ export class ChatCompletionStream<ParsedT = null>
373
373
  options?: RequestOptions,
374
374
  ): Promise<ParsedChatCompletion<ParsedT>> {
375
375
  super._createChatCompletion;
376
- const signal = options?.signal;
377
- if (signal) {
378
- if (signal.aborted) this.controller.abort();
379
- signal.addEventListener('abort', () => this.controller.abort());
380
- }
376
+ this._listenForAbort(options?.signal);
381
377
  this.#beginRequest();
382
378
 
383
379
  const stream = await client.chat.completions.create(
@@ -398,11 +394,7 @@ export class ChatCompletionStream<ParsedT = null>
398
394
  readableStream: ReadableStream,
399
395
  options?: RequestOptions,
400
396
  ): Promise<ChatCompletion> {
401
- const signal = options?.signal;
402
- if (signal) {
403
- if (signal.aborted) this.controller.abort();
404
- signal.addEventListener('abort', () => this.controller.abort());
405
- }
397
+ this._listenForAbort(options?.signal);
406
398
  this.#beginRequest();
407
399
  this._connected();
408
400
  const stream = Stream.fromReadableStream<ChatCompletionChunk>(readableStream, this.controller);
@@ -511,7 +503,8 @@ export class ChatCompletionStream<ParsedT = null>
511
503
  choice.message.content = (choice.message.content || '') + content;
512
504
 
513
505
  if (!choice.message.refusal && this.#getAutoParseableResponseFormat()) {
514
- choice.message.parsed = partialParse(choice.message.content);
506
+ // The partial parser does not accept whitespace-only input.
507
+ choice.message.parsed = choice.message.content.trim() ? partialParse(choice.message.content) : null;
515
508
  }
516
509
  }
517
510
 
@@ -44,7 +44,7 @@ export class ChatCompletionStreamingRunner<ParsedT = null>
44
44
  ...options,
45
45
  headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'runTools' },
46
46
  };
47
- runner._run(() => runner._runTools(client, params, opts));
47
+ runner._run(() => runner._runTools(client, params, runner, opts));
48
48
  return runner;
49
49
  }
50
50
  }
@@ -74,8 +74,17 @@ export class EventEmitter<EventTypes extends Record<string, (...args: any) => an
74
74
  : EventParameters<EventTypes, Event>
75
75
  > {
76
76
  return new Promise((resolve, reject) => {
77
- // TODO: handle errors
78
- this.once(event, resolve as any);
77
+ const onError = (error: unknown) => {
78
+ this.off(event, onEvent as any);
79
+ reject(error);
80
+ };
81
+ const onEvent = (value: unknown) => {
82
+ if (event !== 'error') this.off('error', onError as any);
83
+ resolve(value as any);
84
+ };
85
+
86
+ if (event !== 'error') this.once('error', onError as any);
87
+ this.once(event, onEvent as any);
79
88
  });
80
89
  }
81
90
 
@@ -14,6 +14,7 @@ export class EventStream<EventTypes extends BaseEvents> {
14
14
  #listeners: {
15
15
  [Event in keyof EventTypes]?: EventListeners<EventTypes, Event>;
16
16
  } = {};
17
+ #abortListeners: Array<{ signal: AbortSignal; listener: () => void }> = [];
17
18
 
18
19
  #ended = false;
19
20
  #errored = false;
@@ -72,6 +73,24 @@ export class EventStream<EventTypes extends BaseEvents> {
72
73
  this.controller.abort();
73
74
  }
74
75
 
76
+ protected _listenForAbort(signal: AbortSignal | null | undefined) {
77
+ if (!signal || this.ended) return;
78
+ if (signal.aborted) {
79
+ this.controller.abort();
80
+ return;
81
+ }
82
+
83
+ const listener = () => this.controller.abort();
84
+ signal.addEventListener('abort', listener, { once: true });
85
+ this.#abortListeners.push({ signal, listener });
86
+ }
87
+
88
+ #removeAbortListeners() {
89
+ for (const { signal, listener } of this.#abortListeners.splice(0)) {
90
+ signal.removeEventListener('abort', listener);
91
+ }
92
+ }
93
+
75
94
  /**
76
95
  * Adds the listener function to the end of the listeners array for the event.
77
96
  * No checks are made to see if the listener has already been added. Multiple calls passing
@@ -177,6 +196,7 @@ export class EventStream<EventTypes extends BaseEvents> {
177
196
  }
178
197
 
179
198
  if (event === 'end') {
199
+ this.#removeAbortListeners();
180
200
  this.#ended = true;
181
201
  this.#resolveEndPromise();
182
202
  }
@@ -31,7 +31,7 @@ export function maybeParseResponse<
31
31
  ParsedT = Params extends null ? null : ExtractParsedContentFromParams<NonNullable<Params>>,
32
32
  >(response: Response, params: Params): ParsedResponse<ParsedT> {
33
33
  if (!params || !hasAutoParseableInput(params)) {
34
- return {
34
+ const parsed = {
35
35
  ...response,
36
36
  output_parsed: null,
37
37
  output: response.output.map((item) => {
@@ -55,6 +55,12 @@ export function maybeParseResponse<
55
55
  }
56
56
  }),
57
57
  };
58
+
59
+ if (needsOutputText(response, parsed)) {
60
+ addOutputText(parsed as Response);
61
+ }
62
+
63
+ return parsed;
58
64
  }
59
65
 
60
66
  return parseResponse(response, params);
@@ -64,12 +70,13 @@ export function parseResponse<
64
70
  Params extends ResponseCreateParamsBase,
65
71
  ParsedT = ExtractParsedContentFromParams<Params>,
66
72
  >(response: Response, params: Params): ParsedResponse<ParsedT> {
73
+ const shouldParse = !response.status || response.status === 'completed';
67
74
  const output: Array<ParsedResponseOutputItem<ParsedT>> = response.output.map(
68
75
  (item): ParsedResponseOutputItem<ParsedT> => {
69
76
  if (item.type === 'function_call') {
70
77
  return {
71
78
  ...item,
72
- parsed_arguments: parseToolCall(params, item),
79
+ parsed_arguments: shouldParse ? parseToolCall(params, item) : null,
73
80
  };
74
81
  }
75
82
  if (item.type === 'message') {
@@ -77,7 +84,7 @@ export function parseResponse<
77
84
  if (content.type === 'output_text') {
78
85
  return {
79
86
  ...content,
80
- parsed: parseTextFormat(params, content.text),
87
+ parsed: shouldParse ? parseTextFormat(params, content.text) : null,
81
88
  };
82
89
  }
83
90
 
@@ -95,7 +102,7 @@ export function parseResponse<
95
102
  );
96
103
 
97
104
  const parsed: Omit<ParsedResponse<ParsedT>, 'output_parsed'> = Object.assign({}, response, { output });
98
- if (!Object.getOwnPropertyDescriptor(response, 'output_text')) {
105
+ if (needsOutputText(response, parsed)) {
99
106
  addOutputText(parsed);
100
107
  }
101
108
 
@@ -247,6 +254,13 @@ export function validateInputTools(tools: ChatCompletionTool[] | undefined) {
247
254
  }
248
255
  }
249
256
 
257
+ function needsOutputText(
258
+ response: Response,
259
+ target: { output_text?: Response['output_text'] | null },
260
+ ): boolean {
261
+ return !Object.getOwnPropertyDescriptor(response, 'output_text') || target.output_text == null;
262
+ }
263
+
250
264
  export function addOutputText(rsp: Response): void {
251
265
  const texts: string[] = [];
252
266
  for (const output of rsp.output) {