@ai-sdk/openai 4.0.11 → 4.0.14

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,34 @@
1
1
  # @ai-sdk/openai
2
2
 
3
+ ## 4.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [31c7be8]
8
+ - @ai-sdk/provider-utils@5.0.10
9
+
10
+ ## 4.0.13
11
+
12
+ ### Patch Changes
13
+
14
+ - 7805e4a: Fix realtime transcription auth header handling: per-call `authorization` headers now override configuration headers regardless of header-key casing (last case-variant wins), and the `Bearer` scheme is matched case-insensitively.
15
+ - cd12954: Reject empty OpenAI, Anthropic, and Replicate base URLs with a helpful AI SDK
16
+ invalid argument error.
17
+ - Updated dependencies [4be62c1]
18
+ - Updated dependencies [7805e4a]
19
+ - Updated dependencies [cd12954]
20
+ - @ai-sdk/provider-utils@5.0.9
21
+
22
+ ## 4.0.12
23
+
24
+ ### Patch Changes
25
+
26
+ - e193290: Add `connectToWebSocket` to `@ai-sdk/provider-utils`: a shared WebSocket connect layer (constructor resolution, header hygiene, abort wiring, message decoding) analogous to `postToApi` for HTTP. The openai and xai streaming transcription models now use it instead of hand-rolled connects. For openai and xai this also means WebSocket constructor failures now surface as stream errors instead of throwing synchronously from `doStream`, an already-aborted signal no longer constructs a socket, and the caller's audio stream is cancelled on pre-open failures. Messages are processed in order with close handling deferred behind pending frames, audio send loops apply backpressure via the socket's bufferedAmount, and failed sends cancel the caller's audio stream.
27
+ - e193290: Fix streaming transcription over header-capable WebSocket implementations: the realtime WebSocket handshake sent the api key in both the `openai-insecure-api-key` subprotocol and the `Authorization` header, which OpenAI rejects ("You must only send one of protocol api key and Authorization header"). The Authorization header is now stripped when the subprotocol carries the key.
28
+ - e193290: Strip undefined header values before the streaming transcription WebSocket constructor (header-capable implementations like `ws` throw on undefined values).
29
+ - Updated dependencies [e193290]
30
+ - @ai-sdk/provider-utils@5.0.8
31
+
3
32
  ## 4.0.11
4
33
 
5
34
  ### Patch Changes
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  loadApiKey,
4
4
  loadOptionalSetting,
5
+ validateBaseURL,
5
6
  withoutTrailingSlash,
6
7
  withUserAgentSuffix
7
8
  } from "@ai-sdk/provider-utils";
@@ -7907,16 +7908,16 @@ import {
7907
7908
  convertBase64ToUint8Array as convertBase64ToUint8Array2,
7908
7909
  convertToBase64 as convertToBase643,
7909
7910
  createJsonResponseHandler as createJsonResponseHandler7,
7910
- getWebSocketConstructor,
7911
+ connectToWebSocket,
7911
7912
  mediaTypeToExtension,
7912
7913
  parseProviderOptions as parseProviderOptions9,
7913
7914
  postFormDataToApi as postFormDataToApi3,
7914
- readWebSocketMessageText,
7915
7915
  safeParseJSON,
7916
7916
  serializeModelOptions as serializeModelOptions7,
7917
7917
  toWebSocketUrl,
7918
7918
  WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE7,
7919
- WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE7
7919
+ WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE7,
7920
+ waitForWebSocketBufferDrain
7920
7921
  } from "@ai-sdk/provider-utils";
7921
7922
 
7922
7923
  // src/transcription/openai-transcription-api.ts
@@ -8265,21 +8266,18 @@ function createOpenAIRealtimeTranscriptionStream({
8265
8266
  };
8266
8267
  return new ReadableStream({
8267
8268
  start: (controller) => {
8268
- const WebSocketConstructor = getWebSocketConstructor(webSocket);
8269
- const ws = new WebSocketConstructor(
8270
- url,
8271
- getOpenAIRealtimeProtocols(headers),
8272
- { headers }
8273
- );
8269
+ const realtimeConnection = getOpenAIRealtimeConnection(headers);
8274
8270
  let audioReader;
8271
+ let connection;
8275
8272
  cleanup = (closeCode) => {
8276
- abortSignal == null ? void 0 : abortSignal.removeEventListener("abort", abort);
8277
- void (audioReader == null ? void 0 : audioReader.cancel().catch(() => {
8278
- }));
8279
- try {
8280
- ws.close(closeCode);
8281
- } catch (e) {
8273
+ if (audioReader != null) {
8274
+ void audioReader.cancel().catch(() => {
8275
+ });
8276
+ } else {
8277
+ void audio.cancel().catch(() => {
8278
+ });
8282
8279
  }
8280
+ connection == null ? void 0 : connection.close(closeCode);
8283
8281
  };
8284
8282
  const finishWithError = (error) => {
8285
8283
  if (finished) return;
@@ -8302,42 +8300,42 @@ function createOpenAIRealtimeTranscriptionStream({
8302
8300
  controller.close();
8303
8301
  cleanup(1e3);
8304
8302
  };
8305
- const abort = () => {
8306
- var _a;
8307
- finishWithError((_a = abortSignal == null ? void 0 : abortSignal.reason) != null ? _a : new Error("Aborted"));
8308
- };
8309
- if (abortSignal == null ? void 0 : abortSignal.aborted) {
8310
- abort();
8311
- return;
8312
- }
8313
- abortSignal == null ? void 0 : abortSignal.addEventListener("abort", abort, { once: true });
8314
- const sendAudio = async () => {
8303
+ const sendAudio = async (socket) => {
8315
8304
  audioReader = audio.getReader();
8316
8305
  try {
8317
8306
  while (true) {
8318
8307
  const { done, value } = await audioReader.read();
8319
8308
  if (done || finished) break;
8320
- ws.send(
8309
+ socket.send(
8321
8310
  JSON.stringify({
8322
8311
  type: "input_audio_buffer.append",
8323
8312
  audio: convertToBase643(value)
8324
8313
  })
8325
8314
  );
8315
+ await waitForWebSocketBufferDrain(socket);
8326
8316
  }
8327
8317
  } finally {
8328
8318
  audioReader.releaseLock();
8319
+ audioReader = void 0;
8329
8320
  }
8330
8321
  if (!finished) {
8331
- ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
8322
+ socket.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
8332
8323
  }
8333
8324
  };
8334
- ws.onopen = () => {
8335
- controller.enqueue({ type: "stream-start", warnings });
8336
- ws.send(JSON.stringify(sessionUpdate));
8337
- void sendAudio().catch(finishWithError);
8338
- };
8339
- ws.onmessage = (event) => {
8340
- void readWebSocketMessageText(event.data).then(async (text) => {
8325
+ connection = connectToWebSocket({
8326
+ url,
8327
+ protocols: realtimeConnection.protocols,
8328
+ headers: realtimeConnection.headers,
8329
+ webSocket,
8330
+ abortSignal,
8331
+ onAbort: finishWithError,
8332
+ onProcessingError: finishWithError,
8333
+ onOpen: (socket) => {
8334
+ controller.enqueue({ type: "stream-start", warnings });
8335
+ socket.send(JSON.stringify(sessionUpdate));
8336
+ void sendAudio(socket).catch(finishWithError);
8337
+ },
8338
+ onMessageText: async (text) => {
8341
8339
  var _a, _b, _c, _d;
8342
8340
  const parsed = await safeParseJSON({ text });
8343
8341
  if (!parsed.success) return;
@@ -8365,17 +8363,17 @@ function createOpenAIRealtimeTranscriptionStream({
8365
8363
  break;
8366
8364
  }
8367
8365
  }
8368
- }).catch(finishWithError);
8369
- };
8370
- ws.onerror = () => {
8371
- finishWithError(new Error("OpenAI realtime transcription error"));
8372
- };
8373
- ws.onclose = () => {
8374
- if (finished) return;
8375
- finished = true;
8376
- cleanup();
8377
- controller.close();
8378
- };
8366
+ },
8367
+ onSocketError: () => {
8368
+ finishWithError(new Error("OpenAI realtime transcription error"));
8369
+ },
8370
+ onClose: () => {
8371
+ if (finished) return;
8372
+ finished = true;
8373
+ cleanup();
8374
+ controller.close();
8375
+ }
8376
+ });
8379
8377
  },
8380
8378
  cancel: () => {
8381
8379
  if (finished) return;
@@ -8412,11 +8410,26 @@ function buildOpenAIRealtimeTranscriptionSession({
8412
8410
  }
8413
8411
  };
8414
8412
  }
8415
- function getOpenAIRealtimeProtocols(headers) {
8413
+ function getOpenAIRealtimeConnection(headers) {
8416
8414
  var _a;
8417
- const authorization = (_a = headers.Authorization) != null ? _a : headers.authorization;
8418
- const token = (authorization == null ? void 0 : authorization.startsWith("Bearer ")) ? authorization.slice("Bearer ".length) : void 0;
8419
- return token == null ? ["realtime"] : ["realtime", `openai-insecure-api-key.${token}`];
8415
+ let authorization;
8416
+ for (const [key, value] of Object.entries(headers)) {
8417
+ if (key.toLowerCase() === "authorization" && value != null) {
8418
+ authorization = value;
8419
+ }
8420
+ }
8421
+ const token = (_a = authorization == null ? void 0 : authorization.match(/^bearer\s+(.+)$/i)) == null ? void 0 : _a[1];
8422
+ if (token == null) {
8423
+ return { protocols: ["realtime"], headers };
8424
+ }
8425
+ return {
8426
+ protocols: ["realtime", `openai-insecure-api-key.${token}`],
8427
+ headers: Object.fromEntries(
8428
+ Object.entries(headers).filter(
8429
+ ([key]) => key.toLowerCase() !== "authorization"
8430
+ )
8431
+ )
8432
+ };
8420
8433
  }
8421
8434
 
8422
8435
  // src/skills/openai-skills.ts
@@ -8504,16 +8517,18 @@ var OpenAISkills = class {
8504
8517
  };
8505
8518
 
8506
8519
  // src/version.ts
8507
- var VERSION = true ? "4.0.11" : "0.0.0-test";
8520
+ var VERSION = true ? "4.0.14" : "0.0.0-test";
8508
8521
 
8509
8522
  // src/openai-provider.ts
8510
8523
  function createOpenAI(options = {}) {
8511
8524
  var _a, _b;
8512
8525
  const baseURL = (_a = withoutTrailingSlash(
8513
- loadOptionalSetting({
8514
- settingValue: options.baseURL,
8515
- environmentVariableName: "OPENAI_BASE_URL"
8516
- })
8526
+ validateBaseURL(
8527
+ loadOptionalSetting({
8528
+ settingValue: options.baseURL,
8529
+ environmentVariableName: "OPENAI_BASE_URL"
8530
+ })
8531
+ )
8517
8532
  )) != null ? _a : "https://api.openai.com/v1";
8518
8533
  const providerName = (_b = options.name) != null ? _b : "openai";
8519
8534
  const getHeaders = () => withUserAgentSuffix(