@openai/agents-openai 0.4.14 → 0.5.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 (40) hide show
  1. package/dist/defaults.d.ts +4 -0
  2. package/dist/defaults.js +15 -1
  3. package/dist/defaults.js.map +1 -1
  4. package/dist/defaults.mjs +11 -0
  5. package/dist/defaults.mjs.map +1 -1
  6. package/dist/index.d.ts +3 -2
  7. package/dist/index.js +5 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +3 -2
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/metadata.js +2 -2
  12. package/dist/metadata.js.map +1 -1
  13. package/dist/metadata.mjs +2 -2
  14. package/dist/metadata.mjs.map +1 -1
  15. package/dist/openaiProvider.d.ts +7 -0
  16. package/dist/openaiProvider.js +84 -2
  17. package/dist/openaiProvider.js.map +1 -1
  18. package/dist/openaiProvider.mjs +86 -4
  19. package/dist/openaiProvider.mjs.map +1 -1
  20. package/dist/openaiResponsesModel.d.ts +28 -1
  21. package/dist/openaiResponsesModel.js +435 -33
  22. package/dist/openaiResponsesModel.js.map +1 -1
  23. package/dist/openaiResponsesModel.mjs +433 -32
  24. package/dist/openaiResponsesModel.mjs.map +1 -1
  25. package/dist/responsesTransportUtils.d.ts +29 -0
  26. package/dist/responsesTransportUtils.js +208 -0
  27. package/dist/responsesTransportUtils.js.map +1 -0
  28. package/dist/responsesTransportUtils.mjs +198 -0
  29. package/dist/responsesTransportUtils.mjs.map +1 -0
  30. package/dist/responsesWebSocketConnection.d.ts +22 -0
  31. package/dist/responsesWebSocketConnection.js +273 -0
  32. package/dist/responsesWebSocketConnection.js.map +1 -0
  33. package/dist/responsesWebSocketConnection.mjs +259 -0
  34. package/dist/responsesWebSocketConnection.mjs.map +1 -0
  35. package/dist/responsesWebSocketSession.d.ts +22 -0
  36. package/dist/responsesWebSocketSession.js +56 -0
  37. package/dist/responsesWebSocketSession.js.map +1 -0
  38. package/dist/responsesWebSocketSession.mjs +53 -0
  39. package/dist/responsesWebSocketSession.mjs.map +1 -0
  40. package/package.json +2 -2
@@ -1,7 +1,10 @@
1
1
  import { RequestUsage, Usage, withResponseSpan, createResponseSpan, setCurrentSpan, resetCurrentSpan, UserError, } from '@openai/agents-core';
2
+ import OpenAI from 'openai';
2
3
  import logger from "./logger.mjs";
3
4
  import { z } from 'zod';
4
5
  import { HEADERS } from "./defaults.mjs";
6
+ import { ResponsesWebSocketConnection, ResponsesWebSocketInternalError, isWebSocketNotOpenError, shouldWrapNoEventWebSocketError, throwIfAborted, webSocketFrameToText, withAbortSignal, withTimeout, } from "./responsesWebSocketConnection.mjs";
7
+ import { applyHeadersToAccumulator, createHeaderAccumulator, ensureResponsesWebSocketPath, headerAccumulatorToRecord, headerAccumulatorToSDKHeaders, mergeQueryParamsIntoURL, splitResponsesTransportOverrides, } from "./responsesTransportUtils.mjs";
5
8
  import { CodeInterpreterStatus, FileSearchStatus, ImageGenerationStatus, WebSearchStatus, } from "./tools.mjs";
6
9
  import { camelOrSnakeToSnakeCase } from "./utils/providerData.mjs";
7
10
  import { encodeUint8ArrayToBase64 } from '@openai/agents-core/utils';
@@ -1390,23 +1393,51 @@ function convertToOutputItem(items) {
1390
1393
  });
1391
1394
  }
1392
1395
  export { getToolChoice, converTool, getInputItems, convertToOutputItem };
1396
+ const TERMINAL_RESPONSES_STREAM_EVENT_TYPES = new Set([
1397
+ 'response.completed',
1398
+ 'response.failed',
1399
+ 'response.incomplete',
1400
+ 'response.error',
1401
+ ]);
1402
+ function isTerminalResponsesStreamEventType(eventType) {
1403
+ return (typeof eventType === 'string' &&
1404
+ TERMINAL_RESPONSES_STREAM_EVENT_TYPES.has(eventType));
1405
+ }
1393
1406
  /**
1394
1407
  * Model implementation that uses OpenAI's Responses API to generate responses.
1395
1408
  */
1396
1409
  export class OpenAIResponsesModel {
1397
- #client;
1398
- #model;
1410
+ _client;
1411
+ _model;
1399
1412
  constructor(client, model) {
1400
- this.#client = client;
1401
- this.#model = model;
1413
+ this._client = client;
1414
+ this._model = model;
1402
1415
  }
1403
- async #fetchResponse(request, stream) {
1416
+ async _fetchResponse(request, stream) {
1417
+ const builtRequest = this._buildResponsesCreateRequest(request, stream);
1418
+ const response = await this._client.responses.create(builtRequest.requestData, {
1419
+ headers: builtRequest.sdkRequestHeaders,
1420
+ signal: builtRequest.signal,
1421
+ ...(builtRequest.transportExtraQuery
1422
+ ? { query: builtRequest.transportExtraQuery }
1423
+ : {}),
1424
+ });
1425
+ if (logger.dontLogModelData) {
1426
+ logger.debug('Response received');
1427
+ }
1428
+ else {
1429
+ logger.debug(`Response received: ${JSON.stringify(response, null, 2)}`);
1430
+ }
1431
+ return response;
1432
+ }
1433
+ _buildResponsesCreateRequest(request, stream) {
1404
1434
  const input = getInputItems(request.input);
1405
1435
  const { tools, include } = getTools(request.tools, request.handoffs);
1406
1436
  const toolChoice = getToolChoice(request.modelSettings.toolChoice);
1407
- const { text, ...restOfProviderData } = request.modelSettings.providerData ?? {};
1437
+ const { providerData: providerDataWithoutTransport, overrides: transportOverrides, } = splitResponsesTransportOverrides(request.modelSettings.providerData);
1438
+ const { text, ...restOfProviderData } = providerDataWithoutTransport;
1408
1439
  if (request.modelSettings.reasoning) {
1409
- // Merge top-level reasoning settings with provider data
1440
+ // Merge top-level reasoning settings with provider data.
1410
1441
  restOfProviderData.reasoning = {
1411
1442
  ...request.modelSettings.reasoning,
1412
1443
  ...restOfProviderData.reasoning,
@@ -1414,7 +1445,7 @@ export class OpenAIResponsesModel {
1414
1445
  }
1415
1446
  let mergedText = text;
1416
1447
  if (request.modelSettings.text) {
1417
- // Merge top-level text settings with provider data
1448
+ // Merge top-level text settings with provider data.
1418
1449
  mergedText = { ...request.modelSettings.text, ...text };
1419
1450
  }
1420
1451
  const responseFormat = getResponseFormat(request.outputType, mergedText);
@@ -1435,8 +1466,8 @@ export class OpenAIResponsesModel {
1435
1466
  const shouldOmitToolChoice = Boolean(request.prompt) &&
1436
1467
  !shouldSendTools &&
1437
1468
  typeof toolChoice === 'object';
1438
- const requestData = {
1439
- ...(shouldSendModel ? { model: this.#model } : {}),
1469
+ let requestData = {
1470
+ ...(shouldSendModel ? { model: this._model } : {}),
1440
1471
  instructions: normalizeInstructions(request.systemInstructions),
1441
1472
  input,
1442
1473
  include,
@@ -1462,23 +1493,34 @@ export class OpenAIResponsesModel {
1462
1493
  prompt_cache_retention: request.modelSettings.promptCacheRetention,
1463
1494
  ...restOfProviderData,
1464
1495
  };
1465
- if (logger.dontLogModelData) {
1466
- logger.debug('Calling LLM');
1467
- }
1468
- else {
1469
- logger.debug(`Calling LLM. Request data: ${JSON.stringify(requestData, null, 2)}`);
1496
+ if (transportOverrides.extraBody) {
1497
+ requestData = {
1498
+ ...requestData,
1499
+ ...transportOverrides.extraBody,
1500
+ };
1470
1501
  }
1471
- const response = await this.#client.responses.create(requestData, {
1472
- headers: HEADERS,
1473
- signal: request.signal,
1502
+ // Keep the transport mode aligned with the calling path even if extra_body includes stream.
1503
+ requestData.stream = stream;
1504
+ const requestHeaderAccumulator = createHeaderAccumulator();
1505
+ applyHeadersToAccumulator(requestHeaderAccumulator, HEADERS);
1506
+ applyHeadersToAccumulator(requestHeaderAccumulator, transportOverrides.extraHeaders, {
1507
+ allowBlockedOverride: true,
1474
1508
  });
1509
+ const sdkRequestHeaders = headerAccumulatorToSDKHeaders(requestHeaderAccumulator);
1510
+ const builtRequest = {
1511
+ requestData,
1512
+ sdkRequestHeaders,
1513
+ signal: request.signal,
1514
+ transportExtraHeaders: transportOverrides.extraHeaders,
1515
+ transportExtraQuery: transportOverrides.extraQuery,
1516
+ };
1475
1517
  if (logger.dontLogModelData) {
1476
- logger.debug('Response received');
1518
+ logger.debug('Calling LLM');
1477
1519
  }
1478
1520
  else {
1479
- logger.debug(`Response received: ${JSON.stringify(response, null, 2)}`);
1521
+ logger.debug(`Calling LLM. Request data: ${JSON.stringify(builtRequest.requestData, null, 2)}`);
1480
1522
  }
1481
- return response;
1523
+ return builtRequest;
1482
1524
  }
1483
1525
  /**
1484
1526
  * Get a response from the OpenAI model using the Responses API.
@@ -1487,7 +1529,7 @@ export class OpenAIResponsesModel {
1487
1529
  */
1488
1530
  async getResponse(request) {
1489
1531
  const response = await withResponseSpan(async (span) => {
1490
- const response = await this.#fetchResponse(request, false);
1532
+ const response = await this._fetchResponse(request, false);
1491
1533
  if (request.tracing) {
1492
1534
  span.spanData.response_id = response.id;
1493
1535
  span.spanData._input = request.input;
@@ -1527,10 +1569,11 @@ export class OpenAIResponsesModel {
1527
1569
  span.spanData._input = request.input;
1528
1570
  }
1529
1571
  }
1530
- const response = await this.#fetchResponse(request, true);
1572
+ const response = await this._fetchResponse(request, true);
1531
1573
  let finalResponse;
1532
1574
  for await (const event of response) {
1533
- if (event.type === 'response.created') {
1575
+ const eventType = event.type;
1576
+ if (eventType === 'response.created') {
1534
1577
  yield {
1535
1578
  type: 'response_started',
1536
1579
  providerData: {
@@ -1538,9 +1581,10 @@ export class OpenAIResponsesModel {
1538
1581
  },
1539
1582
  };
1540
1583
  }
1541
- else if (event.type === 'response.completed') {
1542
- finalResponse = event.response;
1543
- const { response, ...remainingEvent } = event;
1584
+ else if (isTerminalResponsesStreamEventType(eventType)) {
1585
+ const terminalEvent = event;
1586
+ finalResponse = terminalEvent.response;
1587
+ const { response, ...remainingEvent } = terminalEvent;
1544
1588
  const { output, usage, id, ...remainingResponse } = response;
1545
1589
  yield {
1546
1590
  type: 'response_done',
@@ -1565,12 +1609,14 @@ export class OpenAIResponsesModel {
1565
1609
  },
1566
1610
  providerData: remainingEvent,
1567
1611
  };
1568
- yield {
1569
- type: 'model',
1570
- event: event,
1571
- };
1612
+ if (eventType === 'response.completed') {
1613
+ yield {
1614
+ type: 'model',
1615
+ event: event,
1616
+ };
1617
+ }
1572
1618
  }
1573
- else if (event.type === 'response.output_text.delta') {
1619
+ else if (eventType === 'response.output_text.delta') {
1574
1620
  const { delta, ...remainingEvent } = event;
1575
1621
  yield {
1576
1622
  type: 'output_text_delta',
@@ -1611,6 +1657,343 @@ export class OpenAIResponsesModel {
1611
1657
  }
1612
1658
  }
1613
1659
  }
1660
+ /**
1661
+ * Model implementation that uses the OpenAI Responses API over a websocket transport.
1662
+ *
1663
+ * @see {@link https://developers.openai.com/api/docs/guides/websocket-mode}
1664
+ */
1665
+ export class OpenAIResponsesWSModel extends OpenAIResponsesModel {
1666
+ #websocketBaseURL;
1667
+ #reuseConnection;
1668
+ #wsConnection;
1669
+ #wsConnectionIdentity;
1670
+ #wsRequestLock = Promise.resolve();
1671
+ constructor(client, model, options = {}) {
1672
+ super(client, model);
1673
+ this.#websocketBaseURL = options.websocketBaseURL;
1674
+ this.#reuseConnection = options.reuseConnection ?? true;
1675
+ }
1676
+ async _fetchResponse(request, stream) {
1677
+ // The websocket transport always uses streamed Responses events, then callers either
1678
+ // consume the stream directly or collapse it into the final terminal response.
1679
+ const builtRequest = this._buildResponsesCreateRequest(request, true);
1680
+ if (stream) {
1681
+ return this.#iterWebSocketResponseEvents(builtRequest);
1682
+ }
1683
+ let finalResponse;
1684
+ for await (const event of this.#iterWebSocketResponseEvents(builtRequest)) {
1685
+ const eventType = event.type;
1686
+ if (isTerminalResponsesStreamEventType(eventType)) {
1687
+ finalResponse = event
1688
+ .response;
1689
+ }
1690
+ }
1691
+ if (!finalResponse) {
1692
+ throw new Error('Responses websocket stream ended without a terminal response event.');
1693
+ }
1694
+ return finalResponse;
1695
+ }
1696
+ async close() {
1697
+ await this.#dropWebSocketConnection();
1698
+ }
1699
+ async *#iterWebSocketResponseEvents(builtRequest) {
1700
+ const requestTimeoutDeadline = this.#createWebSocketRequestTimeoutDeadline();
1701
+ const releaseLock = await this.#acquireWebSocketRequestLock(builtRequest.signal, requestTimeoutDeadline);
1702
+ let receivedAnyEvent = false;
1703
+ let sawTerminalResponseEvent = false;
1704
+ try {
1705
+ throwIfAborted(builtRequest.signal);
1706
+ const { frame, wsURL, headers } = await this.#prepareWebSocketRequest(builtRequest, requestTimeoutDeadline);
1707
+ throwIfAborted(builtRequest.signal);
1708
+ let connection = await this.#ensureWebSocketConnection(wsURL, headers, builtRequest.signal, requestTimeoutDeadline);
1709
+ let reusedConnectionForCurrentAttempt = connection.reused;
1710
+ let activeConnection = connection.connection;
1711
+ const setActiveConnection = (nextConnection) => {
1712
+ connection = nextConnection;
1713
+ activeConnection = nextConnection.connection;
1714
+ reusedConnectionForCurrentAttempt = nextConnection.reused;
1715
+ };
1716
+ throwIfAborted(builtRequest.signal);
1717
+ const serializedFrame = JSON.stringify(frame);
1718
+ const sendSerializedFrame = async () => {
1719
+ try {
1720
+ await activeConnection.send(serializedFrame);
1721
+ }
1722
+ catch (error) {
1723
+ if (!isWebSocketNotOpenError(error)) {
1724
+ throw error;
1725
+ }
1726
+ setActiveConnection(await this.#reconnectWebSocketConnection(wsURL, headers, builtRequest.signal, requestTimeoutDeadline));
1727
+ await activeConnection.send(serializedFrame);
1728
+ }
1729
+ };
1730
+ await sendSerializedFrame();
1731
+ while (true) {
1732
+ const rawFrame = await this.#nextWebSocketFrame(activeConnection, builtRequest.signal, requestTimeoutDeadline);
1733
+ if (rawFrame === null) {
1734
+ if (!receivedAnyEvent && reusedConnectionForCurrentAttempt) {
1735
+ // The request frame was already sent on a reused socket. If the
1736
+ // socket closes before the first response event arrives, the server
1737
+ // may still be processing the request, so replaying `response.create`
1738
+ // can duplicate model work and tool side effects.
1739
+ receivedAnyEvent = true;
1740
+ throw new Error('Responses websocket connection closed after sending a request on a reused connection before any response events were received. The request may have been accepted, so the SDK will not automatically retry this websocket request.');
1741
+ }
1742
+ throw new ResponsesWebSocketInternalError('connection_closed_before_terminal_response_event', 'Responses websocket connection closed before a terminal response event.');
1743
+ }
1744
+ const payloadText = await webSocketFrameToText(rawFrame);
1745
+ const payload = JSON.parse(payloadText);
1746
+ const eventType = isRecord(payload) && typeof payload.type === 'string'
1747
+ ? payload.type
1748
+ : undefined;
1749
+ if (eventType === 'error') {
1750
+ receivedAnyEvent = true;
1751
+ throw new Error(`Responses websocket error: ${JSON.stringify(payload)}`);
1752
+ }
1753
+ const event = payload;
1754
+ const isTerminalResponseEvent = isTerminalResponsesStreamEventType(eventType);
1755
+ receivedAnyEvent = true;
1756
+ if (isTerminalResponseEvent) {
1757
+ sawTerminalResponseEvent = true;
1758
+ }
1759
+ yield event;
1760
+ if (isTerminalResponseEvent) {
1761
+ return;
1762
+ }
1763
+ }
1764
+ }
1765
+ catch (error) {
1766
+ if (!receivedAnyEvent &&
1767
+ !(error instanceof OpenAI.APIUserAbortError) &&
1768
+ shouldWrapNoEventWebSocketError(error)) {
1769
+ const wrappedError = new Error('Responses websocket connection closed before any response events were received. The feature may not be enabled for this account or model yet.');
1770
+ if (error instanceof Error) {
1771
+ wrappedError.cause = error;
1772
+ }
1773
+ throw wrappedError;
1774
+ }
1775
+ throw error;
1776
+ }
1777
+ finally {
1778
+ const shouldDropConnection = !sawTerminalResponseEvent || !this.#reuseConnection;
1779
+ const dropConnectionPromise = shouldDropConnection
1780
+ ? this.#dropWebSocketConnection()
1781
+ : undefined;
1782
+ releaseLock();
1783
+ await dropConnectionPromise;
1784
+ }
1785
+ }
1786
+ async #prepareWebSocketRequest(builtRequest, requestTimeoutDeadline) {
1787
+ const wsURL = this.#prepareWebSocketURL(builtRequest.transportExtraQuery);
1788
+ const headers = await this.#mergeWebSocketHeaders(wsURL, builtRequest.transportExtraHeaders, builtRequest.signal, requestTimeoutDeadline);
1789
+ const frame = {
1790
+ ...builtRequest.requestData,
1791
+ type: 'response.create',
1792
+ stream: true,
1793
+ };
1794
+ return { frame, wsURL, headers };
1795
+ }
1796
+ async #mergeWebSocketHeaders(wsURL, extraHeaders, signal, requestTimeoutDeadline) {
1797
+ await this.#awaitWebSocketRequestTimedOperation(this.#refreshClientApiKey(), signal, requestTimeoutDeadline, (configuredTimeoutMs) => `Responses websocket auth header preparation timed out after ${configuredTimeoutMs}ms.`);
1798
+ const headerAccumulator = createHeaderAccumulator();
1799
+ const clientWithInternals = this._client;
1800
+ const handshakeURL = new URL(wsURL);
1801
+ const handshakeQuery = searchParamsToAuthHeaderQuery(handshakeURL.searchParams);
1802
+ const authHeaders = typeof clientWithInternals.authHeaders === 'function'
1803
+ ? await this.#awaitWebSocketRequestTimedOperation(clientWithInternals.authHeaders({
1804
+ method: 'get',
1805
+ path: handshakeURL.pathname,
1806
+ ...(handshakeQuery ? { query: handshakeQuery } : {}),
1807
+ }), signal, requestTimeoutDeadline, (configuredTimeoutMs) => `Responses websocket auth header preparation timed out after ${configuredTimeoutMs}ms.`)
1808
+ : undefined;
1809
+ applyHeadersToAccumulator(headerAccumulator, authHeaders);
1810
+ if (typeof clientWithInternals.authHeaders !== 'function' &&
1811
+ typeof this._client.apiKey === 'string' &&
1812
+ this._client.apiKey.length > 0 &&
1813
+ this._client.apiKey !== 'Missing Key') {
1814
+ applyHeadersToAccumulator(headerAccumulator, {
1815
+ Authorization: `Bearer ${this._client.apiKey}`,
1816
+ });
1817
+ }
1818
+ if (this._client.organization) {
1819
+ applyHeadersToAccumulator(headerAccumulator, {
1820
+ 'OpenAI-Organization': this._client.organization,
1821
+ });
1822
+ }
1823
+ if (this._client.project) {
1824
+ applyHeadersToAccumulator(headerAccumulator, {
1825
+ 'OpenAI-Project': this._client.project,
1826
+ });
1827
+ }
1828
+ applyHeadersToAccumulator(headerAccumulator, clientWithInternals._options?.defaultHeaders);
1829
+ applyHeadersToAccumulator(headerAccumulator, HEADERS);
1830
+ applyHeadersToAccumulator(headerAccumulator, extraHeaders, {
1831
+ allowBlockedOverride: true,
1832
+ });
1833
+ return headerAccumulatorToRecord(headerAccumulator);
1834
+ }
1835
+ #prepareWebSocketURL(extraQuery) {
1836
+ const baseURL = new URL(this.#websocketBaseURL ?? this._client.baseURL);
1837
+ const explicitBaseQuery = typeof this.#websocketBaseURL === 'string'
1838
+ ? new URLSearchParams(baseURL.search)
1839
+ : undefined;
1840
+ const clientWithInternals = this._client;
1841
+ if (baseURL.protocol === 'https:') {
1842
+ baseURL.protocol = 'wss:';
1843
+ }
1844
+ else if (baseURL.protocol === 'http:') {
1845
+ baseURL.protocol = 'ws:';
1846
+ }
1847
+ else if (baseURL.protocol !== 'ws:' && baseURL.protocol !== 'wss:') {
1848
+ throw new UserError(`Unsupported websocket base URL protocol: ${baseURL.protocol}`);
1849
+ }
1850
+ baseURL.pathname = ensureResponsesWebSocketPath(baseURL.pathname);
1851
+ mergeQueryParamsIntoURL(baseURL, clientWithInternals._options?.defaultQuery);
1852
+ if (explicitBaseQuery && Array.from(explicitBaseQuery.keys()).length > 0) {
1853
+ const explicitTopLevelKeys = new Set();
1854
+ for (const key of explicitBaseQuery.keys()) {
1855
+ const bracketIndex = key.indexOf('[');
1856
+ explicitTopLevelKeys.add(bracketIndex >= 0 ? key.slice(0, bracketIndex) : key);
1857
+ }
1858
+ for (const topLevelKey of explicitTopLevelKeys) {
1859
+ for (const existingKey of Array.from(baseURL.searchParams.keys())) {
1860
+ if (existingKey === topLevelKey ||
1861
+ existingKey.startsWith(`${topLevelKey}[`)) {
1862
+ baseURL.searchParams.delete(existingKey);
1863
+ }
1864
+ }
1865
+ }
1866
+ for (const [key, value] of explicitBaseQuery.entries()) {
1867
+ baseURL.searchParams.append(key, value);
1868
+ }
1869
+ }
1870
+ mergeQueryParamsIntoURL(baseURL, extraQuery);
1871
+ return baseURL.toString();
1872
+ }
1873
+ async #ensureWebSocketConnection(wsURL, headers, signal, requestTimeoutDeadline) {
1874
+ const identity = this.#getConnectionIdentity(wsURL, headers);
1875
+ if (this.#wsConnection &&
1876
+ this.#wsConnectionIdentity &&
1877
+ this.#wsConnectionIdentity === identity &&
1878
+ this.#wsConnection.isReusable()) {
1879
+ return { connection: this.#wsConnection, reused: true };
1880
+ }
1881
+ await this.#dropWebSocketConnection();
1882
+ const connectTimeout = this.#resolveWebSocketRequestTimeout(requestTimeoutDeadline, (configuredTimeoutMs) => `Responses websocket connection timed out before opening after ${configuredTimeoutMs}ms.`);
1883
+ this.#wsConnection = await ResponsesWebSocketConnection.connect(wsURL, headers, signal, connectTimeout.timeoutMs, connectTimeout.errorMessage);
1884
+ this.#wsConnectionIdentity = identity;
1885
+ return { connection: this.#wsConnection, reused: false };
1886
+ }
1887
+ async #reconnectWebSocketConnection(wsURL, headers, signal, requestTimeoutDeadline) {
1888
+ await this.#dropWebSocketConnection();
1889
+ throwIfAborted(signal);
1890
+ const connection = await this.#ensureWebSocketConnection(wsURL, headers, signal, requestTimeoutDeadline);
1891
+ throwIfAborted(signal);
1892
+ return connection;
1893
+ }
1894
+ #getConnectionIdentity(wsURL, headers) {
1895
+ const normalizedHeaders = Object.entries(headers)
1896
+ .map(([key, value]) => [key.toLowerCase(), value])
1897
+ .sort(([leftKey, leftValue], [rightKey, rightValue]) => `${leftKey}:${leftValue}`.localeCompare(`${rightKey}:${rightValue}`));
1898
+ return JSON.stringify([wsURL, normalizedHeaders]);
1899
+ }
1900
+ async #dropWebSocketConnection() {
1901
+ const connectionToClose = this.#wsConnection;
1902
+ if (!connectionToClose) {
1903
+ this.#wsConnectionIdentity = undefined;
1904
+ return;
1905
+ }
1906
+ // Detach cached state before awaiting close so queued requests can proceed
1907
+ // without racing against this teardown path.
1908
+ this.#wsConnection = undefined;
1909
+ this.#wsConnectionIdentity = undefined;
1910
+ try {
1911
+ await connectionToClose.close();
1912
+ }
1913
+ catch {
1914
+ // Ignore close errors and reset the cached connection.
1915
+ }
1916
+ }
1917
+ async #acquireWebSocketRequestLock(signal, requestTimeoutDeadline) {
1918
+ throwIfAborted(signal);
1919
+ const queueWaitTimeout = this.#resolveWebSocketRequestTimeout(requestTimeoutDeadline, (configuredTimeoutMs) => `Responses websocket request queue wait timed out after ${configuredTimeoutMs}ms.`);
1920
+ const previousLock = this.#wsRequestLock;
1921
+ let released = false;
1922
+ let resolveOwnLock;
1923
+ const ownLock = new Promise((resolve) => {
1924
+ resolveOwnLock = resolve;
1925
+ });
1926
+ const releaseLock = () => {
1927
+ if (released) {
1928
+ return;
1929
+ }
1930
+ released = true;
1931
+ resolveOwnLock();
1932
+ };
1933
+ this.#wsRequestLock = previousLock.then(() => ownLock);
1934
+ try {
1935
+ await withAbortSignal(withTimeout(previousLock, queueWaitTimeout.timeoutMs, queueWaitTimeout.errorMessage), signal);
1936
+ throwIfAborted(signal);
1937
+ return releaseLock;
1938
+ }
1939
+ catch (error) {
1940
+ releaseLock();
1941
+ throw error;
1942
+ }
1943
+ }
1944
+ async #refreshClientApiKey() {
1945
+ const clientWithInternals = this._client;
1946
+ if (typeof clientWithInternals._callApiKey === 'function') {
1947
+ await clientWithInternals._callApiKey();
1948
+ }
1949
+ }
1950
+ #getWebSocketFrameReadTimeoutMs() {
1951
+ const clientWithTimeout = this._client;
1952
+ const timeoutCandidate = typeof clientWithTimeout.timeout === 'number'
1953
+ ? clientWithTimeout.timeout
1954
+ : clientWithTimeout._options?.timeout;
1955
+ if (typeof timeoutCandidate === 'number') {
1956
+ return timeoutCandidate;
1957
+ }
1958
+ return OpenAI.DEFAULT_TIMEOUT;
1959
+ }
1960
+ #createWebSocketRequestTimeoutDeadline() {
1961
+ const timeoutMs = this.#getWebSocketFrameReadTimeoutMs();
1962
+ if (typeof timeoutMs !== 'number' ||
1963
+ !Number.isFinite(timeoutMs) ||
1964
+ timeoutMs <= 0) {
1965
+ return undefined;
1966
+ }
1967
+ return {
1968
+ configuredTimeoutMs: timeoutMs,
1969
+ deadlineAtMs: Date.now() + timeoutMs,
1970
+ };
1971
+ }
1972
+ #resolveWebSocketRequestTimeout(requestTimeoutDeadline, errorMessageForConfiguredTimeout) {
1973
+ const configuredTimeoutMs = requestTimeoutDeadline?.configuredTimeoutMs ??
1974
+ this.#getWebSocketFrameReadTimeoutMs();
1975
+ const safeConfiguredTimeoutMs = typeof configuredTimeoutMs === 'number'
1976
+ ? configuredTimeoutMs
1977
+ : OpenAI.DEFAULT_TIMEOUT;
1978
+ const errorMessage = errorMessageForConfiguredTimeout(safeConfiguredTimeoutMs);
1979
+ if (!requestTimeoutDeadline) {
1980
+ return { timeoutMs: configuredTimeoutMs, errorMessage };
1981
+ }
1982
+ const remainingTimeoutMs = Math.ceil(requestTimeoutDeadline.deadlineAtMs - Date.now());
1983
+ if (remainingTimeoutMs <= 0) {
1984
+ throw new Error(errorMessage);
1985
+ }
1986
+ return { timeoutMs: remainingTimeoutMs, errorMessage };
1987
+ }
1988
+ async #awaitWebSocketRequestTimedOperation(promise, signal, requestTimeoutDeadline, errorMessageForConfiguredTimeout) {
1989
+ const timeout = this.#resolveWebSocketRequestTimeout(requestTimeoutDeadline, errorMessageForConfiguredTimeout);
1990
+ return await withAbortSignal(withTimeout(promise, timeout.timeoutMs, timeout.errorMessage), signal);
1991
+ }
1992
+ async #nextWebSocketFrame(connection, signal, requestTimeoutDeadline) {
1993
+ const frameReadTimeout = this.#resolveWebSocketRequestTimeout(requestTimeoutDeadline, (configuredTimeoutMs) => `Responses websocket frame read timed out after ${configuredTimeoutMs}ms.`);
1994
+ return await withTimeout(connection.nextFrame(signal), frameReadTimeout.timeoutMs, frameReadTimeout.errorMessage);
1995
+ }
1996
+ }
1614
1997
  /**
1615
1998
  * Sending an empty string for instructions can override the prompt parameter.
1616
1999
  * Thus, this method checks if the instructions is an empty string and returns undefined if it is.
@@ -1626,6 +2009,24 @@ function normalizeInstructions(instructions) {
1626
2009
  }
1627
2010
  return undefined;
1628
2011
  }
2012
+ function searchParamsToAuthHeaderQuery(searchParams) {
2013
+ const query = {};
2014
+ let hasEntries = false;
2015
+ for (const [key, value] of searchParams.entries()) {
2016
+ hasEntries = true;
2017
+ const existingValue = query[key];
2018
+ if (typeof existingValue === 'undefined') {
2019
+ query[key] = value;
2020
+ continue;
2021
+ }
2022
+ if (Array.isArray(existingValue)) {
2023
+ existingValue.push(value);
2024
+ continue;
2025
+ }
2026
+ query[key] = [existingValue, value];
2027
+ }
2028
+ return hasEntries ? query : undefined;
2029
+ }
1629
2030
  function toRequestUsageEntry(usage, endpoint) {
1630
2031
  return new RequestUsage({
1631
2032
  inputTokens: usage?.input_tokens ?? 0,