openai 7.15.0 → 7.16.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/CHANGELOG.md +34 -0
- package/README.md +6 -0
- package/bedrock.d.mts.map +1 -1
- package/bedrock.d.ts.map +1 -1
- package/bedrock.js +9 -6
- package/bedrock.js.map +1 -1
- package/bedrock.mjs +9 -6
- package/bedrock.mjs.map +1 -1
- package/client.d.mts +13 -3
- package/client.d.mts.map +1 -1
- package/client.d.ts +13 -3
- package/client.d.ts.map +1 -1
- package/client.js +37 -20
- package/client.js.map +1 -1
- package/client.mjs +37 -20
- package/client.mjs.map +1 -1
- package/internal/realtime-credentials.d.mts +1 -1
- package/internal/realtime-credentials.d.ts +1 -1
- package/internal/realtime-credentials.js +1 -1
- package/internal/realtime-credentials.mjs +1 -1
- package/internal/utils/abort.d.mts +11 -0
- package/internal/utils/abort.d.mts.map +1 -0
- package/internal/utils/abort.d.ts +11 -0
- package/internal/utils/abort.d.ts.map +1 -0
- package/internal/utils/abort.js +87 -0
- package/internal/utils/abort.js.map +1 -0
- package/internal/utils/abort.mjs +83 -0
- package/internal/utils/abort.mjs.map +1 -0
- package/internal/ws.d.mts +13 -0
- package/internal/ws.d.mts.map +1 -1
- package/internal/ws.d.ts +13 -0
- package/internal/ws.d.ts.map +1 -1
- package/internal/ws.js +10 -0
- package/internal/ws.js.map +1 -1
- package/internal/ws.mjs +9 -0
- package/internal/ws.mjs.map +1 -1
- package/lib/live/transcript-grouping.d.mts +2 -2
- package/lib/live/transcript-grouping.d.mts.map +1 -1
- package/lib/live/transcript-grouping.d.ts +2 -2
- package/lib/live/transcript-grouping.d.ts.map +1 -1
- package/lib/live/transcript-grouping.js +44 -22
- package/lib/live/transcript-grouping.js.map +1 -1
- package/lib/live/transcript-grouping.mjs +44 -22
- package/lib/live/transcript-grouping.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/beta/responses/internal-base.d.mts.map +1 -1
- package/resources/beta/responses/internal-base.d.ts.map +1 -1
- package/resources/beta/responses/internal-base.js +6 -0
- package/resources/beta/responses/internal-base.js.map +1 -1
- package/resources/beta/responses/internal-base.mjs +6 -0
- package/resources/beta/responses/internal-base.mjs.map +1 -1
- package/resources/beta/responses/ws-base.d.mts +13 -8
- package/resources/beta/responses/ws-base.d.mts.map +1 -1
- package/resources/beta/responses/ws-base.d.ts +13 -8
- package/resources/beta/responses/ws-base.d.ts.map +1 -1
- package/resources/beta/responses/ws-base.js +85 -14
- package/resources/beta/responses/ws-base.js.map +1 -1
- package/resources/beta/responses/ws-base.mjs +87 -16
- package/resources/beta/responses/ws-base.mjs.map +1 -1
- package/resources/beta/responses/ws.d.mts +1 -0
- package/resources/beta/responses/ws.d.mts.map +1 -1
- package/resources/beta/responses/ws.d.ts +1 -0
- package/resources/beta/responses/ws.d.ts.map +1 -1
- package/resources/beta/responses/ws.js.map +1 -1
- package/resources/beta/responses/ws.mjs.map +1 -1
- package/resources/live/forks/ws-base.d.mts +13 -8
- package/resources/live/forks/ws-base.d.mts.map +1 -1
- package/resources/live/forks/ws-base.d.ts +13 -8
- package/resources/live/forks/ws-base.d.ts.map +1 -1
- package/resources/live/forks/ws-base.js +85 -14
- package/resources/live/forks/ws-base.js.map +1 -1
- package/resources/live/forks/ws-base.mjs +87 -16
- package/resources/live/forks/ws-base.mjs.map +1 -1
- package/resources/live/forks/ws.d.mts +1 -0
- package/resources/live/forks/ws.d.mts.map +1 -1
- package/resources/live/forks/ws.d.ts +1 -0
- package/resources/live/forks/ws.d.ts.map +1 -1
- package/resources/live/forks/ws.js.map +1 -1
- package/resources/live/forks/ws.mjs.map +1 -1
- package/resources/live/sideband/ws-base.d.mts +13 -8
- package/resources/live/sideband/ws-base.d.mts.map +1 -1
- package/resources/live/sideband/ws-base.d.ts +13 -8
- package/resources/live/sideband/ws-base.d.ts.map +1 -1
- package/resources/live/sideband/ws-base.js +85 -14
- package/resources/live/sideband/ws-base.js.map +1 -1
- package/resources/live/sideband/ws-base.mjs +87 -16
- package/resources/live/sideband/ws-base.mjs.map +1 -1
- package/resources/live/sideband/ws.d.mts +1 -0
- package/resources/live/sideband/ws.d.mts.map +1 -1
- package/resources/live/sideband/ws.d.ts +1 -0
- package/resources/live/sideband/ws.d.ts.map +1 -1
- package/resources/live/sideband/ws.js.map +1 -1
- package/resources/live/sideband/ws.mjs.map +1 -1
- package/resources/live/ws-base.d.mts +13 -8
- package/resources/live/ws-base.d.mts.map +1 -1
- package/resources/live/ws-base.d.ts +13 -8
- package/resources/live/ws-base.d.ts.map +1 -1
- package/resources/live/ws-base.js +85 -14
- package/resources/live/ws-base.js.map +1 -1
- package/resources/live/ws-base.mjs +87 -16
- package/resources/live/ws-base.mjs.map +1 -1
- package/resources/live/ws.d.mts +1 -0
- package/resources/live/ws.d.mts.map +1 -1
- package/resources/live/ws.d.ts +1 -0
- package/resources/live/ws.d.ts.map +1 -1
- package/resources/live/ws.js.map +1 -1
- package/resources/live/ws.mjs.map +1 -1
- package/resources/responses/internal-base.d.mts.map +1 -1
- package/resources/responses/internal-base.d.ts.map +1 -1
- package/resources/responses/internal-base.js +6 -0
- package/resources/responses/internal-base.js.map +1 -1
- package/resources/responses/internal-base.mjs +6 -0
- package/resources/responses/internal-base.mjs.map +1 -1
- package/resources/responses/ws-base.d.mts +13 -8
- package/resources/responses/ws-base.d.mts.map +1 -1
- package/resources/responses/ws-base.d.ts +13 -8
- package/resources/responses/ws-base.d.ts.map +1 -1
- package/resources/responses/ws-base.js +85 -14
- package/resources/responses/ws-base.js.map +1 -1
- package/resources/responses/ws-base.mjs +87 -16
- package/resources/responses/ws-base.mjs.map +1 -1
- package/resources/responses/ws.d.mts +1 -0
- package/resources/responses/ws.d.mts.map +1 -1
- package/resources/responses/ws.d.ts +1 -0
- package/resources/responses/ws.d.ts.map +1 -1
- package/resources/responses/ws.js.map +1 -1
- package/resources/responses/ws.mjs.map +1 -1
- package/src/bedrock.ts +18 -7
- package/src/client.ts +43 -19
- package/src/internal/realtime-credentials.ts +1 -1
- package/src/internal/utils/abort.ts +111 -0
- package/src/internal/ws.ts +22 -0
- package/src/lib/live/transcript-grouping.ts +59 -27
- package/src/resources/beta/responses/internal-base.ts +5 -0
- package/src/resources/beta/responses/ws-base.ts +110 -21
- package/src/resources/beta/responses/ws.ts +2 -0
- package/src/resources/live/forks/ws-base.ts +110 -21
- package/src/resources/live/forks/ws.ts +2 -0
- package/src/resources/live/sideband/ws-base.ts +110 -21
- package/src/resources/live/sideband/ws.ts +2 -0
- package/src/resources/live/ws-base.ts +110 -21
- package/src/resources/live/ws.ts +2 -0
- package/src/resources/responses/internal-base.ts +5 -0
- package/src/resources/responses/ws-base.ts +110 -21
- package/src/resources/responses/ws.ts +2 -0
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/client.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestIni
|
|
|
5
5
|
import { uuid4 } from './internal/utils/uuid';
|
|
6
6
|
import { validatePositiveInteger, isAbsoluteURL, safeJSON, hasOwn } from './internal/utils/values';
|
|
7
7
|
import { sleep } from './internal/utils/sleep';
|
|
8
|
+
import { addRequestAbortListener, retainRequestAbortCallback } from './internal/utils/abort';
|
|
8
9
|
export type { Logger, LogLevel } from './internal/utils/log';
|
|
9
10
|
import { castToError, isAbortError } from './internal/errors';
|
|
10
11
|
import { addRequestID, defaultParseResponse, type APIResponseProps } from './internal/parse';
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
import * as Uploads from './core/uploads';
|
|
48
49
|
import * as API from './resources/index';
|
|
49
50
|
import { APIPromise } from './core/api-promise';
|
|
51
|
+
import { resolveRealtimeAPIKey } from './internal/realtime-credentials';
|
|
50
52
|
import {
|
|
51
53
|
Batch,
|
|
52
54
|
BatchCreateParams,
|
|
@@ -300,8 +302,9 @@ export interface ClientOptions {
|
|
|
300
302
|
*
|
|
301
303
|
* - Accepts either a static string or an async function that resolves to a string.
|
|
302
304
|
* - Defaults to process.env['OPENAI_API_KEY'].
|
|
303
|
-
* - When a function is provided, it is invoked
|
|
304
|
-
*
|
|
305
|
+
* - When a function is provided, it is invoked when building bearer authentication
|
|
306
|
+
* headers for each attempt, including retries and direct `buildRequest()` calls.
|
|
307
|
+
* Each invocation's result is used for its own request.
|
|
305
308
|
* - The function must return a non-empty string; otherwise an OpenAIError is thrown.
|
|
306
309
|
* - If the function throws, the error is wrapped in an OpenAIError with the original
|
|
307
310
|
* error available as `cause`.
|
|
@@ -816,10 +819,11 @@ export class OpenAI {
|
|
|
816
819
|
: await authentication.getToken();
|
|
817
820
|
return buildHeaders([{ Authorization: `Bearer ${token}` }]);
|
|
818
821
|
}
|
|
819
|
-
|
|
822
|
+
const { apiKey } = await resolveRealtimeAPIKey(this);
|
|
823
|
+
if (apiKey == null) {
|
|
820
824
|
return undefined;
|
|
821
825
|
}
|
|
822
|
-
return buildHeaders([{ Authorization: `Bearer ${
|
|
826
|
+
return buildHeaders([{ Authorization: `Bearer ${apiKey}` }]);
|
|
823
827
|
}
|
|
824
828
|
|
|
825
829
|
protected async adminAPIKeyAuth(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
|
|
@@ -857,7 +861,7 @@ export class OpenAI {
|
|
|
857
861
|
* Returns whether a provider was invoked. Internal callers can capture this
|
|
858
862
|
* invocation's key before another request updates the shared `apiKey` property.
|
|
859
863
|
* Overrides should forward `capture` or invoke it with their own resolved key
|
|
860
|
-
* to preserve
|
|
864
|
+
* to preserve invocation-local credentials in concurrent requests and Realtime factories.
|
|
861
865
|
* @internal
|
|
862
866
|
*/
|
|
863
867
|
async _callApiKey(capture?: (apiKey: string | null) => void): Promise<boolean> {
|
|
@@ -919,15 +923,11 @@ export class OpenAI {
|
|
|
919
923
|
|
|
920
924
|
/**
|
|
921
925
|
* Used as a callback for mutating the given `FinalRequestOptions` object.
|
|
926
|
+
* Function-based credentials are resolved later, when building authentication
|
|
927
|
+
* headers, including for direct `buildRequest()` calls. Overriding this hook
|
|
928
|
+
* does not bypass that resolution.
|
|
922
929
|
*/
|
|
923
|
-
protected async prepareOptions(options: FinalRequestOptions): Promise<void> {
|
|
924
|
-
if (this._provider) return;
|
|
925
|
-
|
|
926
|
-
const security = options.__security ?? { bearerAuth: true };
|
|
927
|
-
if (security.bearerAuth) {
|
|
928
|
-
await this._callApiKey();
|
|
929
|
-
}
|
|
930
|
-
}
|
|
930
|
+
protected async prepareOptions(options: FinalRequestOptions): Promise<void> {}
|
|
931
931
|
|
|
932
932
|
/**
|
|
933
933
|
* Used as a callback for mutating the given `RequestInit` object.
|
|
@@ -1574,7 +1574,8 @@ export class OpenAI {
|
|
|
1574
1574
|
const { signal, method, ...options } = init || {};
|
|
1575
1575
|
const abort = this._makeAbort(controller);
|
|
1576
1576
|
const composed = !!signal && composedCallerSignals.get(controller) === signal;
|
|
1577
|
-
|
|
1577
|
+
const cleanup =
|
|
1578
|
+
signal && !composed ? addRequestAbortListener(signal, abort, controller.signal) : undefined;
|
|
1578
1579
|
|
|
1579
1580
|
const timeout = setTimeout(abort, ms);
|
|
1580
1581
|
|
|
@@ -1596,9 +1597,13 @@ export class OpenAI {
|
|
|
1596
1597
|
|
|
1597
1598
|
try {
|
|
1598
1599
|
// use undefined this binding; fetch errors if bound to something else in browser/cloudflare
|
|
1599
|
-
|
|
1600
|
+
const response = await (this.#x509Fetch ?? this.fetch).call(undefined, url, fetchOptions);
|
|
1601
|
+
if (cleanup) {
|
|
1602
|
+
retainRequestAbortCallback(response.body ?? response, abort, controller.signal);
|
|
1603
|
+
}
|
|
1604
|
+
return response;
|
|
1600
1605
|
} catch (err) {
|
|
1601
|
-
|
|
1606
|
+
cleanup?.();
|
|
1602
1607
|
throw err;
|
|
1603
1608
|
} finally {
|
|
1604
1609
|
clearTimeout(timeout);
|
|
@@ -1701,6 +1706,12 @@ export class OpenAI {
|
|
|
1701
1706
|
return sleepSeconds * jitter * 1000;
|
|
1702
1707
|
}
|
|
1703
1708
|
|
|
1709
|
+
/**
|
|
1710
|
+
* Builds a request, resolving callback credentials when constructing authentication
|
|
1711
|
+
* headers, after any subclass request-option rewrites. Calling this method directly
|
|
1712
|
+
* also resolves credentials. Complete replacement builders own authentication and
|
|
1713
|
+
* can call `this.authHeaders()` to resolve headers with request-local credentials.
|
|
1714
|
+
*/
|
|
1704
1715
|
async buildRequest(
|
|
1705
1716
|
inputOptions: FinalRequestOptions,
|
|
1706
1717
|
{ retryCount = 0 }: { retryCount?: number } = {},
|
|
@@ -1745,6 +1756,10 @@ export class OpenAI {
|
|
|
1745
1756
|
options.signal = snapshot.signal;
|
|
1746
1757
|
}
|
|
1747
1758
|
}
|
|
1759
|
+
const authenticationHeaders =
|
|
1760
|
+
this._provider || x509Authentication
|
|
1761
|
+
? undefined
|
|
1762
|
+
: await this.authHeaders(inputOptions, inputOptions.__security ?? { bearerAuth: true });
|
|
1748
1763
|
const { bodyHeaders, body, isStreamingBody } = this.buildBody({ options });
|
|
1749
1764
|
|
|
1750
1765
|
if (isStreamingBody) {
|
|
@@ -1759,6 +1774,7 @@ export class OpenAI {
|
|
|
1759
1774
|
options: inputOptions,
|
|
1760
1775
|
method,
|
|
1761
1776
|
bodyHeaders,
|
|
1777
|
+
authenticationHeaders,
|
|
1762
1778
|
retryCount,
|
|
1763
1779
|
x509Headers,
|
|
1764
1780
|
x509Timeout: explicitTimeout ? options.timeout : undefined,
|
|
@@ -1783,6 +1799,7 @@ export class OpenAI {
|
|
|
1783
1799
|
options,
|
|
1784
1800
|
method,
|
|
1785
1801
|
bodyHeaders,
|
|
1802
|
+
authenticationHeaders,
|
|
1786
1803
|
retryCount,
|
|
1787
1804
|
x509Headers,
|
|
1788
1805
|
x509Timeout,
|
|
@@ -1791,6 +1808,7 @@ export class OpenAI {
|
|
|
1791
1808
|
options: FinalRequestOptions;
|
|
1792
1809
|
method: HTTPMethod;
|
|
1793
1810
|
bodyHeaders: HeadersLike;
|
|
1811
|
+
authenticationHeaders: NullableHeaders | undefined;
|
|
1794
1812
|
retryCount: number;
|
|
1795
1813
|
x509Headers?: { defaultHeaders: NullableHeaders; requestHeaders: NullableHeaders } | undefined;
|
|
1796
1814
|
x509Timeout: number | undefined;
|
|
@@ -1816,9 +1834,10 @@ export class OpenAI {
|
|
|
1816
1834
|
'OpenAI-Organization': x509Tenant ? x509Tenant.organization : this.organization,
|
|
1817
1835
|
'OpenAI-Project': x509Tenant ? x509Tenant.project : this.project,
|
|
1818
1836
|
},
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1837
|
+
// X.509 owns streaming uploads before authentication so it can retire them on failure.
|
|
1838
|
+
this.#x509Authentication && !this.#x509Authentication.isPlanningRequest()
|
|
1839
|
+
? await this.authHeaders(options, options.__security ?? { bearerAuth: true })
|
|
1840
|
+
: authenticationHeaders,
|
|
1822
1841
|
x509Headers?.defaultHeaders ?? this._options.defaultHeaders,
|
|
1823
1842
|
bodyHeaders,
|
|
1824
1843
|
x509Headers?.requestHeaders ?? options.headers,
|
|
@@ -2024,6 +2043,11 @@ OpenAI.Evals = Evals;
|
|
|
2024
2043
|
OpenAI.Containers = Containers;
|
|
2025
2044
|
OpenAI.Skills = Skills;
|
|
2026
2045
|
OpenAI.Videos = Videos;
|
|
2046
|
+
OpenAI.ConversationCursorPage = Pagination.ConversationCursorPage;
|
|
2047
|
+
OpenAI.CursorPage = Pagination.CursorPage;
|
|
2048
|
+
OpenAI.NextCursorPage = Pagination.NextCursorPage;
|
|
2049
|
+
OpenAI.Page = Pagination.Page;
|
|
2050
|
+
OpenAI.TokenPage = Pagination.TokenPage;
|
|
2027
2051
|
|
|
2028
2052
|
const composedCallerSignals = new WeakMap<AbortController, AbortSignal>();
|
|
2029
2053
|
|
|
@@ -9,7 +9,7 @@ export function getRealtimeAPIKey(
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Captures the key belonging to this factory invocation while retaining the
|
|
12
|
+
* Captures the key belonging to this request or factory invocation while retaining the
|
|
13
13
|
* existing boolean credential-hook contract. Legacy overrides that do not
|
|
14
14
|
* capture a key keep their shared-property behavior and remain responsible for
|
|
15
15
|
* synchronizing concurrent credential updates.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
type AbortCallback = () => void;
|
|
2
|
+
interface WeakReference<T> {
|
|
3
|
+
deref: () => T | undefined;
|
|
4
|
+
}
|
|
5
|
+
type WeakAbortCallback = WeakReference<AbortCallback>;
|
|
6
|
+
interface AbortFinalizer {
|
|
7
|
+
register: (target: AbortCallback, cleanup: AbortCallback, token: object) => void;
|
|
8
|
+
unregister: (token: object) => boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Keep these optional runtime features out of the SDK's ES2020 type requirements.
|
|
12
|
+
const weakGlobals = globalThis as typeof globalThis & {
|
|
13
|
+
WeakRef?: new <T extends object>(target: T) => WeakReference<T>;
|
|
14
|
+
FinalizationRegistry?: new (cleanup: (value: AbortCallback) => void) => AbortFinalizer;
|
|
15
|
+
};
|
|
16
|
+
const finalizer =
|
|
17
|
+
typeof weakGlobals.FinalizationRegistry === 'function'
|
|
18
|
+
? new weakGlobals.FinalizationRegistry((cleanup) => {
|
|
19
|
+
try {
|
|
20
|
+
cleanup();
|
|
21
|
+
} catch {
|
|
22
|
+
// Caller-provided signal methods must not throw out of a GC callback.
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
const callbackOwners = new WeakMap<object, Set<AbortCallback>>();
|
|
27
|
+
const subscriptions = new WeakMap<AbortSignal, { callbacks: Set<WeakAbortCallback>; abort: AbortCallback }>();
|
|
28
|
+
|
|
29
|
+
// This scope receives only a weak reference, so its closures cannot retain the callback.
|
|
30
|
+
function subscribeWeakly(signal: AbortSignal, reference: WeakAbortCallback, registry: AbortFinalizer) {
|
|
31
|
+
let subscription = subscriptions.get(signal);
|
|
32
|
+
if (!subscription) {
|
|
33
|
+
const callbacks = new Set<WeakAbortCallback>();
|
|
34
|
+
const abort = () => {
|
|
35
|
+
subscriptions.delete(signal);
|
|
36
|
+
for (const callback of callbacks) {
|
|
37
|
+
registry.unregister(callback);
|
|
38
|
+
callback.deref()?.();
|
|
39
|
+
}
|
|
40
|
+
callbacks.clear();
|
|
41
|
+
};
|
|
42
|
+
subscription = { callbacks, abort };
|
|
43
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
44
|
+
subscriptions.set(signal, subscription);
|
|
45
|
+
}
|
|
46
|
+
const owner = subscription;
|
|
47
|
+
owner.callbacks.add(reference);
|
|
48
|
+
return () => {
|
|
49
|
+
owner.callbacks.delete(reference);
|
|
50
|
+
registry.unregister(reference);
|
|
51
|
+
if (owner.callbacks.size === 0) {
|
|
52
|
+
if (subscriptions.get(signal) === owner) {
|
|
53
|
+
subscriptions.delete(signal);
|
|
54
|
+
}
|
|
55
|
+
signal.removeEventListener('abort', owner.abort);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The listener must not retain the shared owner or its other request callbacks.
|
|
61
|
+
function releaseOnAbort(
|
|
62
|
+
signal: AbortSignal,
|
|
63
|
+
callbacks: WeakReference<Set<AbortCallback>>,
|
|
64
|
+
abort: AbortCallback,
|
|
65
|
+
) {
|
|
66
|
+
signal.addEventListener('abort', () => callbacks.deref()?.delete(abort), { once: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Keep cancellation alive until abort or collection of the response body or bodyless custom response. */
|
|
70
|
+
export function retainRequestAbortCallback(
|
|
71
|
+
owner: object,
|
|
72
|
+
abort: AbortCallback,
|
|
73
|
+
requestSignal: AbortSignal,
|
|
74
|
+
): void {
|
|
75
|
+
if (typeof weakGlobals.WeakRef === 'function' && finalizer && !requestSignal.aborted) {
|
|
76
|
+
let callbacks = callbackOwners.get(owner);
|
|
77
|
+
if (!callbacks) {
|
|
78
|
+
callbacks = new Set();
|
|
79
|
+
callbackOwners.set(owner, callbacks);
|
|
80
|
+
}
|
|
81
|
+
callbacks.add(abort);
|
|
82
|
+
releaseOnAbort(requestSignal, new weakGlobals.WeakRef(callbacks), abort);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Share one caller listener without it retaining completed requests. Collection removes
|
|
88
|
+
* weak subscriptions eventually; a live response body or custom response retains its callback.
|
|
89
|
+
* Runtimes without weak references keep the existing listener-based behavior.
|
|
90
|
+
*/
|
|
91
|
+
export function addRequestAbortListener(
|
|
92
|
+
signal: AbortSignal,
|
|
93
|
+
abort: AbortCallback,
|
|
94
|
+
requestSignal: AbortSignal,
|
|
95
|
+
): AbortCallback {
|
|
96
|
+
if (signal.aborted) {
|
|
97
|
+
abort();
|
|
98
|
+
return () => {
|
|
99
|
+
// No listener was installed for an already aborted signal.
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (typeof weakGlobals.WeakRef !== 'function' || !finalizer) {
|
|
103
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
104
|
+
return () => signal.removeEventListener('abort', abort);
|
|
105
|
+
}
|
|
106
|
+
const reference = new weakGlobals.WeakRef(abort);
|
|
107
|
+
const cleanup = subscribeWeakly(signal, reference, finalizer);
|
|
108
|
+
finalizer.register(abort, cleanup, reference);
|
|
109
|
+
retainRequestAbortCallback(requestSignal, abort, requestSignal);
|
|
110
|
+
return cleanup;
|
|
111
|
+
}
|
package/src/internal/ws.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { concatBytes, encodeUTF8 } from './utils/bytes';
|
|
2
|
+
import { OpenAIError } from '../core/error';
|
|
3
|
+
|
|
4
|
+
/** Options for an independently buffered WebSocket stream iterator. */
|
|
5
|
+
export interface WebSocketStreamOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum queued records, including raw data, errors and lifecycle events.
|
|
8
|
+
* Must be a positive safe integer. Omitted means unlimited. Overflow discards
|
|
9
|
+
* this iterator's backlog and rejects its next() calls with a WebSocketError,
|
|
10
|
+
* without closing the shared socket or affecting other iterators.
|
|
11
|
+
* This bounds event count, not payload bytes or total memory.
|
|
12
|
+
*/
|
|
13
|
+
maxBufferedEvents?: number | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Snapshots and validates the iterator's limit before listeners are attached. */
|
|
17
|
+
export function getMaxBufferedEvents(options?: WebSocketStreamOptions): number | undefined {
|
|
18
|
+
const limit = options?.maxBufferedEvents;
|
|
19
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit <= 0)) {
|
|
20
|
+
throw new OpenAIError('maxBufferedEvents must be a positive safe integer');
|
|
21
|
+
}
|
|
22
|
+
return limit;
|
|
23
|
+
}
|
|
2
24
|
|
|
3
25
|
/** Reconnection event passed to the `onReconnecting` handler and event listeners. */
|
|
4
26
|
export interface ReconnectingEvent<Parameters = Record<string, unknown>> {
|
|
@@ -15,11 +15,17 @@ export type GroupingUpdate =
|
|
|
15
15
|
| { type: 'updated'; segment: TranscriptSegment }
|
|
16
16
|
| { type: 'closed'; segment: TranscriptSegment; reason: TranscriptSegmentCloseReason };
|
|
17
17
|
|
|
18
|
+
interface Acknowledgment {
|
|
19
|
+
characters: number;
|
|
20
|
+
text: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
type Turn = TranscriptFragment & {
|
|
19
24
|
id: string;
|
|
20
25
|
previousId: string | null;
|
|
21
26
|
emitted: boolean;
|
|
22
27
|
canDropAsBackchannel: boolean;
|
|
28
|
+
acknowledgment?: Acknowledgment | undefined;
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
const ACKNOWLEDGMENTS = [
|
|
@@ -42,14 +48,16 @@ const ACKNOWLEDGMENTS = [
|
|
|
42
48
|
];
|
|
43
49
|
|
|
44
50
|
function normalizeAcknowledgment(text: string): string {
|
|
45
|
-
|
|
51
|
+
const normalized = text
|
|
46
52
|
.toLowerCase()
|
|
47
53
|
.split('-')
|
|
48
54
|
.join(' ')
|
|
49
|
-
.replace(/^[\s.,!?;:"'()[\]{}]+/u, '')
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
.replace(/^[\s.,!?;:"'()[\]{}]+/u, '');
|
|
56
|
+
let end = normalized.length;
|
|
57
|
+
while (end > 0 && /[\s.,!?;:"'()[\]{}]/u.test(normalized.charAt(end - 1))) {
|
|
58
|
+
end -= 1;
|
|
59
|
+
}
|
|
60
|
+
return normalized.slice(0, end).split(/\s+/u).join(' ');
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
/** The v2 grouping policy, driven by timed public text rather than engine frames. */
|
|
@@ -63,6 +71,7 @@ export class TranscriptGrouping {
|
|
|
63
71
|
private readonly options: Required<TranscriptGrouperOptions>;
|
|
64
72
|
private readonly idPrefix: string;
|
|
65
73
|
private readonly acknowledgments: readonly string[];
|
|
74
|
+
private readonly maxAcknowledgmentLength: number;
|
|
66
75
|
|
|
67
76
|
constructor(options: Required<TranscriptGrouperOptions>, idPrefix: string) {
|
|
68
77
|
this.options = options;
|
|
@@ -71,6 +80,11 @@ export class TranscriptGrouping {
|
|
|
71
80
|
...ACKNOWLEDGMENTS,
|
|
72
81
|
...options.additionalAcknowledgments.map(normalizeAcknowledgment).filter(Boolean),
|
|
73
82
|
];
|
|
83
|
+
let maxLength = 0;
|
|
84
|
+
for (const acknowledgment of this.acknowledgments) {
|
|
85
|
+
maxLength = Math.max(maxLength, acknowledgment.length);
|
|
86
|
+
}
|
|
87
|
+
this.maxAcknowledgmentLength = maxLength;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
get speaker(): TranscriptFragment['speaker'] | undefined {
|
|
@@ -183,16 +197,22 @@ export class TranscriptGrouping {
|
|
|
183
197
|
this.buffer(fragment);
|
|
184
198
|
return this.promote();
|
|
185
199
|
}
|
|
200
|
+
const withinDuration =
|
|
201
|
+
fragment.endMs - (this.buffered?.startMs ?? fragment.startMs) < this.options.backchannelMaxDurationMs;
|
|
202
|
+
const acknowledgment = this.acknowledgment(fragment, withinDuration);
|
|
203
|
+
const normalized = withinDuration ? acknowledgment.text : undefined;
|
|
186
204
|
if (separation < this.options.minTurnSeparationMs) {
|
|
187
|
-
this.buffer(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
this.buffer(
|
|
206
|
+
fragment,
|
|
207
|
+
normalized !== undefined &&
|
|
208
|
+
normalized.length > 0 &&
|
|
209
|
+
this.acknowledgments.some((phrase) => phrase.startsWith(normalized)),
|
|
210
|
+
acknowledgment,
|
|
211
|
+
);
|
|
192
212
|
return [];
|
|
193
213
|
}
|
|
194
|
-
if (
|
|
195
|
-
this.buffer(fragment,
|
|
214
|
+
if (normalized !== undefined && this.acknowledgments.includes(normalized)) {
|
|
215
|
+
this.buffer(fragment, this.buffered !== undefined, acknowledgment);
|
|
196
216
|
return [];
|
|
197
217
|
}
|
|
198
218
|
this.buffered = this.maybeDropBackchannel(undefined, fragment);
|
|
@@ -227,7 +247,7 @@ export class TranscriptGrouping {
|
|
|
227
247
|
turn.endMs = Math.max(turn.endMs, fragment.endMs);
|
|
228
248
|
}
|
|
229
249
|
|
|
230
|
-
private buffer(fragment: TranscriptFragment, canDrop?: boolean): void {
|
|
250
|
+
private buffer(fragment: TranscriptFragment, canDrop?: boolean, acknowledgment?: Acknowledgment): void {
|
|
231
251
|
if (this.buffered) {
|
|
232
252
|
TranscriptGrouping.append(this.buffered, fragment);
|
|
233
253
|
} else {
|
|
@@ -236,6 +256,7 @@ export class TranscriptGrouping {
|
|
|
236
256
|
if (canDrop !== undefined) {
|
|
237
257
|
this.buffered.canDropAsBackchannel = canDrop;
|
|
238
258
|
}
|
|
259
|
+
this.buffered.acknowledgment = acknowledgment;
|
|
239
260
|
}
|
|
240
261
|
|
|
241
262
|
private promote(): GroupingUpdate[] {
|
|
@@ -344,19 +365,30 @@ export class TranscriptGrouping {
|
|
|
344
365
|
return this.buffered.canDropAsBackchannel ? undefined : this.buffered;
|
|
345
366
|
}
|
|
346
367
|
|
|
347
|
-
private
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
368
|
+
private acknowledgment(fragment: TranscriptFragment, withinDuration: boolean): Acknowledgment {
|
|
369
|
+
const previous = this.buffered?.acknowledgment;
|
|
370
|
+
const previousCharacters = previous?.characters ?? 0;
|
|
371
|
+
let characters = previousCharacters;
|
|
372
|
+
const separator = /[\s.,!?;:"'()[\]{}-]/u;
|
|
373
|
+
for (
|
|
374
|
+
let index = 0;
|
|
375
|
+
index < fragment.text.length && characters <= this.maxAcknowledgmentLength;
|
|
376
|
+
index += 1
|
|
377
|
+
) {
|
|
378
|
+
if (!separator.test(fragment.text.charAt(index))) {
|
|
379
|
+
characters += 1;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Significant characters cannot disappear during normalization. Once they
|
|
383
|
+
// outgrow the configured phrases, stop counting without allocating tokens.
|
|
384
|
+
let text: string | undefined;
|
|
385
|
+
if (characters === previousCharacters && previous?.text !== undefined) {
|
|
386
|
+
// Keep the raw suffix in the turn: later text can make punctuation internal.
|
|
387
|
+
({ text } = previous);
|
|
388
|
+
} else if (withinDuration && characters <= this.maxAcknowledgmentLength) {
|
|
389
|
+
// Retain whole-string Unicode casing (including context-sensitive sigma).
|
|
390
|
+
text = normalizeAcknowledgment((this.buffered?.text ?? '') + fragment.text);
|
|
391
|
+
}
|
|
392
|
+
return { characters, text };
|
|
361
393
|
}
|
|
362
394
|
}
|
|
@@ -79,6 +79,11 @@ export abstract class ResponsesEmitter extends EventEmitter<WebSocketEvents> {
|
|
|
79
79
|
protected _onError(event: WebSocketErrorEvent | null, message?: string | undefined, cause?: any): void {
|
|
80
80
|
const eventMessage = event && ('error' in event ? event.error?.message : event.message);
|
|
81
81
|
message = message ?? eventMessage ?? safeJSONStringify(event) ?? 'unknown error';
|
|
82
|
+
try {
|
|
83
|
+
message = String(message);
|
|
84
|
+
} catch {
|
|
85
|
+
message = '[unserializable error value]';
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
if (!this._hasListener('error')) {
|
|
84
89
|
const error = new WebSocketError(
|