@ai-sdk/openai 4.0.54 → 4.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @ai-sdk/openai
2
2
 
3
+ ## 4.0.55
4
+
5
+ ### Patch Changes
6
+
7
+ - 3fc40db: feat(openai): implement `getFileMetadata`, `downloadFile` (streaming), and `deleteFile` on the OpenAI files interface, support streaming uploads via `{ type: 'stream' }` data, expose `byteSize`/`createdAt`/`expiresAt` on upload results, and thread `abortSignal`/`headers` through all file operations; blank and dot-segment file ids are rejected/encoded so they cannot retarget request paths
8
+ - Updated dependencies [5190b67]
9
+ - @ai-sdk/provider@4.0.10
10
+ - @ai-sdk/provider-utils@5.0.35
11
+
3
12
  ## 4.0.54
4
13
 
5
14
  ### Patch Changes
package/dist/index.js CHANGED
@@ -2043,12 +2043,19 @@ var OpenAIEmbeddingModel = class _OpenAIEmbeddingModel {
2043
2043
  };
2044
2044
 
2045
2045
  // src/files/openai-files.ts
2046
+ import {
2047
+ InvalidArgumentError
2048
+ } from "@ai-sdk/provider";
2046
2049
  import {
2047
2050
  combineHeaders as combineHeaders4,
2048
2051
  convertInlineFileDataToUint8Array,
2052
+ createBinaryStreamResponseHandler,
2049
2053
  createJsonResponseHandler as createJsonResponseHandler4,
2054
+ deleteFromApi,
2055
+ getFromApi,
2050
2056
  parseProviderOptions as parseProviderOptions4,
2051
- postFormDataToApi
2057
+ postFormDataToApi,
2058
+ postMultipartStreamToApi
2052
2059
  } from "@ai-sdk/provider-utils";
2053
2060
 
2054
2061
  // src/files/openai-files-api.ts
@@ -2068,6 +2075,15 @@ var openaiFilesResponseSchema = lazySchema7(
2068
2075
  })
2069
2076
  )
2070
2077
  );
2078
+ var openaiFileDeleteResponseSchema = lazySchema7(
2079
+ () => zodSchema7(
2080
+ z8.object({
2081
+ id: z8.string(),
2082
+ object: z8.string().nullish(),
2083
+ deleted: z8.boolean()
2084
+ })
2085
+ )
2086
+ );
2071
2087
 
2072
2088
  // src/files/openai-files-options.ts
2073
2089
  import {
@@ -2090,6 +2106,10 @@ var openaiFilesOptionsSchema = lazySchema8(
2090
2106
  );
2091
2107
 
2092
2108
  // src/files/openai-files.ts
2109
+ function encodePathSegment(value) {
2110
+ const encodedValue = encodeURIComponent(value);
2111
+ return encodedValue === "." ? "%252E" : encodedValue === ".." ? "%252E%252E" : encodedValue;
2112
+ }
2093
2113
  var OpenAIFiles = class {
2094
2114
  constructor(config) {
2095
2115
  this.config = config;
@@ -2098,63 +2118,206 @@ var OpenAIFiles = class {
2098
2118
  get provider() {
2099
2119
  return this.config.provider;
2100
2120
  }
2121
+ getFileId(file) {
2122
+ const fileId = file.openai;
2123
+ if (fileId == null || fileId.trim() === "") {
2124
+ throw new InvalidArgumentError({
2125
+ argument: "file",
2126
+ message: "file reference is missing an 'openai' file id."
2127
+ });
2128
+ }
2129
+ return fileId;
2130
+ }
2131
+ getHeaders(headers) {
2132
+ return combineHeaders4(this.config.headers(), headers);
2133
+ }
2101
2134
  async uploadFile({
2102
2135
  data,
2103
2136
  mediaType,
2104
2137
  filename,
2138
+ abortSignal,
2139
+ headers,
2105
2140
  providerOptions
2106
2141
  }) {
2107
2142
  var _a2, _b, _c;
2108
- const openaiOptions = await parseProviderOptions4({
2109
- provider: "openai",
2110
- providerOptions,
2111
- schema: openaiFilesOptionsSchema
2112
- });
2113
- const fileBytes = convertInlineFileDataToUint8Array(data);
2114
- const blob = new Blob([fileBytes], {
2115
- type: mediaType
2116
- });
2117
- const formData = new FormData();
2118
- if (filename != null) {
2119
- formData.append("file", blob, filename);
2120
- } else {
2121
- formData.append("file", blob);
2143
+ let openaiOptions;
2144
+ try {
2145
+ openaiOptions = await parseProviderOptions4({
2146
+ provider: "openai",
2147
+ providerOptions,
2148
+ schema: openaiFilesOptionsSchema
2149
+ });
2150
+ } catch (error) {
2151
+ if (data.type === "stream") {
2152
+ await data.stream.cancel(error).catch(() => {
2153
+ });
2154
+ }
2155
+ throw error;
2122
2156
  }
2123
- formData.append("purpose", (_a2 = openaiOptions == null ? void 0 : openaiOptions.purpose) != null ? _a2 : "assistants");
2124
- if ((openaiOptions == null ? void 0 : openaiOptions.expiresAfter) != null) {
2125
- formData.append("expires_after[anchor]", "created_at");
2126
- formData.append(
2127
- "expires_after[seconds]",
2128
- String(openaiOptions.expiresAfter)
2129
- );
2157
+ const purpose = (_a2 = openaiOptions == null ? void 0 : openaiOptions.purpose) != null ? _a2 : "assistants";
2158
+ const requestHeaders = this.getHeaders(headers);
2159
+ const url = `${this.config.baseURL}/files`;
2160
+ let response;
2161
+ if (data.type === "stream") {
2162
+ const parts = [
2163
+ { type: "field", name: "purpose", value: purpose }
2164
+ ];
2165
+ if ((openaiOptions == null ? void 0 : openaiOptions.expiresAfter) != null) {
2166
+ parts.push(
2167
+ { type: "field", name: "expires_after[anchor]", value: "created_at" },
2168
+ {
2169
+ type: "field",
2170
+ name: "expires_after[seconds]",
2171
+ value: String(openaiOptions.expiresAfter)
2172
+ }
2173
+ );
2174
+ }
2175
+ parts.push({
2176
+ type: "file",
2177
+ name: "file",
2178
+ filename,
2179
+ mediaType,
2180
+ content: data.stream
2181
+ });
2182
+ ({ value: response } = await postMultipartStreamToApi({
2183
+ url,
2184
+ headers: requestHeaders,
2185
+ parts,
2186
+ failedResponseHandler: openaiFailedResponseHandler,
2187
+ successfulResponseHandler: createJsonResponseHandler4(
2188
+ openaiFilesResponseSchema
2189
+ ),
2190
+ abortSignal,
2191
+ fetch: this.config.fetch
2192
+ }));
2193
+ } else {
2194
+ const fileBytes = convertInlineFileDataToUint8Array(data);
2195
+ const blob = new Blob([fileBytes], {
2196
+ type: mediaType
2197
+ });
2198
+ const formData = new FormData();
2199
+ if (filename != null) {
2200
+ formData.append("file", blob, filename);
2201
+ } else {
2202
+ formData.append("file", blob);
2203
+ }
2204
+ formData.append("purpose", purpose);
2205
+ if ((openaiOptions == null ? void 0 : openaiOptions.expiresAfter) != null) {
2206
+ formData.append("expires_after[anchor]", "created_at");
2207
+ formData.append(
2208
+ "expires_after[seconds]",
2209
+ String(openaiOptions.expiresAfter)
2210
+ );
2211
+ }
2212
+ ({ value: response } = await postFormDataToApi({
2213
+ url,
2214
+ headers: requestHeaders,
2215
+ formData,
2216
+ failedResponseHandler: openaiFailedResponseHandler,
2217
+ successfulResponseHandler: createJsonResponseHandler4(
2218
+ openaiFilesResponseSchema
2219
+ ),
2220
+ abortSignal,
2221
+ fetch: this.config.fetch
2222
+ }));
2130
2223
  }
2131
- const { value: response } = await postFormDataToApi({
2132
- url: `${this.config.baseURL}/files`,
2133
- headers: combineHeaders4(this.config.headers()),
2134
- formData,
2224
+ return {
2225
+ warnings: [],
2226
+ providerReference: { openai: response.id },
2227
+ ...((_b = response.filename) != null ? _b : filename) ? { filename: (_c = response.filename) != null ? _c : filename } : {},
2228
+ ...mediaType != null ? { mediaType } : {},
2229
+ ...response.bytes != null ? { byteSize: response.bytes } : {},
2230
+ ...response.created_at != null ? { createdAt: new Date(response.created_at * 1e3) } : {},
2231
+ ...response.expires_at != null ? { expiresAt: new Date(response.expires_at * 1e3) } : {},
2232
+ providerMetadata: {
2233
+ openai: this.toFileMetadata(response)
2234
+ }
2235
+ };
2236
+ }
2237
+ async getFileMetadata({
2238
+ file,
2239
+ abortSignal,
2240
+ headers
2241
+ }) {
2242
+ const fileId = this.getFileId(file);
2243
+ const { value: response } = await getFromApi({
2244
+ url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}`,
2245
+ headers: this.getHeaders(headers),
2135
2246
  failedResponseHandler: openaiFailedResponseHandler,
2136
2247
  successfulResponseHandler: createJsonResponseHandler4(
2137
2248
  openaiFilesResponseSchema
2138
2249
  ),
2139
- fetch: this.config.fetch
2250
+ abortSignal,
2251
+ fetch: this.config.fetch,
2252
+ validateUrl: false
2140
2253
  });
2141
2254
  return {
2142
2255
  warnings: [],
2143
2256
  providerReference: { openai: response.id },
2144
- ...((_b = response.filename) != null ? _b : filename) ? { filename: (_c = response.filename) != null ? _c : filename } : {},
2145
- ...mediaType != null ? { mediaType } : {},
2257
+ ...response.filename != null ? { filename: response.filename } : {},
2258
+ ...response.bytes != null ? { byteSize: response.bytes } : {},
2259
+ ...response.created_at != null ? { createdAt: new Date(response.created_at * 1e3) } : {},
2260
+ ...response.expires_at != null ? { expiresAt: new Date(response.expires_at * 1e3) } : {},
2146
2261
  providerMetadata: {
2147
- openai: {
2148
- ...response.filename != null ? { filename: response.filename } : {},
2149
- ...response.purpose != null ? { purpose: response.purpose } : {},
2150
- ...response.bytes != null ? { bytes: response.bytes } : {},
2151
- ...response.created_at != null ? { createdAt: response.created_at } : {},
2152
- ...response.status != null ? { status: response.status } : {},
2153
- ...response.expires_at != null ? { expiresAt: response.expires_at } : {}
2154
- }
2262
+ openai: this.toFileMetadata(response)
2155
2263
  }
2156
2264
  };
2157
2265
  }
2266
+ async downloadFile({
2267
+ file,
2268
+ abortSignal,
2269
+ headers
2270
+ }) {
2271
+ var _a2;
2272
+ const fileId = this.getFileId(file);
2273
+ const { value: content, responseHeaders } = await getFromApi({
2274
+ url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}/content`,
2275
+ headers: this.getHeaders(headers),
2276
+ failedResponseHandler: openaiFailedResponseHandler,
2277
+ successfulResponseHandler: createBinaryStreamResponseHandler(),
2278
+ abortSignal,
2279
+ fetch: this.config.fetch,
2280
+ validateUrl: false
2281
+ });
2282
+ const mediaType = (_a2 = responseHeaders == null ? void 0 : responseHeaders["content-type"]) == null ? void 0 : _a2.split(";")[0].trim();
2283
+ return {
2284
+ warnings: [],
2285
+ content,
2286
+ ...mediaType ? { mediaType } : {}
2287
+ };
2288
+ }
2289
+ async deleteFile({
2290
+ file,
2291
+ abortSignal,
2292
+ headers
2293
+ }) {
2294
+ const fileId = this.getFileId(file);
2295
+ const { value: response } = await deleteFromApi({
2296
+ url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}`,
2297
+ headers: this.getHeaders(headers),
2298
+ failedResponseHandler: openaiFailedResponseHandler,
2299
+ successfulResponseHandler: createJsonResponseHandler4(
2300
+ openaiFileDeleteResponseSchema
2301
+ ),
2302
+ abortSignal,
2303
+ fetch: this.config.fetch
2304
+ });
2305
+ return {
2306
+ warnings: [],
2307
+ providerReference: { openai: response.id },
2308
+ deleted: response.deleted
2309
+ };
2310
+ }
2311
+ toFileMetadata(response) {
2312
+ return {
2313
+ ...response.filename != null ? { filename: response.filename } : {},
2314
+ ...response.purpose != null ? { purpose: response.purpose } : {},
2315
+ ...response.bytes != null ? { bytes: response.bytes } : {},
2316
+ ...response.created_at != null ? { createdAt: response.created_at } : {},
2317
+ ...response.status != null ? { status: response.status } : {},
2318
+ ...response.expires_at != null ? { expiresAt: response.expires_at } : {}
2319
+ };
2320
+ }
2158
2321
  };
2159
2322
 
2160
2323
  // src/image/openai-image-model.ts
@@ -3355,7 +3518,7 @@ var openaiTools = {
3355
3518
 
3356
3519
  // src/openai-responses-batch.ts
3357
3520
  import {
3358
- InvalidArgumentError,
3521
+ InvalidArgumentError as InvalidArgumentError2,
3359
3522
  InvalidResponseDataError
3360
3523
  } from "@ai-sdk/provider";
3361
3524
  import {
@@ -3363,7 +3526,7 @@ import {
3363
3526
  convertAsyncIteratorToReadableStream,
3364
3527
  createJsonLinesResponseHandler,
3365
3528
  createJsonResponseHandler as createJsonResponseHandler7,
3366
- getFromApi,
3529
+ getFromApi as getFromApi2,
3367
3530
  lazySchema as lazySchema26,
3368
3531
  normalizeBatchRequestCounts,
3369
3532
  postJsonToApi as postJsonToApi6,
@@ -8913,7 +9076,7 @@ var OpenAIResponsesBatch = class {
8913
9076
  const batch = await this.retrieveBatch(options);
8914
9077
  const batchStatus = convertOpenAIBatchStatus(batch);
8915
9078
  if (batchStatus.status === "pending") {
8916
- throw new InvalidArgumentError({
9079
+ throw new InvalidArgumentError2({
8917
9080
  argument: "batchId",
8918
9081
  message: `OpenAI batch "${options.batchId}" is not complete.`
8919
9082
  });
@@ -8932,7 +9095,7 @@ var OpenAIResponsesBatch = class {
8932
9095
  }
8933
9096
  async retrieveBatch(options) {
8934
9097
  var _a2, _b;
8935
- const { value: batch } = await getFromApi({
9098
+ const { value: batch } = await getFromApi2({
8936
9099
  url: this.getUrl(`/batches/${encodeURIComponent(options.batchId)}`),
8937
9100
  headers: combineHeaders7((_b = (_a2 = this.options.config).headers) == null ? void 0 : _b.call(_a2), options.headers),
8938
9101
  failedResponseHandler: openaiFailedResponseHandler,
@@ -8951,7 +9114,7 @@ var OpenAIResponsesBatch = class {
8951
9114
  }) {
8952
9115
  var _a2, _b;
8953
9116
  for (const fileId of fileIds) {
8954
- const { value: lines } = await getFromApi({
9117
+ const { value: lines } = await getFromApi2({
8955
9118
  url: this.getUrl(`/files/${encodeURIComponent(fileId)}/content`),
8956
9119
  headers: combineHeaders7(
8957
9120
  (_b = (_a2 = this.options.config).headers) == null ? void 0 : _b.call(_a2),
@@ -10400,7 +10563,7 @@ function getOpenAIRealtimeConnection(headers) {
10400
10563
 
10401
10564
  // src/speech-translation/openai-speech-translation-model.ts
10402
10565
  import {
10403
- InvalidArgumentError as InvalidArgumentError2
10566
+ InvalidArgumentError as InvalidArgumentError3
10404
10567
  } from "@ai-sdk/provider";
10405
10568
  import {
10406
10569
  combineHeaders as combineHeaders10,
@@ -10447,7 +10610,7 @@ var OpenAISpeechTranslationModel = class _OpenAISpeechTranslationModel {
10447
10610
  async doStream(options) {
10448
10611
  var _a2, _b, _c, _d, _e;
10449
10612
  if (options.targetLanguage == null) {
10450
- throw new InvalidArgumentError2({
10613
+ throw new InvalidArgumentError3({
10451
10614
  argument: "targetLanguage",
10452
10615
  message: `targetLanguage is required for translation model '${this.modelId}'.`
10453
10616
  });
@@ -10687,7 +10850,7 @@ function buildOpenAIRealtimeSpeechTranslationSession({
10687
10850
  }
10688
10851
  function validateOpenAISpeechTranslationInputAudioFormat(inputAudioFormat) {
10689
10852
  if (inputAudioFormat.type !== "audio/pcm" || inputAudioFormat.rate != null && inputAudioFormat.rate !== 24e3) {
10690
- throw new InvalidArgumentError2({
10853
+ throw new InvalidArgumentError3({
10691
10854
  argument: "inputAudioFormat",
10692
10855
  message: "The OpenAI Realtime translation API only supports 24kHz 16-bit PCM input audio."
10693
10856
  });
@@ -10800,7 +10963,7 @@ var OpenAISkills = class {
10800
10963
  };
10801
10964
 
10802
10965
  // src/version.ts
10803
- var VERSION = true ? "4.0.54" : "0.0.0-test";
10966
+ var VERSION = true ? "4.0.55" : "0.0.0-test";
10804
10967
 
10805
10968
  // src/openai-provider.ts
10806
10969
  function createOpenAI(options = {}) {