openai 6.45.0 → 6.47.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 (335) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +9 -2
  3. package/client.d.mts +1 -0
  4. package/client.d.mts.map +1 -1
  5. package/client.d.ts +1 -0
  6. package/client.d.ts.map +1 -1
  7. package/client.js +11 -5
  8. package/client.js.map +1 -1
  9. package/client.mjs +11 -5
  10. package/client.mjs.map +1 -1
  11. package/core/streaming.d.mts.map +1 -1
  12. package/core/streaming.d.ts.map +1 -1
  13. package/core/streaming.js +38 -6
  14. package/core/streaming.js.map +1 -1
  15. package/core/streaming.mjs +38 -6
  16. package/core/streaming.mjs.map +1 -1
  17. package/core/uploads.d.mts +1 -1
  18. package/core/uploads.d.mts.map +1 -1
  19. package/core/uploads.d.ts +1 -1
  20. package/core/uploads.d.ts.map +1 -1
  21. package/core/uploads.js +3 -1
  22. package/core/uploads.js.map +1 -1
  23. package/core/uploads.mjs +1 -0
  24. package/core/uploads.mjs.map +1 -1
  25. package/helpers/audio.d.mts.map +1 -1
  26. package/helpers/audio.d.ts.map +1 -1
  27. package/helpers/audio.js +16 -3
  28. package/helpers/audio.js.map +1 -1
  29. package/helpers/audio.mjs +17 -4
  30. package/helpers/audio.mjs.map +1 -1
  31. package/helpers/zod.d.mts +20 -7
  32. package/helpers/zod.d.mts.map +1 -1
  33. package/helpers/zod.d.ts +20 -7
  34. package/helpers/zod.d.ts.map +1 -1
  35. package/helpers/zod.js +24 -12
  36. package/helpers/zod.js.map +1 -1
  37. package/helpers/zod.mjs +24 -12
  38. package/helpers/zod.mjs.map +1 -1
  39. package/index.d.mts +1 -1
  40. package/index.d.mts.map +1 -1
  41. package/index.d.ts +1 -1
  42. package/index.d.ts.map +1 -1
  43. package/index.js +2 -1
  44. package/index.js.map +1 -1
  45. package/index.mjs +1 -1
  46. package/index.mjs.map +1 -1
  47. package/internal/uploads.d.mts +26 -1
  48. package/internal/uploads.d.mts.map +1 -1
  49. package/internal/uploads.d.ts +26 -1
  50. package/internal/uploads.d.ts.map +1 -1
  51. package/internal/uploads.js +169 -1
  52. package/internal/uploads.js.map +1 -1
  53. package/internal/uploads.mjs +169 -2
  54. package/internal/uploads.mjs.map +1 -1
  55. package/lib/AbstractChatCompletionRunner.d.mts +3 -3
  56. package/lib/AbstractChatCompletionRunner.d.mts.map +1 -1
  57. package/lib/AbstractChatCompletionRunner.d.ts +3 -3
  58. package/lib/AbstractChatCompletionRunner.d.ts.map +1 -1
  59. package/lib/AbstractChatCompletionRunner.js +18 -3
  60. package/lib/AbstractChatCompletionRunner.js.map +1 -1
  61. package/lib/AbstractChatCompletionRunner.mjs +18 -3
  62. package/lib/AbstractChatCompletionRunner.mjs.map +1 -1
  63. package/lib/AssistantStream.d.mts.map +1 -1
  64. package/lib/AssistantStream.d.ts.map +1 -1
  65. package/lib/AssistantStream.js +4 -3
  66. package/lib/AssistantStream.js.map +1 -1
  67. package/lib/AssistantStream.mjs +4 -3
  68. package/lib/AssistantStream.mjs.map +1 -1
  69. package/lib/ChatCompletionRunner.d.mts +26 -2
  70. package/lib/ChatCompletionRunner.d.mts.map +1 -1
  71. package/lib/ChatCompletionRunner.d.ts +26 -2
  72. package/lib/ChatCompletionRunner.d.ts.map +1 -1
  73. package/lib/ChatCompletionRunner.js.map +1 -1
  74. package/lib/ChatCompletionRunner.mjs.map +1 -1
  75. package/lib/ChatCompletionStream.d.mts +14 -1
  76. package/lib/ChatCompletionStream.d.mts.map +1 -1
  77. package/lib/ChatCompletionStream.d.ts +14 -1
  78. package/lib/ChatCompletionStream.d.ts.map +1 -1
  79. package/lib/ChatCompletionStream.js +75 -16
  80. package/lib/ChatCompletionStream.js.map +1 -1
  81. package/lib/ChatCompletionStream.mjs +74 -16
  82. package/lib/ChatCompletionStream.mjs.map +1 -1
  83. package/lib/ChatCompletionStreamingRunner.d.mts +25 -2
  84. package/lib/ChatCompletionStreamingRunner.d.mts.map +1 -1
  85. package/lib/ChatCompletionStreamingRunner.d.ts +25 -2
  86. package/lib/ChatCompletionStreamingRunner.d.ts.map +1 -1
  87. package/lib/ChatCompletionStreamingRunner.js +77 -0
  88. package/lib/ChatCompletionStreamingRunner.js.map +1 -1
  89. package/lib/ChatCompletionStreamingRunner.mjs +78 -1
  90. package/lib/ChatCompletionStreamingRunner.mjs.map +1 -1
  91. package/lib/EventStream.d.mts +13 -0
  92. package/lib/EventStream.d.mts.map +1 -1
  93. package/lib/EventStream.d.ts +13 -0
  94. package/lib/EventStream.d.ts.map +1 -1
  95. package/lib/EventStream.js +107 -2
  96. package/lib/EventStream.js.map +1 -1
  97. package/lib/EventStream.mjs +107 -2
  98. package/lib/EventStream.mjs.map +1 -1
  99. package/lib/RunnableFunction.d.mts +38 -21
  100. package/lib/RunnableFunction.d.mts.map +1 -1
  101. package/lib/RunnableFunction.d.ts +38 -21
  102. package/lib/RunnableFunction.d.ts.map +1 -1
  103. package/lib/RunnableFunction.js.map +1 -1
  104. package/lib/RunnableFunction.mjs.map +1 -1
  105. package/lib/parser.d.mts +2 -2
  106. package/lib/parser.d.mts.map +1 -1
  107. package/lib/parser.d.ts +2 -2
  108. package/lib/parser.d.ts.map +1 -1
  109. package/lib/parser.js.map +1 -1
  110. package/lib/parser.mjs.map +1 -1
  111. package/lib/responses/ResponseAccumulator.d.mts +6 -1
  112. package/lib/responses/ResponseAccumulator.d.mts.map +1 -1
  113. package/lib/responses/ResponseAccumulator.d.ts +6 -1
  114. package/lib/responses/ResponseAccumulator.d.ts.map +1 -1
  115. package/lib/responses/ResponseAccumulator.js +1 -0
  116. package/lib/responses/ResponseAccumulator.js.map +1 -1
  117. package/lib/responses/ResponseAccumulator.mjs +1 -0
  118. package/lib/responses/ResponseAccumulator.mjs.map +1 -1
  119. package/lib/responses/ResponseInputItems.d.mts.map +1 -1
  120. package/lib/responses/ResponseInputItems.d.ts.map +1 -1
  121. package/lib/responses/ResponseInputItems.js +2 -0
  122. package/lib/responses/ResponseInputItems.js.map +1 -1
  123. package/lib/responses/ResponseInputItems.mjs +2 -0
  124. package/lib/responses/ResponseInputItems.mjs.map +1 -1
  125. package/lib/responses/ResponseStream.d.mts +6 -1
  126. package/lib/responses/ResponseStream.d.mts.map +1 -1
  127. package/lib/responses/ResponseStream.d.ts +6 -1
  128. package/lib/responses/ResponseStream.d.ts.map +1 -1
  129. package/lib/responses/ResponseStream.js +21 -0
  130. package/lib/responses/ResponseStream.js.map +1 -1
  131. package/lib/responses/ResponseStream.mjs +21 -0
  132. package/lib/responses/ResponseStream.mjs.map +1 -1
  133. package/package.json +1 -1
  134. package/resources/admin/organization/projects/api-keys.d.mts +12 -0
  135. package/resources/admin/organization/projects/api-keys.d.mts.map +1 -1
  136. package/resources/admin/organization/projects/api-keys.d.ts +12 -0
  137. package/resources/admin/organization/projects/api-keys.d.ts.map +1 -1
  138. package/resources/beta/assistants.d.mts +12 -26
  139. package/resources/beta/assistants.d.mts.map +1 -1
  140. package/resources/beta/assistants.d.ts +12 -26
  141. package/resources/beta/assistants.d.ts.map +1 -1
  142. package/resources/beta/beta.d.mts +6 -1
  143. package/resources/beta/beta.d.mts.map +1 -1
  144. package/resources/beta/beta.d.ts +6 -1
  145. package/resources/beta/beta.d.ts.map +1 -1
  146. package/resources/beta/beta.js +4 -0
  147. package/resources/beta/beta.js.map +1 -1
  148. package/resources/beta/beta.mjs +4 -0
  149. package/resources/beta/beta.mjs.map +1 -1
  150. package/resources/beta/index.d.mts +1 -0
  151. package/resources/beta/index.d.mts.map +1 -1
  152. package/resources/beta/index.d.ts +1 -0
  153. package/resources/beta/index.d.ts.map +1 -1
  154. package/resources/beta/index.js +5 -3
  155. package/resources/beta/index.js.map +1 -1
  156. package/resources/beta/index.mjs +1 -0
  157. package/resources/beta/index.mjs.map +1 -1
  158. package/resources/beta/responses/index.d.mts +6 -0
  159. package/resources/beta/responses/index.d.mts.map +1 -0
  160. package/resources/beta/responses/index.d.ts +6 -0
  161. package/resources/beta/responses/index.d.ts.map +1 -0
  162. package/resources/beta/responses/index.js +11 -0
  163. package/resources/beta/responses/index.js.map +1 -0
  164. package/resources/beta/responses/index.mjs +5 -0
  165. package/resources/beta/responses/index.mjs.map +1 -0
  166. package/resources/beta/responses/input-items.d.mts +69 -0
  167. package/resources/beta/responses/input-items.d.mts.map +1 -0
  168. package/resources/beta/responses/input-items.d.ts +69 -0
  169. package/resources/beta/responses/input-items.d.ts.map +1 -0
  170. package/resources/beta/responses/input-items.js +37 -0
  171. package/resources/beta/responses/input-items.js.map +1 -0
  172. package/resources/beta/responses/input-items.mjs +33 -0
  173. package/resources/beta/responses/input-items.mjs.map +1 -0
  174. package/resources/beta/responses/input-tokens.d.mts +191 -0
  175. package/resources/beta/responses/input-tokens.d.mts.map +1 -0
  176. package/resources/beta/responses/input-tokens.d.ts +191 -0
  177. package/resources/beta/responses/input-tokens.d.ts.map +1 -0
  178. package/resources/beta/responses/input-tokens.js +34 -0
  179. package/resources/beta/responses/input-tokens.js.map +1 -0
  180. package/resources/beta/responses/input-tokens.mjs +30 -0
  181. package/resources/beta/responses/input-tokens.mjs.map +1 -0
  182. package/resources/beta/responses/internal-base.d.mts +71 -0
  183. package/resources/beta/responses/internal-base.d.mts.map +1 -0
  184. package/resources/beta/responses/internal-base.d.ts +71 -0
  185. package/resources/beta/responses/internal-base.d.ts.map +1 -0
  186. package/resources/beta/responses/internal-base.js +48 -0
  187. package/resources/beta/responses/internal-base.js.map +1 -0
  188. package/resources/beta/responses/internal-base.mjs +42 -0
  189. package/resources/beta/responses/internal-base.mjs.map +1 -0
  190. package/resources/beta/responses/responses.d.mts +9986 -0
  191. package/resources/beta/responses/responses.d.mts.map +1 -0
  192. package/resources/beta/responses/responses.d.ts +9986 -0
  193. package/resources/beta/responses/responses.d.ts.map +1 -0
  194. package/resources/beta/responses/responses.js +121 -0
  195. package/resources/beta/responses/responses.js.map +1 -0
  196. package/resources/beta/responses/responses.mjs +116 -0
  197. package/resources/beta/responses/responses.mjs.map +1 -0
  198. package/resources/beta/responses/ws-base.d.mts +106 -0
  199. package/resources/beta/responses/ws-base.d.mts.map +1 -0
  200. package/resources/beta/responses/ws-base.d.ts +106 -0
  201. package/resources/beta/responses/ws-base.d.ts.map +1 -0
  202. package/resources/beta/responses/ws-base.js +476 -0
  203. package/resources/beta/responses/ws-base.js.map +1 -0
  204. package/resources/beta/responses/ws-base.mjs +472 -0
  205. package/resources/beta/responses/ws-base.mjs.map +1 -0
  206. package/resources/beta/responses/ws.d.mts +13 -0
  207. package/resources/beta/responses/ws.d.mts.map +1 -0
  208. package/resources/beta/responses/ws.d.ts +13 -0
  209. package/resources/beta/responses/ws.d.ts.map +1 -0
  210. package/resources/beta/responses/ws.js +31 -0
  211. package/resources/beta/responses/ws.js.map +1 -0
  212. package/resources/beta/responses/ws.mjs +26 -0
  213. package/resources/beta/responses/ws.mjs.map +1 -0
  214. package/resources/beta/responses.d.mts +2 -0
  215. package/resources/beta/responses.d.mts.map +1 -0
  216. package/resources/beta/responses.d.ts +2 -0
  217. package/resources/beta/responses.d.ts.map +1 -0
  218. package/resources/beta/responses.js +6 -0
  219. package/resources/beta/responses.js.map +1 -0
  220. package/resources/beta/responses.mjs +3 -0
  221. package/resources/beta/responses.mjs.map +1 -0
  222. package/resources/beta/threads/runs/runs.d.mts +6 -13
  223. package/resources/beta/threads/runs/runs.d.mts.map +1 -1
  224. package/resources/beta/threads/runs/runs.d.ts +6 -13
  225. package/resources/beta/threads/runs/runs.d.ts.map +1 -1
  226. package/resources/beta/threads/runs/runs.js.map +1 -1
  227. package/resources/beta/threads/runs/runs.mjs.map +1 -1
  228. package/resources/chat/completions/completions.d.mts +170 -20
  229. package/resources/chat/completions/completions.d.mts.map +1 -1
  230. package/resources/chat/completions/completions.d.ts +170 -20
  231. package/resources/chat/completions/completions.d.ts.map +1 -1
  232. package/resources/chat/completions/completions.js.map +1 -1
  233. package/resources/chat/completions/completions.mjs.map +1 -1
  234. package/resources/completions.d.mts +4 -0
  235. package/resources/completions.d.mts.map +1 -1
  236. package/resources/completions.d.ts +4 -0
  237. package/resources/completions.d.ts.map +1 -1
  238. package/resources/conversations/conversations.d.mts +19 -0
  239. package/resources/conversations/conversations.d.mts.map +1 -1
  240. package/resources/conversations/conversations.d.ts +19 -0
  241. package/resources/conversations/conversations.d.ts.map +1 -1
  242. package/resources/conversations/conversations.js.map +1 -1
  243. package/resources/conversations/conversations.mjs.map +1 -1
  244. package/resources/conversations/items.d.mts +45 -1
  245. package/resources/conversations/items.d.mts.map +1 -1
  246. package/resources/conversations/items.d.ts +45 -1
  247. package/resources/conversations/items.d.ts.map +1 -1
  248. package/resources/evals/runs/runs.d.mts +66 -143
  249. package/resources/evals/runs/runs.d.mts.map +1 -1
  250. package/resources/evals/runs/runs.d.ts +66 -143
  251. package/resources/evals/runs/runs.d.ts.map +1 -1
  252. package/resources/evals/runs/runs.js.map +1 -1
  253. package/resources/evals/runs/runs.mjs.map +1 -1
  254. package/resources/graders/grader-models.d.mts +6 -13
  255. package/resources/graders/grader-models.d.mts.map +1 -1
  256. package/resources/graders/grader-models.d.ts +6 -13
  257. package/resources/graders/grader-models.d.ts.map +1 -1
  258. package/resources/realtime/calls.d.mts +1 -1
  259. package/resources/realtime/calls.d.mts.map +1 -1
  260. package/resources/realtime/calls.d.ts +1 -1
  261. package/resources/realtime/calls.d.ts.map +1 -1
  262. package/resources/realtime/client-secrets.d.mts +5 -1
  263. package/resources/realtime/client-secrets.d.mts.map +1 -1
  264. package/resources/realtime/client-secrets.d.ts +5 -1
  265. package/resources/realtime/client-secrets.d.ts.map +1 -1
  266. package/resources/realtime/realtime.d.mts +9 -1
  267. package/resources/realtime/realtime.d.mts.map +1 -1
  268. package/resources/realtime/realtime.d.ts +9 -1
  269. package/resources/realtime/realtime.d.ts.map +1 -1
  270. package/resources/realtime/realtime.js.map +1 -1
  271. package/resources/realtime/realtime.mjs.map +1 -1
  272. package/resources/responses/input-tokens.d.mts +7 -1
  273. package/resources/responses/input-tokens.d.mts.map +1 -1
  274. package/resources/responses/input-tokens.d.ts +7 -1
  275. package/resources/responses/input-tokens.d.ts.map +1 -1
  276. package/resources/responses/responses.d.mts +819 -26
  277. package/resources/responses/responses.d.mts.map +1 -1
  278. package/resources/responses/responses.d.ts +819 -26
  279. package/resources/responses/responses.d.ts.map +1 -1
  280. package/resources/responses/responses.js +1 -1
  281. package/resources/responses/responses.js.map +1 -1
  282. package/resources/responses/responses.mjs +1 -1
  283. package/resources/responses/responses.mjs.map +1 -1
  284. package/resources/shared.d.mts +20 -28
  285. package/resources/shared.d.mts.map +1 -1
  286. package/resources/shared.d.ts +20 -28
  287. package/resources/shared.d.ts.map +1 -1
  288. package/src/client.ts +13 -5
  289. package/src/core/streaming.ts +36 -8
  290. package/src/core/uploads.ts +6 -1
  291. package/src/helpers/audio.ts +18 -4
  292. package/src/helpers/zod.ts +62 -23
  293. package/src/index.ts +1 -1
  294. package/src/internal/uploads.ts +214 -4
  295. package/src/lib/AbstractChatCompletionRunner.ts +32 -9
  296. package/src/lib/AssistantStream.ts +4 -3
  297. package/src/lib/ChatCompletionRunner.ts +46 -6
  298. package/src/lib/ChatCompletionStream.ts +115 -17
  299. package/src/lib/ChatCompletionStreamingRunner.ts +144 -7
  300. package/src/lib/EventStream.ts +114 -4
  301. package/src/lib/RunnableFunction.ts +60 -31
  302. package/src/lib/parser.ts +4 -0
  303. package/src/lib/responses/ResponseAccumulator.ts +10 -1
  304. package/src/lib/responses/ResponseInputItems.ts +2 -0
  305. package/src/lib/responses/ResponseStream.ts +29 -1
  306. package/src/resources/admin/organization/projects/api-keys.ts +15 -1
  307. package/src/resources/beta/assistants.ts +12 -26
  308. package/src/resources/beta/beta.ts +330 -2
  309. package/src/resources/beta/index.ts +163 -0
  310. package/src/resources/beta/responses/index.ts +169 -0
  311. package/src/resources/beta/responses/input-items.ts +105 -0
  312. package/src/resources/beta/responses/input-tokens.ts +245 -0
  313. package/src/resources/beta/responses/internal-base.ts +117 -0
  314. package/src/resources/beta/responses/responses.ts +12633 -0
  315. package/src/resources/beta/responses/ws-base.ts +615 -0
  316. package/src/resources/beta/responses/ws.ts +38 -0
  317. package/src/resources/beta/responses.ts +3 -0
  318. package/src/resources/beta/threads/runs/runs.ts +6 -13
  319. package/src/resources/chat/completions/completions.ts +237 -23
  320. package/src/resources/completions.ts +5 -0
  321. package/src/resources/conversations/conversations.ts +21 -0
  322. package/src/resources/conversations/items.ts +56 -0
  323. package/src/resources/evals/runs/runs.ts +66 -143
  324. package/src/resources/graders/grader-models.ts +6 -13
  325. package/src/resources/realtime/calls.ts +2 -0
  326. package/src/resources/realtime/client-secrets.ts +7 -0
  327. package/src/resources/realtime/realtime.ts +12 -0
  328. package/src/resources/responses/input-tokens.ts +8 -0
  329. package/src/resources/responses/responses.ts +953 -17
  330. package/src/resources/shared.ts +23 -27
  331. package/src/version.ts +1 -1
  332. package/version.d.mts +1 -1
  333. package/version.d.ts +1 -1
  334. package/version.js +1 -1
  335. package/version.mjs +1 -1
@@ -1,16 +1,61 @@
1
1
  import { type RequestOptions } from './request-options';
2
2
  import type { FilePropertyBag, Fetch } from './builtin-types';
3
3
  import type { OpenAI } from '../client';
4
- import { ReadableStreamFrom } from './shims';
4
+ import { buildHeaders } from './headers';
5
+ import { ReadableStreamFrom, ReadableStreamToAsyncIterable } from './shims';
6
+ import type { ReadableStream } from './shim-types';
7
+ import { encodeUTF8 } from './utils/bytes';
5
8
 
6
9
  export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView;
7
10
  type FsReadStream = AsyncIterable<Uint8Array> & { path: string | { toString(): string } };
8
11
 
12
+ export type StreamingFileInput = AsyncIterable<BlobPart> | ReadableStream<BlobPart>;
13
+
14
+ const brand_privateStreamingFile = /* @__PURE__ */ Symbol('brand.privateStreamingFile');
15
+
16
+ /**
17
+ * A file whose contents are read lazily while the multipart request is sent.
18
+ * Create one with {@link toStreamingFile} when buffering an upload into a `File` is undesirable.
19
+ */
20
+ export interface StreamingFile {
21
+ /** Brand check, prevent users from creating a StreamingFile without a filename. */
22
+ readonly [brand_privateStreamingFile]: true;
23
+ readonly data: StreamingFileInput;
24
+ readonly name: string;
25
+ readonly type?: string | undefined;
26
+ }
27
+
28
+ /**
29
+ * Wrap a stream as an uploadable file without reading it into memory.
30
+ *
31
+ * Unlike {@link toFile}, this helper does not create a web `File`, because the `File` constructor
32
+ * must consume all of its contents up front. The stream is instead encoded lazily as multipart
33
+ * form data when the request is sent.
34
+ */
35
+ export function toStreamingFile(
36
+ data: StreamingFileInput,
37
+ name: string,
38
+ options?: Pick<FilePropertyBag, 'type'>,
39
+ ): StreamingFile {
40
+ if (!name) {
41
+ throw new TypeError('toStreamingFile requires a non-empty file name');
42
+ }
43
+
44
+ return {
45
+ [brand_privateStreamingFile]: true,
46
+ data,
47
+ name,
48
+ ...(options?.type ? { type: options.type } : {}),
49
+ };
50
+ }
51
+
9
52
  // https://github.com/oven-sh/bun/issues/5980
10
53
  interface BunFile extends Blob {
11
54
  readonly name?: string | undefined;
12
55
  }
13
56
 
57
+ type NamedBlob = Blob & { readonly name?: string | undefined };
58
+
14
59
  export const checkFileSupport = () => {
15
60
  if (typeof File === 'undefined') {
16
61
  const { process } = globalThis as any;
@@ -34,7 +79,15 @@ export const checkFileSupport = () => {
34
79
  * For convenience, you can also pass a fetch Response, or in Node,
35
80
  * the result of fs.createReadStream().
36
81
  */
37
- export type Uploadable = File | Response | FsReadStream | BunFile;
82
+ export type Uploadable =
83
+ | File
84
+ | Response
85
+ | FsReadStream
86
+ | BunFile
87
+ | NamedBlob
88
+ | AsyncIterable<BlobPart>
89
+ | ReadableStream<BlobPart>
90
+ | StreamingFile;
38
91
 
39
92
  /**
40
93
  * Construct a `File` instance. This is used to ensure a helpful error is thrown
@@ -78,6 +131,10 @@ export const maybeMultipartFormRequestOptions = async (
78
131
  ): Promise<RequestOptions> => {
79
132
  if (!hasUploadableValue(opts.body)) return opts;
80
133
 
134
+ if (hasStreamingUploadableValue(opts.body)) {
135
+ return createStreamingFormRequestOptions(opts);
136
+ }
137
+
81
138
  return { ...opts, body: await createForm(opts.body, fetch) };
82
139
  };
83
140
 
@@ -87,6 +144,10 @@ export const multipartFormRequestOptions = async (
87
144
  opts: MultipartFormRequestOptions,
88
145
  fetch: OpenAI | Fetch,
89
146
  ): Promise<RequestOptions> => {
147
+ if (hasStreamingUploadableValue(opts.body)) {
148
+ return createStreamingFormRequestOptions(opts);
149
+ }
150
+
90
151
  return { ...opts, body: await createForm(opts.body, fetch) };
91
152
  };
92
153
 
@@ -138,12 +199,36 @@ export const createForm = async <T = Record<string, unknown>>(
138
199
 
139
200
  // We check for Blob not File because Bun.File doesn't inherit from File,
140
201
  // but they both inherit from Blob and have a `name` property at runtime.
141
- const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value;
202
+ const isNamedBlob = (value: unknown): value is NamedBlob => value instanceof Blob && 'name' in value;
203
+
204
+ const isReadableStream = (value: unknown): value is ReadableStream<BlobPart> =>
205
+ typeof value === 'object' &&
206
+ value !== null &&
207
+ 'getReader' in value &&
208
+ typeof value.getReader === 'function';
209
+
210
+ const isStreamingFile = (value: unknown): value is StreamingFile =>
211
+ typeof value === 'object' && value !== null && brand_privateStreamingFile in value;
142
212
 
143
213
  const isUploadable = (value: unknown) =>
144
214
  typeof value === 'object' &&
145
215
  value !== null &&
146
- (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value));
216
+ (value instanceof Response ||
217
+ isAsyncIterable(value) ||
218
+ isReadableStream(value) ||
219
+ isStreamingFile(value) ||
220
+ isNamedBlob(value));
221
+
222
+ const hasStreamingUploadableValue = (value: unknown): boolean => {
223
+ if (isStreamingFile(value) || isAsyncIterable(value) || isReadableStream(value)) return true;
224
+ if (Array.isArray(value)) return value.some(hasStreamingUploadableValue);
225
+ if (value && typeof value === 'object' && !isNamedBlob(value) && !(value instanceof Response)) {
226
+ for (const k in value) {
227
+ if (hasStreamingUploadableValue((value as Record<string, unknown>)[k])) return true;
228
+ }
229
+ }
230
+ return false;
231
+ };
147
232
 
148
233
  const hasUploadableValue = (value: unknown): boolean => {
149
234
  if (isUploadable(value)) return true;
@@ -156,6 +241,131 @@ const hasUploadableValue = (value: unknown): boolean => {
156
241
  return false;
157
242
  };
158
243
 
244
+ type FormEntry = { key: string; value: unknown };
245
+
246
+ const createStreamingFormRequestOptions = (opts: RequestOptions): RequestOptions => {
247
+ const boundary = `openai-${Math.random().toString(36).slice(2)}`;
248
+ const body = ReadableStreamFrom(iterateMultipartBody(opts.body, boundary));
249
+
250
+ return {
251
+ ...opts,
252
+ body,
253
+ headers: buildHeaders([{ 'content-type': `multipart/form-data; boundary=${boundary}` }, opts.headers]),
254
+ };
255
+ };
256
+
257
+ async function* iterateMultipartBody(body: unknown, boundary: string): AsyncGenerator<Uint8Array> {
258
+ for await (const { key, value } of iterateFormEntries(body)) {
259
+ yield encodeUTF8(`--${boundary}\r\n`);
260
+ if (isUploadable(value)) {
261
+ const filename = getStreamingFileName(value);
262
+ const type = getStreamingFileType(value);
263
+ yield encodeUTF8(
264
+ `Content-Disposition: form-data; name="${escapeHeaderValue(key)}"; filename="${escapeHeaderValue(
265
+ filename,
266
+ )}"\r\n` + `Content-Type: ${type}\r\n\r\n`,
267
+ );
268
+ yield* iterateBytes(getStreamingFileData(value));
269
+ } else {
270
+ yield encodeUTF8(
271
+ `Content-Disposition: form-data; name="${escapeHeaderValue(key)}"\r\n\r\n${String(value)}`,
272
+ );
273
+ }
274
+ yield encodeUTF8('\r\n');
275
+ }
276
+ yield encodeUTF8(`--${boundary}--\r\n`);
277
+ }
278
+
279
+ async function* iterateFormEntries(body: unknown): AsyncGenerator<FormEntry> {
280
+ if (!body || typeof body !== 'object') return;
281
+
282
+ for (const [key, value] of Object.entries(body)) {
283
+ yield* iterateFormValue(key, value);
284
+ }
285
+ }
286
+
287
+ async function* iterateFormValue(key: string, value: unknown): AsyncGenerator<FormEntry> {
288
+ if (value === undefined) return;
289
+ if (value == null) {
290
+ throw new TypeError(
291
+ `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`,
292
+ );
293
+ }
294
+
295
+ if (
296
+ typeof value === 'string' ||
297
+ typeof value === 'number' ||
298
+ typeof value === 'boolean' ||
299
+ isUploadable(value)
300
+ ) {
301
+ yield { key, value };
302
+ } else if (Array.isArray(value)) {
303
+ for (const entry of value) {
304
+ yield* iterateFormValue(key + '[]', entry);
305
+ }
306
+ } else if (typeof value === 'object') {
307
+ for (const [name, prop] of Object.entries(value)) {
308
+ yield* iterateFormValue(`${key}[${name}]`, prop);
309
+ }
310
+ } else {
311
+ throw new TypeError(
312
+ `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`,
313
+ );
314
+ }
315
+ }
316
+
317
+ function getStreamingFileName(value: Uploadable): string {
318
+ return isStreamingFile(value) ? value.name : getName(value) ?? 'unknown_file';
319
+ }
320
+
321
+ function getStreamingFileType(value: Uploadable): string {
322
+ if (isStreamingFile(value)) return value.type || 'application/octet-stream';
323
+ if (isNamedBlob(value) && value.type) return value.type;
324
+ if (value instanceof Response) return value.headers.get('content-type') || 'application/octet-stream';
325
+ return 'application/octet-stream';
326
+ }
327
+
328
+ function getStreamingFileData(value: Uploadable): unknown {
329
+ if (isStreamingFile(value)) return value.data;
330
+ return value;
331
+ }
332
+
333
+ async function* iterateBytes(value: unknown): AsyncGenerator<Uint8Array> {
334
+ if (typeof value === 'string') {
335
+ yield encodeUTF8(value);
336
+ } else if (ArrayBuffer.isView(value)) {
337
+ yield new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
338
+ } else if (value instanceof ArrayBuffer) {
339
+ yield new Uint8Array(value);
340
+ } else if (value instanceof Response) {
341
+ if (value.body) {
342
+ yield* iterateBytes(value.body);
343
+ } else {
344
+ yield* iterateBytes(await value.blob());
345
+ }
346
+ } else if (value instanceof Blob) {
347
+ if (typeof value.stream === 'function') {
348
+ yield* iterateBytes(value.stream());
349
+ } else {
350
+ yield new Uint8Array(await value.arrayBuffer());
351
+ }
352
+ } else if (isReadableStream(value)) {
353
+ for await (const chunk of ReadableStreamToAsyncIterable<unknown>(value)) {
354
+ yield* iterateBytes(chunk);
355
+ }
356
+ } else if (isAsyncIterable(value)) {
357
+ for await (const chunk of value) {
358
+ yield* iterateBytes(chunk);
359
+ }
360
+ } else {
361
+ throw new TypeError(`Invalid streaming file chunk: ${String(value)}`);
362
+ }
363
+ }
364
+
365
+ function escapeHeaderValue(value: string): string {
366
+ return value.replace(/["\\\r\n]/g, (character) => encodeURIComponent(character));
367
+ }
368
+
159
369
  const addFormValue = async (form: FormData, key: string, value: unknown): Promise<void> => {
160
370
  if (value === undefined) return;
161
371
  if (value == null) {
@@ -1,6 +1,7 @@
1
1
  import { OpenAIError } from '../error';
2
2
  import type OpenAI from '../index';
3
3
  import type { RequestOptions } from '../internal/request-options';
4
+ import { uuid4 } from '../internal/utils/uuid';
4
5
  import { isAutoParsableTool, parseChatCompletion } from '../lib/parser';
5
6
  import type {
6
7
  ChatCompletion,
@@ -14,10 +15,15 @@ import type {
14
15
  ParsedChatCompletion,
15
16
  } from '../resources/chat/completions';
16
17
  import type { CompletionUsage } from '../resources/completions';
17
- import type { ChatCompletionRunner, ChatCompletionToolRunnerParams } from './ChatCompletionRunner';
18
+ import type {
19
+ ChatCompletionRunner,
20
+ ChatCompletionToolRunnerParamsWithContext,
21
+ ChatCompletionToolRunnerParamsWithoutContext,
22
+ } from './ChatCompletionRunner';
18
23
  import type {
19
24
  ChatCompletionStreamingRunner,
20
- ChatCompletionStreamingToolRunnerParams,
25
+ ChatCompletionStreamingToolRunnerParamsWithContext,
26
+ ChatCompletionStreamingToolRunnerParamsWithoutContext,
21
27
  } from './ChatCompletionStreamingRunner';
22
28
  import { isAssistantMessage, isToolMessage } from './chatCompletionUtils';
23
29
  import { BaseEvents, EventStream } from './EventStream';
@@ -30,6 +36,19 @@ import {
30
36
 
31
37
  const DEFAULT_MAX_CHAT_COMPLETIONS = 10;
32
38
 
39
+ function normalizeToolCallIds(chatCompletion: ChatCompletion): void {
40
+ for (const choice of chatCompletion.choices) {
41
+ for (const toolCall of choice.message.tool_calls ?? []) {
42
+ // Some OpenAI-compatible providers omit tool call IDs or return an empty string.
43
+ // Generate a unique ID before the completion is stored or emitted so the assistant
44
+ // tool call and its result message always reference the same value.
45
+ if (!toolCall.id) {
46
+ toolCall.id = `call_${uuid4()}`;
47
+ }
48
+ }
49
+ }
50
+ }
51
+
33
52
  export interface ChatCompletionRunnerContext {
34
53
  messages: ChatCompletionMessageParam[];
35
54
  abort(): void;
@@ -58,6 +77,7 @@ export class AbstractChatCompletionRunner<
58
77
  this: AbstractChatCompletionRunner<AbstractChatCompletionRunnerEvents, ParsedT>,
59
78
  chatCompletion: ParsedChatCompletion<ParsedT>,
60
79
  ): ParsedChatCompletion<ParsedT> {
80
+ normalizeToolCallIds(chatCompletion);
61
81
  this._chatCompletions.push(chatCompletion);
62
82
  this._emit('chatCompletion', chatCompletion);
63
83
  const message = chatCompletion.choices[0]?.message;
@@ -270,16 +290,19 @@ export class AbstractChatCompletionRunner<
270
290
  return await this._createChatCompletion(client, params, options);
271
291
  }
272
292
 
273
- protected async _runTools<FunctionsArgs extends BaseFunctionsArgs>(
293
+ protected async _runTools<FunctionsArgs extends BaseFunctionsArgs, ToolContext>(
274
294
  client: OpenAI,
275
295
  params:
276
- | ChatCompletionToolRunnerParams<FunctionsArgs>
277
- | ChatCompletionStreamingToolRunnerParams<FunctionsArgs>,
296
+ | ChatCompletionToolRunnerParamsWithContext<FunctionsArgs, ToolContext>
297
+ | ChatCompletionToolRunnerParamsWithoutContext<FunctionsArgs>
298
+ | ChatCompletionStreamingToolRunnerParamsWithContext<FunctionsArgs, ToolContext>
299
+ | ChatCompletionStreamingToolRunnerParamsWithoutContext<FunctionsArgs>,
278
300
  runner: ChatCompletionRunner<any> | ChatCompletionStreamingRunner<any>,
279
301
  options?: RunnerOptions,
280
302
  ) {
281
303
  const role = 'tool' as const;
282
- const { tool_choice = 'auto', stream, ...restParams } = params;
304
+ const { tool_choice = 'auto', stream, toolContext: inputToolContext, ...restParams } = params;
305
+ const toolContext = inputToolContext as ToolContext;
283
306
  const singleFunctionToCall =
284
307
  typeof tool_choice !== 'string' && tool_choice.type === 'function' && tool_choice?.function?.name;
285
308
  const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS, afterCompletion } = options || {};
@@ -307,7 +330,7 @@ export class AbstractChatCompletionRunner<
307
330
  return tool as any as RunnableToolFunction<any>;
308
331
  });
309
332
 
310
- const functionsByName: Record<string, RunnableFunction<any>> = {};
333
+ const functionsByName: Record<string, RunnableFunction<any, ToolContext>> = {};
311
334
  for (const f of inputTools) {
312
335
  if (f.type === 'function') {
313
336
  functionsByName[f.function.name || f.function.function.name] = f.function;
@@ -374,9 +397,9 @@ export class AbstractChatCompletionRunner<
374
397
  const content = error instanceof Error ? error.message : String(error);
375
398
  return { message: { role, tool_call_id, content }, functionCalled: false };
376
399
  }
377
- rawContent = await fn.function(parsed, runner);
400
+ rawContent = await fn.function(parsed, runner, toolContext);
378
401
  } else {
379
- rawContent = await fn.function(args, runner);
402
+ rawContent = await fn.function(args, runner, toolContext);
380
403
  }
381
404
 
382
405
  const content = this.#stringifyFunctionCallResult(rawContent);
@@ -103,11 +103,12 @@ export class AssistantStream
103
103
 
104
104
  //Catch all for passing along all events
105
105
  this.on('event', (event) => {
106
+ const eventCopy = structuredClone(event);
106
107
  const reader = readQueue.shift();
107
108
  if (reader) {
108
- reader.resolve(event);
109
+ reader.resolve(eventCopy);
109
110
  } else {
110
- pushQueue.push(event);
111
+ pushQueue.push(eventCopy);
111
112
  }
112
113
  });
113
114
 
@@ -686,7 +687,7 @@ export class AssistantStream
686
687
 
687
688
  const accEntry = accValue[index];
688
689
  if (accEntry == null) {
689
- accValue.push(deltaEntry);
690
+ accValue[index] = deltaEntry;
690
691
  } else {
691
692
  accValue[index] = this.accumulateDelta(accEntry, deltaEntry);
692
693
  }
@@ -16,20 +16,60 @@ export interface ChatCompletionRunnerEvents extends AbstractChatCompletionRunner
16
16
  content: (content: string) => void;
17
17
  }
18
18
 
19
- export type ChatCompletionToolRunnerParams<FunctionsArgs extends BaseFunctionsArgs> = Omit<
20
- ChatCompletionCreateParamsNonStreaming,
21
- 'tools'
22
- > & {
23
- tools: RunnableTools<FunctionsArgs> | AutoParseableTool<any, true>[];
19
+ type ChatCompletionToolRunnerParamsBase = Omit<ChatCompletionCreateParamsNonStreaming, 'tools'>;
20
+
21
+ /**
22
+ * Parameters for tools that do not require a context value.
23
+ */
24
+ export type ChatCompletionToolRunnerParamsWithoutContext<FunctionsArgs extends BaseFunctionsArgs> =
25
+ ChatCompletionToolRunnerParamsBase & {
26
+ tools: RunnableTools<FunctionsArgs> | AutoParseableTool<any, true>[];
27
+ toolContext?: never;
28
+ };
29
+
30
+ /**
31
+ * Parameters for tools that require a context value.
32
+ */
33
+ export type ChatCompletionToolRunnerParamsWithContext<
34
+ FunctionsArgs extends BaseFunctionsArgs,
35
+ ToolContext,
36
+ > = ChatCompletionToolRunnerParamsBase & {
37
+ tools: RunnableTools<FunctionsArgs, ToolContext> | AutoParseableTool<any, true>[];
38
+ /**
39
+ * Context to pass to each tool callback during this run.
40
+ */
41
+ toolContext: ToolContext;
24
42
  };
25
43
 
44
+ /**
45
+ * Parameters for running tools. Supplying a context type makes `toolContext`
46
+ * required; omitting it preserves the existing no-context form.
47
+ */
48
+ export type ChatCompletionToolRunnerParams<FunctionsArgs extends BaseFunctionsArgs, ToolContext = never> = [
49
+ ToolContext,
50
+ ] extends [never] ?
51
+ ChatCompletionToolRunnerParamsWithoutContext<FunctionsArgs>
52
+ : ChatCompletionToolRunnerParamsWithContext<FunctionsArgs, ToolContext>;
53
+
26
54
  export class ChatCompletionRunner<ParsedT = null> extends AbstractChatCompletionRunner<
27
55
  ChatCompletionRunnerEvents,
28
56
  ParsedT
29
57
  > {
58
+ static runTools<ParsedT, ToolContext = unknown>(
59
+ client: OpenAI,
60
+ params: ChatCompletionToolRunnerParamsWithContext<any[], ToolContext>,
61
+ options?: RunnerOptions,
62
+ ): ChatCompletionRunner<ParsedT>;
30
63
  static runTools<ParsedT>(
31
64
  client: OpenAI,
32
- params: ChatCompletionToolRunnerParams<any[]>,
65
+ params: ChatCompletionToolRunnerParamsWithoutContext<any[]>,
66
+ options?: RunnerOptions,
67
+ ): ChatCompletionRunner<ParsedT>;
68
+ static runTools<ParsedT, ToolContext = unknown>(
69
+ client: OpenAI,
70
+ params:
71
+ | ChatCompletionToolRunnerParamsWithContext<any[], ToolContext>
72
+ | ChatCompletionToolRunnerParamsWithoutContext<any[]>,
33
73
  options?: RunnerOptions,
34
74
  ): ChatCompletionRunner<ParsedT> {
35
75
  const runner = new ChatCompletionRunner<ParsedT>();
@@ -8,6 +8,7 @@ import {
8
8
  import OpenAI from '../index';
9
9
  import { RequestOptions } from '../internal/request-options';
10
10
  import { type ReadableStream } from '../internal/shim-types';
11
+ import { uuid4 } from '../internal/utils/uuid';
11
12
  import {
12
13
  AutoParseableResponseFormat,
13
14
  hasAutoParseableInput,
@@ -25,6 +26,7 @@ import {
25
26
  type ChatCompletionCreateParams,
26
27
  type ChatCompletionCreateParamsBase,
27
28
  type ChatCompletionCreateParamsStreaming,
29
+ type ChatCompletionMessageParam,
28
30
  type ChatCompletionRole,
29
31
  } from '../resources/chat/completions/completions';
30
32
  import { Stream } from '../streaming';
@@ -117,6 +119,69 @@ export type ChatCompletionStreamParams = Omit<ChatCompletionCreateParamsBase, 's
117
119
  stream?: true;
118
120
  };
119
121
 
122
+ type ChatCompletionReadableStreamMessage = {
123
+ type: 'message';
124
+ message: ChatCompletionMessageParam;
125
+ tool_call_ids?: string[];
126
+ };
127
+
128
+ // Keep message records readable as empty chunks by older SDKs. Their finalizer
129
+ // overwrites `object`, so the encoded payload does not leak into completions.
130
+ const CHAT_COMPLETION_READABLE_STREAM_MESSAGE_PREFIX = 'chat.completion.chunk.message:';
131
+
132
+ type ChatCompletionReadableStreamMessageChunk = Pick<ChatCompletionChunk, 'id' | 'created' | 'model'> & {
133
+ choices: [];
134
+ object: `${typeof CHAT_COMPLETION_READABLE_STREAM_MESSAGE_PREFIX}${string}`;
135
+ };
136
+
137
+ export type ChatCompletionReadableStreamItem =
138
+ | ChatCompletionChunk
139
+ | ChatCompletionReadableStreamMessage
140
+ | ChatCompletionReadableStreamMessageChunk;
141
+
142
+ export function makeChatCompletionReadableStreamMessageChunk(
143
+ chunk: ChatCompletionChunk,
144
+ message: ChatCompletionMessageParam,
145
+ toolCallIds?: string[],
146
+ ): ChatCompletionReadableStreamMessageChunk {
147
+ const payload: ChatCompletionReadableStreamMessage = {
148
+ type: 'message',
149
+ message,
150
+ ...(toolCallIds ? { tool_call_ids: toolCallIds } : {}),
151
+ };
152
+
153
+ return {
154
+ id: chunk.id,
155
+ choices: [],
156
+ created: chunk.created,
157
+ model: chunk.model,
158
+ object: `${CHAT_COMPLETION_READABLE_STREAM_MESSAGE_PREFIX}${JSON.stringify(payload)}`,
159
+ };
160
+ }
161
+
162
+ function isChatCompletionReadableStreamMessage(
163
+ item: ChatCompletionReadableStreamItem,
164
+ ): item is ChatCompletionReadableStreamMessage | ChatCompletionReadableStreamMessageChunk {
165
+ return (
166
+ ('type' in item && item.type === 'message' && 'message' in item) ||
167
+ ('object' in item &&
168
+ typeof item.object === 'string' &&
169
+ item.object.startsWith(CHAT_COMPLETION_READABLE_STREAM_MESSAGE_PREFIX))
170
+ );
171
+ }
172
+
173
+ function getChatCompletionReadableStreamMessage(
174
+ item: ChatCompletionReadableStreamMessage | ChatCompletionReadableStreamMessageChunk,
175
+ ): ChatCompletionReadableStreamMessage {
176
+ if ('type' in item) {
177
+ return item;
178
+ }
179
+
180
+ return JSON.parse(
181
+ item.object.slice(CHAT_COMPLETION_READABLE_STREAM_MESSAGE_PREFIX.length),
182
+ ) as ChatCompletionReadableStreamMessage;
183
+ }
184
+
120
185
  interface ChoiceEventState {
121
186
  content_done: boolean;
122
187
  refusal_done: boolean;
@@ -204,27 +269,28 @@ export class ChatCompletionStream<ParsedT = null>
204
269
 
205
270
  for (const choice of chunk.choices) {
206
271
  const choiceSnapshot = completion.choices[choice.index]!;
272
+ const { delta } = choice;
207
273
 
208
274
  if (
209
- choice.delta.content != null &&
275
+ delta?.content != null &&
210
276
  choiceSnapshot.message?.role === 'assistant' &&
211
277
  choiceSnapshot.message?.content
212
278
  ) {
213
- this._emit('content', choice.delta.content, choiceSnapshot.message.content);
279
+ this._emit('content', delta.content, choiceSnapshot.message.content);
214
280
  this._emit('content.delta', {
215
- delta: choice.delta.content,
281
+ delta: delta.content,
216
282
  snapshot: choiceSnapshot.message.content,
217
283
  parsed: choiceSnapshot.message.parsed,
218
284
  });
219
285
  }
220
286
 
221
287
  if (
222
- choice.delta.refusal != null &&
288
+ delta?.refusal != null &&
223
289
  choiceSnapshot.message?.role === 'assistant' &&
224
290
  choiceSnapshot.message?.refusal
225
291
  ) {
226
292
  this._emit('refusal.delta', {
227
- delta: choice.delta.refusal,
293
+ delta: delta.refusal,
228
294
  snapshot: choiceSnapshot.message.refusal,
229
295
  });
230
296
  }
@@ -253,7 +319,7 @@ export class ChatCompletionStream<ParsedT = null>
253
319
  }
254
320
  }
255
321
 
256
- for (const toolCall of choice.delta.tool_calls ?? []) {
322
+ for (const toolCall of delta?.tool_calls ?? []) {
257
323
  if (state.current_tool_call_index !== toolCall.index) {
258
324
  this.#emitContentDoneEvents(choiceSnapshot);
259
325
 
@@ -266,7 +332,7 @@ export class ChatCompletionStream<ParsedT = null>
266
332
  state.current_tool_call_index = toolCall.index;
267
333
  }
268
334
 
269
- for (const toolCallDelta of choice.delta.tool_calls ?? []) {
335
+ for (const toolCallDelta of delta?.tool_calls ?? []) {
270
336
  const toolCallSnapshot = choiceSnapshot.message.tool_calls?.[toolCallDelta.index];
271
337
  if (!toolCallSnapshot?.type) {
272
338
  continue;
@@ -397,21 +463,51 @@ export class ChatCompletionStream<ParsedT = null>
397
463
  this._listenForAbort(options?.signal);
398
464
  this.#beginRequest();
399
465
  this._connected();
400
- const stream = Stream.fromReadableStream<ChatCompletionChunk>(readableStream, this.controller);
466
+ const stream = Stream.fromReadableStream<ChatCompletionReadableStreamItem>(
467
+ readableStream,
468
+ this.controller,
469
+ );
401
470
  let chatId;
402
- for await (const chunk of stream) {
403
- if (chatId && chatId !== chunk.id) {
471
+ for await (const item of stream) {
472
+ if (isChatCompletionReadableStreamMessage(item)) {
473
+ const message = getChatCompletionReadableStreamMessage(item);
474
+ if (this.#currentChatCompletionSnapshot) {
475
+ const toolCalls = this.#currentChatCompletionSnapshot.choices[0]?.message.tool_calls;
476
+ for (const [index, id] of message.tool_call_ids?.entries() ?? []) {
477
+ const toolCall = toolCalls?.[index];
478
+ if (toolCall && id) {
479
+ toolCall.id = id;
480
+ }
481
+ }
482
+
483
+ this._addChatCompletion(this.#endRequest());
484
+ chatId = undefined;
485
+ }
486
+ this._addMessage(message.message);
487
+ continue;
488
+ }
489
+
490
+ const chunk = item;
491
+
492
+ if (chatId && chunk.id && chatId !== chunk.id) {
404
493
  // A new request has been made.
405
494
  this._addChatCompletion(this.#endRequest());
406
495
  }
407
496
 
408
497
  this.#addChunk(chunk);
409
- chatId = chunk.id;
498
+ if (chunk.id) chatId = chunk.id;
410
499
  }
411
500
  if (stream.controller.signal?.aborted) {
412
501
  throw new APIUserAbortError();
413
502
  }
414
- return this._addChatCompletion(this.#endRequest());
503
+ if (this.#currentChatCompletionSnapshot) {
504
+ return this._addChatCompletion(this.#endRequest());
505
+ }
506
+ const lastChatCompletion = this._chatCompletions[this._chatCompletions.length - 1];
507
+ if (lastChatCompletion) {
508
+ return lastChatCompletion;
509
+ }
510
+ throw new OpenAIError(`request ended without sending any chunks`);
415
511
  }
416
512
 
417
513
  #getAutoParseableResponseFormat(): AutoParseableResponseFormat<ParsedT> | null {
@@ -431,7 +527,7 @@ export class ChatCompletionStream<ParsedT = null>
431
527
  ...rest,
432
528
  choices: [],
433
529
  };
434
- } else {
530
+ } else if (chunk.id) {
435
531
  Object.assign(snapshot, rest);
436
532
  }
437
533
 
@@ -657,9 +753,6 @@ function finalizeChatCompletion<ParsedT>(
657
753
  tool_calls: tool_calls.map((tool_call, i) => {
658
754
  const { function: fn, type, id, ...toolRest } = tool_call;
659
755
  const { arguments: args, name, ...fnRest } = fn || {};
660
- if (id == null) {
661
- throw new OpenAIError(`missing choices[${index}].tool_calls[${i}].id\n${str(snapshot)}`);
662
- }
663
756
  if (type == null) {
664
757
  throw new OpenAIError(`missing choices[${index}].tool_calls[${i}].type\n${str(snapshot)}`);
665
758
  }
@@ -674,7 +767,12 @@ function finalizeChatCompletion<ParsedT>(
674
767
  );
675
768
  }
676
769
 
677
- return { ...toolRest, id, type, function: { ...fnRest, name, arguments: args } };
770
+ return {
771
+ ...toolRest,
772
+ id: id || `call_${uuid4()}`,
773
+ type,
774
+ function: { ...fnRest, name, arguments: args },
775
+ };
678
776
  }),
679
777
  },
680
778
  };