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